source: trunk/SapphireFrappliance/FRAppliance/SapphireImportHelper.m @ 1090

Revision 1090, 11.5 KB checked in by gbooker, 4 years ago (diff)

Corrected import helper to correctly work with background importers

Line 
1/*
2 * SapphireImportHelper.m
3 * Sapphire
4 *
5 * Created by Graham Booker on Dec. 8, 2007.
6 * Copyright 2007 Sapphire Development Team and/or www.nanopi.net
7 * All rights reserved.
8 *
9 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
10 * General Public License as published by the Free Software Foundation; either version 3 of the License,
11 * or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
14 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
15 * Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along with this program; if not,
18 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19 */
20
21#import <Security/Security.h>
22#include <sys/stat.h>
23#include <sys/mount.h>
24
25#import "SapphireImportHelper.h"
26#import "SapphireMetaData.h"
27#import "SapphireAllImporter.h"
28#import "SapphireFileMetaData.h"
29#import "SapphireMetaDataSupport.h"
30#import "SapphireApplianceController.h"
31
32#define CONNECTION_NAME @"Sapphire Server"
33
34@interface SapphireImportFile : NSObject <SapphireImportFileProtocol>{
35        NSString                                                                *path;
36        id <SapphireImporterBackgroundProtocol> informer;
37        ImportType                                                              type;
38}
39- (id)initWithPath:(NSString *)aPath informer:(id <SapphireImporterBackgroundProtocol>)aInformer type:(ImportType)aType;
40@end
41
42@interface SapphireImportHelperServer ()
43- (void)startClient;
44@end
45
46@implementation SapphireImportHelper
47
48static SapphireImportHelper *shared = nil;
49
50+ (SapphireImportHelper *)sharedHelperForContext:(NSManagedObjectContext *)moc
51{
52        if(shared == nil && moc != nil)
53                shared = [[SapphireImportHelperServer alloc] initWithContext:moc];
54
55        return shared;
56}
57
58+ (void)relinquishHelper
59{
60        if(shared != nil)
61                [shared relinquishHelper];
62}
63
64- (void)relinquishHelper
65{
66}
67
68- (BOOL)importFileData:(SapphireFileMetaData *)file inform:(id <SapphireImporterBackgroundProtocol>)inform;
69{
70        return YES;
71}
72
73- (void)importAllData:(SapphireFileMetaData *)file inform:(id <SapphireImporterBackgroundProtocol>)inform;
74{
75}
76
77- (void)removeObjectsWithInform:(id <SapphireImporterBackgroundProtocol>)inform
78{
79}
80
81@end
82
83@implementation SapphireImportHelperClient
84
85- (id)initWithContext:(NSManagedObjectContext *)context
86{
87        self = [super init];
88        if(!self)
89                return nil;
90       
91        moc = [context retain];
92        allImporter = [[SapphireAllImporter alloc] init];
93        [allImporter setDelegate:self];
94        keepRunning = YES;
95       
96        return self;
97}
98- (void) dealloc
99{
100        [server release];
101        [allImporter release];
102        [moc release];
103        [super dealloc];
104}
105
106- (void)backgroundImporter:(id <SapphireImporter>)importer completedImportOnPath:(NSString *)path withState:(ImportState)state
107{
108        importComplete = YES;
109}
110
111- (BOOL)canDisplayChooser
112{
113        return NO;
114}
115
116- (id)chooserScene
117{
118        return nil;
119}
120
121- (void)displayChooser:(BRLayerController <SapphireChooser> *)chooser forImporter:(id <SapphireImporter>)importer withContext:(id)context
122{
123}
124
125- (BOOL)importFileData:(SapphireFileMetaData *)file inform:(id <SapphireImporterBackgroundProtocol>)inform;
126{
127        updateMetaData(file);
128        return YES;
129}
130
131- (void)startChild
132{
133        /*Child here*/
134        @try {
135                NSConnection *connection = [NSConnection connectionWithRegisteredName:CONNECTION_NAME host:nil];
136                id serverobj = [[connection rootProxy] retain];
137                [serverobj setProtocolForProxy:@protocol(SapphireImportServer)];
138                shared = self;
139                [serverobj setClient:(SapphireImportHelperClient *)shared];
140                server = serverobj;     
141                [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(connectionDidDie:) name:NSConnectionDidDieNotification object:nil];           
142        }
143        @catch (NSException * e) {
144                keepRunning = NO;
145        }
146}
147
148- (BOOL)keepRunning
149{
150        return keepRunning;
151}
152
153- (void)connectionDidDie:(NSNotification *)note
154{
155        [self exitChild];
156}
157
158- (oneway void)exitChild
159{
160        keepRunning = NO;
161}
162
163- (void)realStartQueue
164{
165        @try {
166                id <SapphireImportFileProtocol> file;
167                while((file = [server nextFile]) != nil)
168                {
169                        [moc reset];
170                        NSAutoreleasePool *singleImportPool = [[NSAutoreleasePool alloc] init];
171                        ImportType type = [file importType];
172                        BOOL ret;
173                        NSString *path = [file path];
174                        SapphireFileMetaData *file = [SapphireFileMetaData fileWithPath:path inContext:moc];
175                        [moc refreshObject:file mergeChanges:YES];
176                        if(type == IMPORT_TYPE_FILE_DATA)
177                                ret = updateMetaData(file);
178                        else
179                        {
180                                ImportState state = [allImporter importMetaData:file path:[file path]];
181                                if(state == ImportStateBackground || state == ImportStateMultipleSuspend)
182                                {
183                                        importComplete = NO;
184                                        NSRunLoop *currentRL = [NSRunLoop currentRunLoop];
185                                        while(!importComplete && [currentRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]])
186                                                ;
187                                        ret = YES;
188                                }
189                                else
190                                        ret = (state == ImportStateUpdated);
191                        }
192                        NSDictionary *changes = [SapphireMetaDataSupport changesDictionaryForContext:moc];
193                        [server importCompleteWithChanges:changes updated:ret];
194                        [singleImportPool release];
195                }
196        }
197        @catch (NSException * e) {
198                [SapphireApplianceController logException:e];
199                keepRunning = NO;
200        }
201}
202
203- (oneway void)startQueue
204{
205        [self performSelectorOnMainThread:@selector(realStartQueue) withObject:nil waitUntilDone:NO];
206}
207
208@end
209
210@implementation SapphireImportHelperServer
211
212- (id)initWithContext:(NSManagedObjectContext *)context
213{
214        self = [super init];
215        if (self == nil)
216                return nil;
217       
218        queue = [[NSMutableArray alloc] init];
219        queueSuspended = NO;
220       
221        serverConnection = [NSConnection defaultConnection];
222        [serverConnection setRootObject:self];
223        if([serverConnection registerName:CONNECTION_NAME] == NO)
224                SapphireLog(SAPPHIRE_LOG_GENERAL, SAPPHIRE_LOG_LEVEL_ERROR, @"Register failed");
225       
226        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(connectionDidDie:) name:NSConnectionDidDieNotification object:nil];
227        moc = [context retain];
228       
229        [self startClient];
230       
231        return self;
232}
233
234- (void) dealloc
235{
236        [[NSNotificationCenter defaultCenter] removeObserver:self];
237        [client release];
238        [moc release];
239        [queue release];
240        [currentImporting release];
241        [super dealloc];
242}
243
244- (void)relinquishHelper
245{
246        [client exitChild];
247        [serverConnection registerName:nil];
248        [serverConnection setRootObject:nil];
249        [shared autorelease];
250        shared = nil;
251}
252
253- (BOOL)isSlashReadOnly
254{
255        struct statfs *mntbufp;
256       
257    int i, mountCount = getmntinfo(&mntbufp, MNT_NOWAIT);
258        for(i=0; i<mountCount; i++)
259        {
260                if(!strcmp(mntbufp[i].f_mntonname, "/"))
261                        return (mntbufp[i].f_flags & MNT_RDONLY) ? YES : NO;
262        }
263       
264        return NO;
265}
266
267- (BOOL)fixClientPermissions:(NSString *)path
268{
269        /* Permissions are incorrect */
270        AuthorizationItem authItems[2] = {
271                {kAuthorizationEnvironmentUsername, strlen("frontrow"), "frontrow", 0},
272                {kAuthorizationEnvironmentPassword, strlen("frontrow"), "frontrow", 0},
273        };
274        AuthorizationEnvironment environ = {2, authItems};
275        AuthorizationItem rightSet[] = {{kAuthorizationRightExecute, 0, NULL, 0}};
276        AuthorizationRights rights = {1, rightSet};
277        AuthorizationRef auth;
278        OSStatus result = AuthorizationCreate(&rights, &environ, kAuthorizationFlagPreAuthorize | kAuthorizationFlagExtendRights, &auth);
279        if(result == errAuthorizationSuccess)
280        {
281                BOOL roslash = [self isSlashReadOnly];
282                if(roslash)
283                {
284                        char *command = "mount -uw /";
285                        char *arguments[] = {"-c", command, NULL};
286                        AuthorizationExecuteWithPrivileges(auth, "/bin/sh", kAuthorizationFlagDefaults, arguments, NULL);
287                }
288                char *command = "chmod +rx \"$HELP\"";
289                setenv("HELP", [path fileSystemRepresentation], 1);
290                char *arguments[] = {"-c", command, NULL};
291                result = AuthorizationExecuteWithPrivileges(auth, "/bin/sh", kAuthorizationFlagDefaults, arguments, NULL);
292                unsetenv("HELP");
293                if(roslash)
294                {
295                        char *command = "mount -ur /";
296                        char *arguments[] = {"-c", command, NULL};
297                        AuthorizationExecuteWithPrivileges(auth, "/bin/sh", kAuthorizationFlagDefaults, arguments, NULL);
298                }
299        }
300        if(result != errAuthorizationSuccess)
301                return NO;
302       
303        return YES;
304}
305
306- (void)startClient
307{
308        NSString *path = [[NSBundle bundleForClass:[SapphireImportHelper class]] pathForResource:@"ImportHelper" ofType:@""];
309        NSDictionary *attrs = [[NSFileManager defaultManager] fileAttributesAtPath:path traverseLink:YES];
310        if(([[attrs objectForKey:NSFilePosixPermissions] intValue] | S_IXOTH) || [self fixClientPermissions:path])
311        {
312                @try {
313                        [NSTask launchedTaskWithLaunchPath:path arguments:[NSArray array]];
314                }
315                @catch (NSException * e) {
316                        SapphireLog(SAPPHIRE_LOG_GENERAL, SAPPHIRE_LOG_LEVEL_ERROR, @"Could not launch helper because of exception %@ launching %@.  Make this file executable", e, path);
317                }               
318        }
319        else
320                SapphireLog(SAPPHIRE_LOG_GENERAL, SAPPHIRE_LOG_LEVEL_ERROR, @"Could not correct helper permissions on %@.  Make this file executable!", path);
321}
322
323- (void)connectionDidDie:(NSNotification *)note
324{
325        [client release];
326        client = nil;
327        /*Inform that import completed (since it crashed, no update done)*/
328        [self importCompleteWithChanges:nil updated:NO];
329        if(shared != nil)
330                /* Don't start it again if we are shutting down*/
331                [self startClient];
332}
333
334- (void)itemAdded
335{
336        if(!queueSuspended)
337                return;
338        queueSuspended = NO;
339        [SapphireMetaDataSupport save:moc];
340        [client startQueue];
341}
342
343- (BOOL)importFileData:(SapphireFileMetaData *)file inform:(id <SapphireImporterBackgroundProtocol>)inform;
344{
345        SapphireImportFile *item = [[SapphireImportFile alloc] initWithPath:[file path] informer:inform  type:IMPORT_TYPE_FILE_DATA];
346        [queue addObject:item];
347        [item release];
348        [self itemAdded];
349        return NO;
350}
351
352- (void)importAllData:(SapphireFileMetaData *)file inform:(id <SapphireImporterBackgroundProtocol>)inform;
353{
354        SapphireImportFile *item = [[SapphireImportFile alloc] initWithPath:[file path] informer:inform type:IMPORT_TYPE_ALL_DATA];
355        [queue addObject:item];
356        [item release];
357        [self itemAdded];
358}
359
360- (void)removeObjectsWithInform:(id <SapphireImporterBackgroundProtocol>)inform
361{
362        if(inform == nil)
363                return;
364       
365        int i, count=[queue count];
366        for(i=0; i<count; i++)
367        {
368                id <SapphireImportFileProtocol> file = [queue objectAtIndex:i];
369                if([file informer] == inform)
370                {
371                        [queue removeObjectAtIndex:i];
372                        i--;
373                        count--;
374                }
375        }
376        if([currentImporting informer] == inform)
377        {
378                [currentImporting release];
379                currentImporting = nil;
380        }
381}
382
383- (id <SapphireImportFileProtocol>)nextFile
384{
385        if(![queue count])
386        {
387                queueSuspended = YES;
388                return nil;
389        }
390        [currentImporting release];
391        currentImporting = [[queue objectAtIndex:0] retain];
392        [queue removeObjectAtIndex:0];
393        return currentImporting;
394}
395
396- (oneway void)setClient:(id <SapphireImportClient>)aClient
397{
398        if(shared == nil)
399        {
400                [aClient exitChild];
401                return;
402        }
403        client = [aClient retain];
404        if([queue count])
405        {
406                queueSuspended = NO;
407                [client startQueue];
408        }
409        else
410                queueSuspended = YES;
411}
412
413- (void)importCompleteWithChanges:(bycopy NSDictionary *)changes updated:(BOOL)updated
414{
415        if(changes != nil)
416                [SapphireMetaDataSupport applyChanges:changes toContext:moc];
417        if(currentImporting == nil)
418                return;
419        [[currentImporting informer] informComplete:updated onPath:[currentImporting path]];
420        [currentImporting release];
421        currentImporting = nil;
422}
423
424@end
425
426@implementation SapphireImportFile
427- (id)initWithPath:(NSString *)aPath informer:(id <SapphireImporterBackgroundProtocol>)aInformer type:(ImportType)aType;
428{
429        self = [super init];
430        if(!self)
431                return nil;
432       
433        path = [aPath retain];
434        informer = [aInformer retain];
435        type = aType;
436       
437        return self;
438}
439- (void) dealloc
440{
441        [path release];
442        [informer release];
443        [super dealloc];
444}
445
446- (bycopy NSString *)path
447{
448        return path;
449}
450- (id <SapphireImporterBackgroundProtocol>)informer
451{
452        return informer;
453}
454
455- (ImportType)importType
456{
457        return type;
458}
459
460@end
Note: See TracBrowser for help on using the repository browser.