source: trunk/CPFPerianPrefPaneController.m @ 970

Revision 970, 32.1 KB checked in by gbooker, 5 years ago (diff)

Sanity check all pref types.
Fixes #385

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