@@ -6,66 +6,133 @@ open System.Net.Http
6
6
7
7
open Fake.Core
8
8
9
- open FilePath
10
- open ResultBuilder
9
+ open Fake.Net .Async
10
+ open Fake.Net .Result
11
+ open Fake.Net .List
11
12
12
- /// Contains
13
+ /// HTTP Client for downloading files
13
14
module Http =
14
15
15
- let result = ResultBuilder()
16
+ /// Input parameter type
17
+ type DownloadParameters = {
18
+ Uri: string
19
+ Path: string
20
+ }
16
21
17
- let createUri ( uriStr : string ) =
22
+ /// Type aliases for local file path and error messages
23
+ type private FilePath = string
24
+ type private Err = string
25
+
26
+ /// Contains validated Uri and FilePath info for further download
27
+ type private DownloadInfo = {
28
+ Uri: Uri
29
+ LocalFilePath: FilePath
30
+ }
31
+
32
+ /// [omit]
33
+ let private createFilePath ( filePathStr : string ): Result < FilePath , Err list > =
34
+ try
35
+ let fullPath = Path.GetFullPath( filePathStr)
36
+ Ok ( fullPath)
37
+ with
38
+ | ex ->
39
+ let err = sprintf " [%s ] %s " filePathStr ex.Message
40
+ Error [ err ]
41
+
42
+ /// [omit]
43
+ let private createUri ( uriStr : string ): Result < Uri , Err list > =
18
44
try
19
45
Ok ( Uri uriStr)
20
46
with
21
47
| ex ->
22
- let err = sprintf " [%s ] %A " uriStr ex.Message
48
+ let err = sprintf " [%s ] %s " uriStr ex.Message
23
49
Error [ err ]
24
50
25
- let showDownloadResult ( result : Result < FilePath , string list >) =
51
+ /// [omit]
52
+ let private createDownloadInfo ( input : DownloadParameters ): Result < DownloadInfo , Err list > =
53
+ let (<!>) = Result.map
54
+ let (<*>) = Result.apply
55
+
56
+ let createDownloadInfoRecord ( filePath : FilePath ) ( uri : Uri ) =
57
+ { Uri= uri; LocalFilePath= filePath }
58
+
59
+ let filePathResult = createFilePath input.Path
60
+ let urlResult = createUri input.Uri
61
+ createDownloadInfoRecord <!> filePathResult <*> urlResult
62
+
63
+ /// [omit]
64
+ let private printDownloadResults result =
26
65
match result with
27
- | Ok ( FilePath( filePath)) ->
28
- Trace.log <| sprintf " Downloaded : [%s ]" filePath
29
- | Error errs ->
30
- Trace.traceError <| sprintf " Failed: %A " errs
66
+ | Ok result ->
67
+ Trace.log <| sprintf " Downloaded : [%A ]" result
68
+ | Error errs ->
69
+ Trace.traceError <| sprintf " Failed: %A " errs
70
+ result
31
71
32
- let saveStreamToFile ( filePath : FilePath ) ( stream : Stream ) : Async < Result < FilePath , string list >> =
72
+ /// [omit]
73
+ let private saveStreamToFileAsync ( filePath : FilePath ) ( stream : Stream ) : Async < Result < FilePath , Err list >> =
33
74
async {
34
- let filePathStr = FilePath.value filePath
35
75
try
36
- use fileStream = new FileStream( filePathStr , FileMode.Create, FileAccess.Write, FileShare.None)
76
+ use fileStream = new FileStream( filePath , FileMode.Create, FileAccess.Write, FileShare.None)
37
77
do ! stream.CopyToAsync( fileStream) |> Async.AwaitTask
38
78
return ( Ok filePath)
39
79
with
40
80
| ex ->
41
- let err = sprintf " [%s ] %A " filePathStr ex.Message
81
+ let err = sprintf " [%s ] %s " filePath ex.Message
42
82
return Error [ err ]
43
83
}
44
84
45
- let downloadToFileStream ( filePath : FilePath ) ( uri : Uri ) : Async < Result < FilePath , string list >> =
85
+ /// [omit]
86
+ let private downloadStreamToFileAsync ( info : DownloadInfo ) : Async < Result < FilePath , Err list >> =
46
87
async {
47
88
use client = new HttpClient()
48
89
try
90
+ Trace.log <| sprintf " Downloading [%s ] ..." info.Uri.OriginalString
49
91
// do not buffer the response
50
- let! response = client.GetAsync( uri , HttpCompletionOption.ResponseHeadersRead) |> Async.AwaitTask
92
+ let! response = client.GetAsync( info.Uri , HttpCompletionOption.ResponseHeadersRead) |> Async.AwaitTask
51
93
response.EnsureSuccessStatusCode () |> ignore
52
- use! stream = response.Content.ReadAsStreamAsync() |> Async.AwaitTask
53
- return ! saveStreamToFile filePath stream
94
+ use! stream = response.Content.ReadAsStreamAsync() |> Async.AwaitTask
95
+ return ! saveStreamToFileAsync info.LocalFilePath stream
54
96
with
55
- | ex ->
56
- let err = sprintf " [%s ] %A " uri .Host ex.Message
97
+ | ex ->
98
+ let err = sprintf " [%s ] %s " info.Uri .Host ex.Message
57
99
return Error [ err ]
58
100
}
59
101
60
- /// Download file by the given file path and Url
102
+ /// [omit]
103
+ let private downloadFileAsync ( input : DownloadParameters ): Async < Result < FilePath , Err list >> =
104
+ let valImp = createDownloadInfo input
105
+ match valImp with
106
+ | Ok x ->
107
+ downloadStreamToFileAsync x
108
+ | Error errs ->
109
+ Async.result ( Error errs)
110
+
111
+ /// Download file by the given file path and Uri
61
112
/// string -> string -> Result<FilePath,string list>
62
- let downloadFile ( filePathStr : string ) ( url : string ) : Result < FilePath , string list > =
113
+ /// ## Parameters
114
+ /// - `localFilePath` - A local file path to download file
115
+ /// - `uri` - A Uri to download from
116
+ /// ## Returns
117
+ /// - `Result` type. Success branch contains a downloaded file path. Failure branch contains a list of errors
118
+ let downloadFile ( localFilePath : string ) ( uri : string ) : Result < string , string list > =
119
+ downloadFileAsync { Uri= uri; Path= localFilePath }
120
+ |> Async.RunSynchronously
121
+ |> printDownloadResults
63
122
64
- let downloadResult = result {
65
- let! filePath = FilePath.create filePathStr
66
- let! uri = createUri url
67
- let! result = downloadToFileStream filePath uri |> Async.RunSynchronously
68
- return result
69
- }
70
- do showDownloadResult downloadResult
71
- downloadResult
123
+ /// Download list of Uri's in parallel
124
+ /// DownloadParameters -> Result<FilePath, Err list>
125
+ /// ## Parameters
126
+ /// - `input` - List of Http.DownloadParameters. Each Http.DownloadParameters record type contains Uri and file path
127
+ /// ## Returns
128
+ /// - `Result` type. Success branch contains a list of downloaded file paths. Failure branch contains a list of errors
129
+ let downloadFiles ( input : DownloadParameters list ) : Result < string list , string list > =
130
+ input
131
+ // DownloadParameters -> "Async<Result<FilePath, Err list>> list"
132
+ |> List.map downloadFileAsync
133
+ // "Async<Result<FilePath, Err list>> list" -> "Async<Result<FilePath, Err list> list>"
134
+ |> List.sequenceAsyncA
135
+ // "Async<Result<FilePath, Err list> list>" -> "Async<Result<FilePath list, Err list>>"
136
+ |> Async.map List.sequenceResultA
137
+ |> Async.RunSynchronously
138
+ |> printDownloadResults
0 commit comments