Alamofire Pause Request not contine other Requests wating in Queue - ios

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...

Related

Almofire multiple images Download and save them locally

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

cancel one Alamofire download request with a specific url

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()

Replace Image when if image present in gallery ios

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)
}
}
}
}

Improve efficiency on downloading multiple XML files and parsing them

I'm downloading five XML files from the web server using Alamofire and parsing them using SWXMLHash. The last file depends on the first four files in a way that some of its entries refer to the entries contain in the first four. I use cascading style structure for download code to ensure all four files are downloaded before downloading of the last file can start.
let destination: DownloadRequest.DownloadFileDestination = { _, _ in
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory,
.userDomainMask, true)[0]
let documentsURL = URL(fileURLWithPath: documentsPath, isDirectory: true)
let fileURL = documentsURL.appendingPathComponent("image.png")
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
// Download Category files
Alamofire.download(self.ottawaOpenDataCategoriesURL, to:
destination)
.downloadProgress { progress in
//print("Download Progress for Category: \(progress.fractionCompleted)")
if let pv = self.progressViewController {
pv.updateProgress(progress.fractionCompleted * 100.0) //Alamofire returns 1 for complete process
}
}
.responseData { response in
// Check 304 response to see if new file is available
if response.response?.statusCode == self.HTTP_STATUS_FILE_NOT_CHANGE {
return
}
if let data = response.result.value {
let xml = self.initXMLParser(data: data)
self.storeCategoryXMLStream(xml)
}
// After complete downloading or get error, try download Options
Alamofire.download(self.ottawaOpenDataOptionsURL, to:
destination)
.downloadProgress { progress in
if let pv = self.progressViewController {
pv.updateProgress(progress.fractionCompleted * 100.0) //Alamofire returns 1 for complete process
}
}
.responseData { response in
// Check 304 response to see if new file is available
if response.response?.statusCode == self.HTTP_STATUS_FILE_NOT_CHANGE {
return
}
if let data = response.result.value {
let xml = self.initXMLParser(data: data)
self.storeEventOptionsXMLStream(xml)
}
// After complete downloading or get error, try download Locations
Alamofire.download(self.ottawaOpenDataLocationsURL, to:
destination)
.downloadProgress { progress in
if let pv = self.progressViewController {
pv.updateProgress(progress.fractionCompleted * 100.0) //Alamofire returns 1 for complete process
}
}
.responseData { response in
// Check 304 response to see if new file is available
if response.response?.statusCode == self.HTTP_STATUS_FILE_NOT_CHANGE {
return
}
if let data = response.result.value {
let xml = self.initXMLParser(data: data)
self.storeVenuesXMLStream(xml)
}
// After complete downloading or get error, try download CitrSectors
Alamofire.download(self.ottawaOpenDataCitySectorsURL, to:
destination)
.downloadProgress { progress in
if let pv = self.progressViewController {
pv.updateProgress(progress.fractionCompleted * 100.0) //Alamofire returns 1 for complete process
}
}
.responseData { response in
// Check 304 response to see if new file is available
if response.response?.statusCode == self.HTTP_STATUS_FILE_NOT_CHANGE {
return
}
if let data = response.result.value {
let xml = self.initXMLParser(data: data)
self.storeCitySectorsXMLStream(xml)
}
// After complete downloading or get error, try download events
Alamofire.download(self.ottawaOpenDataEventsURL, to:
destination)
.downloadProgress { progress in
if let pv = self.progressViewController {
pv.updateProgress(progress.fractionCompleted * 100.0) //Alamofire returns 1 for complete process
}
}
.responseData { response in
// Check 304 response to see if new file is available
if response.response?.statusCode == self.HTTP_STATUS_FILE_NOT_CHANGE {
return
}
if let data = response.result.value {
let xml = self.initXMLParser(data: data)
self.storeEventsXMLStream(xml)
}
}
}
}
}
}
As you can see, each download segment waits for the previous one to complete. Also, the progress bar is updated during download process. Most XML files are from small to decent size (80 Lines - 10K lines) and the last one is the largest and contains about 200K lines.
I'm not sure if it's because of cascading style structure, but it takes about 10 seconds to download and parse the first four files. Is it possible to make it faster? I just want to know if I can improve efficiency. Here's a screenshot of cpu usage and mem usage.
P.S I'm running this app on simulator.
Since the first 4 downloads aren't interdependent, you could download them simultaneously, and only when all 4 of those are received start the 5th download. Use a dispatch group to synchronize the execution of the 5th download.
In short, the whole operation looks like:
let group = DispatchGroup()
group.enter()
Alamofire.download(first url).responseData {
process first url
group.leave()
}
group.enter()
Alamofire.download(second url).responseData {
process second url
group.leave()
}
repeat for third and fourth url
group.notify(queue:Dispatch.main) {
Alamofire.download(fifth url).responseData {
process 5th url
}
}

