Alamofire: file download and validation failure - ios

In my iOS project I'm using Alamofire library to download remote documents (from a server with Basic Auth) in this way:
let destination: DownloadRequest.DownloadFileDestination = { _, _ in
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let fileURL = documentsURL.appendingPathComponent("foo.pdf")
return (filePath.url, [.removePreviousFile, .createIntermediateDirectories])
}
Alamofire.download(myUrlRequest, to: destination).authenticate(user: user, password: password, persistence: .none).validate().response { response in
print(response)
if response.error == nil, let path = response.destinationURL?.path {
print(path)
}
}
This works great! The file is correctly downloaded in the app's Documents folder.
My problem is when user or/and password are wrong. In this case server response status is 401 Unauthorized and .validate() method correctly fails, but in my Documents folder I find the file "foo.pdf" where the content is a xml that explains the 401 error. What I would like is the file saved only if the validate doesn't fail.
My questions: is there a way, with Alamofire, to save the file just in case the response is validated? Or do I have to manually delete the file when validate fails?

I am having the similar issue at the moment. So far, the only thing I could think of is crude
if response.error != nil {
try? FileManager.default.removeItem(at: destinationURL)
}
in response closure.
I will investigate further though.
EDIT 1:
It seems this issue quite some time ago brought this behaviour https://github.com/Alamofire/Alamofire/issues/233

Related

GCDWebServer: How do I change file permissions on server for WebDAV operations? (iOS)

I'm currently trying to use GCDWebServer to host a local webpage that's requested by a WKWebView when the app starts. I want to be able to upload external files to the server by grabbing files with a UIDocumentPickerViewController while the app is running. It seems like using a separate GCDWebDAVServer on a different port is a good idea for this.
However, if I try to upload a file to the WebDAV server, I get this data from the response:
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><title>HTTP Error 500</title></head><body><h1>HTTP Error 500: Failed moving uploaded file to "/arbitrary.txt"</h1><h3>[NSCocoaErrorDomain] “35B890AC-7CD2-4A10-A67F-BAED3D6C34AB-3278-000005593C778876” couldn’t be moved because you don’t have permission to access “www”. (513)</h3></body></html>
Cutting out this bit for readability:
Failed moving uploaded file to "/arbitrary.txt"</h1><h3>[NSCocoaErrorDomain] “35B890AC-7CD2-4A10-A67F-BAED3D6C34AB-3278-000005593C778876” couldn’t be moved because you don’t have permission to access “www”.
www in this context is the local folder I'm serving with GCDWebServer. It has no subfolders, only files.
Here's the code I'm using in viewDidLoad:
let webContentUrl = Bundle.main.path(forResource: "www", ofType: nil)!
httpServer = GCDWebServer()
webDAVURL = "http://localhost:\(WEBDAV_PORT)/"
webDAVServer = GCDWebDAVServer(uploadDirectory: webContentUrl)
httpServer.addGETHandler(forBasePath: "/", directoryPath: webContentUrl, indexFilename: "index.html", cacheAge: 3600, allowRangeRequests: true)
httpServer.start(withPort: HTTP_PORT, bonjourName: nil)
let options: [String: Any] = [
GCDWebServerOption_Port: WEBDAV_PORT
]
do {
try webDAVServer.start(options: options)
} catch let error {
print("Could not start WebDAV server. Reason: \(error.localizedDescription)")
}
let request = URLRequest(url: URL(string: "http://localhost:\(HTTP_PORT)/")!)
webView.load(request)
And the code used for a PUT request to upload a file to the WebDAV server:
let importedFileUrl = urls.first!
do {
let data = try Data(contentsOf: importedFileUrl)
var request = URLRequest(url: URL(string: "http://localhost:\(WEBDAV_PORT)/arbitrary.txt")!)
request.httpMethod = "PUT"
let task = URLSession(configuration: .ephemeral).uploadTask(with: request, from: data) { data, response, error in
print("Data: \(String(describing: data))")
print("Response: \(String(describing: response))")
print("Error: \(String(describing: error))")
print(String(decoding: data!, as: UTF8.self))
}
task.resume()
} catch let error {
createErrorAlert(message: error.localizedDescription)
}
This doesn't have anything to do with iOS 14 local network privacy features. I tried modifying the Info.plist to include the new keys, but nothing changed. It seems like the www folder doesn't have write permissions.
Is there a feature I'm missing that lets you change file permissions in GCDWebServer, or is there a workaround? Right now the only workaround I can think of is to create a local database alongside the web servers.
The reason write permissions were not allowed is because I was serving files directly from the app bundle, which cannot be written to.
My solution was to copy the contents of that directory into the Documents directory and use WebDAV to write to that directory instead.

