FileManager.contentsEqual returns false when comparing copied files - swift

I need to preload SQLite files from my bundle's resources into the application support directory. I want to make sure the correct files are there vs. the empty files that Core Data puts there by default. To do this, I'm using FileManager.default.contentsEqual; however, this always returns false.
I tried testing with a playground, but the copy there is creating alias files, still resulting in a false comparison.
In the app, the files do copy over with the same name and size. The dates are different: the copies have the current date/time rather than the original's timestamps. Using contentsEqual, though, I wouldn't think that matters.
Update: diff at the command line shows the files are the same...
What am I missing?
Here's the code from the playground, which is virtually the same as my app code:
// get the URL for the application support directory
let appSupportDir: URL = try!
FileManager.default.url(for: FileManager.SearchPathDirectory.applicationSupportDirectory,
in: FileManager.SearchPathDomainMask.userDomainMask,
appropriateFor: nil, create: true)
// get the source URLs for the preload files
let sqliteFileBundleURL: URL = Bundle.main.url(forResource: "My_DB", withExtension: "sqlite")!
let sqliteShmFileBundleURL: URL = Bundle.main.url(forResource: "My_DB", withExtension: "sqlite-shm")!
let sqliteWalFileBundleURL: URL = Bundle.main.url(forResource: "My_DB", withExtension: "sqlite-wal")!
// create target URLs for copy to application support directory
let sqliteFileAppSptURL: URL = appSupportDir.appendingPathComponent("My_DB.sqlite")
let sqliteShmFileAppSptURL: URL = appSupportDir.appendingPathComponent("My_DB.sqlite-shm")
let sqliteWalFileAppSptURL: URL = appSupportDir.appendingPathComponent("My_DB.sqlite-wal")
// remove the files if they already exist at the target (for test - app doesn't do this)
do {
let filesFound: [URL] = try FileManager.default.contentsOfDirectory(at: appSupportDir,
includingPropertiesForKeys: nil,
options: .skipsHiddenFiles)
if !filesFound.isEmpty {
for fileURL in filesFound {
try FileManager.default.removeItem(at: fileURL)
}
print("Removed \(filesFound.count) files without error.")
}
}
catch {
print("Error:\n\(error)")
}
// copy the files to the application support directory
do {
try FileManager.default.copyItem(at: sqliteFileBundleURL, to: sqliteFileAppSptURL)
try FileManager.default.copyItem(at: sqliteShmFileBundleURL, to: sqliteShmFileAppSptURL)
try FileManager.default.copyItem(at: sqliteWalFileBundleURL, to: sqliteWalFileAppSptURL)
}
catch {
print("Error: \(error)")
}
// compare the copied target files to their source using contentsEqual
let sqliteFileCopied: Bool =
FileManager.default.contentsEqual(atPath: sqliteFileBundleURL.absoluteString, andPath: sqliteFileAppSptURL.absoluteString)
let sqliteShmFileCopied: Bool =
FileManager.default.contentsEqual(atPath: sqliteShmFileBundleURL.absoluteString, andPath: sqliteShmFileAppSptURL.absoluteString)
let sqliteWalFileCopied: Bool =
FileManager.default.contentsEqual(atPath: sqliteWalFileBundleURL.absoluteString, andPath: sqliteWalFileAppSptURL.absoluteString)

Aha! When using FileManager, one should be using path rather than absoluteString to convert a URL to a String:
// compare the copied target files to their source using contentsEqual
let sqliteFileCopied: Bool =
FileManager.default.contentsEqual(atPath: sqliteFileBundleURL.path, andPath: sqliteFileAppSptURL.path)
let sqliteShmFileCopied: Bool =
FileManager.default.contentsEqual(atPath: sqliteShmFileBundleURL.path, andPath: sqliteShmFileAppSptURL.path)
let sqliteWalFileCopied: Bool =
FileManager.default.contentsEqual(atPath: sqliteWalFileBundleURL.path, andPath: sqliteWalFileAppSptURL.path)
The difference between the two is that path generates a file system-type path:
/var/folders/kb/y2d_vrl133d1b04_5kc3kw880000gn/T/com.apple.dt.Xcode.pg/resources/238FF955-236A-42FC-B6EA-9A74FC52F235/My_DB.sqlite
whereas absoluteString generates a browser-friendly path:
file:///var/folders/kb/y2d_vrl133d1b04_5kc3kw880000gn/T/com.apple.dt.Xcode.pg/resources/238FF955-236A-42FC-B6EA-9A74FC52F235/My_DB.sqlite
Note: path also works in the playground with the alias files.

