How to Use SwiftyDropbox's "destination" with a Download - dropbox-api

In reviewing the SwiftyDropbox tutorial in the v2 Dropbox API, it shows how to perform a download:
// Download a file
let destination : (NSURL, NSHTTPURLResponse) -> NSURL = { temporaryURL, response in
let fileManager = NSFileManager.defaultManager()
let directoryURL = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
// generate a unique name for this file in case we've seen it before
let UUID = NSUUID().UUIDString
let pathComponent = "\(UUID)-\(response.suggestedFilename!)"
return directoryURL.URLByAppendingPathComponent(pathComponent)
}
client.files.download(path: "/MyFile.db", destination: destination).response { response, error in
if let (metadata, url) = response {
print("*** Download file ***")
let data = NSData(contentsOfURL: url)
print("Downloaded file name: \(metadata.name)")
print("Downloaded file url: \(url)")
print("Downloaded file data: \(data)")
} else {
print(error!)
}
}
I'm unclear what's going on with the destination part. Why do I need to generate a random string for the filename?
When I try to specify my own filename, the download doesn't seem to work:
let destination : (NSURL, NSHTTPURLResponse) -> NSURL = { temporaryURL, response in
let directoryURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
return directoryURL.URLByAppendingPathComponent("MyFile.db")
}
I want to download a file from Dropbox named MyFile.db and I want to put it in my device's documents directory with the name MyFile.db and overwrite it if it's already there.
How can I do that?

When you say it doesn't seem to work, I expect you mean you get an error like this:
Error Domain=NSCocoaErrorDomain Code=516 "“CFNetworkDownload_bPYhu1.tmp” couldn’t be moved to “Documents” because an item with the same name already exists." UserInfo={NSSourceFilePathErrorKey=..., NSUserStringVariant=(
Move
), NSDestinationFilePath=..., NSUnderlyingError=0x7fda0a67cea0 {Error Domain=NSPOSIXErrorDomain Code=17 "File exists"}}
SwiftyDropbox, by virtue of using AlamoFire, doesn't currently let you overwrite files using the download function.
Specifically, SwiftyDropbox calls download in AlamoFire, and AlamoFire calls NSFileManager.moveItemAtURL. The documentation for NSFileManager.moveItemAtURL says:
If an item with the same name already exists at dstURL, this method aborts the move attempt and returns an appropriate error.
So, it seems like it's just being cautious, and making it hard for your app to accidentally overwrite (ad potentially lose) data. If you definitely know you want to overwrite a particular file, you'll need to do so explicitly, after the Dropbox API call. We'll consider this a feature request though.
Update: SwiftyDropbox now offers the ability to overwrite the files directly as of version 3.1.0, e.g., using download(path:rev:overwrite:destination:).

Related

FileManager and accessing app group error

I've been having problem with using share extension to get a file and unzip it into my app group folder.
Here is the code:
for attachment in content.attachments as! [NSItemProvider] {
if attachment.hasItemConformingToTypeIdentifier(contentType){
attachment.loadItem(forTypeIdentifier: contentType, options: nil) { (data, error) in
let fileManager = FileManager()
let url = data as! URL
let destinationURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.KakaoAnalyzer")?.appendingPathComponent("KakaoFilesTemp")
do {
try fileManager.createDirectory(at: destinationURL!, withIntermediateDirectories: true, attributes: nil)
try fileManager.unzipItem(at: url, to: destinationURL!)
self.mergeFiles()
print("SUCCESSFULLY UNZIPPED")
} catch {
print(error.localizedDescription)
print("UNZIP FAILED")
}
}
}
}
This is part of my didSelectPost()
I'm using share extension to get a ZIP file via share, and ZIPFoundation to unzip it into my destination folder which is my app group with this extension and my main project.
This code runs fine and unzips successfully in a simulator, but when I run it on my phone, I get an error:
The file "FILENAME" couldn't be opened because there is no such file
when there clearly should be one, and
Failed to determine whether URL "FILEURL" is managed by a file provider.
I've tried moving the file at the url to my documentDirectory, but it produces error:
"FILE" couldn't be moved because you don't have permission to access "appGroupFolder"
Does anyone how to solve this? Any help would be greatly appreciated!

How do I write to a local file in Swift/XCTest?

