I'm having trouble understanding this piece of code:
// assuming fm is NSFileManager object
if ([fm moveItemAtPath: #"newfile" toPath: #"newfile2" error: NULL] == NO){
NSLog(#"File rename Failed");
}
the moveItemAtPath method sends message in if condition.
does it actually rename newfile to newfile2 or is this just some test, to se if renaming file is possible?
The expression inside if will move the file if it can. The return value lets you find out after the fact if an actual change occurred. (For instance, if you are passing a path that does not exist you will receive NO.)
Related
I am seeking help to understand why a tutorial I am following is not working for me. I am running macOS 12.3.1, Xcode 13.3.1. The project is in Objective-C and using XIB.
This is a view-based NSTableView, using a folder of PNGs stored on my SSD for the imageView and the stringByDeletingPathExtension as stringValue for the cell's text field. I filled my code with NSLog calls to try and catch what could have been going awry.
Most setup is happening in applicationDidFinishLaunching:, where I initialise an NSMutableArray for the table's content, an NSString for the file path, then set up the file manager and the directory enumerator with said path (note: all working up to here).
Now comes the loop to populate the table contents' mutable array. I cannot understand why said loop gets skipped entirely! Its condition is to set an NSString equal to the nextObject of the directory enumerator. I am sure the loop gets skipped because the NSLog call after the loop runs!
Here is the entire code of applicationDidFinishLaunching:, including my comments and logs (I have just replaced my account name with ):
-(void)applicationDidFinishLaunching:(NSNotification *)aNotification {
_tableContents = [[NSMutableArray alloc] init];
NSString *path = #"/Users/<myUsername>/Developer/Apple-Programming-YT/Cocoa Programming/Flags/PNG/40x30";
// MARK: Debug 1
NSLog(#"path found: %#", path); // the correct path gets printed, as expected
NSFileManager *fileManager = [NSFileManager defaultManager];
NSDirectoryEnumerator *directoryEnum = [fileManager enumeratorAtPath:path];
NSString *file;
// MARK: Debug 2
NSLog(#"Checking that file is empty: %#", file); // (null) gets printed, as expected
// MARK: Debug 3
if (file != directoryEnum.nextObject) {
NSLog(#"File cannot be assigned to the Directory Enumerator");
} else if (file == directoryEnum.nextObject) {
NSLog(#"File properly assigned. Proceed!"); // this gets printed! Is it correct?
} else {
NSLog(#"Something went wrong during assignment of nextObject to file");
}
while (file = [directoryEnum nextObject]) {
NSLog(#"While loop entered!"); // this doesn't get printed! Why?!
// MARK: Debug 4
NSLog(#"File: %#", file);
NSString *filePath = [path stringByAppendingFormat:#"/%#", file];
// MARK: Debug 5
NSLog(#"Image filepath: %#", filePath);
NSDictionary *obj = #{#"image": [[NSImage alloc] initByReferencingFile:filePath],
#"name": [file stringByDeletingPathExtension]};
[self.tableContents addObject:obj];
}
[self.tableView reloadData];
NSLog(#"Table View Reloaded"); // This gets printed!
}
I have uploaded the full app to GitHub, in case you may want to look at it and see if something else could be wrong, but every outlet, delegate, data source is connected.
Now for my diagnosis & ideas:
The Debug 3 mark is what I find most interesting. AFAIK file should still be (null), so how checking if it is equal to directoryEnum.nextObject returns YES?
I created Debug 3 because the NSLog checking whether the loop had been entered didn't get printed. I therefore assumed the condition for the while loop had a problem.
I then tried to create a do-while loop instead of this while loop and, of course, the code ran. For the log with "Image filepath" it returned the address above followed by (null), as if it didn't find the file. But how is it possible if the file is indeed there? Do I require some sort of permission to access it? Being the object empty, the next line in the console was quite clear: "attempt to insert nil object from objects[1]".
But now, how do I solve this?
Any help here is much appreciated. If you download it from GitHub, please replaces the *path string with a folder of PNGs on your SSD.
Thank you.
I don't think you can access the filesystem directly with a path like that any more. If you check the value of file in your code, it is nil, which means that file == directoryEnum.nextObject will evaluate to true.
You have to create a path starting with NSHomeDirectory() or similar and add components to it. This makes a path that goes via your application support folder, which contains an alias to the desktop. I'm not sure why that's OK and accessing it directly is not, but I'm not a Mac developer.
I'd have to say following a tutorial as old as that, you're going to struggle with a lot of things.
I'm facing a problem I using the FSEvent to catch the file created in my app but I also want to check the file extension (ex: photo/ video type) are allowed, otherwise these files will not be copied. How can I check the file extension before copying in Cocoa? Any suggestion would be appreciated. Thanks
Please note I don't use NSFileManager to copy file.
Use following code to check for movies or audio file types. Full list of UTI-Types in the documentation.
NSString * fileUTI = nil;
BOOL success = [self getResourceValue:& fileUTI forKey:NSURLTypeIdentifierKey];
if (success && [uti isKindOfClass:[NSString class]]) {
BOOL fileConformsToUTI = UTTypeConformsTo(fileUTI, kUTTypeMovie);
fileConformsToUTI = fileConformsToUTI || UTTypeConformsTo(fileUTI, kUTTypeAudio);
// check and do the copy
}
Okay,
So I have a Cydia app that I need to update. I am aware with Cydia apps that they don't have a Documents folder, so you have to make one. And here's how I made it before in iOS 4 (which doesn't work on iOS 5):
mkdir("/var/mobile/Library/APPNAME", 0755);
mkdir("/var/mobile/Library/APPNAME/Documents", 0755);
NSString *foofile = #"/var/mobile/Library/APPNAME/Documents/database.db";
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:foofile];
if (fileExists == TRUE) {
NSLog(#"already exists");
} else {
NSLog(#"doesn't exists");
NSFileManager *fileManager = [[NSFileManager defaultManager]autorelease];
NSError *error;
NSString *documentDBFolderPath = #"/var/mobile/Library/APPNAME/Documents/database.db";
NSString *resourceDBFolderPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"database.db"];
[fileManager copyItemAtPath:resourceDBFolderPath toPath:documentDBFolderPath error:&error];
}
I also included code that copies the database file to that folder, too. That doesn't work (even when I create the folder manually via SSH).
Please help! Thanks.
Here is the method I made to create directories
-(void)createDirectory:(NSString *)directoryName atFilePath:(NSString *)filePath
{
NSString *filePathAndDirectory = [filePath stringByAppendingPathComponent:directoryName];
NSError *error;
if (![[NSFileManager defaultManager] createDirectoryAtPath:filePathAndDirectory
withIntermediateDirectories:NO
attributes:nil
error:&error])
{
NSLog(#"Create directory error: %#", error);
}
}
Try using createDirectoryAtURL:withIntermediateDirectories:attributes:error:.
NSFileManager Class Reference:
createDirectoryAtURL:withIntermediateDirectories:attributes:error:
Creates a directory with given attributes at the specified path.
Parameters
url - A file URL that specifies the directory to create.
If you want to specify a relative path, you must set the
current working directory before creating the corresponding
NSURL object. This parameter must not be nil.
createIntermediates - If YES, this method creates any non-existent
parent directories as part of creating the directory in url. If NO,
this method fails if any of the intermediate parent directories does
not exist. This method also fails if any of the intermediate path
elements corresponds to a file and not a directory.
attributes - The file attributes for the new directory and any newly created
intermediate directories. You can set the owner and group numbers,
file permissions, and modification date. If you specify nil for this
parameter or omit a particular value, one or more default values are
used as described in the discussion. For a list of keys you can
include in this dictionary, see “Constants” (page 54) section lists
the global constants used as keys in the attributes dictionary. Some
of the keys, such as NSFileHFSCreatorCode and NSFileHFSTypeCode, do
not apply to directories.
error - On input, a pointer to an error object. If an error occurs,
this pointer is set to an actual error object containing the error
information. You may specify nil for this parameter if you do not
want the error information.
Return Value
YES if the
directory was created or already exists or NO if an error occurred.
Check NSFileManager's class reference. To create folders you need createDirectoryAtPath:withIntermediateDirectories:attributes:error:
Superb Techotopia explanation of iOS5 filesystem
In Swift, returns true if exists or created.
func ensureDirectoryExists(path:String) -> Bool {
if !NSFileManager.defaultManager().fileExistsAtPath(path) {
do {
try NSFileManager.defaultManager().createDirectoryAtPath(path, withIntermediateDirectories: true, attributes: nil)
} catch {
print(error)
return false
}
}
return true
}
Is there anyway to get more detailed error data back from "createFileAtPath" I was kind of expecting an NSError? Currently I am using the BOOL return value.
success = [fileMan createFileAtPath:fileOnDisk contents:dBuffer attributes:nil];
if(success == YES) NSLog(#"FileCreated");
else {
NSLog(#"ERROR: Failed to create file");
return 1;
}
gary
I agree... I'd love to have a function for this that accepts NSError!
Errors returned in this case are usually one of the POSIX errors declared in errno.h (errno is automatically included for you as part of the Cocoa or Foundation headers).
To see the error, use the strerror function from errno.h and reference the global errno integer, which is set by the low-level POSIX io functions when a problem occurs:
if (![fm createFileAtPath:#"/etc/foobar.txt" contents:data attributes:nil])
{
NSLog(#"Error was code: %d - message: %s", errno, strerror(errno));
}
// output will be: Error was code: 13 - message: Permission denied
The list of error code constants are listed in the in the Error Handling Programming Guide for Cocoa (in addition to the errno.h header itself).
You're not supposed to use that method; they forgot to put this in the main docs (!), but if you read the Apple header file you find this comment:
/* These methods are provided here for compatibility. The corresponding
methods on NSData which return NSErrors should be regarded as the
primary method of creating a file from an NSData or retrieving the
contents of a file as an NSData. */
So, instead, Apple expects you to use this (which, from testing, appears to function exactly the same - except that it's located in a bizarre class where you'd never think to look for it (I want to create an empty file ... so I have to ... instantiate a nil NSData object? What?) it has an NSError object):
https://developer.apple.com/library/ios/documentation/cocoa/reference/foundation/Classes/NSData_Class/Reference/Reference.html#//apple_ref/occ/instm/NSData/writeToFile:options:error:
How to check if a folder (directory) exists in Cocoa using Objective-C?
Use NSFileManager's fileExistsAtPath:isDirectory: method. See Apple's docs here.
Some good advice from Apple in the NSFileManager.h regarding checking the file system:
"It's far better to attempt an operation (like loading a file or creating a directory) and handle the error gracefully than it is to try to figure out ahead of time whether the operation will succeed. Attempting to predicate behavior based on the current state of the filesystem or a particular file on the filesystem is encouraging odd behavior in the face of filesystem race conditions."
[NSFileManager fileExistsAtPath:isDirectory:]
Returns a Boolean value that indicates whether a specified file exists.
- (BOOL)fileExistsAtPath:(NSString *)path isDirectory:(BOOL *)isDirectory
Parameters
path
The path of a file or directory. If path begins with a tilde (~), it must first be expanded with stringByExpandingTildeInPath, or this method will return NO.
isDirectory
Upon return, contains YES if path is a directory or if the final path element is a symbolic link that points to a directory, otherwise contains NO. If path doesn’t exist, the return value is undefined. Pass NULL if you do not need this information.
Return Value
YES if there is a file or directory at path, otherwise NO. If path specifies a symbolic link, this method traverses the link and returns YES or NO based on the existence of the file or directory at the link destination.
NSFileManager is the best place to look for file related APIs. The specific API you require is
- fileExistsAtPath:isDirectory:.
Example:
NSString *pathToFile = #"...";
BOOL isDir = NO;
BOOL isFile = [[NSFileManager defaultManager] fileExistsAtPath:pathToFile isDirectory:&isDir];
if(isFile)
{
//it is a file, process it here how ever you like, check isDir to see if its a directory
}
else
{
//not a file, this is an error, handle it!
}
If your have a NSURL object as path, it's better to use path to convert it into NSString.
NSFileManager*fm = [NSFileManager defaultManager];
NSURL* path = [[[fm URLsForDirectory:NSDocumentDirectory
inDomains:NSUserDomainMask] objectAtIndex:0]
URLByAppendingPathComponent:#"photos"];
NSError *theError = nil;
if(![fm fileExistsAtPath:[path path]]){
NSLog(#"dir doesn't exists");
}else
NSLog(#"dir exists");