I have used the statfs(2) system call to get many characteristics of a Mac OS X filesystem, but it doesn't tell me if the filesystem is case-sensitive or not.
I need this information as the application I am developing will be moving many files around and I want to detect potential loss of data due to files being moved from a case-sensitive filesystem to a case-insensitive filesystem.
Can anyone suggest a way of detecting this?
If you're already using stat(2), then you can easily use pathconf(2) with the _PC_CASE_SENSITIVE selector (result 0 = case-insensitve, 1 = case-sensitive. Note that the man page is out of date, but the _PC_CASE_SENSITIVE and _PC_CASE_PRESERVING are supported. By convention, if a file system doesn't support _PC_CASE_SENSITIVE selector then it is case-sensitive.
I’ve looked around and haven’t found an API for that. There are two possibilities I can think of:
Creating a temporary file and trying to open it with a different case pattern, e.g. creating "a9999" and trying to open "A9999". Considering that neither "a9999" nor "A9999" were available on that particular directory, the filesystem is case-sensitive if and only if opening "A9999" fails.
Running diskutil(8) against the filesystem. It reports case-sensitive, -insensitive file systems differently: Name: Mac OS Extended (Case-sensitive) vs. Name: Mac OS Extended (not journaled).
Since diskutil(8) is able to identify that, it could be the case that this information is available via some API or system call.
Edit: It turns out that NSURL has a set of methods that work on file system properties. In particular, -getResourceValue:forKey:error with the key being NSURLVolumeSupportsCaseSensitiveNamesKey will tell you whether a given filesystem (represented as an NSURL instance) supports case sensitive names.
See the following code for an example of its use.
#include <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
NSAutoreleasePool *pool = [NSAutoreleasePool new];
NSString *path = [NSString stringWithCString:argv[1] encoding:NSASCIIStringEncoding];
NSURL *filesystem = [NSURL fileURLWithPath:path isDirectory:YES];
NSNumber *caseSensitiveFS;
BOOL hasCaseSensitiveResource;
hasCaseSensitiveResource = [filesystem getResourceValue:&caseSensitiveFS
forKey:NSURLVolumeSupportsCaseSensitiveNamesKey error:NULL];
if (hasCaseSensitiveResource)
{
if ([caseSensitiveFS intValue] == 1)
{
NSLog(#"%s is a case sensitive filesystem", argv[1]);
}
else
{
NSLog(#"%s is a case insensitive filesystem", argv[1]);
}
}
else
{
NSLog(#"can't query %s for case sensitiveness", argv[1]);
}
[pool drain];
return 0;
}
Output example:
./testcase /
/ is a case insensitive filesystem
./testcase /Volumes/Disk\ Image/
/Volumes/Disk Image/ is a case sensitive filesystem
./testcase nonono
can't query nonono for case sensitiveness
Create a temporary file with uppercase letters and check if the file exists using lowercase letters, if the test fails the file system is case-sensitive.
Look here for some code to find the HFS subtype of a device:
http://www.opensource.apple.com/source/libfs/libfs-3/FSFormatName.c
The routine is_hfs will return the hfs subtype. If the subtype is kHFSXSubType or kHFSXJSubType, then it's an HFSX (case sensitive) device.
Related
I'm a beginner iPhone developer trying to take information out of a sqlite database in Xcode 4.3. I have my database (which is named DB_Info.sqlite) in the same directory as my .h and .m files, and I also dragged the database into the folders section on the left bar in Xcode.
Could you please take a quick look at my code and let me know where my mistake is? I have used NSLogs to identify where the problem occurs, at the very last if statement, and it's written in comments. Thank you so much in advance!
#import <sqlite3.h>
#implementation Player
{
sqlite3 *DB_Info;
NSString *databasePath;
NSString *docsDir;
NSArray *dirPaths;
}
-(Player*)createPlayer:(NSString*)playerName
{
dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
docsDir = [dirPaths objectAtIndex:0];
databasePath = [[NSString alloc] initWithString: [docsDir stringByAppendingPathComponent: #"DB_Info.sqlite"]];
const char *dbpath = [databasePath UTF8String];
sqlite3_stmt *statement;
if (sqlite3_open(dbpath, &DB_Info) == SQLITE_OK) { //works fine
NSString *querySQL = [NSString stringWithFormat: #"SELECT * FROM playerlist WHERE fullName=\"%#\"", playerName];
const char *query_stmt = [querySQL UTF8String];
if (sqlite3_prepare_v2(DB_Info, query_stmt, -1, &statement, NULL) == SQLITE_OK) { //PROBLEM: This is where the problem is, and the if statement never goes through
//....rest of code here
} else {
NSLog(#"Error");
}
}
First, rather than just saying "Error", log the SQL error message
NSLog(#"%s SQL error '%s' (%1d)", __FUNCTION__, sqlite3_errmsg(database), sqlite3_errcode(database));`
It will tell you precisely what's going wrong. A common error on people's first time SQL projects is that the table is not found. If so, read on. (If not, feel free to ignore the rest of this.)
Second, you're looking for your database in your Documents folder. Did you explicitly copy it from your bundle to your Documents folder at some point? Or did you create it programmatically? But if you prepared it in advance, it won't be in the Documents folder until you copy it there.
Third, I'd also suggest that you consider using sqlite3_open_v2 instead of sqlite3_open. You are checking to see if that's SQLITE_OK, but that may be giving a false sense of security. The sqlite3_open will create a database if it's not there, which is clearly not your intent. Your app should presumably being copying the db from the bundle or creating the database and the tables for you before you get to this method (and you're not showing that, so I'm not sure if you're doing that). Anyway, the sqlite3_open_v2 function will not create the database if it's not there unless you explicitly request it does so by including SQLITE_OPEN_CREATE. So, something like the following will not create the database if it's not found:
if (sqlite3_open_v2(dbpath, &DB_Info, SQLITE_OPEN_READWRITE, NULL) == SQLITE_OK) {
On the off chance that a blank database has been created for you, I'd suggest you reset your simulator via "Reset Content and Settings" on the simulator's menu, or explicitly delete the app, so any blank databases that might have been created can be removed. If you're running this on a device, delete the app and reinstall it.
Fourth, if the database has been created in advance, have you confirmed that the database has been included in the "Copy Bundle Resources" setting? E.g.:
Fifth, if you're running the app on the simulator, you can always browse the simulator's file structure and make sure your files are where you think they are. You can also run the Mac OS sqlite program to inspect the database that the simulator is using to make sure everything is ok. You can even test your SQL right in the db that the simulator uses, which can be useful for diagnostic purposes. (Or, if you don't like the Mac text based sqlite program, you can also buy/download graphical tools, such as Base.) Before you can do this, you might first want to configure your Mac so you can easily browse the Simulator's files requires that you fire up the Terminal program, and issue the command:
chflags nohidden ~/Library
Then you can browse to "~/Library/Application\ Support/iPhone\ Simulator/5.1/Applications/" and then browse the various apps that you have and make sure you db file is there.
I've seen in some projects something like:
#if .....
code...
#endif
but i can't find it now...
Let's say, for example, if the app is running on 10.8 the app does 1 thing, if not the app does other thing.
Whats to code to check if it's running on 10.8?
Thanks.
You're probably asking the wrong question. Except in very rare cases, you should not care what system version the user is running. Instead, you should be checking if the specific thing you're interested in is available.
For instance, if Apple introduces a MagicHologram class in Mac OS X 10.9 that you want to use, you don't check if the user is running Mac OS X 10.9. Instead, you check if the MagicHologram class is available. If it is, you can use it. If not, it's not available. It doesn't even matter why. Maybe they're running 10.8. But maybe it's five years later, and Apple's decided to drop the MagicHologram class entirely.
(Also, keep in mind that you'd need to weak link to HologramKit, the library that provides the MagicHologram class.)
Likewise, if they introduce a new method to NSString, instead of checking the OS version you'd check if NSString knows about the new method.
That said, NSApplication.h includes an external constant called NSAppKitVersionNumber. You can compare this to constants like NSAppKitVersionNumber10_7 which (it should be noted) are numbers like 1138, not 10.7. There's only a few places this is appropriate, mostly where classes were private and undocumented but got major changes before being documented and becoming a part of the public parts of the SDK. Also, it might be helpful if you want to avoid a specific bug that's been fixed since.
To recap:
Detect individual classes and methods, which should cover 99.44% of your cases.
Use NSAppKitVersionNumber and NSAppKitVersionNumber10_7 to cover those cases where class or method detection would lie to you.
Those first two points cover all normal cases. You should go no further. But if you must have behaviour based on humane version, look at abarnert's answer below. It's the sanest way to get them.
Don't use operatingSystemVersionString, which is specifically listed as not safe for parsing.
References/more information:
SDK Compatibility Guide "Read this document if you want your application to target a specific version or multiple versions of iOS or Mac OS X."
Using SDK-Based Development Describes how to use weakly linked classes, methods, and functions to support running on multiple versions of an operating system.
A quick way to do it is:
if ( NSAppKitVersionNumber >= NSAppKitVersionNumber10_7 ) {
// Do stuff for Lion or later
}
More here.
You can see all the constants available in NSApplication.h, which you can get to by using Open Quickly... (Cmd-Shift O) in Xcode.
The header files provide for some surprisingly interesting reading material, if you are so inclined.
As others have said above (and I'd pick Steven Fisher's answer), you usually do not actually want to get the version number.
And if you only need to do comparisons against a major OS X version up to the version of the current SDK you're using, NSAppKitVersionNumber (as in Monolo's answer) is the right way to do it.
If you actually do need to get the version number for some reason (e.g., for recording analytics about your users, so you can decide when to stop supporting 10.6.0-10.6.5), here's how to do it:
#import <CoreServices/CoreServices.h>
SInt32 majorVersion, minorVersion, bugFixVersion;
Gestalt(gestaltSystemVersionMajor, &majorVersion);
Gestalt(gestaltSystemVersionMinor, &minorVersion);
Gestalt(gestaltSystemVersionBugFix, &bugFixVersion);
For 10.7.3, this gives majorVersion = 10, minorVersion = 7, bugFixVersion = 3.
The 10.7 documentation removed the paragraph that directly suggested Gestalt as the way to get OS version, but it's still not deprecated or legacy, and there's no other suggestions. In fact, every other way to get this information (parsing -[NSProcessInfo operatingSystemVersionString], calling sysctlbyname on "kern.osrelease" and converting Darwin kernel version to OS X version, etc.) is explicitly counter-indicated somewhere. So, this is the way to do it, if you really want to.
Just keep in mind that, as the release notes for System 6.0.4 said back in 1989, this new API may not be permanent and could be removed in a future version of the OS.
You can get the current release from the uname -r command (that's actually the kernel release, though it's easy to map onto Mac OS X versions), from the sw_vers command which gives you the release name, version and build identifier, or from the Gestalt() function (on current versions of Mac OS X, anyway). The safest way is probably to read the output of sw_vers which is in a stable format that's easy to parse.
Notice that you probably don't want to know what version of the OS you're on, though. What you probably want to do is to test whether a particular feature is available. You can do this by weak-linking frameworks, by weak class references, or by inspecting whether a class responds to selectors appropriate to the feature you're interested in.
As Steven Fisher said, you should not check for the system version but for the availability of the class or method you want to use.
The check if a specific class is available use
if ([NSHologram class]) {
// Create an instance of the class and use it.
} else {
// The Hologram class is not available.
}
To check if a specific method is available use
NSString* hologramText = #"Hologram";
if ([hologramText respondsToSelector:#selector(convertHologram)]) {
[hologramText convertHologram];
}
Yet for the method checking, the method must be available on the system where you build your app, or else you will get a compile error.
Here is code for how I do it. I love it this way, mainly because I don't have to A) Rely on an NSTask or B) Rely on any File I/O that many processes have access to.
static NSString* const kVarSysInfoVersionFormat = #"%#.%#.%# (%#)";
static NSString* const kVarSysInfoKeyOSVersion = #"kern.osrelease";
static NSString* const kVarSysInfoKeyOSBuild = #"kern.osversion";
- (NSString *) _strControlEntry:(NSString *)ctlKey {
size_t size = 0;
if ( sysctlbyname([ctlKey UTF8String], NULL, &size, NULL, 0) == -1 ) return nil;
char *machine = calloc( 1, size );
sysctlbyname([ctlKey UTF8String], machine, &size, NULL, 0);
NSString *ctlValue = [NSString stringWithCString:machine encoding:[NSString defaultCStringEncoding]];
free(machine); return ctlValue;
}
- (NSString *) getOSVersionInfo {
NSString *darwinVer = [self _strControlEntry:kVarSysInfoKeyOSVersion];
NSString *buildNo = [self _strControlEntry:kVarSysInfoKeyOSBuild];
if ( !darwinVer || !buildNo ) return nil;
NSString *majorVer = #"10", *minorVer = #"x", *bugFix = #"x";
NSArray *darwinChunks = [darwinVer componentsSeparatedByCharactersInSet:[NSCharacterSet punctuationCharacterSet]];
if ( [darwinChunks count] > 0 ) {
NSInteger firstChunk = [(NSString *)[darwinChunks objectAtIndex:0] integerValue];
minorVer = [NSString stringWithFormat:#"%ld", (firstChunk - 4)];
bugFix = [darwinChunks objectAtIndex:1];
} return [NSString stringWithFormat:kVarSysInfoVersionFormat, majorVer, minorVer, bugFix, buildNo];
}
Enjoy!
You can get OS version like this:
NSString *version = [[NSProcessInfo processInfo] operatingSystemVersionString];
NSLog(version);
Output:
And I see You want to get just version. It can be done like this:
NSString *version = [[NSProcessInfo processInfo] operatingSystemVersionString];
NSRange range = NSMakeRange(8, 4);
NSString *justVersion = [version substringWithRange: range];
NSLog(#"%#", justVersion);
Result:
And for checking:
if ([justVersion isEqualToString:#"10.7"]) {
code...
}
else {
...
}
I'm getting strange behavior writing NSString and NSData objects to relative file paths. Here's an example:
NSString *string = #"I am a file!";
NSError *error = nil;
NSString *fileName = #"text.txt";
BOOL written = [string writeToFile:fileName atomically:YES encoding:NSUTF8StringEncoding error:&error];
if (written) {
NSLog(#"Successfully written to file.");
} else {
NSLog(#"Error: %#", [error localizedDescription]);
}
When I run this I always get "Successfully written to file.", but the file is never there. Somehow the program thinks it was successful and no error is generated.
What am I doing wrong? (I'm on Mac OS X Lion)
This writes to the current directory. The default current directory when you run something under Xcode 4 is going to be ~/Library/Developer/Xcode/DerivedData/<prodDir>/Build/Products/<configuration>. You can override this using a Scheme. When you run a program from the commandline, then the current directory is whatever the current directory was when you ran the program. If you use Finder to launch the app, then the current directory will often be /.
In principle, it's fine to write the current working directory. It's very common to do this in command-line apps. So regarding #craig's comment about writeToFile: expecting an absolute path, I don't think that's really true. It expects and writes to a path. It doesn't care if it's absolute or relative. This is a Foundation class, and is just as useful in a command-line program as a GUI.
But in a GUI app, you should avoid relative paths. In principle, you could set the current directory and then write the file, but this is usually a bad idea in a large program since it's not thread safe (there is only one cwd for the whole program). And GUI apps tend to have somewhat unpredictable current directories, so it doesn't make for a good user experience.
But to the question of why you didn't get an error, it's because it probably successfully wrote it. You just didn't know where to look.
NSFileManager * fm = [NSFileManager new];
NSString * dirPath = [fm currentDirectoryPath];
NSString * absPath = [dirPath stringByAppendingPathComponent:#"myfile.file"];
[fm release];
keep in mind that currentDirectoryPath reflects your programs working directory until you change it with -changeCurrentDirectoryPath:, the programs working directory can be different depending on how it was launched, and can't be relied upon.
The first parameter to the writeToFile: method (in your example) is a relative path, but you probably want to use an absolute path. Otherwise, the system will place your files relative to the current executable. When you're running inside Xcode, this might not be where you expect them to end up. (As Rob mentioned in his answer, this location is somewhat buried, and can change depending on which version of Xcode you're using.)
If you want to build up a directory path using NSString objects, I would recommend the stringByAppendingPathComponent: method:
...
NSString *directory = #"/Users/Mikael/Desktop";
NSString *filename = #"MyFile.txt";
NSString *fullPath = [directory stringByAppendingPathComponent:filename];
...
Note that this method will take care of making sure your slashes are well-formed.
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Best practices for validating email address in Objective-C on iOS 2.0?
I am developing an iPhone application where I need the user to give his email address at login.
What is the best way to check if an email address is a valid email address?
Good cocoa function:
-(BOOL) NSStringIsValidEmail:(NSString *)checkString
{
BOOL stricterFilter = NO; // Discussion http://blog.logichigh.com/2010/09/02/validating-an-e-mail-address/
NSString *stricterFilterString = #"^[A-Z0-9a-z\\._%+-]+#([A-Za-z0-9-]+\\.)+[A-Za-z]{2,4}$";
NSString *laxString = #"^.+#([A-Za-z0-9-]+\\.)+[A-Za-z]{2}[A-Za-z]*$";
NSString *emailRegex = stricterFilter ? stricterFilterString : laxString;
NSPredicate *emailTest = [NSPredicate predicateWithFormat:#"SELF MATCHES %#", emailRegex];
return [emailTest evaluateWithObject:checkString];
}
Discussion on Lax vs. Strict - http://blog.logichigh.com/2010/09/02/validating-an-e-mail-address/
And because categories are just better, you could also add an interface:
#interface NSString (emailValidation)
- (BOOL)isValidEmail;
#end
Implement
#implementation NSString (emailValidation)
-(BOOL)isValidEmail
{
BOOL stricterFilter = NO; // Discussion http://blog.logichigh.com/2010/09/02/validating-an-e-mail-address/
NSString *stricterFilterString = #"^[A-Z0-9a-z\\._%+-]+#([A-Za-z0-9-]+\\.)+[A-Za-z]{2,4}$";
NSString *laxString = #"^.+#([A-Za-z0-9-]+\\.)+[A-Za-z]{2}[A-Za-z]*$";
NSString *emailRegex = stricterFilter ? stricterFilterString : laxString;
NSPredicate *emailTest = [NSPredicate predicateWithFormat:#"SELF MATCHES %#", emailRegex];
return [emailTest evaluateWithObject:self];
}
#end
And then utilize:
if([#"emailString#email.com" isValidEmail]) { /* True */ }
if([#"InvalidEmail#notreallyemailbecausenosuffix" isValidEmail]) { /* False */ }
To check if a string variable contains a valid email address, the easiest way is to test it against a regular expression. There is a good discussion of various regex's and their trade-offs at regular-expressions.info.
Here is a relatively simple one that leans on the side of allowing some invalid addresses through: ^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,6}$
How you can use regular expressions depends on the version of iOS you are using.
iOS 4.x and Later
You can use NSRegularExpression, which allows you to compile and test against a regular expression directly.
iOS 3.x
Does not include the NSRegularExpression class, but does include NSPredicate, which can match against regular expressions.
NSString *emailRegex = ...;
NSPredicate *emailTest = [NSPredicate predicateWithFormat:#"SELF MATCHES %#", emailRegex];
BOOL isValid = [emailTest evaluateWithObject:checkString];
Read a full article about this approach at cocoawithlove.com.
iOS 2.x
Does not include any regular expression matching in the Cocoa libraries. However, you can easily include RegexKit Lite in your project, which gives you access to the C-level regex APIs included on iOS 2.0.
Heres a good one with NSRegularExpression that's working for me.
[text rangeOfString:#"^.+#.+\\..{2,}$" options:NSRegularExpressionSearch].location != NSNotFound;
You can insert whatever regex you want but I like being able to do it in one line.
to validate the email string you will need to write a regular expression to check it is in the correct form. there are plenty out on the web but be carefull as some can exclude what are actually legal addresses.
essentially it will look something like this
^((?>[a-zA-Z\d!#$%&'*+\-/=?^_`{|}~]+\x20*|"((?=[\x01-\x7f])[^"\\]|\\[\x01-\x7f])*"\x20*)*(?<angle><))?((?!\.)(?>\.?[a-zA-Z\d!#$%&'*+\-/=?^_`{|}~]+)+|"((?=[\x01-\x7f])[^"\\]|\\[\x01-\x7f])*")#(((?!-)[a-zA-Z\d\-]+(?<!-)\.)+[a-zA-Z]{2,}|\[(((?(?<!\[)\.)(25[0-5]|2[0-4]\d|[01]?\d?\d)){4}|[a-zA-Z\d\-]*[a-zA-Z\d]:((?=[\x01-\x7f])[^\\\[\]]|\\[\x01-\x7f])+)\])(?(angle)>)$
Actually checking if the email exists and doesn't bounce would mean sending an email and seeing what the result was. i.e. it bounced or it didn't. However it might not bounce for several hours or not at all and still not be a "real" email address. There are a number of services out there which purport to do this for you and would probably be paid for by you and quite frankly why bother to see if it is real?
It is good to check the user has not misspelt their email else they could enter it incorrectly, not realise it and then get hacked of with you for not replying. However if someone wants to add a bum email address there would be nothing to stop them creating it on hotmail or yahoo (or many other places) to gain the same end.
So do the regular expression and validate the structure but forget about validating against a service.
My Mac OS X application receives a file over the network (in this case, text/x-vcard). In my code, how can I open the related application (typically the Address Book) without hard-coding paths or application name so that it processes the file ?
You'll be able to do this by linking in the ApplicationServices framework, which has a really handy "LSCopyApplicationForMIMEType" function. It works like this:
CFURLRef appURL = nil;
OSStatus err = LSCopyApplicationForMIMEType(CFSTR("text/x-vcard"), kLSRolesAll, &appURL);
if (err != kLSApplicationNotFoundErr) {
NSLog(#"URL: %#", (NSURL *)appURL);
}
CFRelease(appURL);
I'll explain what the parameters mean. The first parameter is a CFStringRef of the MIME type you're looking up. The second parameter indicates what kind of application you're looking for, ie an app that can edit this file, or an app that can view this file, etc. kLSRolesAll means you don't care. The final parameter is a pointer to the CFURLRef where the function will stick the app's URL (if it can find one).
On my machine, this prints out:
2009-08-01 12:38:58.159 EmptyFoundation[33121:a0f] URL: file://localhost/Applications/Address%20Book.app/
One of the cool things about CFURLRefs is that they're toll-free bridged to NSURL. This means you can take a CFURLRef and cast it to an NSURL, and vice versa. Once you've got your NSURL of the app, it's pretty trivial to use something like NSWorkspace's -launchApplicationAtURL:options:configuration:error: method to open the application.
If you want to open a specific file in that application (like the file from which you got the MIME type), you could use something like -[NSWorkspace openFile:withApplication:].
If you can't get the MIME type (despite what you say in your question), there are a bunch of similar LaunchServices functions. You can read all about them here.
Rather than even bothering to try to find the application you can use LSOpenItemsWithRole.
//Opens items specified as an array of values of type FSRef with a specified role.
OSStatus LSOpenItemsWithRole (
const FSRef *inItems,
CFIndex inItemCount,
LSRolesMask inRole,
const AEKeyDesc *inAEParam,
const LSApplicationParameters *inAppParams,
ProcessSerialNumber *outPSNs,
CFIndex inMaxPSNCount
);