My ultimate question is about saving a screenshot from an AppleTV application using XCTest and Swift4 (running on a MacBook paired to the TV device), but I'm having trouble even writing a simple text string to a local file. If I can get this simple file-save working, I'm hoping I can resolve the screenshot issue. (Apologies for making this look like two questions but they appear to be related and resulted from my troubleshooting efforts.)
First, here's what I'm trying to do with a screenshot, based on sample code I found somewhere online:
let appshot = XCUIApplication().windows.firstMatch.screenshot()
let shotpath = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask)[0].appendingPathComponent("appshot.png")
let shotpathUrl = URL(string: "file://\(shotpath)")
print("Saving to: \(shotpath)")
do {
try appshot.pngRepresentation.write(to: shotpathUrl!)
} catch {
print("Failed saving screenshot due to \(error)")
}
This gives me the following output:
Saving to: file:///var/mobile/Containers/Data/Application/77D52C66-353B-4029-97D5-48E6BAE35C92/Downloads/appshot.png
Failed saving screenshot due to Error Domain=NSCocoaErrorDomain Code=4 "The file “appshot.png” doesn’t exist." UserInfo={NSFilePath=///var/mobile/Containers/Data/Application/77D52C66-353B-4029-97D5-48E6BAE35C92/Downloads/appshot.png, NSUnderlyingError=0x1c405bc60 {Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"}}
Of course, the file doesn't exist because that's the file I'm trying to create. But /var/mobile doesn't exist on my laptop either -- it looks like the path FileManager is building may exist on the AppleTV device, but I want it on my laptop where my test script is executing.
So I backed out to a much more simple case, and even this is giving me problems:
let str = "This is a test"
let path = "file:///Users/haljor/foo.txt"
let pathUrl = URL(string: path)!
print("Path: \(path)")
print("URL: \(pathUrl)")
do {
try str.write(to: pathUrl, atomically: true, encoding: .utf8)
} catch {
print("Caught error writing to \(pathUrl): \(error)")
}
And here's the output:
Path: file:///Users/haljor/foo.txt
URL: file:///Users/haljor/foo.txt
Caught error writing to file:///Users/haljor/foo.txt: Error Domain=NSCocoaErrorDomain Code=4 "The folder “foo.txt” doesn’t exist." UserInfo={NSURL=file:///Users/haljor/foo.txt, NSUserStringVariant=Folder, NSUnderlyingError=0x1c40553f0 {Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"}}
Here, it looks like it's trying to write to a folder at the path I specified, not a file. Clearly there's something I'm not understanding in each of these cases.
I don't really have a preference for whether I use a fully-specified path or something using FileManager -- it just needs to land somewhere on my laptop (not the TV device). What am I missing?
You can add an attachment to the test case and save it to disk too. The problem was that Downloads folder may not exist in the container yet. The best way to handle this is via init-once property:
var downloadsFolder: URL = {
let fm = FileManager.default
let folder = fm.urls(for: .downloadsDirectory, in: .userDomainMask)[0]
var isDirectory: ObjCBool = false
if !(fm.fileExists(atPath: folder.path, isDirectory: &isDirectory) && isDirectory.boolValue) {
try! fm.createDirectory(at: folder, withIntermediateDirectories: false, attributes: nil)
}
return folder
}()
func test() {
let appshot = XCUIScreen.main.screenshot()
let attachment = XCTAttachment(screenshot: appshot)
attachment.lifetime = .keepAlways
self.add(attachment)
// Save to container
let url = downloadsFolder.appendingPathComponent("appshot.png")
try! appshot.pngRepresentation.write(to: url)
}
If you want to view the attachment, right-click on the test case, select Jump to Report and expand the tree. You will see the screenshot eventually:

Copy TPK file from AppGroup Container to Documents

I have a file that exists within the AppGroup Shared Container and I was wondering if it was possible to copy the file from the Shared Container into the application bundle.
I am getting the file path as follows :
let filePath = NSFileManager.defaultManager().containerURLForSecurityApplicationGroupIdentifier("group.com.sharedBasemap")!.URLByAppendingPathComponent("localLayer.tpk")!.path
The reason I am trying to do this is it seems that the ArcGIS SDK will not recognize the TPK file from within the App Group so I am wondering if it will recognize it if I copy it into the app bundle.
EDIT: Based on Leo's comment it appears that you can not copy to the bundle, so I am trying to copy to the App Support folder.Here is my code now, I see the "file exists" message but then it is displaying the Oops message indicating it can not move the file :
let filePath = NSFileManager.defaultManager().containerURLForSecurityApplicationGroupIdentifier("group.com.sharedBasemap")!.URLByAppendingPathComponent("localLayer.tpk")!.path!
let appSupportFolder = String(NSFileManager.defaultManager().URLsForDirectory(.ApplicationSupportDirectory, inDomains: .UserDomainMask)[0]) + "localLayer.tpk"
let fileManager = NSFileManager.defaultManager()
if NSFileManager.defaultManager().fileExistsAtPath(filePath){
print("File exists at \(filePath)")
do {
try fileManager.copyItemAtPath(filePath, toPath: appSupportFolder)
}
catch let error as NSError {
print("Ooops! Something went wrong: \(error)")
}
} else {
print("File does not exist")
}
EDIT 2: I have modified the code again to just move the TPK file into the documents directory.I believe that piece is working but I receive an error message when trying to load the TPK file into ArcGIS.At this point in time, I am thinking that the issue is related to the ArcGIS SDK and that it does not support loading a TPK file from anywhere except the application bundle.
let destPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first!
let fullDestPath = NSURL(fileURLWithPath: destPath).URLByAppendingPathComponent("localLayer.tpk")
let fullDestPathString = fullDestPath!.path!
im pretty sure the appSupportFolder doesn't exist by default -- nobody creates it unless needed -- try to verify that first and create it if needed
pseudocode if(!fileExists(supportFolder)) { createDirectory(supportFolder) }

Alamofire not saving file to disk

I have a video file on S3 that I am trying to save to disk. However, if the file already exists on disk, I want to overwrite it. I wrote this function to download the file but it never saves the file. I can see the progress % increasing. But, how do I access the resulting file and save it to disk?
var finalPath: NSURL?
Alamofire.download(.GET, s3Url) { temporaryURL, response in
let fileManager = NSFileManager.defaultManager()
if let directoryURL = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0] as? NSURL {
let pathComponent = response.suggestedFilename
finalPath = directoryURL.URLByAppendingPathComponent(pathComponent!)
println(finalPath)
//remove the file if it exists
if fileManager.fileExistsAtPath(finalPath!.absoluteString!) {
println("file exists on disk, removing..")
fileManager.removeItemAtPath(finalPath!.absoluteString!, error: nil)
}
return finalPath!
}
return temporaryURL
}
.validate()
.progress { bytesRead, totalBytesRead, totalBytesExpectedToRead in
let progress = (Double(totalBytesRead) / Double(totalBytesExpectedToRead)) * 100
println(String(format: "%.2f", progress))
}
.response { request, response, data, error in
println(request)
println(response)
if let mediaData = data {
println("saving file to disk")
mediaData.writeToURL(finalPath!, atomically: true)
}
}
Normally I would use the example provided in the docs, but It fails if the file already exists. ie:
let destination = Alamofire.Request.suggestedDownloadDestination(directory: .DocumentDirectory, domain: .UserDomainMask)
So, how can I download the file, overwrite it if it exists and record the path that the file is written to to my coreData database?
You need to delete the file first. Alamofire only tries to move the file from the temp location to the final location that you provide in the destination closure.
You can create an extension on Alamofire.DownloadRequest to provide options for how to download the file where you can use the option DownloadRequest.DownloadOptions.removePreviousFile.
for details on how to do that see my answer to this question.

