I'm basically doing variations on this:
let dir = fileManager.url(forUbiquityContainerIdentifier: nil)?
let files = try fileManager.contentsOfDirectory(at: dir)
trying to get to the root (i.e., the "/") of iCloud Drive, where I have some files that aren't in any folder.
However, dir is (expectedly, I guess) my app's folder.
Is it even possible to access the root of iCloud Drive? I mean, users can obviously drop files here, but can I get to that directory? (Note that for the moment I'm trying to avoid using a DocumentPickerViewController.)
It is not possible to access directly files outside of your app's iCloud container. I you want to allow users to give your app access to files that are potentially stored somewhere else, you need to UIDocumentPickerViewController.
More information on iCloud documents can be found here:
https://developer.apple.com/library/content/documentation/General/Conceptual/iCloudDesignGuide/Chapters/DesigningForDocumentsIniCloud.html#//apple_ref/doc/uid/TP40012094-CH2-SW1
Related
Basically my app works like a iCloud-based note app (for example, logseq). Users first select a folder in Files app, then my app starts downloading/updating the contents of the folder.
So I implemented folder selection via UIDocumentPickerViewController, after I got the folder path in Files app. I tried downloading a hardcoded file like (<the folder path user just selected>/fileList.json) via FileManager.startDownloadingUbiquitousItem, got 257 permission error (the file does exist in my icloud drive). Then I followed the answer from this answer to call startAccessingSecurityScopedResource. It always returns false and the same 257 error returned.
Perhaps, I cannot access a random file from a selected folder? but how can those note apps work by simply letting users selecting a root folder?
Answering my own question after some debugging. Call startAccessingSecurityScopedResource on the folder path you grabbed from picker, not its subfiles. After that, you can start downloading its subfiles via startDownloadingUbiquitousItem
My iOS app is writing to local files to:
/var/mobile/Containers/Data/Application/A.../Library/a.txt
When my app updates, it gets a new application container ID and folder:
/var/mobile/Containers/Data/Application/B...
What happens to all the files I wrote to container ID A?
Is there an "update hook" that will allow me to copy all the "A" container files to path "B"?
Can the new version of the app (B) read the old versions files (A)?
Is there any documentation around what happens to the filesystem during updates?
Is it possible to recover files from container A after B has been installed?
When you update an app, by changing its version number in the .plist file, iOS creates a new directory for that app with a different hexadecimal name and it copies all the files to the new directories. Now if you are using the absolute paths to get the details of files from the directories then those paths would be incorrect and you won't get the file details.
I just tried this in simulator. I created a function as below which will return the document directory path url
func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentsDirectory = paths[0]
return documentsDirectory
}
I called this function from didFinishLaunchingWithOptions in app delegate when version number was 1.0 and it returned the below path :
file:///Users/BhargavRathod/Library/Developer/CoreSimulator/Devices/3082611F-BCCA-4D17-B390-E0EF4CA454DA/data/Containers/Data/Application/72759097-38F3-4292-825E-1D2343219973/Documents/
When I updated the version number to 1.1 it returned me the new path as :
file:///Users/BhargavRathod/Library/Developer/CoreSimulator/Devices/3082611F-BCCA-4D17-B390-E0EF4CA454DA/data/Containers/Data/Application/72DC31E9-C32F-42CC-8449-F1946ADB1018/Documents/
So if you are using a absolute path to access any file from document directory then it is not good practice. You can just save the file name(or relative path after the document directory) and whenever the access to that file name is required then get the file name and append it after the document directory path and access the file.
I hope this will be of some help to you.
My iOS app is writing to local files to: /var/mobile/Containers/Data/Application/A.../Library/a.txt
Okay, stop right there. This is wrong.
Your app has its own sandbox. This sandbox persists forever, thru updates, as long as your app keeps its Bundle ID, and as long as the user does not delete the app.
You thus have no business knowing or thinking about the full absolute path to where your file is. All you know, and all you need to know, is that it is in your app’s sandbox in the Library directory. You can, at any time, obtain the URL of the Library directory by means of its search path:
https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory/librarydirectory
And that is where the file will always be. As long as you ask the FileManager for your Library directory and for the a.txt file within it, you will find the same file, regardless of any updates, as long as the user doesn’t actually delete your app (because that deletes the sandbox).
Can explain what happens to the files
Nothing. They stay where they are within the sandbox. The absolute URL of the sandbox may change, but your files are unaffected.
how to keep the files written by the previous version of the app
They are kept automatically. You don’t have to do anything.
(Having said all that, keep in mind that if you submit an app with a different bundle id, that is not a new version of your app. It is a totally different app. In that case you would have a very different problem to solve. This would be no different from any other problem of communicating files from one app to another. You’d need to put the files in a common location, make them available thru the Document Browser, make them exportable by the user, or whatever.)
I have developed an iOS app that has been downloading files and storing data in the Documents directory. I am storing document’s directory path (where downloaded files get store) in sqlite database. Now if App version get changes then path for database file and document’s directory get changes. If I have manually delete the app and install it again then downloaded files get removed from document’s directory and user has to download files again. Is there any way to get my database file with previously downloaded document’s directory path so that no need to download files again Or any other solution for this?
Understand this concept for document directory
If you update the newer version of the App, Data in the document directory will not getting erased. It will be there. Just the path to reach the document directory get's updated.
So, to overcome the path changing problem. Don't store the entire path to your database file. Just store the ending path or name of the downloaded content to the document directory. Now, if you wants to get some file, lets say example.jpg from the document directory, then you should first get the path to document directory and then append the path of your file.
PATH_TO_DOCUMENT_DIRECTORY/example.jpg this is the complete path for your image. and same is applies for your database.sqlite file.
You can use this handy functions:
func documentsDirPath() -> String {
var paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true) as [String]
return paths[0]
}
And can call it in this way:
let pathToDocumentDirectory = documentsDirPath()
let fileName = "example.jpg" // This you can get from the database.
let filePath = pathToDocumentDirectory + "/\(fileName)"
print(pathToDocumentDirectory)
And for the app that get's deleted, there is no option to recover the contents of the document directory, unless you code and save the data to iCloud and on newer installation of the app, in applicationDidFinishLaunching check if there is some files, present on iCloud with name database.sqlite and other, then you can fetch it and put it to document directory and later you can use.
Hope it helps
I'm trying to adopt iCloud Drive to store backups of the data of my application using a single file for each backup, if it's relevant they're simple XML files with a custom extension. File creation and upload are working fine but as I'm now trying to let the user manually delete backups I found out that I cannot delete the file programmatically but I can do it if I go to iCloud Drive from the app on iOS or the folder in Finder on macOS.
To save the files I first retrieve the root for the container with
FileManager.default.url(forUbiquityContainerIdentifier: nil)?.appendingPathComponent("Documents")
and create the Document folder if it doesn't exists as I want the user to be able to view and edit the files, after creating the file locally in the temporary directory I upload it with
let file = FileManager.default
if file.fileExists(atPath: remotePath.path) {
try? file.removeItem(at: remotePath)
}
try? file.setUbiquitous(true, itemAt: localPath, destinationURL: remotePath)
where remotePath is the path retrieved before with the file name appended. To load the files I use a NSMetadataQuery and get the path of each file with NSMetadataItemURLKey on each returned item, which is working fine but when I attempt to
try? FileManager.default.removeItem(at: retrievedPath)
it works if the same device created the file but if it is from another one it always fail and the error says that the file does not exists at the specified path.
I've removed error handling code for clarity but I've tested this behaviour inspecting the thrown error.
How can I delete any file even from other devices, am I missing some steps?
I was indeed missing something, after reading this question, in particular this answer, I found out that even if the NSMetadataQuery returns the path correctly is possible that the file has not been downloaded to the the device, hence the error.
To correctly delete a file you have to mimic the behaviour of UIDocument and use a NSFileCoordinator on a background queue, refer to the answer referenced before for implementation details.
On my app (swift 2.3, xcode 7+, for iOS 9+) I'm write to a file, use the file, and then delete the file from a tmp directory created with NSTemporaryDirectory (on the app sandboxed). Although its a quick automated sequence, for a brief moment the file is written to the tmp directory in order to be used and then deleted.
My concern is: How secure, for that brief moment, is the file stored at tmp directory? Could an attacker get access to the file at the moment it's on the tmp directory?
If you're writing a file just to delete it, you should try to avoid storing the file on the system altogether. If you absolutely have to store the data on the device, you can use the .completeFileProtection option.
According to the documentation, .completeFileProtection will make it so the file is accessible only when the device is unlocked. This means that the only way that someone would have access to your data is if they have your phone's password, are somehow able to remotely unlock it using said password, and then execute code that has access to your app's sandboxed temporary file storage.
As far as I know, this would be very hard to achieve.