NSFilemanager doesn't create file - objective-c

I have a problem with NSFileManager, because i only can store a file into Application Documents Directory, but i want to create a file into a sub directory this i don't think why, i couldn't create. my code below:
+(NSString *)applicationDocumentsDirectory {
return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
}
+(BOOL)storeFile:(NSData*)file withName:(NSString*)name atDirectory:(NSString*)dir{
NSFileManager *filemgr;
NSString *docsDir;
NSString *newDir;
BOOL create=NO;
filemgr =[NSFileManager defaultManager];
docsDir = [StorageManager applicationDocumentsDirectory];
newDir = [docsDir stringByAppendingPathComponent:dir];
if(![filemgr fileExistsAtPath:newDir]){
if([filemgr createDirectoryAtPath:newDir withIntermediateDirectories:NO attributes:nil error:nil]){
create=YES;
}
}else
create=YES;
if(create){
if(![filemgr createFileAtPath:newDir contents:file attributes:nil]){
[filemgr release];
return YES;
}
}
[filemgr release];
return NO;
}

I am not sure why the file is not created. At first glance, it looks like your code should work. But I may be overlooking something. It also depends on what exactly you are passing as arguments to the storeFile:withName:atDirectory: method.
Nevertheless I am posting an answer because I did spot another error in your code: you should not send release to filemgr since you also did not send retain to it first and you did not create the object. In this case, there is no need to send retain to it either, since you are only using it locally within your method. You may want to review the Apple Developer Connection document "Cocoa Core Competencies: Memory Management".
I don't think this error explains why the file is not created; though I'm surprised your application doesn't crash because of it.

Related

Create a Folder (bundle) in Cocoa

I'm trying to programmatically create a folder with Cocoa.
I've written an NSString category and we've got the following function there :
- (void)createAsFolder
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError* err = nil;
[fileManager createDirectoryAtPath:self withIntermediateDirectories:YES attributes:nil error:&err];
if (err)
{
NSLog(#"ERROR : %#",err);
}
}
So, in a few words, let's say we have an NSString* path = #"/some/path/is/here";, we can create it simply by :
[path createAsFolder];
The thing is, although it works PERFECTLY for normal folders, it does NOT when the path specified is a bundle (that is : WITH an extension). E.g.
NSString* path = #"/this/is/a/path/to/some/bundle.bun";
[path createAsFolder];
The above does NOT work.
Any ideas on how to fix that?
OK, here's the answer (thanks to #thundersteele), if you want to copy a full file tree from on place to another :
NSFileWrapper* w = [[NSFileWrapper alloc] initWithPath:initialPath];
[w writeToFile:destinationPath atomically:YES updateFilenames:YES];
And yep : it has ABSOLUTELY no problem whether the subfolders are packages/bundles or whatever. Not that hard, huh? Just 2 lines... lol
Try NSFileWrapper instead. I think it can do what you want to do.
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/NSFileWrapper_Class/Reference/Reference.html

Objective-C: need to call a method in another class from FinishedLaunching

