root/trunk/CPFPerianPrefPaneController.m

Revision 917, 29.5 kB (checked in by astrange, 3 months ago)

Delete progress indicators from the prefpane, they're ugly and pointless.
Get rid of alert panels in the update checker and use IPC to show them in the prefpane instead.
Fix the update checker never cleaning up its locks.
Synchronize user defaults more often since it seems necessary.
Delete code for not running too often in the update checker, it's a duplicate of the same more-maintained code in Perian.

This requires merging the nib into release branch again...

Line 
1 #import "CPFPerianPrefPaneController.h"
2 #import "UpdateCheckerAppDelegate.h"
3 #import "ECQTComponent.h"
4 #include <sys/stat.h>
5
6 #define AC3DynamicRangeKey CFSTR("dynamicRange")
7 #define LastInstalledVersionKey CFSTR("LastInstalledVersion")
8 #define AC3TwoChannelModeKey CFSTR("twoChannelMode")
9 #define ExternalSubtitlesKey CFSTR("LoadExternalSubtitles")
10
11 //Old
12 #define AC3StereoOverDolbyKey CFSTR("useStereoOverDolby")
13 #define AC3ProLogicIIKey CFSTR("useDolbyProLogicII")
14
15 //A52 Constants
16 #define A52_STEREO 2
17 #define A52_DOLBY 10
18 #define A52_CHANNEL_MASK 15
19 #define A52_LFE 16
20 #define A52_ADJUST_LEVEL 32
21 #define A52_USE_DPLII 64
22
23 @interface NSString (VersionStringCompare)
24 - (BOOL)isVersionStringOlderThan:(NSString *)older;
25 @end
26
27 @implementation NSString (VersionStringCompare)
28 - (BOOL)isVersionStringOlderThan:(NSString *)older
29 {
30         if([self compare:older] == NSOrderedAscending)
31                 return TRUE;
32         if([self hasPrefix:older] && [self length] > [older length] && [self characterAtIndex:[older length]] == 'b')
33                 //1.0b1 < 1.0, so check for it.
34                 return TRUE;
35         return FALSE;
36 }
37 @end
38
39 @interface CPFPerianPrefPaneController(_private)
40 - (void)setAC3DynamicRange:(float)newVal;
41 - (void)saveAC3DynamicRange:(float)newVal;
42 @end
43
44 @implementation CPFPerianPrefPaneController
45
46 #pragma mark Preferences Functions
47
48 - (BOOL)getBoolFromKey:(CFStringRef)key forAppID:(CFStringRef)appID withDefault:(BOOL)defaultValue
49 {
50         CFPropertyListRef value;
51         BOOL ret = defaultValue;
52        
53         value = CFPreferencesCopyAppValue(key, appID);
54         if(value && CFGetTypeID(value) == CFBooleanGetTypeID())
55                 ret = CFBooleanGetValue(value);
56        
57         if(value)
58                 CFRelease(value);
59        
60         return ret;
61 }
62
63 - (void)setKey:(CFStringRef)key forAppID:(CFStringRef)appID fromBool:(BOOL)value
64 {
65         CFPreferencesSetAppValue(key, value ? kCFBooleanTrue : kCFBooleanFalse, appID);
66 }
67
68 - (float)getFloatFromKey:(CFStringRef)key forAppID:(CFStringRef)appID withDefault:(float)defaultValue
69 {
70         CFPropertyListRef value;
71         float ret = defaultValue;
72        
73         value = CFPreferencesCopyAppValue(key, appID);
74         if(value && CFGetTypeID(value) == CFNumberGetTypeID())
75                 CFNumberGetValue(value, kCFNumberFloatType, &ret);
76        
77         if(value)
78                 CFRelease(value);
79        
80         return ret;
81 }
82
83 - (void)setKey:(CFStringRef)key forAppID:(CFStringRef)appID fromFloat:(float)value
84 {
85         CFNumberRef numRef = CFNumberCreate(NULL, kCFNumberFloatType, &value);
86         CFPreferencesSetAppValue(key, numRef, appID);
87         CFRelease(numRef);
88 }
89
90 - (int)getIntFromKey:(CFStringRef)key forAppID:(CFStringRef)appID withDefault:(int)defaultValue
91 {
92         CFPropertyListRef value;
93         int ret = defaultValue;
94        
95         value = CFPreferencesCopyAppValue(key, appID);
96         if(value && CFGetTypeID(value) == CFNumberGetTypeID())
97                 CFNumberGetValue(value, kCFNumberIntType, &ret);
98        
99         if(value)
100                 CFRelease(value);
101        
102         return ret;
103 }
104
105 - (void)setKey:(CFStringRef)key forAppID:(CFStringRef)appID fromInt:(int)value
106 {
107         CFNumberRef numRef = CFNumberCreate(NULL, kCFNumberIntType, &value);
108         CFPreferencesSetAppValue(key, numRef, appID);
109         CFRelease(numRef);
110 }
111
112 - (NSString *)getStringFromKey:(CFStringRef)key forAppID:(CFStringRef)appID
113 {
114         CFPropertyListRef value;
115         NSString *ret = nil;
116        
117         value = CFPreferencesCopyAppValue(key, appID);
118         if(value && CFGetTypeID(value) == CFStringGetTypeID())
119                 ret = [NSString stringWithString:(NSString *)value];
120        
121         if(value)
122                 CFRelease(value);
123        
124         return ret;
125 }
126
127 - (void)setKey:(CFStringRef)key forAppID:(CFStringRef)appID fromString:(NSString *)value
128 {
129         CFPreferencesSetAppValue(key, value, appID);
130 }
131
132 #pragma mark Private Functions
133
134 - (NSString *)installationBasePath:(BOOL)userInstallation
135 {
136         if(userInstallation)
137                 return NSHomeDirectory();
138         return [NSString stringWithString:@"/"];
139 }
140
141 - (NSString *)quickTimeComponentDir:(BOOL)userInstallation
142 {
143         return [[self installationBasePath:userInstallation] stringByAppendingPathComponent:@"Library/QuickTime"];
144 }
145
146 - (NSString *)coreAudioComponentDir:(BOOL)userInstallation
147 {
148         return [[self installationBasePath:userInstallation] stringByAppendingPathComponent:@"Library/Audio/Plug-Ins/Components"];
149 }
150
151 - (NSString *)frameworkComponentDir:(BOOL)userInstallation
152 {
153         return [[self installationBasePath:userInstallation] stringByAppendingPathComponent:@"Library/Frameworks"];
154 }
155
156 - (NSString *)basePathForType:(ComponentType)type user:(BOOL)userInstallation
157 {
158         NSString *path = nil;
159        
160         switch(type)
161         {
162                 case ComponentTypeCoreAudio:
163                         path = [self coreAudioComponentDir:userInstallation];
164                         break;
165                 case ComponentTypeQuickTime:
166                         path = [self quickTimeComponentDir:userInstallation];
167                         break;
168                 case ComponentTypeFramework:
169                         path = [self frameworkComponentDir:userInstallation];
170                         break;
171         }
172         return path;
173 }
174
175 - (InstallStatus)installStatusForComponent:(NSString *)component type:(ComponentType)type withMyVersion:(NSString *)myVersion
176 {
177         NSString *path = nil;
178         InstallStatus ret = InstallStatusNotInstalled;
179        
180         path = [[self basePathForType:type user:userInstalled] stringByAppendingPathComponent:component];
181        
182         NSDictionary *infoDict = [NSDictionary dictionaryWithContentsOfFile:[path stringByAppendingPathComponent:@"Contents/Info.plist"]];
183         if(infoDict != nil)
184         {
185                 NSString *currentVersion = [infoDict objectForKey:BundleVersionKey];
186                 if([currentVersion isVersionStringOlderThan:myVersion])
187                         ret = InstallStatusOutdated;
188                 else
189                         ret = InstallStatusInstalled;
190         }
191        
192         /* Check other installation type */
193         path = [[self basePathForType:type user:!userInstalled] stringByAppendingPathComponent:component];
194        
195         infoDict = [NSDictionary dictionaryWithContentsOfFile:[path stringByAppendingPathComponent:@"Contents/Info.plist"]];
196         if(infoDict == nil)
197                 /* Above result is all there is */
198                 return ret;
199        
200         return setWrongLocationInstalled(ret);
201 }
202
203 - (void)setInstalledVersionString
204 {
205         NSString *path = [[self basePathForType:ComponentTypeQuickTime user:userInstalled] stringByAppendingPathComponent:@"Perian.component"];
206         NSString *currentVersion = @"-";
207         NSDictionary *infoDict = [NSDictionary dictionaryWithContentsOfFile:[path stringByAppendingPathComponent:@"Contents/Info.plist"]];
208         if (infoDict != nil)
209                 currentVersion = [infoDict objectForKey:BundleVersionKey];
210         [textField_currentVersion setStringValue:[NSLocalizedString(@"Installed Version: ", @"") stringByAppendingString:currentVersion]];
211 }
212
213 #pragma mark Preference Pane Support
214
215 - (id)initWithBundle:(NSBundle *)bundle
216 {
217         if ( ( self = [super initWithBundle:bundle] ) != nil ) {
218                 perianForumURL = [[NSURL alloc] initWithString:@"http://forums.cocoaforge.com/index.php?c=12"];
219                 perianDonateURL = [[NSURL alloc] initWithString:@"http://perian.org"];
220                 perianWebSiteURL = [[NSURL alloc] initWithString:@"http://perian.org"];
221                
222                 perianAppID = CFSTR("org.perian.Perian");
223                 a52AppID = CFSTR("com.cod3r.a52codec");
224                
225                 NSString *myPath = [[self bundle] bundlePath];
226                
227                 if([myPath hasPrefix:@"/Library"])
228                         userInstalled = NO;
229                 else
230                         userInstalled = YES;
231         }
232        
233         return self;
234 }
235
236 - (NSDictionary *)myInfoDict;
237 {
238         return [NSDictionary dictionaryWithContentsOfFile:[[[self bundle] bundlePath] stringByAppendingPathComponent:@"Contents/Info.plist"]];
239 }
240
241 - (void)checkForInstallation
242 {
243         NSDictionary *infoDict = [self myInfoDict];
244         NSString *myVersion = [infoDict objectForKey:BundleVersionKey];
245        
246         [self setInstalledVersionString];
247         installStatus = [self installStatusForComponent:@"Perian.component" type:ComponentTypeQuickTime withMyVersion:myVersion];
248         if(currentInstallStatus(installStatus) == InstallStatusNotInstalled)
249         {
250                 [textField_installStatus setStringValue:NSLocalizedString(@"Perian is not Installed", @"")];
251                 [button_install setTitle:NSLocalizedString(@"Install Perian", @"")];
252         }
253         else if(currentInstallStatus(installStatus) == InstallStatusOutdated)
254         {
255                 [textField_installStatus setStringValue:NSLocalizedString(@"Perian is Installed, but Outdated", @"")];
256                 [button_install setTitle:NSLocalizedString(@"Update Perian", @"")];
257         }
258         else
259         {
260                 //Perian is fine, but check components
261                 NSDictionary *myComponentsInfo = [infoDict objectForKey:ComponentInfoDictionaryKey];
262                 if(myComponentsInfo != nil)
263                 {
264                         NSEnumerator *componentEnum = [myComponentsInfo objectEnumerator];
265                         NSDictionary *componentInfo = nil;
266                         while((componentInfo = [componentEnum nextObject]) != nil)
267                         {
268                                 InstallStatus tstatus = [self installStatusForComponent:[componentInfo objectForKey:ComponentNameKey] type:[[componentInfo objectForKey:ComponentTypeKey] intValue] withMyVersion:[componentInfo objectForKey:BundleVersionKey]];
269                                 if(tstatus < installStatus)
270                                         installStatus = tstatus;
271                         }
272                         switch(installStatus)
273                         {
274                                 case InstallStatusInstalledInWrongLocation:
275                                 case InstallStatusNotInstalled:
276                                         [textField_installStatus setStringValue:NSLocalizedString(@"Perian is Installed, but parts are Not Installed", @"")];
277                                         [button_install setTitle:NSLocalizedString(@"Install Perian", @"")];
278                                         break;
279                                 case InstallStatusOutdatedWithAnotherInWrongLocation:
280                                 case InstallStatusOutdated:
281                                         [textField_installStatus setStringValue:NSLocalizedString(@"Perian is Installed, but parts are Outdated", @"")];
282                                         [button_install setTitle:NSLocalizedString(@"Update Perian", @"")];
283                                         break;
284                                 case InstallStatusInstalledInBothLocations:
285                                         [textField_installStatus setStringValue:NSLocalizedString(@"Perian is Installed Twice", @"")];
286                                         [button_install setTitle:NSLocalizedString(@"Correct Installation", @"")];
287                                         break;
288                                 case InstallStatusInstalled:
289                                         [textField_installStatus setStringValue:NSLocalizedString(@"Perian is Installed", @"")];
290                                         [button_install setTitle:NSLocalizedString(@"Remove Perian", @"")];
291                                         break;
292                         }
293                 }
294                 else if(isWrongLocationInstalled(installStatus))
295                 {
296                         [textField_installStatus setStringValue:NSLocalizedString(@"Perian is Installed Twice", @"")];
297                         [button_install setTitle:NSLocalizedString(@"Correct Installation", @"")];
298                 }
299                 else
300                 {
301                         [textField_installStatus setStringValue:NSLocalizedString(@"Perian is Installed", @"")];
302                         [button_install setTitle:NSLocalizedString(@"Remove Perian", @"")];
303                 }
304                
305         }
306 }
307
308 - (int)upgradeA52Prefs
309 {
310         int twoChannelMode;
311         if([self getBoolFromKey:AC3StereoOverDolbyKey forAppID:a52AppID withDefault:NO])
312                 twoChannelMode = A52_STEREO;
313         else if([self getBoolFromKey:AC3ProLogicIIKey forAppID:a52AppID withDefault:NO])
314                 twoChannelMode = A52_DOLBY | A52_USE_DPLII;
315         else
316                 twoChannelMode = A52_DOLBY;
317        
318         [self setKey:AC3TwoChannelModeKey forAppID:a52AppID fromInt:twoChannelMode];
319         return twoChannelMode;
320 }
321
322 - (void)didSelect
323 {
324         /* General */
325         [self checkForInstallation];
326         NSString *lastInstVersion = [self getStringFromKey:LastInstalledVersionKey forAppID:perianAppID];
327         NSString *myVersion = [[self myInfoDict] objectForKey:BundleVersionKey];
328        
329         NSAttributedString              *about;
330     about = [[[NSAttributedString alloc] initWithPath:[[self bundle] pathForResource:@"Read Me" ofType:@"rtf"]
331                                                                          documentAttributes:nil] autorelease];
332         [[textView_about textStorage] setAttributedString:about];
333         [[textView_about enclosingScrollView] setLineScroll:10];
334         [[textView_about enclosingScrollView] setPageScroll:20];
335        
336         if((lastInstVersion == nil || [lastInstVersion isVersionStringOlderThan:myVersion]) && installStatus != InstallStatusInstalled)
337         {
338                 /*Check for temp after an update */
339                 BOOL isDir = NO;
340                 NSString *tempPrefPane = [NSTemporaryDirectory() stringByAppendingPathComponent:@"PerianPane.prefPane"];
341                 int tag;
342                
343                 if([[NSFileManager defaultManager] fileExistsAtPath:tempPrefPane isDirectory:&isDir] && isDir)
344                         [[NSWorkspace sharedWorkspace] performFileOperation:NSWorkspaceRecycleOperation
345                                                                                                                  source:[tempPrefPane stringByDeletingLastPathComponent]
346                                                                                                         destination:@""
347                                                                                                                   files:[NSArray arrayWithObject:[tempPrefPane lastPathComponent]]
348                                                                                                                         tag:&tag];
349                
350                 [self installUninstall:nil];
351                 [self setKey:LastInstalledVersionKey forAppID:perianAppID fromString:myVersion];
352         }
353        
354         NSDate *updateDate = (NSDate *)CFPreferencesCopyAppValue((CFStringRef)NEXT_RUN_KEY, perianAppID);
355         if([updateDate timeIntervalSinceNow] > 1000000000) //futureDate
356                 [button_autoUpdateCheck setIntValue:0];
357         else
358                 [button_autoUpdateCheck setIntValue:1];
359         [updateDate release];
360        
361         /* A52 Prefs */
362         unsigned twoChannelMode = [self getIntFromKey:AC3TwoChannelModeKey forAppID:a52AppID withDefault:0xffffffff];
363         if (twoChannelMode != 0xffffffff)
364         {
365                 /* sanity checks */
366                 if(twoChannelMode & A52_CHANNEL_MASK & 0xf7 != 2)
367                 {
368                         /* matches 2 and 10, which is Stereo and Dolby */
369                         twoChannelMode = A52_DOLBY;
370                 }
371                 twoChannelMode &= ~A52_ADJUST_LEVEL & ~A52_LFE;         
372         }
373         else
374                 twoChannelMode = [self upgradeA52Prefs];
375         CFPreferencesSetAppValue(AC3StereoOverDolbyKey, NULL, a52AppID);
376         CFPreferencesSetAppValue(AC3ProLogicIIKey, NULL, a52AppID);
377         switch(twoChannelMode)
378         {
379                 case A52_STEREO:
380                         [popup_outputMode selectItemAtIndex:0];
381                         break;
382                 case A52_DOLBY:
383                         [popup_outputMode selectItemAtIndex:1];
384                         break;
385                 case A52_DOLBY | A52_USE_DPLII:
386                         [popup_outputMode selectItemAtIndex:2];
387                         break;
388                 default:
389                         [popup_outputMode selectItemAtIndex:3];
390                         break;                 
391         }
392        
393         [self setAC3DynamicRange:[self getFloatFromKey:AC3DynamicRangeKey forAppID:a52AppID withDefault:1.0]];
394 }
395
396 - (void)didUnselect
397 {
398         CFPreferencesAppSynchronize(perianAppID);
399         CFPreferencesAppSynchronize(a52AppID);
400 }
401
402 - (void) dealloc {
403         [[NSDistributedNotificationCenter defaultCenter] removeObserver:self name:UPDATE_STATUS_NOTIFICATION object:nil];
404         [perianForumURL release];
405         [perianDonateURL release];
406         [perianWebSiteURL release];
407         if(auth != nil)
408                 AuthorizationFree(auth, 0);
409         [errorString release];
410         [super dealloc];
411 }
412
413 #pragma mark Install/Uninstall
414
415 /* Shamelessly ripped from Sparkle */
416 - (BOOL)_extractArchivePath:archivePath toDestination:(NSString *)destination finalPath:(NSString *)finalPath
417 {
418         BOOL ret = NO, oldExist = NO;
419         struct stat sb;
420        
421         if(stat([finalPath fileSystemRepresentation], &sb) == 0)
422                 oldExist = YES;
423        
424         char *buf = NULL;
425         if(oldExist)
426                 asprintf(&buf,
427                                  "mv -f \"$DST_COMPONENT\" \"$TMP_PATH\" && "
428                                  "ditto -x -k --rsrc \"$SRC_ARCHIVE\" \"$DST_PATH\" && "
429                                  "rm -rf \"$TMP_PATH\"");
430         else
431                 asprintf(&buf,
432                                  "mkdir -p \"$DST_PATH\" && "
433                                  "ditto -x -k --rsrc \"$SRC_ARCHIVE\" \"$DST_PATH\"");
434         if(!buf)
435         {
436                 [errorString appendFormat:NSLocalizedString(@"Could not allocate memory for extraction command\n", @"")];
437                 return FALSE;
438         }
439        
440         setenv("SRC_ARCHIVE", [archivePath fileSystemRepresentation], 1);
441         setenv("DST_PATH", [destination fileSystemRepresentation], 1);
442         setenv("DST_COMPONENT", [finalPath fileSystemRepresentation], 1);
443         setenv("TMP_PATH", [[finalPath stringByAppendingPathExtension:@"old"] fileSystemRepresentation], 1);
444
445         int status = system(buf);
446         if(WIFEXITED(status) && WEXITSTATUS(status) == 0)
447                 ret = YES;
448         else
449                 [errorString appendFormat:NSLocalizedString(@"Extraction for %@ failed\n", @""), archivePath];
450
451         free(buf);
452         unsetenv("SRC_ARCHIVE");
453         unsetenv("$DST_COMPONENT");
454         unsetenv("TMP_PATH");
455         unsetenv("DST_PATH");
456         return ret;
457 }
458
459 - (BOOL)_authenticatedExtractArchivePath:(NSString *)archivePath toDestination:(NSString *)destination finalPath:(NSString *)finalPath
460 {
461         BOOL ret = NO, oldExist = NO;
462         struct stat sb;
463         if(stat([finalPath fileSystemRepresentation], &sb) == 0)
464                 oldExist = YES;
465        
466         char *buf = NULL;
467         if(oldExist)
468                 asprintf(&buf,
469                                  "mv -f \"$DST_COMPONENT\" \"$TMP_PATH\" && "
470                                  "ditto -x -k --rsrc \"$SRC_ARCHIVE\" \"$DST_PATH\" && "
471                                  "rm -rf \"$TMP_PATH\" && "
472                                  "chown -R root:admin \"$DST_COMPONENT\"");
473         else
474                 asprintf(&buf,
475                                  "mkdir -p \"$DST_PATH\" && "
476                                  "ditto -x -k --rsrc \"$SRC_ARCHIVE\" \"$DST_PATH\" && "
477                                  "chown -R root:admin \"$DST_COMPONENT\"");
478         if(!buf)
479         {
480                 [errorString appendFormat:NSLocalizedString(@"Could not allocate memory for extraction command\n", @"")];
481                 return FALSE;
482         }
483        
484         setenv("SRC_ARCHIVE", [archivePath fileSystemRepresentation], 1);
485         setenv("DST_COMPONENT", [finalPath fileSystemRepresentation], 1);
486         setenv("TMP_PATH", [[finalPath stringByAppendingPathExtension:@"old"] fileSystemRepresentation], 1);
487         setenv("DST_PATH", [destination fileSystemRepresentation], 1);
488        
489         char* arguments[] = { "-c", buf, NULL };
490         if(AuthorizationExecuteWithPrivileges(auth, "/bin/sh", kAuthorizationFlagDefaults, arguments, NULL) == errAuthorizationSuccess)
491         {
492                 int status;
493                 int pid = wait(&status);
494                 if(pid != -1 && WIFEXITED(status) && WEXITSTATUS(status) == 0)
495                         ret = YES;
496                 else
497                         [errorString appendFormat:NSLocalizedString(@"Extraction for %@ failed\n", @""), archivePath];
498         }
499         else
500                 [errorString appendFormat:NSLocalizedString(@"Authentication failed for extraction for %@\n", @""), archivePath];
501        
502         free(buf);
503         unsetenv("SRC_ARCHIVE");
504         unsetenv("$DST_COMPONENT");
505         unsetenv("TMP_PATH");
506         unsetenv("DST_PATH");
507         return ret;
508 }
509
510 - (BOOL)_authenticatedRemove:(NSString *)componentPath
511 {
512         BOOL ret = NO;
513         struct stat sb;
514         if(stat([componentPath fileSystemRepresentation], &sb) != 0)
515                 /* No error, just forget it */
516                 return FALSE;
517        
518         char *buf = NULL;
519         asprintf(&buf,
520                          "rm -rf \"$COMP_PATH\"");
521         if(!buf)
522         {
523                 [errorString appendFormat:NSLocalizedString(@"Could not allocate memory for removal command\n", @"")];
524                 return FALSE;
525         }
526        
527         setenv("COMP_PATH", [componentPath fileSystemRepresentation], 1);
528        
529         char* arguments[] = { "-c", buf, NULL };
530         if(AuthorizationExecuteWithPrivileges(auth, "/bin/sh", kAuthorizationFlagDefaults, arguments, NULL) == errAuthorizationSuccess)
531         {
532                 int status;
533                 int pid = wait(&status);
534                 if(pid != -1 && WIFEXITED(status) && WEXITSTATUS(status) == 0)
535                         ret = YES;
536                 else
537                         [errorString appendFormat:NSLocalizedString(@"Removal for %@ failed\n", @""), componentPath];
538         }
539         else
540                 [errorString appendFormat:NSLocalizedString(@"Authentication failed for removal for %@\n", @""), componentPath];
541         free(buf);
542         unsetenv("COMP_PATH");
543         return ret;
544 }
545
546
547 - (BOOL)installArchive:(NSString *)archivePath forPiece:(NSString *)component type:(ComponentType)type withMyVersion:(NSString *)myVersion
548 {
549         NSString *containingDir = [self basePathForType:type user:userInstalled];
550         BOOL ret = YES;
551
552         InstallStatus pieceStatus = [self installStatusForComponent:component type:type withMyVersion:myVersion];
553         if(!userInstalled && currentInstallStatus(pieceStatus) != InstallStatusInstalled)
554         {
555                 BOOL result = [self _authenticatedExtractArchivePath:archivePath toDestination:containingDir finalPath:[containingDir stringByAppendingPathComponent:component]];
556                 if(result == NO)
557                         ret = NO;
558         }
559         else
560         {
561                 //Not authenticated
562                 if(currentInstallStatus(pieceStatus) == InstallStatusOutdated)
563                 {
564                         //Remove the old one here
565                         int tag = 0;
566                         BOOL result = [[NSWorkspace sharedWorkspace] performFileOperation:NSWorkspaceRecycleOperation source:containingDir destination:@"" files:[NSArray arrayWithObject:component] tag:&tag];
567                         if(result == NO)
568                                 ret = NO;
569                 }
570                 if(currentInstallStatus(pieceStatus) != InstallStatusInstalled)
571                 {
572                         //Decompress and install new one
573                         BOOL result = [self _extractArchivePath:archivePath toDestination:containingDir finalPath:[containingDir stringByAppendingPathComponent:component]];
574                         if(result == NO)
575                                 ret = NO;
576                 }               
577         }
578         if(ret != NO && isWrongLocationInstalled(pieceStatus) != 0)
579         {
580                 /* Let's try and remove the wrong one, if we can, but only if install succeeded */
581                 containingDir = [self basePathForType:type user:!userInstalled];
582
583                 if(userInstalled)
584                         [self _authenticatedRemove:[containingDir stringByAppendingPathComponent:component]];
585                 else
586                 {
587                         int tag = 0;
588                         [[NSWorkspace sharedWorkspace] performFileOperation:NSWorkspaceRecycleOperation source:containingDir destination:@"" files:[NSArray arrayWithObject:component] tag:&tag];
589                 }
590         }
591         return ret;
592 }
593
594 - (void)install:(id)sender
595 {
596         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
597         NSDictionary *infoDict = [self myInfoDict];
598         NSDictionary *myComponentsInfo = [infoDict objectForKey:ComponentInfoDictionaryKey];
599         NSString *componentPath = [[[self bundle] resourcePath] stringByAppendingPathComponent:@"Components"];
600         NSString *coreAudioComponentPath = [componentPath stringByAppendingPathComponent:@"CoreAudio"];
601         NSString *quickTimeComponentPath = [componentPath stringByAppendingPathComponent:@"QuickTime"];
602         NSString *frameworkComponentPath = [componentPath stringByAppendingPathComponent:@"Frameworks"];
603
604         [errorString release];
605         errorString = [[NSMutableString alloc] init];
606         /* This doesn't ask the user, so create it anyway.  If we don't need it, no problem */
607         if(AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &auth) != errAuthorizationSuccess)
608                 /* Oh well, hope we don't need it */
609                 auth = nil;
610        
611         [self installArchive:[componentPath stringByAppendingPathComponent:@"Perian.zip"] forPiece:@"Perian.component" type:ComponentTypeQuickTime withMyVersion:[infoDict objectForKey:BundleVersionKey]];
612        
613         NSEnumerator *componentEnum = [myComponentsInfo objectEnumerator];
614         NSDictionary *myComponent = nil;
615         while((myComponent = [componentEnum nextObject]) != nil)
616         {
617                 NSString *archivePath = nil;
618                 ComponentType type = [[myComponent objectForKey:ComponentTypeKey] intValue];
619                 switch(type)
620                 {
621                         case ComponentTypeCoreAudio:
622                                 archivePath = [coreAudioComponentPath stringByAppendingPathComponent:[myComponent objectForKey:ComponentArchiveNameKey]];
623                                 break;
624                         case ComponentTypeQuickTime:
625                                 archivePath = [quickTimeComponentPath stringByAppendingPathComponent:[myComponent objectForKey:ComponentArchiveNameKey]];
626                                 break;
627                         case ComponentTypeFramework:
628                                 archivePath = [frameworkComponentPath stringByAppendingPathComponent:[myComponent objectForKey:ComponentArchiveNameKey]];
629                                 break;
630                 }
631                 [self installArchive:archivePath forPiece:[myComponent objectForKey:ComponentNameKey] type:type withMyVersion:[myComponent objectForKey:BundleVersionKey]];
632         }
633         if(auth != nil)
634         {
635                 AuthorizationFree(auth, 0);
636                 auth = nil;
637         }
638         [self performSelectorOnMainThread:@selector(installComplete:) withObject:nil waitUntilDone:NO];
639         [pool release];
640 }
641
642 - (void)uninstall:(id)sender
643 {
644         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
645         NSDictionary *infoDict = [self myInfoDict];
646         NSDictionary *myComponentsInfo = [infoDict objectForKey:ComponentInfoDictionaryKey];
647         NSFileManager *fileManager = [NSFileManager defaultManager];
648         NSString *componentPath;
649
650         [errorString release];
651         errorString = [[NSMutableString alloc] init];
652         /* This doesn't ask the user, so create it anyway.  If we don't need it, no problem */
653         if(AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &auth) != errAuthorizationSuccess)
654                 /* Oh well, hope we don't need it */
655                 auth = nil;
656        
657         componentPath = [[self quickTimeComponentDir:userInstalled] stringByAppendingPathComponent:@"Perian.component"];
658         if(auth != nil && !userInstalled)
659                 [self _authenticatedRemove:componentPath];
660         else
661                 [fileManager removeFileAtPath:componentPath handler:nil];
662        
663         NSEnumerator *componentEnum = [myComponentsInfo objectEnumerator];
664         NSDictionary *myComponent = nil;
665         while((myComponent = [componentEnum nextObject]) != nil)
666         {
667                 ComponentType type = [[myComponent objectForKey:ComponentTypeKey] intValue];
668                 NSString *directory = [self basePathForType:type user:userInstalled];
669                 componentPath = [directory stringByAppendingPathComponent:[myComponent objectForKey:ComponentNameKey]];
670                 if(auth != nil && !userInstalled)
671                         [self _authenticatedRemove:componentPath];
672                 else
673                         [fileManager removeFileAtPath:componentPath handler:nil];
674         }
675         if(auth != nil)
676         {
677                 AuthorizationFree(auth, 0);
678                 auth = nil;
679         }
680        
681         [self performSelectorOnMainThread:@selector(installComplete:) withObject:nil waitUntilDone:NO];
682         [pool release];
683 }
684
685 - (IBAction)installUninstall:(id)sender
686 {
687         if(installStatus == InstallStatusInstalled)
688                 [NSThread detachNewThreadSelector:@selector(uninstall:) toTarget:self withObject:nil];
689         else
690                 [NSThread detachNewThreadSelector:@selector(install:) toTarget:self withObject:nil];
691 }
692
693 - (void)installComplete:(id)sender
694 {
695         [self checkForInstallation];
696 }
697
698 #pragma mark Check Updates
699 - (void)updateCheckStatusChanged:(NSNotification*)notification
700 {
701         NSString *status = [notification object];
702        
703         //FIXME localize these
704         if ([status isEqualToString:@"Starting"]) {
705                 [textField_updateStatus setStringValue:@"Checking..."];
706         } else if ([status isEqualToString:@"Error"]) {
707                 [textField_updateStatus setStringValue:@"Couldn't reach the update server."];
708         } else if ([status isEqualToString:@"NoUpdates"]) {
709                 [textField_updateStatus setStringValue:@"There are no updates."];
710         } else if ([status isEqualToString:@"NoUpdates"]) {
711                 [textField_updateStatus setStringValue:@"Updates found!"];
712         }
713 }
714
715 - (IBAction)updateCheck:(id)sender
716 {
717         FSRef updateCheckRef;
718        
719         [[NSDistributedNotificationCenter defaultCenter] removeObserver:self name:UPDATE_STATUS_NOTIFICATION object:nil];
720         [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(updateCheckStatusChanged:) name:UPDATE_STATUS_NOTIFICATION object:nil];
721         CFPreferencesSetAppValue((CFStringRef)NEXT_RUN_KEY, NULL, perianAppID);
722         CFPreferencesSetAppValue((CFStringRef)MANUAL_RUN_KEY, [NSNumber numberWithBool:YES], perianAppID);
723         CFPreferencesAppSynchronize(perianAppID);
724         OSStatus status = FSPathMakeRef((UInt8 *)[[[[self bundle] bundlePath] stringByAppendingPathComponent:@"Contents/Resources/PerianUpdateChecker.app"] fileSystemRepresentation], &updateCheckRef, NULL);
725         if(status != noErr)
726                 return;
727        
728         LSOpenFSRef(&updateCheckRef, NULL);
729 }
730
731 - (IBAction)setAutoUpdateCheck:(id)sender
732 {
733         CFStringRef key = (CFStringRef)NEXT_RUN_KEY;
734         if([button_autoUpdateCheck intValue])
735                 CFPreferencesSetAppValue(key, [NSDate dateWithTimeIntervalSinceNow:TIME_INTERVAL_TIL_NEXT_RUN], perianAppID);
736         else
737                 CFPreferencesSetAppValue(key, [NSDate distantFuture], perianAppID);
738    
739     CFPreferencesAppSynchronize(perianAppID);
740 }
741
742
743 #pragma mark AC3
744 - (IBAction)setAC3DynamicRangePopup:(id)sender
745 {
746         int selected = [popup_ac3DynamicRangeType indexOfSelectedItem];
747         switch(selected)
748         {
749                 case 0:
750                         [self saveAC3DynamicRange:1.0];
751                         break;
752                 case 1:
753                         [self saveAC3DynamicRange:2.0];
754                         break;
755                 case 3:
756                         [NSApp beginSheet:window_dynRangeSheet modalForWindow:[[self mainView] window] modalDelegate:nil didEndSelector:nil contextInfo:NULL];
757                         break;
758                 default:
759                         break;
760         }
761 }
762
763 - (IBAction)set2ChannelModePopup:(id)sender;
764 {
765         int selected = [popup_outputMode indexOfSelectedItem];
766         switch(selected)
767         {
768                 case 0:
769                         [self setKey:AC3TwoChannelModeKey forAppID:a52AppID fromInt:A52_STEREO];
770                         break;
771                 case 1:
772                         [self setKey:AC3TwoChannelModeKey forAppID:a52AppID fromInt:A52_DOLBY];
773                         break;
774                 case 2:
775                         [self setKey:AC3TwoChannelModeKey forAppID:a52AppID fromInt:A52_DOLBY | A52_USE_DPLII];
776                         break;
777                 case 3:
778                         [self setKey:AC3TwoChannelModeKey forAppID:a52AppID fromInt:0];
779                         break;
780                 default:
781                         break;
782         }       
783 }
784
785 - (void)setAC3DynamicRange:(float)newVal
786 {
787         if(newVal > 4.0)
788                 newVal = 4.0;
789         if(newVal < 0.0)
790                 newVal = 0.0;
791        
792         nextDynValue = newVal;
793         [textField_ac3DynamicRangeValue setFloatValue:newVal];
794         [slider_ac3DynamicRangeSlider setFloatValue:newVal];
795         if(newVal == 1.0)
796                 [popup_ac3DynamicRangeType selectItemAtIndex:0];
797         else if(newVal == 2.0)
798                 [popup_ac3DynamicRangeType selectItemAtIndex:1];
799         else
800                 [popup_ac3DynamicRangeType selectItemAtIndex:3];
801 }
802
803 - (void)saveAC3DynamicRange:(float)newVal
804 {
805         [self setKey:AC3DynamicRangeKey forAppID:a52AppID fromFloat:newVal];
806         [self setAC3DynamicRange:newVal];
807 }
808
809 - (IBAction)setAC3DynamicRangeValue:(id)sender
810 {
811         float newVal = [textField_ac3DynamicRangeValue floatValue];
812        
813         [self setAC3DynamicRange:newVal];
814 }
815
816 - (IBAction)setAC3DynamicRangeSlider:(id)sender
817 {
818         float newVal = [slider_ac3DynamicRangeSlider floatValue];
819        
820         [self setAC3DynamicRange:newVal];
821 }
822
823 - (IBAction)cancelDynRangeSheet:(id)sender
824 {
825         [self setAC3DynamicRange:[self getFloatFromKey:AC3DynamicRangeKey forAppID:a52AppID withDefault:1.0]];
826         [NSApp endSheet:window_dynRangeSheet];
827         [window_dynRangeSheet orderOut:self];
828 }
829
830 - (IBAction)saveDynRangeSheet:(id)sender;
831 {
832         [NSApp endSheet:window_dynRangeSheet];
833         [self saveAC3DynamicRange:nextDynValue];
834         [window_dynRangeSheet orderOut:self];
835 }
836
837 #pragma mark Subtitles
838 - (IBAction)setLoadExternalSubtitles:(id)sender
839 {       
840         [self setKey:ExternalSubtitlesKey forAppID:perianAppID fromBool:(BOOL)[sender state]];
841     CFPreferencesAppSynchronize(perianAppID);
842 }
843
844 #pragma mark About
845 - (IBAction)launchWebsite:(id)sender
846 {
847         [[NSWorkspace sharedWorkspace] openURL:perianWebSiteURL];
848 }
849
850 - (IBAction)launchDonate:(id)sender
851 {
852        
853         [[NSWorkspace sharedWorkspace] openURL:perianDonateURL];
854 }
855
856 - (IBAction)launchForum:(id)sender
857 {
858        
859         [[NSWorkspace sharedWorkspace] openURL:perianForumURL];
860        
861 }
862
863 #pragma mark Component Manager
864 - (NSArray *)installedComponents
865 {
866         NSMutableArray *mComps = [[NSMutableArray alloc] init];
867        
868         NSMutableArray* allComps = [NSMutableArray array];
869        
870         [allComps addObjectsFromArray:  GetComponentsInFolder(@"/Library/Components")];
871         [allComps addObjectsFromArray:  GetComponentsInFolder(@"/Library/QuickTime")];
872         [allComps addObjectsFromArray:  GetComponentsInFolder(@"~/Library/QuickTime")];
873         [allComps addObjectsFromArray:  GetComponentsInFolder(@"~/Library/Components")];
874        
875         [allComps addObjectsFromArray:  GetComponentsInFolder(@"/Library/Components (Disabled)")];
876         [allComps addObjectsFromArray:  GetComponentsInFolder(@"/Library/QuickTime (Disabled)")];
877         [allComps addObjectsFromArray:  GetComponentsInFolder(@"~/Library/QuickTime (Disabled)")];
878         [allComps addObjectsFromArray:  GetComponentsInFolder(@"~/Library/Components (Disabled)")];
879
880         unsigned x= 0;
881         for (x = 0; x < [allComps count]; x++)
882         {       
883                 NSString* comp = [allComps objectAtIndex:x];
884                 ECQTComponent* compobj = [ECQTComponent componentWithPath:comp];
885                 if (compobj && [compobj name])
886                         [mComps addObject:compobj];
887         }
888         return [mComps autorelease];
889 }
890
891 @end
892
893 NSArray* GetComponentsInFolder(NSString* folder)
894 {
895         folder = [folder stringByExpandingTildeInPath];
896         NSFileManager* fm = [NSFileManager defaultManager];
897         NSArray* contents = [fm directoryContentsAtPath:folder];
898         NSMutableArray* newContents = [NSMutableArray array];
899         unsigned x;
900         for (x = 0; x < [contents count]; x++)
901         {
902                 NSString* comp = [contents objectAtIndex:x];
903                
904                 [newContents addObject:[folder stringByAppendingPathComponent:comp]];
905         }
906         return newContents;
907 }
Note: See TracBrowser for help on using the browser.