Swift - Download a video from distant URL and save it in an photo album

I'm currently displaying a video in my app and I want the user to be able to save it to its device gallery/album photo/camera roll.
Here it's what I'm doing but the video is not saved in the album :/
func downloadVideo(videoImageUrl:String)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
//All stuff here
print("downloadVideo");
let url=NSURL(string: videoImageUrl);
let urlData=NSData(contentsOfURL: url!);
if((urlData) != nil)
{
let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0];
let fileName = videoImageUrl; //.stringByDeletingPathExtension
let filePath="\(documentsPath)/\(fileName)";
//saving is done on main thread
dispatch_async(dispatch_get_main_queue(), { () -> Void in
urlData?.writeToFile(filePath, atomically: true);
print("videoSaved");
})
}
})
}
I'va also look into this :
let url:NSURL = NSURL(string: fileURL)!;
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
let assetChangeRequest = PHAssetChangeRequest.creationRequestForAssetFromVideoAtFileURL(url);
let assetPlaceHolder = assetChangeRequest!.placeholderForCreatedAsset;
let albumChangeRequest = PHAssetCollectionChangeRequest(forAssetCollection: self.assetCollection)
albumChangeRequest!.addAssets([assetPlaceHolder!])
}, completionHandler: saveVideoCallBack)
But I have the error "Unable to create data from file (null)". My "assetChangeRequest" is nil. I don't understand as my url is valid and when I go to it with a browser, it download a quick time file.
If anyone can help me, it would be appreciated ! I'm using Swift and targeting iOS 8.0 min.
Update
Wanted to update the answer for Swift 3 using URLSession and figured out that the answer already exists in related topic here. Use it.
Original Answer
The code below saves a video file to Camera Roll. I reused your code with a minor change - I removed let fileName = videoImageUrl; because it leads to incorrect file path.
I tested this code and it saved the asset into camera roll. You asked what to place into creationRequestForAssetFromVideoAtFileURL - put a link to downloaded video file as in the example below.
let videoImageUrl = "http://www.sample-videos.com/video/mp4/720/big_buck_bunny_720p_1mb.mp4"
DispatchQueue.global(qos: .background).async {
if let url = URL(string: urlString),
let urlData = NSData(contentsOf: url) {
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0];
let filePath="\(documentsPath)/tempFile.mp4"
DispatchQueue.main.async {
urlData.write(toFile: filePath, atomically: true)
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: URL(fileURLWithPath: filePath))
}) { completed, error in
if completed {
print("Video is saved!")
}
}
}
}
}
Swift 3 version of the code from #Nimble:
DispatchQueue.global(qos: .background).async {
if let url = URL(string: urlString),
let urlData = NSData(contentsOf: url)
{
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0];
let filePath="\(documentsPath)/tempFile.mp4"
DispatchQueue.main.async {
urlData.write(toFile: filePath, atomically: true)
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: URL(fileURLWithPath: filePath))
}) { completed, error in
if completed {
print("Video is saved!")
}
}
}
}
}
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: video.url!)}) {
saved, error in
if saved {
print("Save status SUCCESS")
}
}
following #Nimble and #Yuval Tal solution, it is much more preferable to use the URLSession dataTask(with:completionHandler:) method to download a file before writing it as stated in the warning section of NSData(contentsOf:) Apple documentation
Important
Don't use this synchronous initializer to request network-based URLs.
For network-based URLs, this method can block the current thread for
tens of seconds on a slow network, resulting in a poor user
experience, and in iOS, may cause your app to be terminated.
Instead, for non-file URLs, consider using the
dataTask(with:completionHandler:) method of the URLSession
a correct implementation could be :
let defaultSession = URLSession(configuration: .default)
var dataTask: URLSessionDataTask? = nil
func downloadAndSaveVideoToGallery(videoURL: String, id: String = "default") {
DispatchQueue.global(qos: .background).async {
if let url = URL(string: videoURL) {
let filePath = FileManager.default.temporaryDirectory.appendingPathComponent("\(id).mp4")
print("work started")
self.dataTask = self.defaultSession.dataTask(with: url, completionHandler: { [weak self] data, res, err in
DispatchQueue.main.async {
do {
try data?.write(to: filePath)
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: filePath)
}) { completed, error in
if completed {
print("Saved to gallery !")
} else if let error = error {
print(error.localizedDescription)
}
}
} catch {
print(error.localizedDescription)
}
}
self?.dataTask = nil
})
self.dataTask?.resume()
}
}
}
One more advantage is that you can pause, resume and terminate your download by calling the corresponding method on dataTask: URLSessionDataTask .resume() .suspend() .cancel()

Resources