Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit4d87c0c

Browse files
committed
download progress
1 parent06ddf08 commit4d87c0c

File tree

2 files changed

+115
-42
lines changed

2 files changed

+115
-42
lines changed

‎Coder-Desktop/VPN/Manager.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,13 @@ actor Manager {
3535
// Timeout after 5 minutes, or if there's no data for 60 seconds
3636
sessionConfig.timeoutIntervalForRequest=60
3737
sessionConfig.timeoutIntervalForResource=300
38-
tryawaitdownload(src: dylibPath, dest: dest, urlSession:URLSession(configuration: sessionConfig))
38+
tryawaitdownload(
39+
src: dylibPath,
40+
dest: dest,
41+
urlSession:URLSession(configuration: sessionConfig)
42+
){ progressin
43+
pushProgress(msg:"Downloading library...\n\(progress.description)")
44+
}
3945
}catch{
4046
throw.download(error)
4147
}

‎Coder-Desktop/VPNLib/Download.swift

Lines changed: 108 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -125,47 +125,13 @@ public class SignatureValidator {
125125
}
126126
}
127127

128-
publicfunc download(src:URL, dest:URL, urlSession:URLSession)asyncthrows(DownloadError){
129-
varreq=URLRequest(url: src)
130-
ifFileManager.default.fileExists(atPath: dest.path){
131-
iflet existingFileData=try?Data(contentsOf: dest, options:.mappedIfSafe){
132-
req.setValue(etag(data: existingFileData), forHTTPHeaderField:"If-None-Match")
133-
}
134-
}
135-
// TODO: Add Content-Length headers to coderd, add download progress delegate
136-
lettempURL:URL
137-
letresponse:URLResponse
138-
do{
139-
(tempURL, response)=tryawait urlSession.download(for: req)
140-
}catch{
141-
throw.networkError(error, url: src.absoluteString)
142-
}
143-
defer{
144-
ifFileManager.default.fileExists(atPath: tempURL.path){
145-
try?FileManager.default.removeItem(at: tempURL)
146-
}
147-
}
148-
149-
guardlet httpResponse= responseas?HTTPURLResponseelse{
150-
throw.invalidResponse
151-
}
152-
guard httpResponse.statusCode!=304else{
153-
// We already have the latest dylib downloaded on disk
154-
return
155-
}
156-
157-
guard httpResponse.statusCode==200else{
158-
throw.unexpectedStatusCode(httpResponse.statusCode)
159-
}
160-
161-
do{
162-
ifFileManager.default.fileExists(atPath: dest.path){
163-
tryFileManager.default.removeItem(at: dest)
164-
}
165-
tryFileManager.default.moveItem(at: tempURL, to: dest)
166-
}catch{
167-
throw.fileOpError(error)
168-
}
128+
publicfunc download(
129+
src:URL,
130+
dest:URL,
131+
urlSession:URLSession,
132+
progressUpdates:((DownloadProgress)->Void)?=nil
133+
)asyncthrows(DownloadError){
134+
tryawaitDownloadManager().download(src: src, dest: dest, urlSession: urlSession, progressUpdates: progressUpdates)
169135
}
170136

171137
func etag(data:Data)->String{
@@ -195,3 +161,104 @@ public enum DownloadError: Error {
195161

196162
publicvarlocalizedDescription:String{ description}
197163
}
164+
165+
// The async `URLSession.download` api ignores the passed-in delegate, so we
166+
// wrap the older delegate methods in an async adapter with a continuation.
167+
privatefinalclassDownloadManager:NSObject,@uncheckedSendable{
168+
privatevarcontinuation:CheckedContinuation<Void,Error>!
169+
privatevarprogressHandler:((DownloadProgress)->Void)?
170+
privatevardest:URL!
171+
172+
func download(
173+
src:URL,
174+
dest:URL,
175+
urlSession:URLSession,
176+
progressUpdates:((DownloadProgress)->Void)?
177+
)asyncthrows(DownloadError){
178+
varreq=URLRequest(url: src)
179+
ifFileManager.default.fileExists(atPath: dest.path){
180+
iflet existingFileData=try?Data(contentsOf: dest, options:.mappedIfSafe){
181+
req.setValue(etag(data: existingFileData), forHTTPHeaderField:"If-None-Match")
182+
}
183+
}
184+
185+
letdownloadTask= urlSession.downloadTask(with: req)
186+
progressHandler= progressUpdates
187+
self.dest= dest
188+
downloadTask.delegate=self
189+
do{
190+
tryawaitwithCheckedThrowingContinuation{ continuationin
191+
self.continuation= continuation
192+
downloadTask.resume()
193+
}
194+
}catchlet error asDownloadError{
195+
throw error
196+
}catch{
197+
throw.networkError(error, url: src.absoluteString)
198+
}
199+
}
200+
}
201+
202+
extensionDownloadManager:URLSessionDownloadDelegate{
203+
// Progress
204+
func urlSession(
205+
_:URLSession,
206+
downloadTask:URLSessionDownloadTask,
207+
didWriteData _:Int64,
208+
totalBytesWritten:Int64,
209+
totalBytesExpectedToWrite _:Int64
210+
){
211+
letmaybeLength=(downloadTask.responseas?HTTPURLResponse)?
212+
.value(forHTTPHeaderField:"X-Original-Content-Length")
213+
.flatMap(Int64.init)
214+
progressHandler?(.init(totalBytesWritten: totalBytesWritten, totalBytesToWrite: maybeLength))
215+
}
216+
217+
// Completion
218+
func urlSession(_:URLSession, downloadTask:URLSessionDownloadTask, didFinishDownloadingTo location:URL){
219+
guardlet httpResponse= downloadTask.responseas?HTTPURLResponseelse{
220+
continuation.resume(throwing:DownloadError.invalidResponse)
221+
return
222+
}
223+
guard httpResponse.statusCode!=304else{
224+
// We already have the latest dylib downloaded in dest
225+
continuation.resume()
226+
return
227+
}
228+
229+
guard httpResponse.statusCode==200else{
230+
continuation.resume(throwing:DownloadError.unexpectedStatusCode(httpResponse.statusCode))
231+
return
232+
}
233+
234+
do{
235+
ifFileManager.default.fileExists(atPath: dest.path){
236+
tryFileManager.default.removeItem(at: dest)
237+
}
238+
tryFileManager.default.moveItem(at: location, to: dest)
239+
}catch{
240+
continuation.resume(throwing:DownloadError.fileOpError(error))
241+
}
242+
243+
continuation.resume()
244+
}
245+
246+
// Failure
247+
func urlSession(_:URLSession, task _:URLSessionTask, didCompleteWithError error:Error?){
248+
iflet error{
249+
continuation.resume(throwing: error)
250+
}
251+
}
252+
}
253+
254+
publicstructDownloadProgress:Sendable,CustomStringConvertible{
255+
lettotalBytesWritten:Int64
256+
lettotalBytesToWrite:Int64?
257+
258+
publicvardescription:String{
259+
letfmt=ByteCountFormatter()
260+
letdone= fmt.string(fromByteCount: totalBytesWritten)
261+
lettotal= totalBytesToWrite.map{ fmt.string(fromByteCount: $0)}??"Unknown"
262+
return"\(done) /\(total)"
263+
}
264+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp