My app writes files to the users shared Documents directory. I save the files path but this breaks if the user changes the documents name, moves the document to a new location etc. Is there some kind of persistent file id i can use to keep track of these documents?
i've been poking around
FileManager.default.attributesOfItem(atPath: docUrl.path)
and i find attributes like FileAttributeKey.systemFileNumber but this number seems to change every time i run the app. There has to be some way to keep track of files other than their path
You need to use url bookmarks, like the following (sketch)
if let bookmark = try? URL(fileURLWithPath: docUrl.path)
.bookmarkData(includingResourceValuesForKeys: nil,
relativeTo:nil) {
// store somewhere persistent bookmark to your file
}
...
let bookmark = // read bookmark from persisten storage
var stale = false
if let url = try? URL(resolvingBookmarkData:bookmark, bookmarkDataIsStale: &stale) {
// work with resolved url
} else {
// file disappeared - handle the situation
}
Related
I am adding an option in my App to backup everything to a file.
For that, I am serializing all my objects to a dictionary that I save in a single file in the path provided by FileManager.default.temporaryDirectory. Then I ask the user for a path to save this file using a UIDocumentPickerViewController.
Everything works fine, but I wonder what happens with the temporal file that I created in the temporaryDirectory. Is this automatically removed by the operating system? should I take care of removing it by myself?
This is how I get the path of the temporary directory for my file:
private var exportURL: URL {
let documentURL = FileManager.default.temporaryDirectory
let fileName = "archive"
let filePath = "\(fileName).backup"
return documentURL.appendingPathComponent(filePath)
}
I am working on an Application which deals with audio files. For this application, it is best not to stream the files, since you might download the files to your device and then disconnect from the server and listen to them later, perhaps in areas with no cell reception.
I created a caching system that stores the data to a file path and then stores the file location in CoreData with it's associated data.
self.name = name
var domainMask = UserDefaults.standard.string(forKey: "userDomainMask")
if domainMask == nil {
domainMask = NSSearchPathForDirectoriesInDomains(.cachesDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).first!
UserDefaults.standard.setValue(domainMask, forKey: "userDomainMask")
}
var cachePath = domainMask!
cachePath = (cachePath as NSString).appendingPathComponent(DataCache.cacheDirectoryPrefix + name)
self.cachePath = cachePath
ioQueue = DispatchQueue(label: DataCache.ioQueuePrefix + name)
self.fileManager = FileManager()
}
But when I try it on device, it is able to access the data at the file path, but it fails to load the file and play it.
The kicker is, when I share a file from my phone, it is able to find the file and upload it to the server, then retrieve it from the cache and display and play the file. If I re-run the application, it returns to a state where it can access the data, but the audio file does not load.
I have defined a UTI for a custom document format. I can export files from my app and append them to text messages, email, etc. I can import the files into my app by tapping on the document icon in iMessage. By tapping on the document icon, I have the option to copy to my app. That triggers a call in my AppDelegate to handle the incoming file.
What's bugging me is that the url for the incoming file is:
file:///private/var/mobile/Containers/Data/Application/21377C94-1C3C-4766-A62A-0116B369140C/Documents/Inbox/...
Whereas, when saving documents to the .documents directory, I use this URL:
file:///var/mobile/Containers/Data/Application/21377C94-1C3C-4766-A62A-0116B369140C/Documents/...
The difference being the /private/ and /Inbox/ path components.
Question: how can I purge the /private/.../Inbox/ path of the files that were copied to my app from iMessage? I noticed this when testing my app and when I tapped on the same document icon in iMessage it started generating file copies with the same name but adding -1, then -2, then -3 to the file name of the document from iMessage. It appears that copies are building up in that /private/.../Inbox/ path.
Is that something that gets flushed on its own or can I access that directory to remove those files? It's also annoying because based upon the filename, it appears to be a different file thus allowing multiple copies of the same file to be imported with a slightly different file name.
Ok, this took a fair amount of digging, but I'll post my solution that seems to work thus far in case anyone runs across the same problem.
let fileManager = FileManager.default
// get the URL for the "Inbox"
let tmpDirURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("Inbox")
// get all the files in the "Inbox" directory
let anythingThere = try? fileManager.contentsOfDirectory(at: tmpDirURL, includingPropertiesForKeys: nil)
if anythingThere?.count ?? 0 > 0 {
for eachURL in anythingThere! {
// for each url pointing to a file in that directory, get its path extension
let pathExtension = eachURL.pathExtension
// test to see if it's a UTI that you're interested in deleting
// in my case, the three "OCC" strings are the relevant UTI extensions
if pathExtension == "OCCrcf" || pathExtension == "OCCrdi" || pathExtension == "OCCsrf" {
// attempt to delete the temporary file that was copied to the
// "Inbox" directory from importing via email, iMessage, etc.
try fileManager.removeItem(at: eachURL)
}
}
}
If anyone has a more elegant solution, please respond as well. Thanks.
I have a UNNotificationServiceExtension that downloads videos and images to the Documents directory for use by classes that adopt UNNotificationContentExtension. I want to delete the media files that are no longer being used by any notifications. I am not sure how to go about doing this.
I tried to delete the files in my AppDelegate, but I believe the UNNotificationServiceExtension has its own Documents directory per the "Sharing Data With Your Containing App" section of this document: https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html, so I cannot access these files from my main app. They are in a different container.
I don't want to create an App Group to share the data between the app and the extension just so that I can delete the unused files.
I don't want to delete the unused files in the UNNotificationServiceExtension, because the extension has a limited amount of time in which to complete its work, and if I try to download files and delete other files, it may time out.
I think the best option is to check to see which files are needed by any delivered notifications and to delete the unneeded files in the Notification Service Extension's Documents directory. My concern with this is that the UNNotificationServiceExtension is only given a short period of time during which it must complete all of its work, after which it will time out.
So, my question is, "Is this the right way to clean up unused files from a Notification Service Extension, or is there a better way?"
Thanks to manishsharma93, I was able to implement a good solution. I am now storing the files in a directory shared by the main app and the notification service extension. I first had to set up a shared App Group using the information found here: https://developer.apple.com/library/archive/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/EnablingAppSandbox.html#//apple_ref/doc/uid/TP40011195-CH4-SW19
Then in my AppDelegate, I added this private function, which I call at the end of the applicationDidFinishLaunching(_:) method:
// I call this at the end of the AppDelegate.applicationDidFinishLaunching(_:) method
private func clearNotificationMedia() {
// Check to see if there are any delivered notifications. If there are, don't delete the media yet,
// because the notifications may be using them. If you wanted to be more fine-grained here,
// you could individually check to see which files the notifications are using, and delete everything else.
UNUserNotificationCenter.current().getDeliveredNotifications { (notifications) in
guard notifications.isEmpty else { return }
let fileManager = FileManager.default
guard let mediaCacheUrl = fileManager.containerURL(forSecurityApplicationGroupIdentifier: "group.com.yourGroupHere")?.appendingPathComponent("media_cache", isDirectory: true) else { return }
// Check to see if the directory exists. If it doesn't, we have nothing to do here.
var isDirectory: ObjCBool = false
let directoryExists = FileManager.default.fileExists(atPath: mediaCacheUrl.path, isDirectory: &isDirectory)
guard directoryExists && isDirectory.boolValue else {
print("No media_cache directory to delete.", terminator: "\n")
return
}
// The directory exists and there aren't any notifications using media stored there,
// so go ahead and delete it. Use a lock to make sure that there isn't data corruption,
// since the directory is shared.
let lock = NSLock()
lock.lock()
do {
try FileManager.default.removeItem(at: mediaCacheUrl)
DebugLog("Successfully deleted media_cache directory.")
} catch let error as NSError {
DebugLog("Error: \(error.localizedDescription). Failed to delete media_cache directory.")
}
lock.unlock()
}
}
It works like a charm. Thanks again for pointing me in the right direction manishsharma93.
my app was rejected because of the size of the content that it uploads to iCloud. The only file in my app's Documents folder is the default.realm database file. I think that this is the file that iCloud is uploading. How can I prevent iCloud to upload the database to iCloud?
Thanks.
According to the App Backup Best Practices section of the iOS App Programming Guide, <Application_Data>/Library/Caches or <Application_Data>/tmp will not backup to iCloud. Generally, you can use <Application_Data>/Library/Caches directory to save your data that you won't backup to iCloud.
To change the file path of Realm, you can pass the path parameter when creating Realm instance, like below:
let realmPath = NSSearchPathForDirectoriesInDomains(.CachesDirectory, .UserDomainMask, true)[0] as! String
let realm = Realm(path: realmPath.stringByAppendingPathComponent("data.realm"))
Otherwise, you can use NSURLIsExcludedFromBackupKey file system property to exclude files and directories from backups (See Technical Q&A QA1719). If you want to use the default path, there is the only way to exclude Realm file from backups.
let realm = Realm()
if let realmPathURL = NSURL(fileURLWithPath: realm.path) {
realmPathURL.setResourceValue(NSNumber(bool: true), forKey: NSURLIsExcludedFromBackupKey, error: nil)
}
Looks like the URL API has changed since the previous answer was posted. This is how you can disable the backup now:
let realm = try! Realm()
guard var url = realm.configuration.fileURL else {
return
}
var resourceValues = URLResourceValues()
resourceValues.isExcludedFromBackup = true
try? url.setResourceValues(resourceValues)