Swift iOS filemanager copy changes sqlite file - ios

I have a routine that when my dataModelObject is initiate, its to copy myDataBase.sqlite file from the App Bundle folder, to the app document directory so that the app can use it. It only copies it if its not there, meaning in live environment it will not replace users existing db. The operation executes with no problem, however the database at the destination location is completely different than the database i designed. Has anyone else run into this, and might know what is causing this?
2 tables in said database one record only in one table with a weird number and a blob.

Yes, i have confirmed that the file in the bundle points to the correct .sqlite file in the project folder.
Here is my routine that does the copy, does something stand out as bad? Should i use move instead?
class func copyFile(fileName: NSString) {
let dbPath: String = getPath(fileName)
println("copyFile fileName=\(fileName) to path=\(dbPath)")
var fileManager = NSFileManager.defaultManager()
var fromPath: String? = NSBundle.mainBundle().resourcePath?.stringByAppendingPathComponent(fileName)
if !fileManager.fileExistsAtPath(dbPath) {
println("dB not found in document directory filemanager will copy this file from this path=\(fromPath) :::TO::: path=\(dbPath)")
fileManager.copyItemAtPath(fromPath!, toPath: dbPath, error: nil)
} else {
println("DID-NOT copy dB file, file allready exists at path:\(dbPath)")
}
}
class func getPath(fileName: String) -> String {
return NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)[0].stringByAppendingPathComponent(fileName)
}

Related

Saving database changes into .db file with SQLite (Swift)

In my Project I am opening an already existing database from a .db with sqlite3_open. This file was created with the command-line tool sqlite3. I am updating some rows in a table, which works fine, but those updates are only made in the temporary instance of the database. How can I also update my .db file for future runs of my project to work with updated data?
Somebody asked here how to save a SQLite database in Swift. But the answer seems to me somehow unsatisfying, because the file should be written in the same format as created by sqlite3, so that it can be opened again via sqlite3_open.
Is there maybe even a function presented by SQLite? I couldn't find anything for that...
You cannot modify the contents of the bundle. So you will want to copy the file to some a directory where it can be modified. Historically we might have advised using “documents” folder for this. But nowadays we use the “application support directory”. See iOS Storage Best Practices.
Anyway, the basic idea is:
include the original database in the bundle (by adding it to your app’s target, if you haven’t already);
try to open the database (with SQLITE_READWRITE option but not the SQLITE_CREATE option ... we don’t want it creating a blank database if it doesn’t find it) in the application support directory; and
if that fails (because the file is not found), copy the database from the bundle to the application support directory and try again
Thus, perhaps something like:
var db: OpaquePointer?
enum DatabaseError: Error {
case bundleDatabaseNotFound
case sqliteError(Int32, String?)
}
func openDatabase() throws {
let fileURL = try FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent("database.db")
// try opening database and just return if it succeeded
if sqlite3_open_v2(fileURL.path, &db, SQLITE_OPEN_READWRITE, nil) == SQLITE_OK {
return
}
// if it failed, clean up before trying again ...
sqlite3_close(db)
db = nil
// then copy database from bundle ...
guard let bundlePath = Bundle.main.url(forResource: "database", withExtension: "db") else {
throw DatabaseError.bundleDatabaseNotFound
}
try FileManager.default.copyItem(at: bundlePath, to: fileURL)
// and try again
let rc = sqlite3_open_v2(fileURL.path, &db, SQLITE_OPEN_READWRITE, nil)
if rc == SQLITE_OK { return }
// and if it still failed, again, clean up, and then throw error
let errorMessage = sqlite3_errmsg(db)
.flatMap { String(cString: $0) }
sqlite3_close(db)
db = nil
throw DatabaseError.sqliteError(rc, errorMessage)
}

Get all URLs for resources in sub-directory in Swift