FileProvider: content downloaded from FTP server not showing up inside directory

I'm trying to download a file (namely 'xp-en.zip') from a certain server. I've already checked that this file exists using contentsOfDirectory(::), but for some reason, after the download is completed the file doesn't show up in the directory it was supposed to have been copied to. I'm using XCode and I thought it could have something to do with the app's permissions (since the destination directory is .documentDirectory), but I tried changing it to the main bundle to no avail.
I also tried downloading from another server ('speedtest.tele2.net') but it didn't work either.
let credential = URLCredential(user: "anonymous", password: "123456", persistence: .permanent)
let fileManager = FileManager.default
func FTPDownloadUpload(download: Bool = true, server: String, path: String){
let localFilePath = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
guard let url = URL(string: server) else{
print("Invalid URL")
return
}
let FTPProvider = FTPFileProvider(baseURL: URL(string: "ftp://" + server)!, mode: .passive, credential: credential, cache: URLCache())
if download{
FTPProvider?.copyItem(path: "/" + path, toLocalURL: localFilePath, completionHandler: {x in print("Download completed")})
//I know this completionHandler closure kinda sucks but I don't know what else to put inside it. Keep in mind my program doesn't have anything to do after calling this function
}else{
//This is the upload section. Didn't include it to keep the code shorter
}
}
My function call is:
FTPDownloadUpload(server: "176.74.128.88", path: "xp-en.zip")

AlamoFire 5.0 force permanent caching

I am using AlamoFire 5.0 and want to force permanent cache storage of data fetched from urls (.obj files, .png images etc.). Is there some way to ignore/intercept the HTTP headers sent by the server and just force permanent caching?
let destination: DownloadRequest.Destination = { _, _ in
var documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
return (documentsURL, [.removePreviousFile])
}
let request = AF.download("http://someurl.com", to: destination)
request
.response(completionHandler: { (response) in
//handle response data
})
You may want to consider Disk caching your response.
This answer might be of help:
Disk Cache Images

How to correctly reference/retrieve a temp file created in AppData for file upload to a server?

