| 1 | // |
|---|
| 2 | // SUHost.m |
|---|
| 3 | // Sparkle |
|---|
| 4 | // |
|---|
| 5 | // Copyright 2008 Andy Matuschak. All rights reserved. |
|---|
| 6 | // |
|---|
| 7 | |
|---|
| 8 | #import "SUHost.h" |
|---|
| 9 | |
|---|
| 10 | #import "SUSystemProfiler.h" |
|---|
| 11 | #import <sys/mount.h> // For statfs for isRunningOnReadOnlyVolume |
|---|
| 12 | |
|---|
| 13 | @implementation SUHost |
|---|
| 14 | |
|---|
| 15 | - (id)initWithBundle:(NSBundle *)aBundle |
|---|
| 16 | { |
|---|
| 17 | if (aBundle == nil) aBundle = [NSBundle mainBundle]; |
|---|
| 18 | if ((self = [super init])) |
|---|
| 19 | { |
|---|
| 20 | bundle = [aBundle retain]; |
|---|
| 21 | if (![bundle bundleIdentifier]) |
|---|
| 22 | NSLog(@"Sparkle Error: the bundle being updated at %@ has no CFBundleIdentifier! This will cause preference read/write to not work properly.", [bundle bundlePath]); |
|---|
| 23 | } |
|---|
| 24 | return self; |
|---|
| 25 | } |
|---|
| 26 | |
|---|
| 27 | - (void)dealloc |
|---|
| 28 | { |
|---|
| 29 | [bundle release]; |
|---|
| 30 | [super dealloc]; |
|---|
| 31 | } |
|---|
| 32 | |
|---|
| 33 | - (NSString *)description { return [NSString stringWithFormat:@"%@ <%@>", [self class], [self bundlePath]]; } |
|---|
| 34 | |
|---|
| 35 | - (NSBundle *)bundle |
|---|
| 36 | { |
|---|
| 37 | return bundle; |
|---|
| 38 | } |
|---|
| 39 | |
|---|
| 40 | - (NSString *)bundlePath |
|---|
| 41 | { |
|---|
| 42 | return [bundle bundlePath]; |
|---|
| 43 | } |
|---|
| 44 | |
|---|
| 45 | - (NSString *)name |
|---|
| 46 | { |
|---|
| 47 | NSString *name = [bundle objectForInfoDictionaryKey:@"CFBundleDisplayName"]; |
|---|
| 48 | if (name) return name; |
|---|
| 49 | |
|---|
| 50 | name = [self objectForInfoDictionaryKey:@"CFBundleName"]; |
|---|
| 51 | if (name) return name; |
|---|
| 52 | |
|---|
| 53 | return [[[NSFileManager defaultManager] displayNameAtPath:[bundle bundlePath]] stringByDeletingPathExtension]; |
|---|
| 54 | } |
|---|
| 55 | |
|---|
| 56 | - (NSString *)version |
|---|
| 57 | { |
|---|
| 58 | return [bundle objectForInfoDictionaryKey:@"CFBundleVersion"]; |
|---|
| 59 | } |
|---|
| 60 | |
|---|
| 61 | - (NSString *)displayVersion |
|---|
| 62 | { |
|---|
| 63 | NSString *shortVersionString = [bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; |
|---|
| 64 | if (shortVersionString) |
|---|
| 65 | return shortVersionString; |
|---|
| 66 | else |
|---|
| 67 | return [self version]; // Fall back on the normal version string. |
|---|
| 68 | } |
|---|
| 69 | |
|---|
| 70 | - (NSImage *)icon |
|---|
| 71 | { |
|---|
| 72 | // Cache the application icon. |
|---|
| 73 | NSString *iconPath = [bundle pathForResource:[bundle objectForInfoDictionaryKey:@"CFBundleIconFile"] ofType:@"icns"]; |
|---|
| 74 | // According to the OS X docs, "CFBundleIconFile - This key identifies the file containing |
|---|
| 75 | // the icon for the bundle. The filename you specify does not need to include the .icns |
|---|
| 76 | // extension, although it may." |
|---|
| 77 | // |
|---|
| 78 | // However, if it *does* include the '.icns' the above method fails (tested on OS X 10.3.9) so we'll also try: |
|---|
| 79 | if (!iconPath) |
|---|
| 80 | iconPath = [bundle pathForResource:[bundle objectForInfoDictionaryKey:@"CFBundleIconFile"] ofType: nil]; |
|---|
| 81 | NSImage *icon = [[[NSImage alloc] initWithContentsOfFile:iconPath] autorelease]; |
|---|
| 82 | // Use a default icon if none is defined. |
|---|
| 83 | if (!icon) { icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGenericApplicationIcon)]; } |
|---|
| 84 | return icon; |
|---|
| 85 | } |
|---|
| 86 | |
|---|
| 87 | - (BOOL)isRunningOnReadOnlyVolume |
|---|
| 88 | { |
|---|
| 89 | struct statfs statfs_info; |
|---|
| 90 | statfs([[bundle bundlePath] fileSystemRepresentation], &statfs_info); |
|---|
| 91 | return (statfs_info.f_flags & MNT_RDONLY); |
|---|
| 92 | } |
|---|
| 93 | |
|---|
| 94 | - (BOOL)isBackgroundApplication |
|---|
| 95 | { |
|---|
| 96 | ProcessSerialNumber PSN; |
|---|
| 97 | GetCurrentProcess(&PSN); |
|---|
| 98 | NSDictionary * processInfo = (NSDictionary *)ProcessInformationCopyDictionary(&PSN, kProcessDictionaryIncludeAllInformationMask); |
|---|
| 99 | BOOL isElement = [[processInfo objectForKey:@"LSUIElement"] boolValue]; |
|---|
| 100 | if (processInfo) |
|---|
| 101 | CFRelease(processInfo); |
|---|
| 102 | return isElement; |
|---|
| 103 | } |
|---|
| 104 | |
|---|
| 105 | - (NSString *)publicDSAKey |
|---|
| 106 | { |
|---|
| 107 | // Maybe the key is just a string in the Info.plist. |
|---|
| 108 | NSString *key = [bundle objectForInfoDictionaryKey:SUPublicDSAKeyKey]; |
|---|
| 109 | if (key) { return key; } |
|---|
| 110 | |
|---|
| 111 | // More likely, we've got a reference to a Resources file by filename: |
|---|
| 112 | NSString *keyFilename = [self objectForInfoDictionaryKey:SUPublicDSAKeyFileKey]; |
|---|
| 113 | if (!keyFilename) { return nil; } |
|---|
| 114 | NSError *ignoreErr; |
|---|
| 115 | return [NSString stringWithContentsOfFile:[bundle pathForResource:keyFilename ofType:nil] encoding:NSASCIIStringEncoding error: &ignoreErr]; |
|---|
| 116 | } |
|---|
| 117 | |
|---|
| 118 | - (NSArray *)systemProfile |
|---|
| 119 | { |
|---|
| 120 | return [[SUSystemProfiler sharedSystemProfiler] systemProfileArrayForHost:self]; |
|---|
| 121 | } |
|---|
| 122 | |
|---|
| 123 | - (id)objectForInfoDictionaryKey:(NSString *)key |
|---|
| 124 | { |
|---|
| 125 | return [bundle objectForInfoDictionaryKey:key]; |
|---|
| 126 | } |
|---|
| 127 | |
|---|
| 128 | - (BOOL)boolForInfoDictionaryKey:(NSString *)key |
|---|
| 129 | { |
|---|
| 130 | return [[self objectForInfoDictionaryKey:key] boolValue]; |
|---|
| 131 | } |
|---|
| 132 | |
|---|
| 133 | - (id)objectForUserDefaultsKey:(NSString *)defaultName |
|---|
| 134 | { |
|---|
| 135 | // Under Tiger, CFPreferencesCopyAppValue doesn't get values from NSRegistratioDomain, so anything |
|---|
| 136 | // passed into -[NSUserDefaults registerDefaults:] is ignored. The following line falls |
|---|
| 137 | // back to using NSUserDefaults, but only if the host bundle is the main bundle. |
|---|
| 138 | if (bundle == [NSBundle mainBundle]) |
|---|
| 139 | return [[NSUserDefaults standardUserDefaults] objectForKey:defaultName]; |
|---|
| 140 | |
|---|
| 141 | CFPropertyListRef obj = CFPreferencesCopyAppValue((CFStringRef)defaultName, (CFStringRef)[bundle bundleIdentifier]); |
|---|
| 142 | #if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4 |
|---|
| 143 | return [NSMakeCollectable(obj) autorelease]; |
|---|
| 144 | #else |
|---|
| 145 | return [(id)obj autorelease]; |
|---|
| 146 | #endif |
|---|
| 147 | } |
|---|
| 148 | |
|---|
| 149 | - (void)setObject:(id)value forUserDefaultsKey:(NSString *)defaultName; |
|---|
| 150 | { |
|---|
| 151 | // If we're using a .app, we'll use the standard user defaults mechanism; otherwise, we have to get CF-y. |
|---|
| 152 | if (bundle == [NSBundle mainBundle]) |
|---|
| 153 | { |
|---|
| 154 | [[NSUserDefaults standardUserDefaults] setObject:value forKey:defaultName]; |
|---|
| 155 | } |
|---|
| 156 | else |
|---|
| 157 | { |
|---|
| 158 | CFPreferencesSetValue((CFStringRef)defaultName, value, (CFStringRef)[bundle bundleIdentifier], kCFPreferencesCurrentUser, kCFPreferencesAnyHost); |
|---|
| 159 | CFPreferencesSynchronize((CFStringRef)[bundle bundleIdentifier], kCFPreferencesCurrentUser, kCFPreferencesAnyHost); |
|---|
| 160 | } |
|---|
| 161 | } |
|---|
| 162 | |
|---|
| 163 | - (BOOL)boolForUserDefaultsKey:(NSString *)defaultName |
|---|
| 164 | { |
|---|
| 165 | if (bundle == [NSBundle mainBundle]) |
|---|
| 166 | return [[NSUserDefaults standardUserDefaults] boolForKey:defaultName]; |
|---|
| 167 | |
|---|
| 168 | BOOL value; |
|---|
| 169 | CFPropertyListRef plr = CFPreferencesCopyAppValue((CFStringRef)defaultName, (CFStringRef)[bundle bundleIdentifier]); |
|---|
| 170 | if (plr == NULL) |
|---|
| 171 | value = NO; |
|---|
| 172 | else |
|---|
| 173 | { |
|---|
| 174 | value = (BOOL)CFBooleanGetValue((CFBooleanRef)plr); |
|---|
| 175 | CFRelease(plr); |
|---|
| 176 | } |
|---|
| 177 | return value; |
|---|
| 178 | } |
|---|
| 179 | |
|---|
| 180 | - (void)setBool:(BOOL)value forUserDefaultsKey:(NSString *)defaultName |
|---|
| 181 | { |
|---|
| 182 | // If we're using a .app, we'll use the standard user defaults mechanism; otherwise, we have to get CF-y. |
|---|
| 183 | if (bundle == [NSBundle mainBundle]) |
|---|
| 184 | { |
|---|
| 185 | [[NSUserDefaults standardUserDefaults] setBool:value forKey:defaultName]; |
|---|
| 186 | } |
|---|
| 187 | else |
|---|
| 188 | { |
|---|
| 189 | CFPreferencesSetValue((CFStringRef)defaultName, (CFBooleanRef)[NSNumber numberWithBool:value], (CFStringRef)[bundle bundleIdentifier], kCFPreferencesCurrentUser, kCFPreferencesAnyHost); |
|---|
| 190 | CFPreferencesSynchronize((CFStringRef)[bundle bundleIdentifier], kCFPreferencesCurrentUser, kCFPreferencesAnyHost); |
|---|
| 191 | } |
|---|
| 192 | } |
|---|
| 193 | |
|---|
| 194 | - (id)objectForKey:(NSString *)key { |
|---|
| 195 | return [self objectForUserDefaultsKey:key] ?: [self objectForInfoDictionaryKey:key]; |
|---|
| 196 | } |
|---|
| 197 | |
|---|
| 198 | - (BOOL)boolForKey:(NSString *)key { |
|---|
| 199 | return [self objectForUserDefaultsKey:key] ? [self boolForUserDefaultsKey:key] : [self boolForInfoDictionaryKey:key]; |
|---|
| 200 | } |
|---|
| 201 | |
|---|
| 202 | + (NSString *)systemVersionString |
|---|
| 203 | { |
|---|
| 204 | // This returns a version string of the form X.Y.Z |
|---|
| 205 | // There may be a better way to deal with the problem that gestaltSystemVersionMajor |
|---|
| 206 | // et al. are not defined in 10.3, but this is probably good enough. |
|---|
| 207 | NSString* verStr = nil; |
|---|
| 208 | #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4 |
|---|
| 209 | SInt32 major, minor, bugfix; |
|---|
| 210 | OSErr err1 = Gestalt(gestaltSystemVersionMajor, &major); |
|---|
| 211 | OSErr err2 = Gestalt(gestaltSystemVersionMinor, &minor); |
|---|
| 212 | OSErr err3 = Gestalt(gestaltSystemVersionBugFix, &bugfix); |
|---|
| 213 | if (!err1 && !err2 && !err3) |
|---|
| 214 | { |
|---|
| 215 | verStr = [NSString stringWithFormat:@"%d.%d.%d", major, minor, bugfix]; |
|---|
| 216 | } |
|---|
| 217 | else |
|---|
| 218 | #endif |
|---|
| 219 | { |
|---|
| 220 | NSString *versionPlistPath = @"/System/Library/CoreServices/SystemVersion.plist"; |
|---|
| 221 | verStr = [[[NSDictionary dictionaryWithContentsOfFile:versionPlistPath] objectForKey:@"ProductVersion"] retain]; |
|---|
| 222 | } |
|---|
| 223 | return verStr; |
|---|
| 224 | } |
|---|
| 225 | |
|---|
| 226 | @end |
|---|