Related

How to get the url path of Apple ODR resource?

I am new in Xcode and Swift. Currently working on a script dealing Apple's ODR:
https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/On_Demand_Resources_Guide/Managing.html#//apple_ref/doc/uid/TP40015083-CH4-SW1
I am using NSBundleResourceRequest as
assetsPack = NSBundleResourceRequest(tags: [tag])
and than download a specific resource from my Assets.xcassets by a given tag with:
conditionallyBeginAccessingResources instance method.
The main code snippet I have is:
var assetsPack: NSBundleResourceRequest?
if let req = assetsPack{
req.assetsPack()
}
assetsPack = NSBundleResourceRequest(tags: [tag])
guard let req = assetsPack else {
return
}
req.conditionallyBeginAccessingResources{available in
if available{
print("available")
print(available)
self.anotherFunction(tag)
} else {
req.beginAccessingResources{error in
guard error == nil else{
return
}
self.anotherFunction(tag)
}
}
What I need is to return the path of the ODR resource here or pass it to another function. I need to be able and use this path to copy my file to another place, or access to it once its downloaded with another external plugin.
I've been trying some method like:
let path = req.bundle.url(forResource: "myFile", withExtension: "data")
print(path)
Considering that i have a myFile of type data in my Assets.xcassets.
But it returns nil.
I've tried also:
let stringPath = Bundle.main.path(forResource: "myFile", ofType: "data")
let urlPath = Bundle.main.url(forResource: "myFile", withExtension: "data")
It's is possible that you called endAccessingResources before you accessed the resource, or your request is released/nilified too early, which caused the system to deallocate the files.
An irrelevant side note: ODR tags don't seem to support whitespaces in them. If one adds a tag with whitespace, the request will execute without actually downloading the files. The behavior would be conditionallyBeginAccessingResources constantly giving isResourceAvailable = false.

Swift Create file with integrity FileManager

I'm currently trying to store some files on my iOS device. The contents of the file are encrypted, but I was wondering if I can append some kind of integrity check to the file as well, preferably using the FileAttributeKey.
I tried the following, which doesn't work
extension FileAttributeKey {
static let integrity = FileAttributeKey("NSFileIntegrity")
}
let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let fileName = "test"
let filePath = docs.appendingPathComponent(fileName).path
defer {
try! FileManager.default.removeItem(atPath: filePath)
}
let data = Data("Hello world".utf8)
// This line fails too
// FileManager.default.createFile(atPath: filePath, contents: data, attributes: [.integrity: "SHA256"])
FileManager.default.createFile(atPath: filePath, contents: data, attributes: [:])
do {
try FileManager.default.setAttributes([.integrity: "SHA256"], ofItemAtPath: filePath)
} catch {
print(error)
}
print(try FileManager.default.attributesOfItem(atPath: filePath))
So the questions are:
Is there a way to create and append a custom FileAttributeKey to a file.
Is there a (better) way to add integrity checks to a file?
If you use authenticated encryption then you get integrity checks for free. Every time you decrypt, integrity will be checked for you and in case of errors the decryption will fail. Just use a mode like GCM or OCB and you are done.

Swift: unzipping file

I’m trying to get String from txt file inside the zip file using native libcompression library. Actually I use the code from
https://github.com/mw99/DataCompression/blob/master/Sources/DataCompression.swift.
At first, I was doing:
let zip = try? Data(contentsOf: "/.../test.zip")
let tmp: Data? = zip?.unzip()
let txt: String? = String(data: tmp!, encoding: .utf8)
But how do I get the contents of zip file and how do I get data from certain txt file?
ZIP Foundation supports accessing individual entries in ZIP archives.
You have to initialize an archive by passing a file URL to the Archive initializer.
Afterwards you can access a specific entry via subscripting:
let fileManager = FileManager()
let currentWorkingPath = fileManager.currentDirectoryPath
var archiveURL = URL(fileURLWithPath: currentWorkingPath)
archiveURL.appendPathComponent("test.zip")
guard let archive = Archive(url: archiveURL, accessMode: .read) else {
return
}
guard let entry = archive["file.txt"] else {
return
}
var destinationURL = URL(fileURLWithPath: currentWorkingPath)
destinationURL.appendPathComponent("out.txt")
do {
try archive.extract(entry, to: destinationURL)
} catch {
print("Extracting entry from archive failed with error:\(error)")
}
You can also directly access the contents of entry by using the closure based API. This allows you to process the entry without writing it to the file system first:
try archive.extract(entry, consumer: { (data) in
print(data.count)
})

Read all files from a directory?

I am trying to create a folder in my assets, then get a list of files inside.
Sounds simple but there is no clean answer on how to do exactly this.
Even to get the list from the main directory, most people can't do on Swift 3, reading here : Getting list of files in documents folder
using :
let fileMngr = FileManager.default;
// Full path to documents directory
let docs = fileMngr.urls(for: .documentDirectory, in: .userDomainMask)[0].path
// List all contents of directory and return as [String] OR nil if failed
return try? fileMngr.contentsOfDirectory(atPath:docs)
Not working.
Reading from a specific folder, I couldn't understand how to get it's path for swift.
Any example that really work that reads from a folder ?
If you want to get all files in a personal directory, here is the simple answer
do {
let documentURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let Path = documentURL.appendingPathComponent("yourDirectoyName").absoluteURL
let directoryContents = try FileManager.default.contentsOfDirectory(at: Path, includingPropertiesForKeys: nil, options: [])
}
catch {
print(error.localizedDescription)
}
And then if you want for example to read all files with special extension, you can do it that way
static func listAllFileNamesExtension(nameDirectory: String, extensionWanted: String) -> (names : [String], paths : [URL]) {
let documentURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let Path = documentURL.appendingPathComponent(nameDirectory).absoluteURL
do {
try FileManager.default.createDirectory(atPath: Path.relativePath, withIntermediateDirectories: true)
// Get the directory contents urls (including subfolders urls)
let directoryContents = try FileManager.default.contentsOfDirectory(at: Path, includingPropertiesForKeys: nil, options: [])
// if you want to filter the directory contents you can do like this:
let FilesPath = directoryContents.filter{ $0.pathExtension == extensionWanted }
let FileNames = FilesPath.map{ $0.deletingPathExtension().lastPathComponent }
return (names : FileNames, paths : FilesPath);
} catch {
print(error.localizedDescription)
}
return (names : [], paths : [])
}
So if you want to have all your json files in your personal directory
let allJsonNamePath = listAllFileNamesExtension(nameDirectory:"yourDirectoryName", extensionWanted: "json")
Swift 4/5
let documentDirectoryPath:String = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
let myFilesPath = "\(documentDirectoryPath)/myfolder"
let filemanager = FileManager.default
let files = filemanager.enumerator(atPath: myFilesPath)
while let file = files?.nextObject() {
print(file)
}
Actually, amazingly, for 2 days no one could tell me the real issue here.
Creating a group, is completely different from dragging a folder into the project.
For some reason, with Apple, its always complicated with files. I have to figure out the NOT so intuitive approach that a group that looks like a folder, is nothing but a nice way to look at something, and will not create a real folder accessible by the file manager.
This strange approach is maybe intutitive to a very pro programmer, but really not to any simple person.
Simply put, create a blue folder outside Xcode and drag it in.

NSKeyedArchiver.archiveRootObject fails to save

I have a group of custom objects that I'm converting to NSMutableDictionary's, and then creating an array out of them (this part is currently working as expected).
I'm then attempting to save that array of data as a file using NSKeyedArchiver. However, the result of NSKeyedArchiver.archiveRootObject always returns false.
Below is my logic for saving - am I missing something obvious, or perhaps is this the wrong approach? Thank you!
var groupsArray = [Any?]()
for group in file!.groups{
for obj in group.children {
let objDict = obj.convertToDictionary()
groupsArray.append(objDict)
}
}
let documents: String = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let filePath: String = URL(fileURLWithPath: documents).appendingPathComponent("file.archive").absoluteString
let save: Bool = NSKeyedArchiver.archiveRootObject(groupsArray, toFile: filePath)
EDIT: This also fails if trying to save to the .desktop or the .caches directories.
The issue here is .absoultestring. If the URL object contains a file URL, then we should use .path for working with FileManager or PathUtilities etc. So here replacing .absoultestring with .path will solve the issue
for more details about their difference please refer this answer
try this method to save
1.Method returns filepath.
func filePath(key:String) -> String {
let manager = FileManager.default
let url = manager.urls(for: .documentDirectory, in: .userDomainMask).first
return (url!.appendingPathComponent(key).path)
}
2.Code to save to a file using NSKeyedArchiver.
NSKeyedArchiver.archiveRootObject(groupsArray, toFile: filePath(key: "file.archive"))