How to generate smaller url bookmarks in Swift? - ios

I want to create bookmarks for locally stored files so that the files can be tracked if they are moved to new locations.
My attempts to use Swift 3's NSURL function bookmarkData(options:includingResourceValuesForKeys:relativeTo) finds it obligatorily generating bookmark data objects that are almost a megabyte in size, even in cases in which the files themselves are orders of magnitude smaller than that.
For example, for a 4-byte text file, the following code produces 410kb bookmark data.
(Running in AppDelegate's applicationDidFinishLaunching, OS X, Xcode 8.2)
(Note that when I tried using NSURL.BookmarkCreationOptions.minimalBookmark, instead of .suitableForBookmarkFile, the output was only 0kb and I could not use it to track the original file.)
Similarly, bookmarking a 19kb .jpg file produces an 888kb bookmark.
Am I doing something wrong? If not, is there a way to get bookmarks that is not quite so greedy?
let url = URL(fileURLWithPath: "/Users/U/test.txt", isDirectory: false)
if let urlBookmark = try? url.bookmarkData(options: .suitableForBookmarkFile, includingResourceValuesForKeys: nil, relativeTo: nil) {
print("bookmark size = \(urlBookmark.count / 1024) kb")
}
Edit:
From a few further quick and dirty tests, I see that the size of the bookmark is fixed and is determined by the file's category rather than by the file's size:
.pages 479kb
.txt 419kb
.pdf 898kb
.numbers 309kb
.jpg 888kb
.png 915kb
.exe 342kb
.mp4 691kb
.mkv 594kb
Still, I would like to know if there is any way within those constraints to get even smaller bookmarks.

Related

How to check the size of CoreData in Swift

I am storing data in CoreData. I want to know what is the size of whole CoreData in MBs. Basically the requirement is to clear the whole CoreData once it reaches 10 MB. I did not find any working answers for this. I have tried using below code but unable to get the size.
public func getSqliteStoreSize(forPersistentContainerUrl storeUrl: URL) -> String {
do {
let size = try Data(contentsOf: storeUrl)
if size.count < 1 {
print("Size could not be determined.")
return ""
}
let bcf = ByteCountFormatter()
bcf.allowedUnits = [.useMB] // optional: restricts the units to MB only
bcf.countStyle = .file
let string = bcf.string(fromByteCount: Int64(size.count))
print(string)
return string
} catch {
print("Failed to get size of store: \(error)")
return ""
}
}
guard let storeUrl = self.managedObjectContext!.persistentStoreCoordinator!.persistentStores.first?.url else {
print("There is no store url")
return
}
print("The size of the store is: \(self.getSqliteStoreSize(forPersistentContainerUrl: storeUrl))")
Reference: How to get the size of data present in coredata store?
Below is my coredata path.
file:///var/mobile/Containers/Data/Application/XXXXXX-XXX-XXXX-XXXX-XXXXXXXXXX/Library/Application Support/APPNAME.sqlite
Part of the answer is that you need to count the SQLite journal files. That's where most of the data actually lives. Getting a very low number is normal if you only count the central SQLite file. You find those by adding .-wal and -shm to file file name.
Another issue with your code is that using Data(contentsOf:) is a really bad idea here unless your store files are really small. You're reading the entire file into memory to find out how big it is, and that's not needed. The question you linked to has an answer showing how to use FileManager (actually NSFileManager there since it's ObjC but the function names and arguments are the same) to get the size without reading the file.
A final problem which might or might not affect you is that if you have external binary support turned on for any attribute in your model, the external files that get created are not in any of the SQLite files (that's the point). You also need to count those. The location is undocumented, but it's a directory named something like .APPNAME_SUPPORT/_EXTERNAL_DATA/. If you need this, try using a simulator build so that you can easily browse the app's files and make sure you have it right. You'd count every file in that folder.
By default, CoreData uses SQLite’s WAL mode (https://www.sqlite.org/wal.html) so SQLite creates 3 files for the database: the usual .sqlite file, a .sqlite-wal file, and a .sqlite-shm file. You will need to combine the sizes for all these.

Adding metadata to generated audio file

I'm generating an audio file programmatically, and I'd like to add metadata to it, such as the title and artist. I don't particularly care what format the file is written in, as long as AVPlayer will read it and send it to the playing device. (The whole goal is to send this generated audio and its track name to a Bluetooth device. I'm happy to explore easier ways to achieve this on iPhone that don't require writing the file or adding metadata directly to the file.)
So far I've discovered that AVAssetWriter will often just throw away metadata that it doesn't understand, without generating errors, so I'm stumbling a bit trying to find what combinations of file formats and keys are acceptable. So far I have not found a file format that I can auto-generate that AVAssetWriter will add any metadata to. For example:
let writer = try AVAssetWriter(outputURL: output, fileType: .aiff)
let title = AVMutableMetadataItem()
title.identifier = .commonIdentifierTitle
title.dataType = kCMMetadataBaseDataType_UTF8 as String
title.value = "The Title" as NSString
writer.metadata = [title]
// setup the input and write the file.
I haven't found any combination of identifiers or fileTypes (that I can actually generate) that will include this metadata in the file.
My current approach is to create the file as an AIFF, and then use AVAssetExportSession to rewrite it as an m4a. Using that I've been able to add enough metadata that iTunes will show the title. However, Finder's "File Info" is not able to read the title (which it does for iTunes m4a files). My assumption is that if it doesn't even show up in File Info, it's not going to be sent over Bluetooth (I'll be testing that soon, but I don't have the piece of hardware I need handy).
Studying iTunes m4a files, I've found some tags that I cannot recreate with AVMetadataItem. For example, Sort Name (sonm). I don't know how to write tags that aren't one of the known identifiers (and I've tested all 263 AVMetadataIdentifiers).
With that background, my core questions:
What metadata tags are read by AVPlayer and sent to Bluetooth devices (i.e. AVRCP)?
Is it possible to write metadata directly with AVAssetWriter to a file format that supports Linear PCM (or some other easy-to-generate format)?
Given a known tag/value that does not match any of the AVMetadataIdentifiers), is it possible to write it in AVAssetExportSession?
I'll explore third-party id3 frameworks later, but I'd like to achieve it with AVFoundation (or other built-in framework) if possible.
I've been able to use AVAssetWriter to store metadata values in a .m4a file using the iTunes key space:
let songID = AVMutableMetadataItem()
songID.value = "songID" as NSString
songID.identifier = .iTunesMetadataSongID
let songName = AVMutableMetadataItem()
songName.value = "songName" as NSString
songName.identifier = .iTunesMetadataSongName
You can write compressed .m4a files directly using AVAssetWriter by specifying the correct settings when you set up the input object, so there’s no need to use an intermediate AIFF file.

iOS reading from a file

I'm trying to get my iPhone app to load text from a file into a string array, with 1 line from the file per array element.
I've created an input file as a text file using sublime text. I dragged the file (which is located inside of a folder inside of my project directory) into xCode into a folder in the same location in the project heirarchy.
I also tried adding it as a bundle (by copying the folder and renaming it with the .bundle extension), to no avail. Currently, my app has the file in 2 places (Obviously I plan to delete the unneeded version, but I'm not sure how this will work so I've left it for now).
I've written a function that I want to read my file, and assemble its contents into an array:
func readFromFile(filename: String) -> [String]? {
guard let theFile = Bundle.main.path( forResource: fileName, ofType: "txt") else {
return nil // ALWAYS returns nil here: Seems 'filename' can't be found?????
}
do { // Extract the file contents, and return them as a split string array
let fileContents = try String(contentsOfFile: theFile)
return fileContents.components(separatedBy: "\n")
} catch _ as NSError {
return nil
}
}
As it stands, the function always returns nil at the location commented in the code.
I've been working on this for ~6hrs (and tried every suggestion I could find on StackOverflow, google etc) and I'm just getting more and more confused by the differences between the various versions of Swift and intricacies of iOS development. I can't seem to find a consistent answer anywhere. I've checked the apple documentation but it's too high level with no example code for me to understand at my swift beginner level.
I also tried naming the file with a ".txt" extension but that didn't help either.
The file must certainly be named alert01.txt if you are going to refer to it as forResource: "alert01", ofType: "txt".
Loading from a bundle will not work. The file needs to be part of your project as shown in the first entry.
However, your code is not going to work because you have created a folder reference. That means the folder PanicAlertFiles is being copied with all its contents into your bundle. Your code will need to dive into that folder in order to retrieve your file. Use path(forResource:ofType:inDirectory:) to do that, or (if you don't want to have to code the file name explicitly) get the folder and then use the FileManager to examine its contents.

how to read data in rich content files using NSFilehandler

This is my swift code simplified:
func readContent(url:NSURL!){
do {
let file: NSFileHandle? = try NSFileHandle(forReadingFromURL: url)
if (file != nil){
file?.seekToFileOffset(10)
let content=file!.readDataOfLength(5)
print(String(data: content, encoding: NSASCIIStringEncoding))
file!.closeFile()
}
}catch {
}
}
I am building a file reader, that could read text files and pdfs. I am using NSFileHandler because I need to keep track of the position where the reading is happening.
I am able to read text files without a problem. However with pdfs I am having two problems:
How do I get the type of encoding used to encore the pdf? NSASCIIStringEncoding for instance does not work fine with text file but not with the pdf file. I am getting strange characters. I imaging that there is a way to detect the encoding. I have been following https://developer.apple.com/library/ios/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/Introduction/Introduction.html and I haven't found anything addressing this issue on stack overflow.
Given the fact that pdfs may contain text, images and videos how do I identify these contents while reading.. I read that magic numbers might do it https://en.wikipedia.org/wiki/Magic_number_(programming)#Magic_numbers_in_files but I read that there are not advisable http://www.techrepublic.com/article/avoid-using-magic-numbers-and-string-literals-in-your-code/. In addition I have not yet found a guide on how to use them.
please note that it is important for me to keep track of the Offset while reading.

Delete File After contentsOfFile Load

I am loading an array of UIImage from the iOS Documents directory:
var images = [UIImage]()
for fileName in fileNames {
images.append(UIImage(contentsOfFile: "\(imagesPath)/\(fileName).png")!)
}
I'm going to continue using this array but I don't need the files anymore, so I go ahead and delete them:
for fileName in fileNames {
do {
try NSFileManager.defaultManager().removeItemAtPath("\(imagesPath)/\(fileName).png")
} catch {
print("Error")
}
}
When I do this, my array of UIImage is now invalid and gives me errors while trying to access them. Shouldn't this be in memory and not related to the files on disk?
I tried using the ".copy()" command on the images when I load them but that made no difference.
I have confirmed that the delete is the issue above because if I comment out that line the app works great with no errors. I only get errors accessing the array after I delete the files from disk.
Is there a way to sever this connection?
Edit: Per the correct answer from #Wain, the code works fine if I change it to:
var images = [UIImage]()
for fileName in fileNames {
let imgData = NSFileManager.defaultManager().contentsAtPath("\(imagesPath)/\(fileName).png")!
images.append(UIImage(data: imgData)!)
}
Doing this doesn't keep the link back to the file on disk.
The images haven't been displayed so the data hasn't fully been loaded yet, only enough to know the image details and size has been loaded. This is for memory efficiency. depending on the image format and usage different parts of data may be loaded at multiple different times.
If you load the file into NSData, ensuring that it isn't memory mapped, and create the image from that then the data and image should be unlinked from the underlying file. This is less memory efficient and it would be better to keep your current code and delete the files when you know you're finished with the images all together.

Resources