Swift How to Unzip file from URL with Alamofire and SSZipArchive - ios

func downLoad(fileName:String) {
let urlString : String = "\(myurl)\(fileName)"
var localPath: NSURL?
let destination = DownloadRequest.suggestedDownloadDestination(for: .documentDirectory)
Alamofire.download(urlString, method: .get, encoding: JSONEncoding.default, to: destination)
.downloadProgress(queue: DispatchQueue.global(qos: .utility)) { progress in
print("Progress: \(progress.fractionCompleted)")
}
.validate { request, response, temporaryURL, destinationURL in
// Custom evaluation closure now includes file URLs (allows you to parse out error messages if necessary)
return .success
}
.responseJSON { response in
debugPrint(response)
print(response.destinationURL?.path)
print(response.destinationURL?.absoluteString)
let unzipDirectory = self.unzipPath(fileURL:fileName)
let success = SSZipArchive.unzipFile(atPath: (response.destinationURL?.path)!, toDestination: unzipDirectory!)
print(success)
if !success {
return
}
}
}
func unzipPath(fileName:String) -> String? {
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let url = NSURL(fileURLWithPath: path)
let pathComponent = url.appendingPathComponent("test\(fileName)")
do {
try FileManager.default.createDirectory(at: pathComponent!, withIntermediateDirectories: true, attributes: nil)
} catch {
return nil
}
return pathComponent?.absoluteString
}
i get right path in response.destinationURL
but success is false
i tried atPath : to response.destinationURL?.path and
response.destinationURL?.absoluteString
but failed too
What i am doing wrong
destinationURL?.path return this
Optional("/Users/MyUser/Library/Developer/CoreSimulator/Devices/3FBAD207-E5AB-4FC1-8199-2269A1249D97/data/Containers/Data/Application/CB1C2EF5-3100-430B-B869-774C09B8EA7F/Documents/testFile.zip")
response.destinationURL?.absoluteString
return this
Optional("file:///Users/MyUser/Library/Developer/CoreSimulator/Devices/3FBAD207-E5AB-4FC1-8199-2269A1249D97/data/Containers/Data/Application/CB1C2EF5-3100-430B-B869-774C09B8EA7F/Documents/testFile.zip")
i think this is correct URL
why failed unzip?

By looking at your code, the path you are using for the source is perfect.
For the destination path, try updating it to
func unzipPath(fileName:String) -> String? {
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString
let pathWithComponent = path.appendingPathComponent("test\(fileName)")
do {
try FileManager.default.createDirectory(atPath: pathWithComponent, withIntermediateDirectories: true, attributes: nil)
} catch {
return nil
}
return pathWithComponent
}
Try and share the results.

Related

URLSession moving slow when caching a firebase video url