Uploading a video to S3 with swift The operation couldn’t be completed. (Cocoa error 260.)"

I am trying to upload a video file to Amazon S3
I constantly get an error 260:
Error in uploading the video: Error
Domain=NSCocoaErrorDomain Code=260 "The operation couldn’t be
completed. (Cocoa error 260.)
I read somewhere that amazon does not support asset library - is it true? and if so what do you suggest
Thanks Eran
func saveVideoToS3 () {
var uploadRequest: AWSS3TransferManagerUploadRequest = AWSS3TransferManagerUploadRequest()
uploadRequest.bucket = "BucketName"
uploadRequest.key = "KeyText"
//Move video file to the application folder so it can be read
var savedVideoURLToBeUsed = NSUserDefaults.standardUserDefaults().objectForKey("ThisIsTheVideoIWantToUse") as! String
println("Video saved in Store: \(savedVideoURLToBeUsed)")
var url: NSURL = NSURL(fileURLWithPath: savedVideoURLToBeUsed)!
uploadRequest.body = url
//uploadRequest.body = NSURL(fileURLWithPath: "file:///\(url)")
println("URL: \(url)")
let transferManager: AWSS3TransferManager = AWSS3TransferManager.defaultS3TransferManager()
transferManager.upload(uploadRequest).continueWithExecutor(AWSExecutor.mainThreadExecutor(), withBlock: { (AWSTask) -> AnyObject! in
//Handle errors
if AWSTask.error != nil {
println("Error in uploading the video: \(AWSTask.error)")
if AWSTask.error.code == 1001 {
self.saveVideoToS3()
}
// Retrive information important for later downloading
} else {
println("Video upload successful..")
var uploadResult: AnyObject! = AWSTask.result
println("Upload result: \(uploadResult)")
//Delete file from the application folder
}
return nil
})
}
Cocoa error 260 is a NSFileReadNoSuchFileError, meaning the path you specified is not valid (file is just not where you say it is), so it probably has nothing with S3 itself. There are three things why this is happening that come to my mind:
you did not use .synchronize() when saving the key to user settings
your file URL contains invalid characters
you did not write the file into filesystem properly
iOS8 Breaking change
Also please note that as of iOS8, due to changes how application work with their assigned sandboxes, you can't save absolute URL to file because next time you open application, it will be different.
Beginning in iOS 8, the Documents and Library directories are no
longer siblings of your application's bundle.
I am using two quick convenience functions that I wrote to get file from cache directory:
func cachePathWithFileName(fileName : String) -> String {
let cacheDirectoryPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as! String
return cacheDirectoryPath.stringByAppendingPathComponent(fileName)
}
and documents directory:
func documentsPathWithFileName(fileName : String) -> String {
let documentsDirectoryPath = NSSearchPathForDirectoriesInDomains(.CachesDirectory, .UserDomainMask, true)[0] as! String
return documentsDirectoryPath.stringByAppendingPathComponent(fileName)
}
Hopefully some of this tips help!