source: trunk/CPFPerianPrefPaneController.m @ 1126

Revision 1126, 35.1 KB checked in by astrange, 5 years ago (diff)

Add a opener app so we have something to define public.movie UTI for new file types.

Refs #163.

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