Downloading a zip file from Dropbox or Google drive URL results in a zip file that is extracted to cpgz file. Using below code to accomplish download of zip file. I want to know is there any specific way to download any zip file so that it does not result in cpgz cycle.
if let zipUrl = URL(string: "https://www.dropbox.com/s/n785nwy2tbaxgz8/app_dfu_package_1.zip?dl=0") {
// create your document folder url
let documentsUrl = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
// your destination file url
let destination = documentsUrl.appendingPathComponent(zipUrl.lastPathComponent)
//print(destination)
var urlRequest = URLRequest.init(url: zipUrl)
urlRequest.httpMethod = "get"
urlRequest.setValue("application/json", forHTTPHeaderField: "content-Type")
// check if it exists before downloading it
if FileManager().fileExists(atPath: destination.path) {
print("The file already exists at path")
} else {
// if the file doesn't exist just download the data from your url
URLSession.shared.downloadTask(with: urlRequest, completionHandler: { (location, response, error) in
// after downloading your data you need to save it to your destination url
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let location = location, error == nil
else { return }
do {
try FileManager.default.moveItem(at: location, to: destination)
print("file saved")
} catch {
print(error)
}
}).resume()
}
}
How we can fix it in Swift code
Sharing the link that solved my issue. I was also getting zip file's mime type text/html. Changing the dropbox url solved my query.
How to download a Dropbox share linkage (aimed at a zip file) to local
if let zipUrl = URL(string: "https://www.dropbox.com/s/n785nwy2tbaxgz8/app_dfu_package_1.zip?dl=1") {
// create your document folder url
let documentsUrl = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
// your destination file url
let destination = documentsUrl.appendingPathComponent(zipUrl.lastPathComponent)
//print(destination)
var urlRequest = URLRequest.init(url: zipUrl)
urlRequest.httpMethod = "get"
urlRequest.setValue("application/json", forHTTPHeaderField: "content-Type")
// check if it exists before downloading it
if FileManager().fileExists(atPath: destination.path) {
print("The file already exists at path")
} else {
// if the file doesn't exist just download the data from your url
URLSession.shared.downloadTask(with: urlRequest, completionHandler: { (location, response, error) in
// after downloading your data you need to save it to your destination url
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let mimeType = response?.mimeType,
let location = location, error == nil
else { return }
do {
try FileManager.default.moveItem(at: location, to: destination)
print("file saved")
print("Mime Type:- \(mimeType)")
} catch {
print(error.localizedDescription)
}
}).resume()
}
}
After making URL change(dl=0 to dl=1), the mime type I get is application/binary. It solved .zip to .cpgz cycles on the extraction of the zip file.
First make sure that your server is returning a zip file itself or not by adding following code :
let response = response as! HTTPURLResponse
guard (200...299).contains(response.statusCode) else {
… deal with the server-side error …
}
guard response.mimeType == "application/zip" else {
… deal with the bogus `Content-Type` …
}
If its a zip file then it will execute forward and you have to do like follows to save that file in disk:
let destinationDir = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let destination = destinationDir.appendingPathComponent("SomeFileName.zip")
try data!.write(to: destination)
Related
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.
This question already has answers here:
How to write a file to a folder located at Apple's Files App in Swift
(2 answers)
Closed 3 years ago.
I tried downloading pdf files with the below code. Here it's storing in the app data. But I need to show the downloaded pdf in "Files" folder in iPhone.
// Create destination URL
let documentsUrl:URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first as URL!
let destinationFileUrl = documentsUrl.appendingPathComponent("downloadedFile.jpg")
//Create URL to the source file you want to download
let fileURL = URL(string: "http://swift-lang.org/guides/tutorial.pdf")
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig)
let request = URLRequest(url:fileURL!)
let task = session.downloadTask(with: request) { (tempLocalUrl, response, error) in
if let tempLocalUrl = tempLocalUrl, error == nil {
// Success
if let statusCode = (response as? HTTPURLResponse)?.statusCode {
print("Successfully downloaded. Status code: \(statusCode)")
}
do {
try FileManager.default.copyItem(at: tempLocalUrl, to: destinationFileUrl)
} catch (let writeError) {
print("Error creating a file \(destinationFileUrl) : \(writeError)")
}
} else {
print("Error took place while downloading a file. Error description: %#", error?.localizedDescription ?? "");
}
}
task.resume()
Is it possible??
Here's how to download any files and save to Photos(if image file) or Files (if pdf)
let urlString = "your file url"
let url = URL(string: urlString)
let fileName = String((url!.lastPathComponent)) as NSString
// Create destination URL
let documentsUrl:URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first as URL!
let destinationFileUrl = documentsUrl.appendingPathComponent("\(fileName)")
//Create URL to the source file you want to download
let fileURL = URL(string: urlString)
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig)
let request = URLRequest(url:fileURL!)
let task = session.downloadTask(with: request) { (tempLocalUrl, response, error) in
if let tempLocalUrl = tempLocalUrl, error == nil {
// Success
if let statusCode = (response as? HTTPURLResponse)?.statusCode {
print("Successfully downloaded. Status code: \(statusCode)")
}
do {
try FileManager.default.copyItem(at: tempLocalUrl, to: destinationFileUrl)
do {
//Show UIActivityViewController to save the downloaded file
let contents = try FileManager.default.contentsOfDirectory(at: documentsUrl, includingPropertiesForKeys: nil, options: .skipsHiddenFiles)
for indexx in 0..<contents.count {
if contents[indexx].lastPathComponent == destinationFileUrl.lastPathComponent {
let activityViewController = UIActivityViewController(activityItems: [contents[indexx]], applicationActivities: nil)
self.present(activityViewController, animated: true, completion: nil)
}
}
}
catch (let err) {
print("error: \(err)")
}
} catch (let writeError) {
print("Error creating a file \(destinationFileUrl) : \(writeError)")
}
} else {
print("Error took place while downloading a file. Error description: \(error?.localizedDescription ?? "")")
}
}
task.resume()
I have written code for retrieving data from file or download that file and show in your phone. But I don't understand why it is not displaying data. When I opened that file it shows blank and but it is printing some hexadecimal code in a console.
I am very new to this IOS development, kindly please help me this. I want to retrieve data from the server through a link, and display it on the mobile.
Thanks in advance you can help with some alternate way.
Below is my code
let username = "xxxx"
let password = "xyz"
let loginData = String(format: "%#:%#", username, password).data(using: String.Encoding.utf8)!
let base64LoginData = loginData.base64EncodedString()
// create the request
let url = URL(string: "http://demo.xyz.com/dctm-rest/repositories/iol_ref2/objects/0900a1848039590d/content-media?format=crtext&modifier=&page=0")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("Basic \(base64LoginData)", forHTTPHeaderField: "Authorization")
let session = URLSession.shared
let taskk = session.downloadTask(with: request) { (tempLocalUrl, response, error) in
if let tempLocalUrl = tempLocalUrl, error == nil {
// Success
if let statusCode = (response as? HTTPURLResponse)?.statusCode {
print("Success: \(statusCode)")
}
do {
let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
if paths.count > 0
{
documentsDirectory = paths.first!
}
var fileURL : URL = URL(fileURLWithPath: documentsUrl!.absoluteString+filename)
if(mimetype == "text/plain"){
fileURL = URL(fileURLWithPath: documentsUrl!.absoluteString+filename+".txt")
}else if(mimetype == "application/pdf"){
fileURL = URL(fileURLWithPath: documentsUrl!.absoluteString+filename+".pdf")
}
print(fileURL)
let dataFromURL = NSData(contentsOf: tempLocalUrl)
dataFromURL?.write(to: fileURL, atomically: true)
print(dataFromURL)
try FileManager.default.copyItem(at: tempLocalUrl, to: fileURL)
OperationQueue.main.addOperation {
self.activityIndicator.stopAnimating()
UIApplication.shared.endIgnoringInteractionEvents()
let documentController = UIDocumentInteractionController.init(url: fileURL)
documentController.delegate = self as? UIDocumentInteractionControllerDelegate
documentController.presentPreview(animated: true)
}
} catch (let writeError) {
print("error writing file : \(writeError)")
}
} else {
print("Failure: %#", error?.localizedDescription);
}
}
taskk.resume()
I have a mini file manager in my app that user can copy files with iTunes - FileSharing into the app and then upload them to the server
I used post Method But Didn't worked for me! I got the files url in the device But I don't know this way that I choose is right or not
here is the file detection codes
// Get the document directory url
let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
do {
// Get the directory contents urls (including subfolders urls)
let directoryContents = try FileManager.default.contentsOfDirectory(at: documentsUrl, includingPropertiesForKeys: nil, options: [])
print(directoryContents)
// if you want to filter the directory contents you can do like this:
let pdfFiles = directoryContents.filter{ $0.pathExtension == "pdf" }
print("pdf urls:",pdfFiles)
filesurl = pdfFiles
print(pdfFiles.count)
filesCount = pdfFiles.count
let pdfFileNames = pdfFiles.map{ $0.deletingPathExtension().lastPathComponent }
print("pdf list:", pdfFileNames)
filestext = pdfFileNames
} catch let error as NSError {
print(error.localizedDescription)
}
and here is the Url of files in the device
var names = String()
let rows = fileTableView.indexPathsForSelectedRows.map{$0.map{$0.row}}
print(rows!)
for i in rows! {
print(filesurl[i])
print(filestext[i])
names.append("\(filestext[i])")
fileManagerViewController.filesForUpload.append(filesurl[i])
}
and here is the post method that I used
let parameters = ["myfile" : "\(fileManagerViewController.filesForUpload)"]
print(parameters)
guard let url = URL(string: "http://example.com/api/file?api_token=\(EmailSignInViewController.api_token)") else {return}
var request = URLRequest(url: url)
request.httpMethod = "POST"
guard let httpBody = try? JSONSerialization.data(withJSONObject: parameters, options: []) else {return }
request.httpBody = httpBody
let session = URLSession.shared
session.dataTask(with: request) { (data , response , error) in
if let response = response {
print(response)
}
if let data = data {
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
print(json)
} catch {
print(error)
}
}
}.resume()
How can I check if a given file has been already downloaded before re-downloading it by using Alamofire? I'm using suggestedDownloadDestination so Alamofire will automatically choose the name of the file and save it in the choosen directory, for example the .CachesDirectory. The problem is that the value given by suggestedDownloadDestination is of type DownloadFileDestination which will return a NSURL only by calling him with the request's response, but in this way I could not ever know the file path without performing a request before.
This is the code I currently use to download a file with Alamofire:
Alamofire.download(.GET, downloadLink, destination: destination).progress {
bytesRead, totalBytesRead, totalBytesExpectedToRead in
}.response {
request, response, data, error in
guard error == nil else {
return
}
// This will give me the file path but we're already in a Request!
print("\(destination(NSURL(string: "")!, response!))")
}
What am I missing?
Not sure if you figured this out yet, but you can create an extension over Alamofire.DownloadRequest like:
extension Alamofire.DownloadRequest {
open class func suggestedDownloadDestination(
for directory: FileManager.SearchPathDirectory = .documentDirectory,
in domain: FileManager.SearchPathDomainMask = .userDomainMask,
with options: DownloadOptions)
-> DownloadFileDestination
{
return { temporaryURL, response in
let destination = DownloadRequest.suggestedDownloadDestination(for: directory, in: domain)(temporaryURL, response)
return (destination.destinationURL, options)
}
}
}
Now you can specify in the options parameter if you want the file to be overwritten:
let destination = DownloadRequest.suggestedDownloadDestination(for: .cachesDirectory,
in: .userDomainMask,
with: [DownloadRequest.DownloadOptions.removePreviousFile])
Here is the solution which I used
Download a file
check if it exist if so just return the path otherwise
func downloadDocumentFile(filePath: String,onDownloadProgress: #escaping(_ progress: Double) -> Void,onError: #escaping(_ errorMessage: String) -> Void,onSuccess: #escaping(_ destinationUrl: URL) -> Void){
guard let url = URL(string: filePath) else {
onError("Couldn't create url from passed file path")
assertionFailure()
return
}
let destination = DownloadRequest.suggestedDownloadDestination(for: .documentDirectory, in: .userDomainMask)
Alamofire.download(url, to: destination)
.downloadProgress { (progress) in
onDownloadProgress(progress.fractionCompleted)
}
.responseData(queue: .main) { (response) in
switch response.result {
case .success:
if let destinationUrl = response.destinationURL {
onSuccess(destinationUrl)
}else {
onError("Couldn't get destination url")
assertionFailure()
}
case .failure(let error):
// check if file exists before
if let destinationURL = response.destinationURL {
if FileManager.default.fileExists(atPath: destinationURL.path){
// File exists, so no need to override it. simply return the path.
onSuccess(destinationURL)
print()
}else {
onError(error.localizedDescription)
assertionFailure()
}
}else {
onError(error.localizedDescription)
assertionFailure()
}
}
}
}