I initially asked this question, got the answer, and in the comments #LeoDabus said:
NSData(contentsOf: url) it is not mean to use with non local resources
urls
He suggested I use URLSession which I did, but the response is very slow. I'm wondering am I doing something wrong. The video is 2mb if that makes any difference.
Inside the the session's completionHandler I tried updating the returned data on the main queue but there was a scrolling glitch while doing that. Using DispatchQueue.global().async there is no scrolling glitch but it seems like it takes longer return
// all of this occurs inside my data model
var cachedURL: URL?
let videoUrl = dict["videoUrl"] as? String ?? "" // eg. "https://firebasestorage.googleapis.com/v0/b/myApp.appspot.com/o/abcd%277920FHqFBkl7D6j%2F-MC65EFG_qT0KZbdtFhU%2F48127-8C29-4666-96C9-E95BE178B268.mp4?alt=media&token=bf85dcd1-8cee-428e-87bc-91800b7316de"
guard let url = URL(string: videoUrl) else { return }
useURLSessionToCacheVideo(url)
func useURLSessionToCacheVideo(_ url: URL) {
let lastPathComponent = url.lastPathComponent
let cachesDir = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
let file = cachesDir.appendingPathComponent(lastPathComponent)
if FileManager.default.fileExists(atPath: file.path) {
self.cachedURL = file
print("url already exists in cache")
return
}
URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
if let error = error { return }
if let response = response as? HTTPURLResponse {
guard response.statusCode == 200 else {
return
}
}
guard let data = data else {
return
}
DispatchQueue.global().async { // main queue caused a hiccup while scrolling a cv
do {
try data.write(to: file, options: .atomic)
DispatchQueue.main.async { [weak self] in
self?.cachedURL = file
}
} catch {
print("couldn't cache video file")
}
}
}).resume()
}
You should write the file from the session's background thread:
func useURLSessionToCacheVideo(_ url: URL) {
let lastPathComponent = url.lastPathComponent
let fileURL = try! FileManager.default
.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent(lastPathComponent)
if FileManager.default.fileExists(atPath: fileURL.path) {
self.cachedURL = fileURL
print("url already exists in cache")
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
guard
error == nil,
let httpResponse = response as? HTTPURLResponse,
200 ..< 300 ~= httpResponse.statusCode,
let data = data
else {
return
}
do {
try data.write(to: fileURL, options: .atomic)
DispatchQueue.main.async { [weak self] in
self?.cachedURL = fileURL
}
} catch {
print("couldn't cache video file")
}
}.resume()
}
This also accepts any 2xx HTTP response code.
That having been said, I’d suggest using a download task, which reduces the peak memory usage and writes the data to the file as you go along:
func useURLSessionToCacheVideo(_ url: URL) {
let lastPathComponent = url.lastPathComponent
let fileURL = try! FileManager.default
.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent(lastPathComponent)
if FileManager.default.fileExists(atPath: fileURL.path) {
self.cachedURL = fileURL
print("url already exists in cache")
return
}
URLSession.shared.downloadTask(with: url) { location, response, error in
guard
error == nil,
let httpResponse = response as? HTTPURLResponse,
200 ..< 300 ~= httpResponse.statusCode,
let location = location
else {
return
}
do {
try FileManager.default.moveItem(at: location, to: fileURL)
DispatchQueue.main.async { [weak self] in
self?.cachedURL = fileURL
}
} catch {
print("couldn't cache video file")
}
}.resume()
}
Personally, rather than having this routine update cachedURL itself, I'd use a completion handler pattern:
enum CacheError: Error {
case failure(URL?, URLResponse?)
}
func useURLSessionToCacheVideo(_ url: URL, completion: #escaping (Result<URL, Error>) -> Void) {
let lastPathComponent = url.lastPathComponent
let fileURL = try! FileManager.default
.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent(lastPathComponent)
if FileManager.default.fileExists(atPath: fileURL.path) {
completion(.success(fileURL))
return
}
URLSession.shared.downloadTask(with: url) { location, response, error in
if let error = error {
DispatchQueue.main.async {
completion(.failure(error))
}
return
}
guard
let httpResponse = response as? HTTPURLResponse,
200 ..< 300 ~= httpResponse.statusCode,
let temporaryLocation = location
else {
DispatchQueue.main.async {
completion(.failure(CacheError.failure(location, response)))
}
return
}
do {
try FileManager.default.moveItem(at: temporaryLocation, to: fileURL)
DispatchQueue.main.async {
completion(.success(fileURL))
}
} catch {
DispatchQueue.main.async {
completion(.failure(error))
}
}
}.resume()
}
And call it like so:
useURLSessionToCacheVideo(url) { result in
switch result {
case .failure(let error):
print(error)
case .success(let cachedURL):
self.cachedURL = cachedURL
}
}
That way, the caller is responsible for updating cachedURL, it now knows when it's done (in case you want to update the UI to reflect the success or failure of the download), and your network layer isn't entangled with the model structure of the caller.

PDF File reading Issue only on Offline mode swift

