I have more than 500 image links I want to download those images and store locally in app document directory when app starts. I am using Almofire for download but I am getting error like
"URLSession task was cancelled" and Request timeOut
func downloadAllImages(images:[String: String], retryCount: Int = 0,completion: #escaping((Bool)->Void)){
var success: Bool = true
var failedImages = [String: String]()
for (localName, severPath) in images {
self.dispatchGroup.enter()
let destination: DownloadRequest.Destination = { _, _ in
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let fileURL = documentsURL.appendingPathComponent(localName)
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
let path = severPath.replacingOccurrences(of: "\\", with: "/").addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? ""
//AF.sessionConfiguration.httpShouldSetCookies = false
AF.download(path, to: destination).response { response in
switch response.result {
case .success(_):
break
case .failure(let error):
if response.response?.statusCode != 404 {
success = false
failedImages[localName] = path
print("Image Download Error = \(error.localizedDescription)")
}
break
}
debugPrint(response)
self.dispatchGroup.leave()
}
}
dispatchGroup.notify(queue: .main) {
//retry If some Images failed to download
if failedImages.isEmpty || retryCount >= self.maximumRetryCount {
completion(success)
}else {
self.downloadAllImages(images: failedImages, retryCount: retryCount + 1) { (success) in
completion(success)
}
}
}
}
images dictionary contains
localName as key
serverPath as value
AFImageDownloaders have limit of active downloads, and I believe changing maximumActiveDownloads or something similar in your API will fix that. The new downloads just cancel the previous ones. But it's better to download them in chunks.
For example this one is for ImageDownloader
public init(session: Session,
downloadPrioritization: DownloadPrioritization = .fifo,
maximumActiveDownloads: Int = 4,
imageCache: ImageRequestCache? = AutoPurgingImageCache()) {
precondition(!session.startRequestsImmediately, "Session must set `startRequestsImmediately` to `false`.")
self.session = session
self.downloadPrioritization = downloadPrioritization
self.maximumActiveDownloads = maximumActiveDownloads
self.imageCache = imageCache
}
UPD:
The limit is not on AF, but URLSession's. And one AF downloader uses one URLSession. You have to pass custom URLSessionConfigurations to handle more active downloads HTTPMaximumConnectionsPerHost. And pass it AF Session class
Related
I use this function to download file from the Google Drive API and I want to get download progress. Maybe anybody knows how to do it?
func download(file: GTLRDrive_File) {
let url = "https://www.googleapis.com/drive/v3/files/\(file.identifier!)?alt=media"
let fetcher = drive.fetcherService.fetcher(withURLString: url)
fetcher.beginFetch(completionHandler: { data, error in
if let error = error {
print(error.localizedDescription)
}
//Here I save data to the Documents
})
}
I tried to get it from fetcher.receivedProgressBlock but it's always return nil
Solution. Actual for Swift 5:
func download(file: GTLRDrive_File) {
let fileSize = file.size?.doubleValue //You need to fetch file size in your request q.fields = "kind,nextPageToken,files(mimeType,id,name,size)"
let url = "https://www.googleapis.com/drive/v3/files/\(file.identifier!)?alt=media"
let fetcher = drive.fetcherService.fetcher(withURLString: url)
fetcher.beginFetch(completionHandler: { data, error in
if let error = error {
print(error.localizedDescription)
}
//Here I save data to the Documents
})
//Here you can get total bytes received (value is updated in real time)
fetcher.receivedProgressBlock = { _, totalBytesReceived in
guard let fileSize = fileSize else { return }
let progress = Double(totalBytesReceived) / fileSize
print(progress) //Here you can update UI (progress bar) or do something else
}
}
For some reason, file.size returns nil so I used fetcher.response?.expectedContentLength instead.
func download(file: GTLRDrive_File, service: GTLRDriveService) {
let url = "https://www.googleapis.com/drive/v3/files/\(file.identifier!)?alt=media"
let fetcher = service.fetcherService.fetcher(withURLString: url)
fetcher.beginFetch(completionHandler: { fileData, error in
if error == nil {
print("finished downloading file Data...")
print(fileData as Any)
// do anything with data here
} else {
print("Error: \(error?.localizedDescription)")
}
})
fetcher.receivedProgressBlock = { _, totalBytesReceived in
print("totalBytesReceived: \(totalBytesReceived)")
print("size: \(fetcher.response?.expectedContentLength)")
if let fileSize = fetcher.response?.expectedContentLength {
let progress: Double = Double(totalBytesReceived) / Double(fileSize)
print(progress)
// Update progress bar here
}
}
}
I am downloading the zip file using the Alamofire in my ios(swift4) application. I am able to download the file serially using Alamofire.
But I also want to show the one progress bar for the all the downloaded files. Means If I have 4 zip files and when all the file is downloaded then the progress should be 100%.
Till I have tried below code, which gives a progress value for each of the url and progress is shown as one file downloaded showing progress as 100% then again it start from 0 for second url and when second url is downloaded then progress is shown 100% complete.
Please guide me for this. I want to get progress value as 100% when all the files are downloaded using Alamofire.
Can it be possible with Alamofire?
CODE:
func serialZipFileDownload(downloadPath: String){
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let name = self.offlineDownloadFileName?[urlCount]
let currentVideoURL = documentsURL.appendingPathComponent(name ?? "Default.zip")
let str = downloadPath
let urlString = str.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
let url = URL(string: urlString ?? "")
if super.isConnectedToNetwork() == true {
let manager = Alamofire.SessionManager.default
let headers = ["Accept-Encoding" : ""]
manager.request(url ?? "", method: .get, parameters: nil, encoding: JSONEncoding.default, headers: headers).downloadProgress { (progress) in
print(progress.fractionCompleted)
DispatchQueue.main.async{
self.progressDownload.setProgress((Float(progress.fractionCompleted)), animated: true)
let per = round((Float(progress.fractionCompleted)) * 100)
self.lblDownloadPercent.text = "\(Int(per))%"
}
}.responseData { (response) in
switch (response.result){
case .success(_) :
print(response)
print(response.result.value!)
print(response.result.description)
if let data = response.result.value {
do {
try data.write(to: currentVideoURL)
self.showToast(message: "File downloaded successfully")
}
catch {
print("Something went wrong!")
}
}
case .failure(let error) :
print(response)
if error._code == NSURLErrorNetworkConnectionLost {
DispatchQueue.main.async {
super.showPopup(title: msgStruct.networkTitle, message: msgStruct.noInternet)
}
}
else if error._code == NSURLErrorTimedOut {
DispatchQueue.main.async {
super.showPopup(title: msgStruct.networkTitle, message: msgStruct.noInternet)
}
}
else if error._code == NSURLErrorDownloadDecodingFailedMidStream {
print("error",error.localizedDescription)
}
break
}
}
}
else{
super.showPopup(title: msgStruct.networkTitle, message: msgStruct.noInternet)
}
You can do it like this:
Maintain a global variable - tatalPercentage some other swift class.
static let tatalPercentage = 0
tatalPercentage = tatalPercentage + Int(per/4)
self.lblDownloadPercent.text = "\(tatalPercentage) %"
I have table view that download video file for each cell. Here is my code for downloading video file.
func beginItemDownload(id:String,url:String,selectRow:Int) {
let pathComponent = "pack\(self.packID)-\(selectRow + 1).mp4"
let destination: DownloadRequest.DownloadFileDestination = { _, _ in
let directoryURL: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let folderPath: URL = directoryURL.appendingPathComponent("Downloads", isDirectory: true)
let fileURL: URL = folderPath.appendingPathComponent(pathComponent)
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
let url = URL(string:url)
Alamofire.download(
url!,
method: .get,
parameters: nil,
encoding: JSONEncoding.default,
headers: nil,
to: destination).downloadProgress(closure: { (progress) in
DispatchQueue.main.async {
if progress.fractionCompleted < 1 {
print(Float(progress.fractionCompleted))
}
if progress.fractionCompleted == 1 {
print("Completed")
}
}
}).response(completionHandler: { (defaultDownloadResponse) in
if let destinationUrl = defaultDownloadResponse.destinationURL {
DispatchQueue.main.async {
print("destination url -****",destinationUrl.absoluteString)
}
}
if let error = defaultDownloadResponse.error {
debugPrint("Download Failed with error - \(error)")
return
}
})
}
When tapping download button for each tableview cell I can download video file and assign progress value for each cell. But now I want to cancel download request for this cell when tapping on cancel button in each cell, .
I search different solution for this issue but I can't find any solution for cancelling one request with a specific url string. How can solve this issue.
Thanks for all reply.
Not tested but should work, use originalRequest(which is the original request when the task was created) or optionally currentRequest(which is the request currently being handled) to locate the specific task you want cancel:
func cancelSpecificTask(byUrl url:URL) {
Alamofire.SessionManager.default.session.getAllTasks{sessionTasks in
for task in sessionTasks {
if task.originalRequest?.url == url {
task.cancel()
}
}
}
}
Or only cancel download task:
func cancelSepcificDownloadTask(byUrl url:URL) {
let sessionManager = Alamofire.SessionManager.default
sessionManager.session.getTasksWithCompletionHandler { dataTasks, uploadTasks, downloadTasks in
for task in downloadTasks {
if task.originalRequest?.url == url {
task.cancel()
}
}
}
Though luiyezheng's answer is really good and should do the job it is sometimes overlooked by developers (by me for example) that Alamofire.download() actually returns DownloadRequest which could be stored somewhere if needed and later cancel() `ed:
let request = Alamofire.download("http://test.com/file.png")
r.cancel()
In this code we used "alamofire" for download the image but present there i want to replace the image so please help me. When i get response then image present then i have get error code 516 (Already Image existing) so please help me after get response.
i want replace the old image or delete old image.
func setUI() {
self.btnCancel.layer.borderWidth = 1.0
self.btnCancel.layer.cornerRadius = 3.0
self.btnCancel.addShadowView()
print("-------------------------------- method Call")
if let url = NSURL(string: strFileName) {
print(url)
let destination = Alamofire.Request.suggestedDownloadDestination(directory: .DocumentDirectory, domain: .UserDomainMask)
self.request = Alamofire.download(.GET, url, parameters: nil, encoding: ParameterEncoding.URL,destination:destination)
.progress { bytesRead, totalBytesRead, totalBytesExpectedToRead in
dispatch_async(dispatch_get_main_queue()) {
print("Total bytes read on main queue: \(totalBytesRead)")
print("Progress on main queue: \(Float(totalBytesRead) / Float(totalBytesExpectedToRead))")
print("totalBytesExpectedToRead",totalBytesExpectedToRead)
self.CountPercentage.text! = String(format: "%.f%%",self.viewProgressBar.progress * 100)
self.viewProgressBar.progress = (Float(totalBytesRead) / Float(totalBytesExpectedToRead))
}
}
.response { request, response, _, error in
print("\(request?.URL)") // original URL request
print(error)
if !(error == nil) {
if !(error!.code != NSURLErrorCancelled) {
print("cancel----------")
} else if (error!.code == 516) {
print("fileURL: \(destination(NSURL(string: "")!, response!))")
let filePath = destination(NSURL(string: "")!, response!)
self.alertAction("File downloaded successfully.", filePath: filePath)
} else {
CommonMethods.sharedInstance.showAlertWithMessage((error?.localizedDescription)!, withController: self)
}
} else {
print("fileURL: \(destination(NSURL(string: "")!, response!))")
let filePath = destination(NSURL(string: "")!, response!)
self.alertAction("File downloaded successfully.", filePath: filePath)
}
}
}
}
I'm using Alamofire library to make a download manager.
I'm configuring the Alamofire Manager with
HTTPMaximumConnectionsPerHost = 4.
And I'm start Downloading 5 items, then 4 items start downloading and
the the 5th item is waiting in queue.
When I Pause one of the first 4th item, I'm expecting the 5th item to
start downloading. This is not happening and this is my issue 💃 👯
This is my Code:
func startDownload(thisItem url: String, folderName: String) {
let startDownloadRequest = AlamofireManager!.download(.GET, url,
destination: { temporaryURL, response in
let fileManager = NSFileManager.defaultManager()
let directoryURL = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
self.createFolderWithName(folderName)
let pathComponent = "\(folderName)/\(response.suggestedFilename!)"
return directoryURL.URLByAppendingPathComponent(pathComponent)
})
.progress { bytesRead, totalBytesRead, totalBytesExpectedToRead in
dispatch_async(dispatch_get_main_queue())
{
let progress = String(Float(totalBytesRead) / Float(totalBytesExpectedToRead))
}
}
.response { _, _, _, error in
if let error = error {
print("Failed with error: \(error)")
} else {
print("Downloaded file successfully")
}
}
}
And this is the method for Pause Request
func pauseDownload(thisItem item: String) {
var request : Request
for req in DownloadCenter.defaultCenter.currentDownloadsList {
if req.request?.URLString == item {
request = req
request.suspend()
break
}
}
}
This is my case:
Start Downloading 5 items, 4 are Downloading and the 5th item is waiting..
After Pausing...