I got this far... here is my code:
- (void)applicationDidFinishLaunching:(UIApplication *)application
{
// create the d/b or get the connection value
SQLiteDB *dbInstance = [[SQLiteDB alloc] init];
}
Now, the question is: this bit of code is supposed to check to see if a database exists, and if not, create it. My problem is I am having a problem figuring out exactly how to write the first line of the called method and where to place it in SQLiteDB.m. Is this an instance method (-) or a class method (+)?
I'm sorry for being so lame on this, but once I see it, I'll have the hang of it... the rest of the code is written in C#, and I can handle the conversion to Obj_C.
The following is a method to copy an existing database from your Bundle to the Documents directory, but can easily be adapted for a new database. Just use the fileExistsAtPath: method logic below and replace the actions to take with your custom database creation code.
Put this in your AppDelegate.m file:
- (void)prepareDatabase
{
//add Database Versioning check to see if the resources database is newer
// generally as simple as naming your database with a version on the end
NSFileManager *filemanager = [NSFileManager defaultManager];
NSString *databasePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingString:#"/YOURDATABASE.s3db"];
if(![filemanager fileExistsAtPath:databasePath]) {
//Database doesn't exist yet, so we copy it from our resources
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:#"/YOURDATABASE.s3db"];
if([filemanager copyItemAtPath:defaultDBPath toPath:databasePath error:nil]) {
NSLog(#"Database Copied from resources");
} else {
NSLog(#"Database copy FAILED from %# to %#",defaultDBPath,databasePath);
}
}
}
Then in your applicationDidFinishLaunching: method call this:
[self prepareDatabase];
I'm assuming that by "see if the database exists" you mean "see if the database file exists on disk". For that, you use method fileExistsAtPath: of class NSFileManager. It's an instance method, but you can use [NSFileManager defaultIntance].
Calculate the path to the file first (it's up to you how). Check if the file exists. If yes, open the file, if not, create a new database with that filename.

sqlite db creation issue

Im teaching myself objective-c and currently trying to work out how to integrate a database into my application. Ive looked at many example, forums and tutorial but none have worked, what am i doing wrong?
Most of the examples come ready with a [projectName].sqlite db in the application. How does that get added? Not seen one explanation of how that ends up in the project.
I've created my Object Model and classes. Im running a method which checks if DB exists, if not, it creates the db. Ive found several examples where different paths are used and im not sure which is correct, Some examples copied part of my project files into the documents/mainDatabase.sqlite folder! Is the following correct, if so why am i getting an error?
* Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Failed to create writable database file with message 'The operation couldn’t be completed. No such file or directory'.
Using these causes the above error, and yes the defaultDBPath does exist
defaultDBPath:
/Users/myuser/Library/Application Support/iPhone Simulator/4.2/Applications/DFBAD921-CEBF-471E-B98B-04FDF2620146/Documents
defaultDBPath:
/Users/myuser/Library/Application Support/iPhone Simulator/4.2/Applications/DFBAD921-CEBF-471E-B98B-04FDF2620146/Documents/mainDatabase.sqlite
writableDBPath:
/Users/myuser/Library/Application Support/iPhone Simulator/4.2/Applications/DFBAD921-CEBF-471E-B98B-04FDF2620146/dbProj12.app/mainDatabase.sqlit
- (void)createEditableCopyOfDatabaseIfNeeded {
// First, test for existence - we don't want to wipe out a user's DB
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *documentDirectory = [self applicationDocumentsDirectory];
NSLog(#"documentDirectory = %#", documentDirectory);
NSString *writableDBPath = [documentDirectory stringByAppendingPathComponent:#"iBountyHunter.sqlite"];
NSLog(#"defaultDBPath = %#", writableDBPath);
BOOL dbexits = [fileManager fileExistsAtPath:writableDBPath];
if (!dbexits) {
// The writable database does not exist, so copy the default to the appropriate location.
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"iBountyHunter.sqlite"];
NSLog(#"defaultDBPath = %#", defaultDBPath);
NSError *error;
BOOL success = [fileManager copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
if (!success) {
NSAssert1(0, #"Failed to create writable database file with message '%#'.", [error localizedDescription]);
}
}
}
Look forward to your reply.
I'm assuming you are using CoreData, but the solution should be valid for every database type.
The message stated that iBountyHunter.sqlite doesn't exists and it's a true message: that db should be added in the XCode project and packaged when you build the app.
Some apps ships with an already populated database probably modified by the developer on the mac, based on what is created in the simulator (or even in a development device).
Build and run you app on the simulator;
Look in the Documents folder on your device and copy the database;
Edit it adding custom data;
Add the db to XCode;
Add the code you've reported;
Build and let the app copy the populated database on the device.
It might be late, but i wanted to answer this question so that in future no one gets this issue.
The answer has to do with the "Target Membership".
click on the sql file in the left-pane of Xcode
Under "Target Membership", make sure the "check" is "checked" for your target build.
that's it, this solves my issue. I hope this helps you also.
Mac makes a good point, are you using sqlite or core data?
If you are using sqlite then one of the easier things to do is on application launch, copy it into your apps documents directors (unless it already exists of course, in which case do nothing)
I generally create an sqliteDB external to xcode and add it as a file (just like an image). Then in the 'applicationDidFinishLaunchingWithOptions' method (in your app delegate I call the same method, but my code is a little different. Take a look:
- (void)createEditableCopyOfDatabaseIfNeeded
{
//We always need to overwrite the DB, but first we need to extract usedQuestionIds, ObjectiveData and stats
//Once database is replaced, reinsert data
BOOL success;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:#"yourdatabasename.sqlite"];
success = [fileManager fileExistsAtPath:writableDBPath];
if (success)
{
//might want to check for update and if so, back up users unique data, replace database and reinsert
}
else
{
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"yourdatabasename.sqlite"];
[fileManager copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
}
It then create another method in my app delegate that allows me to get a db connection which I can then use:
+(sqlite3 *)getNewDBConnection
{
sqlite3 *newDBconnection;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"yoursqliteDatabaseName.sqlite"];
// Open the database. The database was prepared outside the application.
if (sqlite3_open([path UTF8String], &newDBconnection) == SQLITE_OK)
{
NSLog(#"Database opened successfully");
}
else
{
NSLog(#"Error in opening database ");
}
return newDBconnection;
}
Now wherever you are in your app you can get a connection to you db by calling:
sqlite3 *db = [YourAppDelegateName getNewDBConnection];
//do stuff with your sqlite db connection
sqlite3_close(db);
Hope that helps :)

iOS/iPhone SDK: initWithCoder and encodeWithCoder not being called

I'm trying to save an NSMutableArray called queueArray so it can be loaded again after the app has been quit. I used a few tutorials to get me going and this is the code I have come up with. The problem seems to be that "initWithCoder" and "encodeWithCoder" are not being called, shown by no NSLog calls and no stopping at breakpoints. I have added the NSCoding protocol to the .h file and I know that queueArray is not nil and it contains MPMediaItems. Here is some of the code I use to try to save and load the array:
-(IBAction)saveQueuePressed {
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *filePath = [rootPath stringByAppendingPathComponent:#"queueArray.archive"];
//should cause encodeWithCoder to be called
[NSKeyedArchiver archiveRootObject:queueArray toFile:filePath];
}
-(IBAction)loadQueuePressed {
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *filePath = [rootPath stringByAppendingPathComponent:#"queueArray.archive"];
//should cause initWithCoder to be called
queueArray = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
}
-(void)encodeWithCoder:(NSCoder *)coder {
NSLog(#"encodeWithCoder");
[coder encodeObject:queueArray forKey:#"savedQueueArray"];
}
-(id)initWithCoder:(NSCoder *)decoder {
NSLog(#"initWithCoder");
queueArray = [decoder decodeObjectForKey:#"savedQueueArray"];
return self;
}
Any help will be greatly appreciated!
The encodeWithCoder: and initWithCoder methods are called when you archive/unarchive an object that responds to them. From what I understand, you have those methods in your class, but the object you are actually archiving (queueArray) is not an instance of that class, it's an NSMutableArray.
If you do want to save your entire object, you can change your saveQueue method to this
-(IBAction)saveQueuePressed {
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *filePath = [rootPath stringByAppendingPathComponent:#"queueArray.archive"];
// saving the array shouldn't, but saving the self object
//should cause encodeWithCoder to be called:
[NSKeyedArchiver archiveRootObject:self toFile:filePath];
}
But if you just want to save the array, I guess you can just use saveQueuePressed and loadQueuePressed, I don't think you need the encode/init WithCoder: methods
Update:
Maybe your path is not right.
Try
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *filePath = [[rootPath stringByAppendingPathComponent:#"queueArray.archive"] stringByExpandingTildeInPath]];
Filipe is right! Your comment said you still didn't use his method.
I had this issue too. Switching from the dictionary's atomic write method to the keyedArchiver fixed it, luckily I only had to change one thing, the line that said writeToFile: is now the archive function.
Now my program's working. For some reason, even when responding to NSCoding, the custom object is not being encoded and breaks my dictionary. Is this a bug with iOS? I've read a fair number of Apple Manuals, but I've also seen a fair number of typos and missing info (For example, try MKMapRect functions without the videos to explain them), or Core Animations referencing the Run Loop before you learn threading, I could go on, half finished sentences in Quartz... so yeah, I've read the manuals and this perplexes me, we have to get a more open iOS SDK at some point, hopefully

Failure to write NSString to file on iPad

I'm using a text file to save the changes made by a user on a list (the reason that I'm doing this is so that I can upload the text file to a PC later on, and from there insert it into an Excel spreadsheet). I have 3 data structures: A NSMutableArray of keys, and a NSMutableDictionary who's key values are MSMutableArrays of NSStrings.
I iterate through these data structures and compile a file string that looks much like this:
(Key);(value)\t(value)\t(value):\n(Key);(value).. .so on.
SO, onto the actual question: When I attempt to save it, it fails. I'm 99% sure this is because of the file path that I'm using, but I wanted backup to check this out. Code follows:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
NSString *filePath = [paths objectAtIndex:0];
NSString *fileString = [NSString stringWithString:[self toFileString]];
if(![fileString writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:NULL]){
NSLog(#"File save failed");
} else {
// do stuff
}
(Code above is re-copied, since the actual code is on a different computer. It compiles, so ignore spelling errors?)
I tried using NSError, but I got bogged down in documentation and figured I might as well ask SO while trying to figure out how to properly use NSError (might be a little bit of an idiot, sorry).
99% sure it's the NSArray *paths line that's tripping it up, but I don't know how else to get the documents directory.
Edit: Problem solved, and one final question: If I save it to the App's document directory, where can I go after I close the app to see if it saved properly? If it works like I think it does, isn't it sandboxed in with the app's installation on the simulator? (i.e. no way of checking it)
NSLog() that filePath string. I think you're trying to write to the directory itself, not to a file.
Try this instead:
filePath = [[paths objectAtIndex:0]stringByAppendingPathComponent:#"myfile.txt"];
What is the file name you want to save? The method
NSArray *paths = NSSearchPathForDirectoriesInDomains(...);
NSString *filePath = [paths objectAtIndex:0];
...
if(![fileString writeToFile:filePath ...
means you are saving the string into a file path which has the same name as a folder. This will of course fail. Please give it a name, e.g.
NSString* fileName = [filePath stringByAppendingPathComponent:#"file.txt"];
if(![fileString writeToFile:fileName ...
and try again.
BTW, to use NSError:
NSError* theError = nil;
if(![fileString writeToFile:fileName ... error:&theError]) {
// ^^^^^^^^^
NSLog(#"Failed with reason %#", theError);
// theError is autoreleased.
}