I have successfully Implemented downloading mechanism for pdf files. Files are downloading and save to document directory of simulator.
Issue is when I try to disconnect internet the files locally stored not accessible. In my application I have two modes Online and Offline. In Online mode I store data and its accessible at Offline mode. Only problem is getting with pdf files.
How to access pdf files in offline mode?
viewController Code:
let fileType = (URL != nil) ? ".pdf" : ".png"
DataProvider.main.downloadFile(url: String(describing: self.URL!), filetype: fileType, callback: { success, response in
if !success || response == nil {
return false
}
if((response as! String).hasSuffix("pdf")){
self.imageScrollView.isHidden = true
self.contentContainer.isHidden = false
self.document = ReaderDocument(filePath:(response as! String).removingPercentEncoding, password: nil)
self.loadDocument()
self._contentView.removeFromSuperview()
self._contentView.frame = self.contentContainer.bounds
self.contentContainer.addSubview(self._contentView)
}else if (FileManager.default.fileExists(atPath: (response as! String).removingPercentEncoding!) ){
self.imageScrollView.isHidden = false
self.contentContainer.isHidden = true
let image = UIImage(contentsOfFile:(response as! String).removingPercentEncoding!)!
self.imageScrollView.display(image)
}
return true
})
DataProvider Class with custom directory:
#objc public func downloadFile(url:String, filetype: String, callback:#escaping (_ success:Bool, _ result:Any?)->(Bool)) -> Void {
var destination = DownloadRequest.suggestedDownloadDestination(for: .documentDirectory)
if filetype.elementsEqual(".pdf"){
destination = { _, _ in
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let fileURL = documentsURL.appendingPathComponent(String(abs(url.hashValue)) + ".pdf") //giving different name to file
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
}
Alamofire.download(
url,
method: .get,
parameters: nil,
encoding: JSONEncoding.default,
headers: [ "Accept":"*/*", "Authorization":"Bearer \(token ?? "")"],
to: destination).downloadProgress(closure: { (progress) in
//progress closure
print(progress)
}).response(completionHandler: { (DefaultDownloadResponse) in
//here you able to access the DefaultDownloadResponse
//result closure
callback(DefaultDownloadResponse.response?.statusCode == 200, DefaultDownloadResponse.destinationURL?.absoluteString.replacingOccurrences(of: "file://", with: ""))
print(DefaultDownloadResponse)
})
}
DefaultDownloadResponse.destinationURL when Online downloaded
▿ destinationURL : Optional<URL>
▿ some : file:///Users/macuser/Library/Developer/CoreSimulator/Devices/4B6C2727-46AB-4797-8CBB-D5A897867013/data/Containers/Data/Application/08441079-027E-4A5C-A504-4151A8405CF0/Documents/660444321507407187.pdf
- _url : file:///Users/macuser/Library/Developer/CoreSimulator/Devices/4B6C2727-46AB-4797-8CBB-D5A897867013/data/Containers/Data/Application/08441079-027E-4A5C-A504-4151A8405CF0/Documents/660444321507407187.pdf
when Offline its not downloaded or not picked from local
finished with error [-1009] Error Domain=NSURLErrorDomain Code=-1009 "The Internet connection appears to be offline." UserInfo={_kCFStreamErrorCodeKey=50, NSUnderlyingError=0x600002fd50e0 {Error Domain=kCFErrorDomainCFNetwork Code=-1009 "(null)" UserInfo={_kCFStreamErrorCodeKey=50, _kCFStreamErrorDomainKey=1}}, _NSURLErrorFailingURLSessionTaskErrorKey=LocalDownloadTask <98102CE1-D70F-4A0E-AB89-8CAAEFE29213>.<13>, _NSURLErrorRelatedURLSessionTaskErrorKey=(
"LocalDownloadTask <98102CE1-D70F-4A0E-AB89-8CAAEFE29213>.<13>"
), NSLocalizedDescription=The Internet connection appears to be offline., NSErrorFailingURLStringKey=http://pdfv2.zta.com/Pdf/GetPageFromPdfUrl?url=http://d4static.zed.com/31/2019/8585/6999605/6999605.pdf&page=1,
Here I am checking if file exists or not first.
//Call it like this
checkIfFileExists(urlString: "your_url")
func checkIfFileExists(urlString: String) {
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let url = NSURL(fileURLWithPath: path)
let fileName = urlString.fileName()
let fileManager = FileManager.default
let filePath = url.appendingPathComponent("\(fileName).pdf")?.path
print("filePath : \(String(describing: filePath))")
if fileManager.fileExists(atPath: filePath!) {
print("File exists")
} else {
print("File doesn't exists")
stackoverFlowTask()
}
}
func stackoverFlowTask() {
downloadFile(url: "your_url", filetype: ".pdf", callback: { success, response in
if !success || response == nil {
return false
}
return true
})
}
public func downloadFile(url:String, filetype: String, callback:#escaping (_ success:Bool, _ result:Any?)->(Bool)) -> Void {
var destination = DownloadRequest.suggestedDownloadDestination(for: .documentDirectory)
if filetype.elementsEqual(".pdf"){
destination = { _, _ in
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let downloadFileName = url.fileName()
let fileURL = documentsURL.appendingPathComponent("\(downloadFileName).pdf")
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
}
Alamofire.download(
url,
method: .get,
parameters: nil,
encoding: JSONEncoding.default,
headers: nil,
to: destination).downloadProgress(closure: { (progress) in
//progress closure
print(progress)
}).response(completionHandler: { (DefaultDownloadResponse) in
//here you able to access the DefaultDownloadResponse
//result closure
callback(DefaultDownloadResponse.response?.statusCode == 200, DefaultDownloadResponse.destinationURL?.absoluteString.replacingOccurrences(of: "file://", with: ""))
print(DefaultDownloadResponse)
})
}
Extension:
extension String {
func fileName() -> String {
return self.replacingOccurrences(of: "[/+.:.-=.%&]", with: "", options: .regularExpression, range: nil)
}
}

Not getting eject file after unzipping downloaded file URL

First of all, after successfully download from the URL using Alamofire, I am changing the file extension to.ZIP, then getting an error while unzipping.
Not getting expected file.
let destination = DownloadRequest.suggestedDownloadDestination(for: .documentDirectory)
Alamofire.download(fileURL!, method: .get, parameters: nil, encoding: JSONEncoding.default, headers: headers, to: destination).downloadProgress(closure: { (progress) in
print(progress.completedUnitCount)
}).responseData { (responce) in
let destiUrl = responce.destinationURL
print(destiUrl!)
let newUrl = destiUrl?.deletingPathExtension().appendingPathExtension("zip")
do {
try FileManager.default.copyItem(at: destiUrl!, to: newUrl!)
let unzipDirectory = try Zip.quickUnzipFile(newUrl!)
print(unzipDirectory.absoluteString)
}
catch let error as NSError{
print(error)
}
}
File URL after successful download-->
file:///var/mobile/Containers/Data/Application/9D96958C-903E-4693-9965-6FB919BB24F1/Documents/'87dc4a8ddce24cf9ad35a251d6a98195.hub'
File URL after converting to .zip
file:///var/mobile/Containers/Data/Application/9D96958C-903E-4693-9965-6FB919BB24F1/Documents/'87dc4a8ddce24cf9ad35a251d6a98195.zip
Final url after unzipping
file:///var/mobile/Containers/Data/Application/9D96958C-903E-4693-9965-6FB919BB24F1/Documents/'87dc4a8ddce24cf9ad35a251d6a98195/
Actual result should be audio file.
Tried replacing the name of the file at the time of successfully download using below code. -->
func saveFileInDocDirectory(data: Data?, fileName: String?, successblock: #escaping (_ path: String?) -> Void) { // To add the image to cache for given identifier.
let paths = NSSearchPathForDirectoriesInDomains( .documentDirectory, .userDomainMask, true)[0] as String
let path = paths.appending("/\(fileName!)")
if (FileManager.default.fileExists(atPath: path)) {
try! FileManager.default.removeItem(atPath: path)
} else {
do {
try data?.write(to: URL(fileURLWithPath: path, isDirectory: false))
successblock(path)
} catch {
successblock(nil)
print("Error while caching the data in cache folder.")
}
}
}
And after that unzipped using SSZipArchive library in Alamofire download function -->
Alamofire.download(fileURL!, method: .get, parameters: nil, encoding: JSONEncoding.default, headers: headers, to: destination).downloadProgress(closure: { (progress) in
print(progress.completedUnitCount)
}).responseData { (responce) in
let destiUrl = responce.destinationURL
print(destiUrl!)
let name = destiUrl?.deletingPathExtension().lastPathComponent
self.saveFileInDocDirectory(data: responce.result.value, fileName: "\(name!).zip", successblock: { (path) in
print(path!)
var filepath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)[0]
let url = URL(fileURLWithPath: filepath)
do {
try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil)
let done = SSZipArchive.unzipFile(atPath: path!, toDestination: url.path)
if done{
let items = try FileManager.default.contentsOfDirectory(atPath: url.path)
print(items)
let destinationUrl = url.appendingPathComponent(items[0])
print(destinationUrl)
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
player = AVQueuePlayer(url: destinationUrl)
player.play()
}
} catch let error as NSError{
print(error)
}
})
}