I am attempting to create an array of URLs for all of the resources in a sub-directory in my iOS app. I can not seem to get to the correct path, I want to be able to retrieve the URLs even if I do not know the names (i.e. I don't want to hard code the file names into the code).
Below is a screen shot of the hierarchy, I am attempting to get all the files in the 'test' folder:
Any help is greatly appreciated, I have attempted to use file manager and bundle main path but to no joy so far.
This is the only code I have currently:
let fileManager = FileManager.default
let path = Bundle.main.urls(forResourcesWithExtension: "pdf", subdirectory: "Files/test")
print(path)
I have also tried this code but this prints all resources, I can't seem to specify a sub-directory:
let fm = FileManager.default
let path = Bundle.main.resourcePath!
do {
let items = try fm.contentsOfDirectory(atPath: path)
for item in items {
print("Found \(item)")
}
} catch {
// failed to read directory – bad permissions, perhaps?
}
Based on an answer from #vadian , The folders were changed from virtual groups to real folders. Using the following code I was able to get a list of resources:
let fileManager = FileManager.default
let path = Bundle.main.resourcePath
let enumerator:FileManager.DirectoryEnumerator = fileManager.enumerator(atPath: "\(path!)/Files/test")!
while let element = enumerator.nextObject() as? String {
if element.hasSuffix("pdf") || element.hasSuffix("jpg") { // checks the extension
print(element)
}
}
Consider that the yellow folders are virtual groups, not real folders (although Xcode creates real folders in the project directory). All files in the yellow folders are moved into the Resources directory in the bundle when the app is built.
Real folders in the bundle are in the project navigator.
You can follow the following steps to get them:
Create a new folder inside your project folder with the extension is .bundle (for example: Images.bundle).
Copy resource files into that new folder.
Drag that new folder into the project that opening in Xcode.
Retrieve the URLs by using the following code snippet:
let urls = Bundle.main.urls(forResourcesWithExtension: nil, subdirectory: "Images.bundle")
You can also view the guide video here: https://youtu.be/SpMaZp0ReEo
I came across a similar issue today. I needed to retrieve the URL of a resource file in a bundle ignoring its path.
I wrote the following:
public extension Bundle {
/// Returns the file URL for the resource identified by the specified name, searching all bundle resources.
/// - Parameter resource: The name of the resource file, including the extension.
/// - Returns: The file URL for the resource file or nil if the file could not be located.
func recurseUrl(forResource resource: String) -> URL? {
let contents = FileManager.default.allContentsOfDirectory(atPath: self.bundlePath)
for content in contents {
let fileNameWithPath = NSString(string: content)
if let fileName = fileNameWithPath.pathComponents.last {
if resource == fileName {
return URL(fileURLWithPath: content)
}
}
}
return nil
}
Based on this:
public extension FileManager {
/// Performs a deep search of the specified directory and returns the paths of any contained items.
/// - Parameter path: The path to the directory whose contents you want to enumerate.
/// - Returns: An array of String objects, each of which identifies a file or symbolic link contained in path. Returns an empty array if the directory does not exists or has no contents.
func allContentsOfDirectory(atPath path: String) -> [String] {
var paths = [String]()
do {
let url = URL(fileURLWithPath: path)
let contents = try FileManager.default.contentsOfDirectory(atPath: path)
for content in contents {
let contentUrl = url.appendingPathComponent(content)
if contentUrl.hasDirectoryPath {
paths.append(contentsOf: allContentsOfDirectory(atPath: contentUrl.path))
}
else {
paths.append(contentUrl.path)
}
}
}
catch {}
return paths
}
}
Which achieves the goal of retrieving the URL for the first match of a given resource filename in a bundle's resources, all directories wide.
I tend to think that Swift's func url(forResource name: String?, withExtension ext: String?) -> URL? should behave this way in the first place.

How to create a pre bundled realm file and upload data to it?

I am new to Realm and I want to ship a pre bundled Realm file my app, however the realm documentation is unclear to me on how to actually create a .realm file and also upload the desired pre bundled data to it. I have not been able to find any solution via SO, Google, or Youtube. Step-by-step instructions would be very helpful.
We're still looking at ways to officially make generating pre-populated Realms more easy. It's definitely a desired feature for the Realm Browser, but due to the way that Realm files require a migration when changing the schema, it's somewhat tricky to incorporate into a UI. It's definitely something we want to improve down the line.
At the moment, the Browser has a converter in it that can perform minimal data import like CSV files.
The Realm documentation is saying that the easiest way for you to produce a pre-populated Realm specific for your apps needs is to build a separate macOS app whose sole role is to generate the Realm file, pre-populate the data and then expose the resulting Realm file so you can copy it to your app.
The operation to do this is just like any normal Realm interaction (try! Realm() is what causes the file to actually be initially created), except you manually interact with the Realm file on disk upon completion.
I'm working on an app for an iOS conference, and the schedule data for the event is going to be stored in a Realm that is pre-bundled with the app when it ships.
Since I thought creating an extra macOS app would be overkill for an iOS app, I instead used iOS unit tests that would generate the pre-bundled Realm from scratch every time it was executed.
class Tests: XCTestCase {
func testGenerateNewDefaultRealm() {
let sources = [conferences, sponsors, conferenceDays] as [Any]
XCTAssert(generateDefaultRealm(named: "MyConferenceRealm.realm", sources: sources))
}
}
extension Tests {
public func generateDefaultRealm(named name: String, sources: [Any]) -> Bool {
// Create and configure the Realm file we'll be writing to
let realm = generateRealm(named: name)
// Open a Realm write transaction
realm.beginWrite()
// Loop through each source and add it to Realm
for source in sources {
if let objectArray = source as? [Object] {
for object in objectArray {
realm.add(object)
}
}
else if let objectDictionary = source as? [String : Object] {
for (_, object) in objectDictionary {
realm.add(object)
}
}
}
// Commit the write transaction
do {
try realm.commitWrite()
}
catch let error {
print(error.localizedDescription)
return false
}
// Print the file location of the generated Realm
print("=====================================================================")
print(" ")
print("Successfully generated at")
print(realm.configuration.fileURL!.path)
print(" ")
print("=====================================================================")
return true
}
public func generateRealm(named name: String) -> Realm {
let exportPath = NSTemporaryDirectory()
let realmPath = exportPath.appending(name)
// Delete previous Realm file
if FileManager.default.fileExists(atPath: realmPath) {
try! FileManager.default.removeItem(atPath: realmPath)
}
// Create new Realm file at path
let objectTypes: [Object.Type] = [Conference.self, ConferenceDay.self, SessionBlock.self, Event.self, Presentation.self,
Session.self, Location.self, Speaker.self, Sponsor.self, Venue.self]
let configuration = Realm.Configuration(fileURL: URL(string: realmPath), objectTypes: objectTypes)
let realm = try! Realm(configuration: configuration)
return realm
}
}
Running this unit test will generate a new Realm file in the NSTemporaryDirectory() directory of the Mac, and then feed in a set of Array and Dictionary objects of un-persisted Realm Object instances that are created each time the test is run. The location of the final Realm is then printed in the console so I can grab it and move it to the app bundle.

How to get partial file path after downloading with Alamofire?

I want to save the path of where I downloaded a file to my model object. Based on this StackOverflow answer, I should "only store the filename, and then combine it with the location of the documents directory on the fly". Well in my case it's the Application Support directory. I am not sure how to do this.
The full path of where the file will be downloaded is: /Library/ApplicationSupport/com.Company.DemoApp/MainFolder/myFile.jpg
/Library/Application Support/ is the part I can't save anywhere and I have to get it during runtime from FileManager.
com.Company.DemoApp/MainFolder/myFile.jpg is part of that file path that I can store in database/config files.
However I do not know how to get this path: com.Company.DemoApp/MainFolder/myFile.jpg from Alamofire after downloading it. For example, here is the code to download the file, how do I get this path?
func destination(named name: String, pathExtension: String) -> DownloadRequest.DownloadFileDestination {
let destination: DownloadRequest.DownloadFileDestination = { _, _ in
let appSupportDirURL = FileManager.createOrFindApplicationSupportDirectory()
let fileURL = appSupportDirURL?.appendingPathComponent("com.Company.DemoApp/MainFolder/\(name).\(pathExtension)")
return (fileURL!, [.removePreviousFile, .createIntermediateDirectories])
}
return destination
}
let finalDestination = destination(named: image.title, pathExtension: image.preview.pathExtension)
Alamofire.download(urlString, to: finalDestination).response { response in
if let imagePath = response.destinationURL?.path {
/// I want to this path here: com.Company.DemoApp/MainFolder/myFile.jpg
/// But not sure how to get it. How do I get this path?
}
}
The problem is that Alamofire gives me the full path only: /Library/ApplicationSupport/com.Company.DemoApp/MainFolder/myFile.jpg. But I only want this part: com.Company.DemoApp/MainFolder/myFile.jpg.
Any ideas on how to get this path?
Also, Apple seems to refer to Bookmark's if you want to get a file at runtime here: Apple Docs for Bookmarks
Note this is a follow up to a previous question.
UPDATE 1
This is one way I think this could work. (Based of an answer above).
enum DataDirectory: String {
case feed = "com.Compnay.DemoApp/MainFolder/"
}
Alamofire.download(urlString, to: finalDestination).response { response in
let destURL = response.destinationURL!
/// One way to save this path is to do so like this:
myImageObject.partialLocalPath = "\(DataDirectory.feed.rawValue)\(destURL.lastPathComponent)"
}
}
So I am saving the part in an enum and appending the name to it once the download finishes - then I add this to my model object for saving.
Any thoughts?

How can I take out a value as a string from RealmSwift?

I am developing an iOS app with RealmSwift by referring here. https://realm.io/docs/swift/latest/#in-memory-realms
And, what I don't understand is, how can I indicate the location(record and column) of the data in the realm file.
I've saved a realm file that named "DicData.realm" on the main folder where the same location as ViewController.swift is saved.
The data of DicData.realm is something like this:
1,face,423
2,rain,435
3,airplane,555
If I run the code below, it only printed like this: "results: Results ( )". It seems the filter method is just neglected. When I want to take out the word "airplane" and store in a variable as a string, how should I modify my code?
override func didMoveToView(view: SKView) {
func test()->Int {
let realm = try! Realm(configuration: Realm.Configuration(inMemoryIdentifier: "DicData"))
let results = realm.objects(DBData).filter("id == 3")
print("results: \(results)")
}
class DBData: Object {
dynamic var id = 0
dynamic var name = ""
dynamic var code = ""
}
You're referring here to the chapter of in-memory Realms and have setup your configuration to use those. An in-memory Realm is not a file. It lives exclusively in memory and is not actually written to disk.
If you've a prepared file, you want to bundle with your app, you need to make sure, that it is part of the Copy-Files Build Phase of the corresponding target of your Xcode project.
You can then copy the file from the app bundle initially via NSFileManager to a path, where the copy can be modified.
let defaultURL = Realm.Configuration.defaultConfiguration.fileURL!
let bundleURL = NSBundle.mainBundle().URLForResource("DicData", withExtension: "realm")!
do {
try NSFileManager.defaultManager().copyItemAtURL(bundleURL, toURL: defaultURL)
} catch {}
}

Resources