source: trunk/CPFPerianPrefPaneController.m @ 1450

Revision 1450, 34.8 KB checked in by astrange, 3 years ago (diff)

Delete the user's QT web plugin cache after installing

Fixes #572

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