So the app I'm making creates a file called "logfile" and I'm trying to send that file via Alamofire upload to a server. The file path printed in the console log is
/var/mobile/Containers/Data/Application/3BE13D78-3BF0-4880-A79A-27B488ED9EFE/Documents/logfile.txt
and the file path I can use to manually access the log created in the .xcappdata is
/AppData/Documents/logfile.txt
To access it, I'm using
let fileURL = Bundle.main.url(forResource: "", withExtension: "txt")
where inbetween the double quotes for "forResource", I've tried both file paths I listed in the previous paragraph as well as just the file name but I'm getting a nil value for file found for either. The file isn't recognized to be there, presumably because the file path I'm using is wrong as Alamofire is returning nil when trying to locate send the file. Anyone know the direct file path I'm supposed to use to be able to grab my file since the other two don't supposedly work? Thank you!
Use below code to get string data from text file to upload to server:
let fileName = "logfile"
let documentDirURL = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let fileURL = documentDirURL.appendingPathComponent(fileName).appendingPathExtension("txt")
print("FilePath: \(fileURL.path)")
var readString = "" // Used to store the file contents
do {
// Read the file contents
readString = try String(contentsOf: fileURL)
} catch let error as NSError {
print("Failed reading from URL: \(fileURL), Error: " + error.localizedDescription)
}
print("File Text: \(readString)") // Send 'readString' to server
If you're dynamically creating the file at runtime, it won't be in your app bundle so the Bundle class won't be able to find it. The directories you see are also dynamically-generated and not only platform-specific, but also device-specific, so you can't use the file paths directly. Instead, you'll have to ask for the proper directory at runtime from the FileManager class, like this:
guard let documents = FileManager.default.urls(for: .documentsDirectory, in: .userDomainMask).first else{
// This case will likely never happen, but forcing anything in Swift is bad
return
}
let logURL = URL(string: "logfile.txt", relativeTo: documents)
do{
let fileContents = String(contentsOf: logURL)
// Send your file to your sever here
catch{
// Handle any errors you might've encountered
}
Note that I'm guessing based on the paths you pasted in your answer you put it in your application's documents directory. That's a perfectly fine place to put this type of thing, but if I'm wrong and you put it in a different place, you'll have to modify this code to point to the right place

Swift File Download Issue

I am trying to download a plist file from a remote location and use it in the iOS app I am creating. The file is going to be used for calendar details within the app's calendar. The goal is obviously that I can update the remote file instead of having to push updates to the app itself every time we need to make changes to calendar details.
I started with the code used in this example: Download File From A Remote URL
Here is my modified version:
// Create destination URL
let documentsUrl:URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first as URL!
let destinationFileUrl = documentsUrl.appendingPathComponent("2017.plist")
//let destinationFileUrl = URL(string: Bundle.main.path(forResource: String(currentYear), ofType: "plist")!)
//Create URL to the source file you want to download
let fileURL = URL(string: "https://drive.google.com/open?id=0BwHDQFwaL9DuLThNYWwtQ1VXblk")
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.removeItem(at: destinationFileUrl)
try FileManager.default.moveItem(at: tempLocalUrl, to: destinationFileUrl)
print("File was replaced")
print(NSArray(contentsOf: tempLocalUrl))
//print(tempLocalUrl)
} catch (let writeError) {
print("Error creating a file \(String(describing: destinationFileUrl)) : \(writeError)")
}
} else {
print("Error took place while downloading a file. Error description: %#", error?.localizedDescription as Any);
}
}
task.resume()
I originally tried to overwrite the file that is bundled with the app to being with, that resulted in errors. So I instead tried to just save it in the app's documents folder and that removed that error. I had to make sure and remove any previous version of the file because it was giving me a file already exists error after the first run.
While it says everything is working (The outputs for both successful download and replaced file happen) when I print the contents of the array from the downloaded URL it just gives me nil.
This is my first attempt to use any kind of external resources in an app. Before I have always kept everything internal, so I am sure there is something glaringly obvious I am missing.
Update 1:
I realized I didn't have the correct URL to use to download a file from a Google drive. That line of code has been changed to:
let fileURL = URL(string: "https://drive.google.com/uc?export=download&id=0BwHDQFwaL9DuLThNYWwtQ1VXblk")
So now I actually am downloading the plist like I originally thought I was. Even removing the deletion issue mentioned in the first comment, I still can't get the downloaded file to actually replace the existing one.
Update 2:
I have reduced the actual file manipulation down to the following:
do {
try FileManager.default.replaceItemAt(destinationFileUrl, withItemAt: tempLocalUrl)
print("File was replaced")
print(NSArray(contentsOf: destinationFileUrl))
} catch (let writeError) {
print("Error creating a file \(String(describing: destinationFileUrl)) : \(writeError)")
}
} else {
print("Error took place while downloading a file. Error description: %#", error?.localizedDescription as Any);
}
After the replacement is performed the output of the file shows the correct new contents that were downloaded from the internet.
Later in the code when I try and access the file it seems to be nil in content again.
Look at your download completion code. You:
Delete the file at the destination URL (in case there was one
leftover)
MOVE the temp file to the destination URL (removing it from the temp
URL)
Try to load the file from the temp URL.
What's wrong with this picture?
You are trying to get the contents of the moved file. You already moved the file to destination url and then you are trying to get the contents of the file from temporary location.
For getting file data, Please try the following :
let fileData = try! String(contentsOf: destinationFileUrl, encoding: String.Encoding.utf8)
print(fileData)

Resources