source: branches/CoreData/SapphireFrappliance/FRAppliance/SapphireImportHelper.m @ 801

Revision 801, 11.3 KB checked in by gbooker, 5 years ago (diff)

Added mechanism for another process to send changes to the primary holder of the db. This gets around the multiple writers problem in Core Data. Also added test mechanism as well as adding XML import to the helper.

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 "SapphireXMLFileDataImporter.h"
29#import "SapphireFileDataImporter.h"
30#import "SapphireTVShowImporter.h"
31#import "SapphireMovieImporter.h"
32#import "SapphireFileMetaData.h"
33#import "SapphireMetaDataSupport.h"
34#import "SapphireApplianceController.h"
35
36#define CONNECTION_NAME @"Sapphire Server"
37
38@interface SapphireImportFile : NSObject <SapphireImportFileProtocol>{
39        NSString                                                                *path;
40        id <SapphireImporterBackgroundProtocol> informer;
41        ImportType                                                              type;
42}
43- (id)initWithPath:(NSString *)aPath informer:(id <SapphireImporterBackgroundProtocol>)aInformer type:(ImportType)aType;
44@end
45
46@interface SapphireImportHelperServer (private)
47- (void)startClient;
48@end
49
50@implementation SapphireImportHelper
51
52static SapphireImportHelper *shared = nil;
53
54+ (SapphireImportHelper *)sharedHelperForContext:(NSManagedObjectContext *)moc
55{
56        if(shared == nil)
57                shared = [[SapphireImportHelperServer alloc] initWithContext:moc];
58
59        return shared;
60}
61
62+ (void)relinquishHelper
63{
64        if(shared != nil)
65                [shared relinquishHelper];
66}
67
68- (void)relinquishHelper
69{
70}
71
72- (void)importFileData:(SapphireFileMetaData *)file inform:(id <SapphireImporterBackgroundProtocol>)inform;
73{
74}
75
76- (void)importAllData:(SapphireFileMetaData *)file inform:(id <SapphireImporterBackgroundProtocol>)inform;
77{
78}
79
80- (void)removeObjectsWithInform:(id <SapphireImporterBackgroundProtocol>)inform
81{
82}
83
84@end
85
86@implementation SapphireImportHelperClient
87
88- (id)initWithContext:(NSManagedObjectContext *)context
89{
90        self = [super init];
91        if(!self)
92                return nil;
93       
94        moc = [context retain];
95        SapphireXMLFileDataImporter *xmlImpr = [[SapphireXMLFileDataImporter alloc] init];
96        SapphireFileDataImporter *fileImp = [[SapphireFileDataImporter alloc] init];
97        SapphireTVShowImporter *tvImp = [[SapphireTVShowImporter alloc] initWithContext:moc];
98        SapphireMovieImporter *movImp = [[SapphireMovieImporter alloc] initWithContext:moc];
99        allImporter = [[SapphireAllImporter alloc] initWithImporters:[NSArray arrayWithObjects:xmlImpr,tvImp,movImp,fileImp,nil]];
100        [xmlImpr release];
101        [fileImp release];
102        [tvImp release];
103        [movImp release];
104        keepRunning = YES;
105       
106        return self;
107}
108- (void) dealloc
109{
110        [server release];
111        [allImporter release];
112        [moc release];
113        [super dealloc];
114}
115
116- (void)importFileData:(SapphireFileMetaData *)file inform:(id <SapphireImporterBackgroundProtocol>)inform;
117{
118        updateMetaData(file);
119}
120
121- (void)startChild
122{
123        /*Child here*/
124        @try {
125                NSConnection *connection = [NSConnection connectionWithRegisteredName:CONNECTION_NAME host:nil];
126                id serverobj = [[connection rootProxy] retain];
127                [serverobj setProtocolForProxy:@protocol(SapphireImportServer)];
128                shared = self;
129                [serverobj setClient:(SapphireImportHelperClient *)shared];
130                server = serverobj;     
131                [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(connectionDidDie:) name:NSConnectionDidDieNotification object:nil];           
132        }
133        @catch (NSException * e) {
134                keepRunning = NO;
135        }
136}
137
138- (BOOL)keepRunning
139{
140        return keepRunning;
141}
142
143- (void)connectionDidDie:(NSNotification *)note
144{
145        [self exitChild];
146}
147
148- (oneway void)exitChild
149{
150        keepRunning = NO;
151}
152
153- (void)realStartQueue
154{
155        @try {
156                id <SapphireImportFileProtocol> file;
157                while((file = [server nextFile]) != nil)
158                {
159                        [moc reset];
160                        NSAutoreleasePool *singleImportPool = [[NSAutoreleasePool alloc] init];
161                        ImportType type = [file importType];
162                        BOOL ret;
163                        NSString *path = [file path];
164                        SapphireFileMetaData *file = [SapphireFileMetaData fileWithPath:path inContext:moc];
165                        [moc refreshObject:file mergeChanges:YES];
166                        if(type == IMPORT_TYPE_FILE_DATA)
167                                ret = updateMetaData(file);
168                        else
169                                ret = ([allImporter importMetaData:file path:[file path]] == IMPORT_STATE_UPDATED);
170                        NSDictionary *changes = [SapphireMetaDataSupport changesDictionaryForContext:moc];
171                        [server importCompleteWithChanges:changes updated:ret];
172                        [singleImportPool release];
173                }
174        }
175        @catch (NSException * e) {
176                [SapphireApplianceController logException:e];
177                keepRunning = NO;
178        }
179}
180
181- (oneway void)startQueue
182{
183        [self performSelectorOnMainThread:@selector(realStartQueue) withObject:nil waitUntilDone:NO];
184}
185@end
186
187@implementation SapphireImportHelperServer
188
189- (id)initWithContext:(NSManagedObjectContext *)context
190{
191        self = [super init];
192        if (self == nil)
193                return nil;
194       
195        queue = [[NSMutableArray alloc] init];
196        queueSuspended = NO;
197       
198        serverConnection = [NSConnection defaultConnection];
199        [serverConnection setRootObject:self];
200        if([serverConnection registerName:CONNECTION_NAME] == NO)
201                SapphireLog(SAPPHIRE_LOG_GENERAL, SAPPHIRE_LOG_LEVEL_ERROR, @"Register failed");
202       
203        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(connectionDidDie:) name:NSConnectionDidDieNotification object:nil];
204        moc = [context retain];
205       
206        [self startClient];
207       
208        return self;
209}
210
211- (void) dealloc
212{
213        [[NSNotificationCenter defaultCenter] removeObserver:self];
214        [client release];
215        [moc release];
216        [queue release];
217        [currentImporting release];
218        [super dealloc];
219}
220
221- (void)relinquishHelper
222{
223        [client exitChild];
224        [serverConnection registerName:nil];
225        [serverConnection setRootObject:nil];
226        [shared autorelease];
227        shared = nil;
228}
229
230- (BOOL)isSlashReadOnly
231{
232        struct statfs *mntbufp;
233       
234    int i, mountCount = getmntinfo(&mntbufp, MNT_NOWAIT);
235        for(i=0; i<mountCount; i++)
236        {
237                if(!strcmp(mntbufp[i].f_mntonname, "/"))
238                        return (mntbufp[i].f_flags & MNT_RDONLY) ? YES : NO;
239        }
240       
241        return NO;
242}
243
244- (BOOL)fixClientPermissions:(NSString *)path
245{
246        /* Permissions are incorrect */
247        AuthorizationItem authItems[2] = {
248                {kAuthorizationEnvironmentUsername, strlen("frontrow"), "frontrow", 0},
249                {kAuthorizationEnvironmentPassword, strlen("frontrow"), "frontrow", 0},
250        };
251        AuthorizationEnvironment environ = {2, authItems};
252        AuthorizationItem rightSet[] = {{kAuthorizationRightExecute, 0, NULL, 0}};
253        AuthorizationRights rights = {1, rightSet};
254        AuthorizationRef auth;
255        OSStatus result = AuthorizationCreate(&rights, &environ, kAuthorizationFlagPreAuthorize | kAuthorizationFlagExtendRights, &auth);
256        if(result == errAuthorizationSuccess)
257        {
258                BOOL roslash = [self isSlashReadOnly];
259                if(roslash)
260                {
261                        char *command = "mount -uw /";
262                        char *arguments[] = {"-c", command, NULL};
263                        AuthorizationExecuteWithPrivileges(auth, "/bin/sh", kAuthorizationFlagDefaults, arguments, NULL);
264                }
265                char *command = "chmod +rx \"$HELP\"";
266                setenv("HELP", [path fileSystemRepresentation], 1);
267                char *arguments[] = {"-c", command, NULL};
268                result = AuthorizationExecuteWithPrivileges(auth, "/bin/sh", kAuthorizationFlagDefaults, arguments, NULL);
269                unsetenv("HELP");
270                if(roslash)
271                {
272                        char *command = "mount -ur /";
273                        char *arguments[] = {"-c", command, NULL};
274                        AuthorizationExecuteWithPrivileges(auth, "/bin/sh", kAuthorizationFlagDefaults, arguments, NULL);
275                }
276        }
277        if(result != errAuthorizationSuccess)
278                return NO;
279       
280        return YES;
281}
282
283- (void)startClient
284{
285        NSString *path = [[NSBundle bundleForClass:[SapphireImportHelper class]] pathForResource:@"ImportHelper" ofType:@""];
286        NSDictionary *attrs = [[NSFileManager defaultManager] fileAttributesAtPath:path traverseLink:YES];
287        if(([[attrs objectForKey:NSFilePosixPermissions] intValue] | S_IXOTH) || [self fixClientPermissions:path])
288        {
289                @try {
290                        [NSTask launchedTaskWithLaunchPath:path arguments:[NSArray array]];
291                }
292                @catch (NSException * e) {
293                        SapphireLog(SAPPHIRE_LOG_GENERAL, SAPPHIRE_LOG_LEVEL_ERROR, @"Could not launch helper because of exception %@ launching %@.  Make this file executable", e, path);
294                }               
295        }
296        else
297                SapphireLog(SAPPHIRE_LOG_GENERAL, SAPPHIRE_LOG_LEVEL_ERROR, @"Could not correct helper permissions on %@.  Make this file executable!", path);
298}
299
300- (void)connectionDidDie:(NSNotification *)note
301{
302        [client release];
303        client = nil;
304        /*Inform that import completed (since it crashed, no update done)*/
305        [self importCompleteWithChanges:nil updated:NO];
306        if(shared != nil)
307                /* Don't start it again if we are shutting down*/
308                [self startClient];
309}
310
311- (void)itemAdded
312{
313        if(!queueSuspended)
314                return;
315        queueSuspended = NO;
316        [SapphireMetaDataSupport save:moc];
317        [client startQueue];
318}
319
320- (void)importFileData:(SapphireFileMetaData *)file inform:(id <SapphireImporterBackgroundProtocol>)inform;
321{
322        SapphireImportFile *item = [[SapphireImportFile alloc] initWithPath:[file path] informer:inform  type:IMPORT_TYPE_FILE_DATA];
323        [queue addObject:item];
324        [item release];
325        [self itemAdded];
326}
327
328- (void)importAllData:(SapphireFileMetaData *)file inform:(id <SapphireImporterBackgroundProtocol>)inform;
329{
330        SapphireImportFile *item = [[SapphireImportFile alloc] initWithPath:[file path] informer:inform  type:IMPORT_TYPE_ALL_DATA];
331        [queue addObject:item];
332        [item release];
333        [self itemAdded];
334}
335
336- (void)removeObjectsWithInform:(id <SapphireImporterBackgroundProtocol>)inform
337{
338        if(inform == nil)
339                return;
340       
341        int i, count=[queue count];
342        for(i=0; i<count; i++)
343        {
344                id <SapphireImportFileProtocol> file = [queue objectAtIndex:i];
345                if([file informer] == inform)
346                {
347                        [queue removeObjectAtIndex:i];
348                        i--;
349                        count--;
350                }
351        }
352        if([currentImporting informer] == inform)
353        {
354                [currentImporting release];
355                currentImporting = nil;
356        }
357}
358
359- (id <SapphireImportFileProtocol>)nextFile
360{
361        if(![queue count])
362        {
363                queueSuspended = YES;
364                return nil;
365        }
366        [currentImporting release];
367        currentImporting = [[queue objectAtIndex:0] retain];
368        [queue removeObjectAtIndex:0];
369        return currentImporting;
370}
371
372- (oneway void)setClient:(id <SapphireImportClient>)aClient
373{
374        if(shared == nil)
375        {
376                [aClient exitChild];
377                return;
378        }
379        client = [aClient retain];
380        if([queue count])
381        {
382                queueSuspended = NO;
383                [client startQueue];
384        }
385        else
386                queueSuspended = YES;
387}
388
389- (oneway void)importCompleteWithChanges:(bycopy NSDictionary *)changes updated:(BOOL)updated
390{
391        if(changes != nil)
392                [SapphireMetaDataSupport applyChanges:changes toContext:moc];
393        if(currentImporting == nil)
394                return;
395        [[currentImporting informer] informComplete:updated];
396        [currentImporting release];
397        currentImporting = nil;
398}
399
400@end
401
402@implementation SapphireImportFile
403- (id)initWithPath:(NSString *)aPath informer:(id <SapphireImporterBackgroundProtocol>)aInformer type:(ImportType)aType;
404{
405        self = [super init];
406        if(!self)
407                return nil;
408       
409        path = [aPath retain];
410        informer = [aInformer retain];
411        type = aType;
412       
413        return self;
414}
415- (void) dealloc
416{
417        [path release];
418        [informer release];
419        [super dealloc];
420}
421
422- (bycopy NSString *)path
423{
424        return path;
425}
426- (id <SapphireImporterBackgroundProtocol>)informer
427{
428        return informer;
429}
430
431- (ImportType)importType
432{
433        return type;
434}
435
436@end
Note: See TracBrowser for help on using the repository browser.