Downloading to camera roll using alamofire

I'm using this function to download videos to a file called downloads using alamofire.
How would I edit it so it saves videos to the camera roll
func downloadVideoToCameraRoll() {
let destination: DownloadRequest.DownloadFileDestination = { _, response in
let pathComponent = response.suggestedFilename!
var documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
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])
}
Alamofire.download(firstId, method: .get, parameters: nil, encoding: JSONEncoding.default, to: destination)
.downloadProgress(queue: DispatchQueue.global(qos: .utility)) { progress in
self.progresss.setProgress(Float(progress.fractionCompleted), animated: true)
//print("Progress: \(progress.fractionCompleted)")
}
.validate { request, response, temporaryURL, destinationURL in
// Custom evaluation closure now includes file URLs (allows you to parse out error messages if necessary)
return .success
}
.responseJSON { response in
debugPrint(response)
print(response.temporaryURL!)
print(response.destinationURL!)
}
You can use this in swift 3:
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: urlToYourVideo)
}) { saved, error in
if saved {
print("Saved")
}
}
Noted: Need to import Photos
Try the code below
func downloadVideoToCameraRoll() {
let destination: DownloadRequest.DownloadFileDestination = { _, response in
let pathComponent = response.suggestedFilename!
var documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let directoryURL: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
return (directoryURL, [.removePreviousFile, .createIntermediateDirectories])
}
Alamofire.download(firstId, method: .get, parameters: nil, encoding: JSONEncoding.default, to: destination)
.downloadProgress(queue: DispatchQueue.global(qos: .utility)) { progress in
self.progresss.setProgress(Float(progress.fractionCompleted), animated: true)
//print("Progress: \(progress.fractionCompleted)")
}
.validate { request, response, temporaryURL, destinationURL in
// Custom evaluation closure now includes file URLs (allows you to parse out error messages if necessary)
return .success
}
.responseJSON { response in
debugPrint(response)
print(response.temporaryURL!)
print(response.destinationURL!)
saveVideoTo(destinationURL)
}
func saveVideoTo(_ videoUrl:Url?){
if videoUrl != nil {
PHPhotoLibrary.sharedPhotoLibrary().performChanges({ () -> Void in
let createAssetRequest: PHAssetChangeRequest = PHAssetChangeRequest.creationRequestForAssetFromVideoAtFileURL(NSURL(string: videoUrl)!)!
createAssetRequest.placeholderForCreatedAsset
}) { (success, error) -> Void in
if success {
//saved successfully
}
else {
//error occured
}
}
}
}

Path to downloaded file with Alamofire4 (Swift3)

I download a zip file with Alamofire4 and unzip it with SSZipArchive but the unzip does not work. I am not sure if the path of the downloaded file is good with Alamofire.
Here is the code:
let destination: DownloadRequest.DownloadFileDestination = { _, _ in
var documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
return (documentsURL, [.removePreviousFile])
}
Alamofire.download(urlString, method: .get, parameters: parameters, encoding: JSONEncoding.default, to: destination)
.response{ response in
if response.error == nil {
let filename = response.response?.suggestedFilename
var folderDestination=response.destinationURL?.path
folderDestination=folderDestination?.appending("/\(nameCategory)")
archiveToUnzip=(folderDestination?.appending("/\(filename!)"))!
//unzip
let successUnZip=SSZipArchive.unzipFile(atPath: archiveToUnzip, toDestination:folderDestination!)
if !successUnZip {
SpeedLog.print("Problem unzip")
}
}
}
It displays "Problem unzip", so am I wrong in the path to the zip file?
Before unzipping try to check that all paths were correct:
guard let zipPath = (folderDestination?.appending("/\(filename!)"))! else {
print("Error: zipPath are not correct: \(zipPath)")
return
}
guard let unzipPath = folderDestination! else {
print("Error: unzipPath are not correct: \(unzipPath)")
return
}
let success = SSZipArchive.unzipFile(atPath: zipPath, toDestination: unzipPath)
if !success {
print("Error: unzipFile operation failed")
return
}
Simply you can't create folder name by appending the path, You need to create folder separately. Here is the code try this!
let filename = response.response?.suggestedFilename
var folderDestination=response.destinationURL?.path
folderDestination=folderDestination?.appending("/\(nameCategory)")
try! FileManager.default.createDirectory(at: folderDestination!, withIntermediateDirectories: false, attributes: nil)
archiveToUnzip=(folderDestination?.appending("/\(filename!)"))!
//unzip
let successUnZip=SSZipArchive.unzipFile(atPath: archiveToUnzip,toDestination:folderDestination!)

Resources