source: trunk/SapphireFrappliance/MetaDataImporting/SapphireTVShowImporter.m @ 1319

Revision 1319, 27.7 KB checked in by gbooker, 4 years ago (diff)

Added QT Metadata importer and changed another enum to better value names.

Line 
1/*
2 * SapphireTVShowImporter.m
3 * Sapphire
4 *
5 * Created by Graham Booker on Jun. 30, 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 <SapphireCompatClasses/SapphireFrontRowCompat.h>
22#import "SapphireTVShowImporter.h"
23#import "SapphireFileMetaData.h"
24#import "NSString-Extensions.h"
25#import "NSFileManager-Extensions.h"
26#import "SapphireShowChooser.h"
27#import "SapphireMetaDataSupport.h"
28#import "SapphireTVTranslation.h"
29#import "SapphireEpisode.h"
30#import "SapphireSettings.h"
31#import "NSImage-Extensions.h"
32#import "SapphireTVShow.h"
33#import "NSXMLDocument-Extensions.h"
34#import "SapphireScraper.h"
35#import "SapphireApplianceController.h"
36#import "SapphireURLLoader.h"
37
38/* TVRage XPATHS  */
39#define TVRAGE_SHOWNAME_XPATH @".//font[@size=2][@color=\"white\"]/b/text()"
40#define TVRAGE_EPLIST_XPATH @"//*[@class='b']"
41#define TVRAGE_EP_INFO @".//*[@class='b2']/*"
42#define TVRAGE_EP_TEXT @".//*[@class='b2']/text()"
43#define TVRAGE_SCREEN_CAP_XPATH @".//img[contains(@src, 'screencap')]"
44#define TVRAGE_SEARCH_XPATH @"//*[@class='b1']/a"
45#define TVRAGE_UNKNOWN_XPATH @"//*[contains(text(), 'Unknown Page')]"
46#define TVRAGE_SHOW_FULL_DESC_XPATH @"//div[@id='sft_1']//text()"
47#define TVRAGE_SHOW_RAW_DESC_XPATH @"//tr[@id='iconn1']/td/table/tr/td//text()"
48#define TVRAGE_SHOW_IMG_XPATH @"//img[contains(@src, 'shows')]"
49
50#define LINK_KEY                                @"Link"
51#define IMG_URL                                 @"imgURL"
52
53@interface SapphireTVShowImportStateData : SapphireImportStateData
54{
55@public
56        SapphireSiteTVShowScraper       *siteScraper;
57        SapphireTVTranslation           *translation;
58        NSString                                        *showName;
59        NSString                                        *episodeListURL;
60        SapphireTVShow                          *show;
61        int                                                     episode;
62        int                                                     secondEp;
63        int                                                     season;
64        NSString                                        *episodeTitle;
65        NSMutableArray                          *episodeInfoArray;
66        int                                                     episodesCompleted;
67}
68- (id)initWithFile:(SapphireFileMetaData *)aFile atPath:(NSString *)aPath scraper:(SapphireSiteTVShowScraper *)siteScaper;
69- (void)setTranslation:(SapphireTVTranslation *)translation;
70- (void)setShowName:(NSString *)showName;
71- (void)setEpisodeListURL:(NSString *)episodeListURL;
72- (void)setShow:(SapphireTVShow *)show;
73- (void)setEpisodeTitle:(NSString *)episodeTitle;
74- (void)setEpisodeInfoArray:(NSMutableArray *)episodeInfoArray;
75@end
76
77@implementation SapphireTVShowImportStateData
78
79- (id)initWithFile:(SapphireFileMetaData *)aFile atPath:(NSString *)aPath scraper:(SapphireSiteTVShowScraper *)aSiteScaper
80{
81        self = [super initWithFile:aFile atPath:aPath];
82        if(!self)
83                return self;
84       
85        siteScraper = [aSiteScaper retain];
86       
87        return self;
88}
89
90- (void)dealloc
91{
92        [siteScraper release];
93        [translation release];
94        [showName release];
95        [episodeListURL release];
96        [show release];
97        [episodeTitle release];
98        [episodeInfoArray release];
99        [super dealloc];
100}
101
102- (void)setTranslation:(SapphireTVTranslation *)aTranslation
103{
104        [translation autorelease];
105        translation = [aTranslation retain];
106        NSString *epListURL = translation.episodeListURL;
107        if([epListURL length])
108                [self setEpisodeListURL:epListURL];
109}
110
111- (void)setShowName:(NSString *)aShowName
112{
113        [showName autorelease];
114        showName = [aShowName retain];
115}
116
117- (void)setEpisodeListURL:(NSString *)aEpisodeListURL
118{
119        [episodeListURL autorelease];
120        episodeListURL = [aEpisodeListURL retain];
121}
122
123- (void)setShow:(SapphireTVShow *)aShow
124{
125        [show autorelease];
126        show = [aShow retain];
127        NSString *aShowName = aShow.name;
128        if([aShowName length])
129                [self setShowName:aShowName];
130}
131
132- (void)setEpisodeTitle:(NSString *)anEpisodeTitle
133{
134        [episodeTitle autorelease];
135        episodeTitle = [anEpisodeTitle retain];
136}
137
138- (void)setEpisodeInfoArray:(NSMutableArray *)anEpisodeInfoArray
139{
140        [episodeInfoArray release];
141        episodeInfoArray = [anEpisodeInfoArray retain];
142}
143
144@end
145
146@interface SapphireSingleTVShowEpisodeImportStateData : NSObject
147{
148@public
149        SapphireTVShowImportStateData   *state;
150        int                                                             index;
151        SapphireSiteTVShowScraper               *siteScraper;
152        int                                                             absEpisode;
153        NSString                                                *epID;
154}
155
156- (id)initWithState:(SapphireTVShowImportStateData *)state index:(int)index episodeID:(NSString *)epID;
157@end
158
159@implementation SapphireSingleTVShowEpisodeImportStateData
160
161- (id)initWithState:(SapphireTVShowImportStateData *)aState index:(int)anIndex episodeID:(NSString *)anEpID;
162{
163        self = [super init];
164        if(!self)
165                return self;
166       
167        state = [aState retain];
168        index = anIndex;
169        if(anIndex == 0)
170                siteScraper = [aState->siteScraper retain];
171        else
172                siteScraper = [aState->siteScraper copy];
173        epID = [anEpID retain];
174       
175        return self;
176}
177
178- (void)dealloc
179{
180        [state release];
181        [siteScraper release];
182        [epID release];
183        [super dealloc];
184}
185
186
187@end
188 
189@interface SapphireTVShowImporter ()
190- (void)getTVShowResultsForState:(SapphireTVShowImportStateData *)state;
191- (void)getTVShowEpisodeListForState:(SapphireTVShowImportStateData *)state;
192- (void)getTVShowEpisodesForState:(SapphireSingleTVShowEpisodeImportStateData *)state atURL:(NSString *)url;
193- (void)completeWithState:(SapphireTVShowImportStateData *)state withStatus:(ImportState)status importComplete:(BOOL)importComplete;
194- (void)completedEpisode:(NSDictionary *)dict forState:(SapphireTVShowImportStateData *)state atIndex:(int)index;
195@end
196
197@implementation SapphireTVShowImporter
198
199- (id)init
200{
201        self = [super init];
202        if(!self)
203                return nil;
204       
205        scraper = [[SapphireScraper scrapperWithName:@"TV Rage"] retain];
206       
207        /*Initialize the regexes*/
208        regcomp(&letterMarking, "[\\. -]?S[0-9]+E[S0-9]+([-E]+[0-9]+)?", REG_EXTENDED | REG_ICASE);
209        regcomp(&seasonByEpisode, "[\\. -]?[0-9]+x[S0-9]+(-[0-9]+)?", REG_EXTENDED | REG_ICASE);
210        regcomp(&seasonEpisodeTriple, "[\\. -][0-9]{1,3}[S0-9]{2}[\\. -]", REG_EXTENDED | REG_ICASE);   
211        return self;
212}
213
214- (void)dealloc
215{
216        [scraper release];
217        regfree(&letterMarking);
218        regfree(&seasonByEpisode);
219        regfree(&seasonEpisodeTriple);
220        [super dealloc];
221}
222
223- (void)setDelegate:(id <SapphireImporterDelegate>)aDelegate
224{
225        delegate = aDelegate;
226}
227
228- (void)cancelImports
229{
230        cancelled = YES;
231}
232
233- (void)retrievedSearchResuls:(NSXMLDocument *)results forObject:(id)stateObj
234{
235        SapphireTVShowImportStateData *state = (SapphireTVShowImportStateData *)stateObj;
236        [state->siteScraper setObject:nil];     //Avoid retain loop
237        if(cancelled)
238                return;
239       
240        if(results == nil)
241        {
242                /*Failed to get data, network likely, don't mark this as imported*/
243                [self completeWithState:state withStatus:ImportStateNotUpdated importComplete:NO];
244                return;
245        }
246        NSXMLElement *root = [results rootElement];
247        NSArray *entities = [root elementsForName:@"entity"];
248        NSMutableArray *shows = [[NSMutableArray alloc] initWithCapacity:[entities count]];
249        NSEnumerator *entityEnum = [entities objectEnumerator];
250        NSXMLElement *entity;
251        while((entity = [entityEnum nextObject]) != nil)
252        {
253                NSString *title = stringValueOfChild(entity, @"title");
254                NSString *url = stringValueOfChild(entity, @"url");
255                NSString *itemID = stringValueOfChild(entity, @"id");
256                if([title length] && [url length])
257                        [shows addObject:[NSDictionary dictionaryWithObjectsAndKeys:
258                                                          title, tvShowTranslationNameKey,
259                                                          url, tvShowTranslationLinkKey,
260                                                          itemID, tvShowTranslationItemIDKey,
261                                                          nil]];
262        }
263       
264        SapphireLog(SapphireLogTypeImport, SapphireLogLevelDetail, @"Found shows: %@", shows);
265       
266        /* No need to prompt the user for an empty set */
267        if(![shows count])
268        {
269                /* We tried to import but found nothing - mark this file to be skipped on future imports */
270                [self completeWithState:state withStatus:ImportStateNotUpdated importComplete:YES];
271        }
272        if([[SapphireSettings sharedSettings] autoSelection])
273        {
274                SapphireFileMetaData *metaData = state->file;
275                NSManagedObjectContext *moc = [metaData managedObjectContext];
276                NSString *showURL = [[shows objectAtIndex:0] objectForKey:tvShowTranslationLinkKey];
277                NSString *showID = [[shows objectAtIndex:0] objectForKey:tvShowTranslationItemIDKey];
278                SapphireTVTranslation *tran = [SapphireTVTranslation createTVTranslationForName:state->lookupName withURL:showURL itemID:showID importer:[[state->siteScraper scraper] name] inContext:moc];
279                [state setTranslation:tran];
280                [self getTVShowResultsForState:state];
281        }
282        else
283        {
284                /*Bring up the prompt*/
285                SapphireShowChooser *chooser = [[SapphireShowChooser alloc] initWithScene:[delegate chooserScene]];
286                [chooser setShows:shows];
287                [chooser setFileName:[NSString stringByCroppingDirectoryPath:state->path toLength:3]];
288                [chooser setListTitle:BRLocalizedString(@"Select Show Title", @"Prompt the user for showname with a file")];
289                /*And display prompt*/
290                [delegate displayChooser:chooser forImporter:self withContext:state];
291                [chooser release];
292        }
293        [shows release];       
294}
295
296- (void)getTVShowResultsForState:(SapphireTVShowImportStateData *)state
297{
298        SapphireTVShow *show = [state->translation tvShow];
299        BOOL fetchShowData = NO;
300        if(show == nil)
301                fetchShowData = YES;
302        else if(![[show showDescription] length])
303                fetchShowData = YES;
304        else if(![state->episodeListURL length])
305                fetchShowData = YES;
306       
307        if(!fetchShowData)
308        {
309                NSString *coverArtPath = [[SapphireMetaDataSupport collectionArtPath] stringByAppendingPathComponent:[NSString stringWithFormat:@"@TV/%@/cover.jpg", [show name]]];
310                if(![[NSFileManager defaultManager] fileExistsAtPath:coverArtPath])
311                        fetchShowData = YES;
312        }
313        if(fetchShowData)
314        {
315                SapphireSiteTVShowScraper *siteScraper = state->siteScraper;
316                [siteScraper setObject:state];
317                NSString *fullURL = state->translation.url;
318                [siteScraper getShowDetailsAtURL:fullURL forShowID:state->translation.itemID];
319        }
320        else
321        {
322                [state setShow:show];
323                [self getTVShowEpisodeListForState:state];
324        }
325}
326
327- (void)retrievedShowDetails:(NSXMLDocument *)details forObject:(id)stateObj
328{
329        SapphireTVShowImportStateData *state = (SapphireTVShowImportStateData *)stateObj;
330        [state->siteScraper setObject:nil];     //Avoid retain loop
331        if(cancelled)
332                return;
333       
334        if(details == nil)
335        {
336                /*Failed to get data, network likely, don't mark this as imported*/
337                [self completeWithState:state withStatus:ImportStateNotUpdated importComplete:NO];
338                return;
339        }
340        NSXMLElement *root = [details rootElement];
341        NSString *plot = stringValueOfChild(root, @"plot");
342        NSString *thumb = stringValueOfChild(root, @"thumb");
343       
344        /*Check for series info*/
345        NSManagedObjectContext *moc = [state->file managedObjectContext];
346        SapphireTVShow *show = state->show;
347        if(!show)
348        {
349                SapphireTVTranslation *tran = state->translation;
350                show = tran.tvShow;
351                if(!show)
352                {
353                        NSString *title = stringValueOfChild(root, @"title");
354                        if(![title length])
355                        {
356                                //We can't import anymore.  Importer or site broken; abort.
357                                [self completeWithState:state withStatus:ImportStateNotUpdated importComplete:NO];
358                                return;
359                        }
360                        show = [SapphireTVShow show:title inContext:moc];
361                        tran.tvShow = show;
362                }
363                [state setShow:show];
364        }
365        if([thumb length])
366        {
367                NSString *coverArtPath = [[SapphireMetaDataSupport collectionArtPath] stringByAppendingPathComponent:[NSString stringWithFormat:@"@TV/%@/cover.jpg", [show name]]];
368                [[SapphireApplianceController urlLoader] saveDataAtURL:thumb toFile:coverArtPath];
369        }
370        if([plot length])
371                show.showDescription = plot;
372       
373        if(![state->episodeListURL length])
374        {
375                NSArray *episodeListURLs = arrayStringValueOfXPath(root, @"episodeguide/url");
376                if([episodeListURLs count])
377                {
378                        NSString *episodeListURL = [episodeListURLs objectAtIndex:0];
379                        [state setEpisodeListURL:episodeListURL];
380                        [state->translation setEpisodeListURL:episodeListURL];
381                }
382        }
383       
384        [self getTVShowEpisodeListForState:state];
385}
386
387- (void)getTVShowEpisodeListForState:(SapphireTVShowImportStateData *)state
388{
389        SapphireSiteTVShowScraper *siteScraper = state->siteScraper;
390        [siteScraper setObject:state];
391        NSString *fullURL = state->episodeListURL;
392        [siteScraper getEpisodeListAtURL:fullURL];
393}
394
395- (void)getEpisode:(int)episode withSeason:(int)season title:(NSString *)episodeTitle state:(SapphireTVShowImportStateData *)state inList:(NSXMLDocument *)episodeList index:(int)index
396{
397        NSString *xquery;
398        if(episode)
399                xquery = [NSString stringWithFormat:@"//episode[number(season)=%d and number(epnum)=%d]/url", season, episode];
400        else
401        {
402                NSString *escapedTitle = [[episodeTitle lowercaseString] stringByReplacingAllOccurancesOf:@"'" withString:@"\\'"];
403                xquery = [NSString stringWithFormat:@"//episode[number(season)=%d and lower-case(title)='%@']/url", season, escapedTitle];
404        }
405        NSArray *epElements = [episodeList objectsForXQuery:xquery error:nil];
406        if([epElements count])
407        {
408                NSXMLElement *epURLElement = [epElements objectAtIndex:0];
409                NSXMLElement *episodeElement = (NSXMLElement *)[epURLElement parent];
410                NSNumber *absoluteNumber = intValueOfChild(episodeElement, @"absoluteEp");
411                NSString *epID = stringValueOfChild(episodeElement, @"id");
412                int absNumber = [absoluteNumber intValue];
413                SapphireSingleTVShowEpisodeImportStateData *epState = [[SapphireSingleTVShowEpisodeImportStateData alloc] initWithState:state index:index episodeID:epID];
414                epState->absEpisode = absNumber;
415                [self getTVShowEpisodesForState:epState atURL:[epURLElement stringValue]];
416                [epState release];
417        }
418        else
419                [self completedEpisode:nil forState:state atIndex:index];
420}
421
422- (void)retrievedEpisodeList:(NSXMLDocument *)episodeList forObject:(id)stateObj
423{
424        SapphireTVShowImportStateData *state = (SapphireTVShowImportStateData *)stateObj;
425        [state->siteScraper setObject:nil];     //Avoid retain loop
426        if(cancelled)
427                return;
428       
429        if(episodeList == nil)
430        {
431                /*Failed to get data, network likely, don't mark this as imported*/
432                [self completeWithState:state withStatus:ImportStateNotUpdated importComplete:NO];
433                return;
434        }
435        NSMutableArray *infoArray;
436        int secondEp = state->secondEp;
437        if(secondEp)
438                infoArray = [[NSMutableArray alloc] initWithObjects:[NSNull null], [NSNull null], nil];
439        else
440                infoArray = [[NSMutableArray alloc] initWithObjects:[NSNull null], nil];
441        [state setEpisodeInfoArray:infoArray];
442        [infoArray release];
443       
444        [self getEpisode:state->episode withSeason:state->season title:state->episodeTitle state:state inList:episodeList index:0];
445        if(secondEp)
446                [self getEpisode:secondEp withSeason:state->season title:nil state:state inList:episodeList index:1];
447}
448
449- (void)getTVShowEpisodesForState:(SapphireSingleTVShowEpisodeImportStateData *)epState atURL:(NSString *)url
450{
451        SapphireSiteTVShowScraper *siteScraper = epState->siteScraper;
452        [siteScraper setObject:epState];
453        [siteScraper getEpisodeDetailsAtURL:url forEpisodeID:epState->epID];
454}
455
456- (void)retrievedEpisodeDetails:(NSXMLDocument *)details forObject:(id)stateObj
457{
458        SapphireSingleTVShowEpisodeImportStateData *state = (SapphireSingleTVShowEpisodeImportStateData *)stateObj;
459        SapphireTVShowImportStateData *tvState = state->state;
460        [state->siteScraper setObject:nil];     //Avoid retain loop
461        if(cancelled)
462                return;
463       
464        if(details == nil)
465        {
466                /*Failed to get data, network likely, don't mark this as imported*/
467                [self completeWithState:tvState withStatus:ImportStateNotUpdated importComplete:NO];
468                return;
469        }
470        int index = state->index;
471        NSXMLElement *root = [details rootElement];
472        NSString *epTitle = stringValueOfChild(root, @"title");
473        int ep = tvState->episode;
474        if(index != 0)
475                ep = tvState->secondEp;
476        if(ep == 0 && ![epTitle length])
477        {
478                //No episode number or title; something is seriously wrong here
479                [self completedEpisode:nil forState:tvState atIndex:state->index];
480                return;
481        }
482       
483        NSMutableDictionary *epDict = [NSMutableDictionary dictionary];
484        /*Add info*/
485        [epDict setObject:tvState->show.name forKey:META_SHOW_NAME_KEY];
486        if(ep != 0)
487                [epDict setObject:[NSNumber numberWithInt:ep] forKey:META_EPISODE_NUMBER_KEY];
488        [epDict setObject:[NSNumber numberWithInt:tvState->season] forKey:META_SEASON_NUMBER_KEY];
489
490        if(epTitle != nil)
491                [epDict setObject:epTitle forKey:META_TITLE_KEY];
492        NSString *summary = stringValueOfChild(root, @"plot");
493        if(summary != nil)
494                [epDict setObject:summary forKey:META_DESCRIPTION_KEY];
495        if(state->absEpisode)
496                [epDict setObject:[NSNumber numberWithInt:state->absEpisode] forKey:META_ABSOLUTE_EP_NUMBER_KEY];
497        NSDate *airDate = dateValueOfChild(root, @"aired");
498        if(airDate != nil)
499                [epDict setObject:airDate forKey:META_SHOW_AIR_DATE];
500        NSString *imgURL = stringValueOfChild(root, @"thumb");
501        if([imgURL length])
502        {
503                NSString *previewArtPath = [NSFileManager previewArtPathForTV:tvState->show.name season:tvState->season];
504                NSString *newPath = nil;
505                if(ep != 0)
506                        newPath = [previewArtPath stringByAppendingFormat:@"/Episode %d", ep];
507                else
508                        newPath = [previewArtPath stringByAppendingPathComponent:epTitle];
509                NSString *imageDestination = [newPath stringByAppendingPathExtension:@"jpg"];
510                BOOL isDir = NO;
511                BOOL imageExists = [[NSFileManager defaultManager] fileExistsAtPath:imageDestination isDirectory:&isDir] && !isDir;
512                if(imgURL && !imageExists)
513                {
514                        /*Download the screen cap*/
515                        [[SapphireApplianceController urlLoader] saveDataAtURL:imgURL toFile:imageDestination];
516                }
517                else if(!imageExists)
518                {
519                        //QTMovie is broken on ATV, don't fetch images there
520                        SapphireFileMetaData *metaData = tvState->file;
521                        if ([SapphireFrontRowCompat usingLeopard] && [metaData fileContainerTypeValue] == FILE_CONTAINER_TYPE_QT_MOVIE)
522                        {
523                                // NSImage-Extensions
524                                [[NSImage imageFromMovie:tvState->path] writeToFile:imageDestination atomically:YES];
525                        }
526                }               
527        }
528        [self completedEpisode:epDict forState:tvState atIndex:index];
529}
530
531- (void)completedEpisode:(NSDictionary *)dict forState:(SapphireTVShowImportStateData *)state atIndex:(int)index
532{
533        state->episodesCompleted++;
534        NSMutableArray *infoArray = state->episodeInfoArray;
535        if(dict == nil)
536                dict = [NSMutableDictionary dictionary];
537        [infoArray replaceObjectAtIndex:index withObject:dict];
538        if(state->episodesCompleted == [infoArray count])
539        {
540                int i;
541                for(i=0; i<[infoArray count]; i++)
542                {
543                        if(![[infoArray objectAtIndex:i] count])
544                        {
545                                [infoArray removeObjectAtIndex:i];
546                                i--;
547                        }
548                }
549                if([infoArray count])
550                {
551                        SapphireFileMetaData *file = state->file;
552                        SapphireEpisode *ep = [SapphireEpisode episodeWithDictionaries:infoArray inContext:[file managedObjectContext]];
553                        file.tvEpisode = ep;
554                        [self completeWithState:state withStatus:ImportStateUpdated importComplete:YES];
555                }
556                else
557                        [self completeWithState:state withStatus:ImportStateNotUpdated importComplete:YES];
558        }
559}
560
561- (void)completeWithState:(SapphireTVShowImportStateData *)state withStatus:(ImportState)status importComplete:(BOOL)importComplete;
562{
563        SapphireFileMetaData *currentData = state->file;
564        if(importComplete)
565        {
566                [currentData didImportType:ImportTypeMaskTVShow];
567                if (status == ImportStateNotUpdated && [currentData fileClassValue] != FILE_CLASS_MOVIE)
568                        [currentData setFileClassValue:FILE_CLASS_UNKNOWN];
569        }
570        [delegate backgroundImporter:self completedImportOnPath:state->path withState:status];
571}
572
573- (NSString *)showURLFromNfoFilePath:(NSString *)filepath withID:(NSString * *)showID;
574{
575        NSString *nfoContent = [NSString stringWithContentsOfFile:filepath];
576
577        if(![nfoContent length])
578                return nil;
579       
580        NSString *results = [scraper searchResultsForNfoContent:nfoContent];
581        if(![results length])
582                return nil;
583       
584        NSString *fullResults = [NSString stringWithFormat:@"<results>%@</results>", results];
585        NSError *error = nil;
586        NSXMLDocument *doc = [[[NSXMLDocument alloc] initWithXMLString:fullResults options:0 error:&error] autorelease];
587        if(!doc)
588                return nil;
589       
590        NSXMLElement *root = [doc rootElement];
591        NSString *urlStr = stringValueOfChild(root, @"url");
592        if(![urlStr length])
593                return nil;
594       
595        *showID = stringValueOfChild(root, @"id");
596        return urlStr;
597}
598
599- (ImportState)importMetaData:(SapphireFileMetaData *)metaData path:(NSString *)path
600{
601        cancelled = NO;
602        /*Check to see if it is already imported*/
603        if([metaData importTypeValue] & ImportTypeMaskTVShow)
604                return ImportStateNotUpdated;
605        //      NSArray *pathComponents = [path pathComponents];
606        NSString *extLessPath = path;
607        if([metaData fileContainerTypeValue] != FILE_CONTAINER_TYPE_VIDEO_TS)
608                extLessPath = [extLessPath stringByDeletingPathExtension];
609
610        NSString *fileName = [path lastPathComponent];
611       
612        /*Check regexes to see if this is a tv show*/
613        int index = NSNotFound;
614        int secondEp = -1;
615        regmatch_t matches[3];
616        const char *theFileName = [fileName fileSystemRepresentation];
617        NSString *scanString = nil;
618        if(!regexec(&letterMarking, theFileName, 3, matches, 0))
619        {
620                index = matches[0].rm_so;
621                scanString = [fileName substringFromIndex:index];
622                secondEp = matches[1].rm_so;
623        }
624        else if(!regexec(&seasonByEpisode, theFileName, 3, matches, 0))
625        {
626                index = matches[0].rm_so;
627                scanString = [fileName substringFromIndex:index];
628                secondEp = matches[1].rm_so;
629        }
630        else if(!regexec(&seasonEpisodeTriple, theFileName, 3, matches, 0))
631        {
632                index = matches[0].rm_so;
633                /*Insert an artificial season/ep seperator so things are easier later*/
634                NSMutableString *tempStr = [fileName mutableCopy];
635                //              if(index > [tempStr length] || index <= 0 )return NO;
636                [tempStr deleteCharactersInRange:NSMakeRange(0, index+1)];
637                [tempStr insertString:@"x" atIndex:matches[0].rm_eo - index - 4];
638                scanString = [tempStr autorelease];
639        }
640       
641        /*See if we found a match*/
642        if(index == NSNotFound)
643        {       
644                [metaData didImportType:ImportTypeMaskTVShow];
645                return ImportStateNotUpdated;
646        }
647       
648        int season = 0;
649        int ep = 0;
650        /*Get the season*/
651        NSScanner *scanner = [NSScanner scannerWithString:scanString];
652        NSCharacterSet *digits = [NSCharacterSet decimalDigitCharacterSet];
653        [scanner scanUpToCharactersFromSet:digits intoString:nil];
654        [scanner scanInt:&season];
655        /*Get the episode number*/
656        NSString *skipped = nil;
657        [scanner scanUpToCharactersFromSet:digits intoString:&skipped];
658        [scanner scanInt:&ep];
659        /*Was there an S before the episode number?*/
660        if([skipped hasSuffix:@"S"] || [skipped hasSuffix:@"s"])
661                ep = 0;
662       
663        int overriddenSeason = [metaData searchSeasonNumber];
664        if(overriddenSeason != -1)
665                season = overriddenSeason;
666       
667        int overriddenEpisode = [metaData searchEpisodeNumber];
668        if(overriddenEpisode != -1)
669                ep = overriddenEpisode;
670       
671        /*No season, no info*/
672        if(season == 0)
673        {       
674                [metaData didImportType:ImportTypeMaskTVShow];
675                return ImportStateNotUpdated;
676        }
677       
678        int otherEp = 0;
679        if(secondEp != -1)
680        {
681                [scanner setScanLocation:secondEp - index];
682                [scanner scanUpToCharactersFromSet:digits intoString:nil];
683                [scanner scanInt:&otherEp];
684        }
685       
686        overriddenEpisode = [metaData searchLastEpisodeNumber];
687        if(overriddenEpisode != -1)
688                otherEp = overriddenEpisode;
689       
690        /*Get the show title*/
691        NSString *searchStr = [fileName substringToIndex:index];
692        NSString *searchShowName = [metaData searchShowName];
693        if(searchShowName != nil)
694                searchStr = searchShowName;
695        searchStr = [searchStr lowercaseString];
696        SapphireLog(SapphireLogTypeImport, SapphireLogLevelDebug, @"%@ matched regex; checking show name %@", fileName, searchStr);
697        /*Check to see if we know this title*/
698        NSManagedObjectContext *moc = [metaData managedObjectContext];
699        SapphireTVTranslation *tran = [SapphireTVTranslation tvTranslationForName:searchStr inContext:moc];
700        SapphireSiteTVShowScraper *siteScraper = [[SapphireSiteTVShowScraper alloc] initWithTVShowScraper:scraper delegate:self loader:[SapphireApplianceController urlLoader]];
701        SapphireTVShowImportStateData *state = [[[SapphireTVShowImportStateData alloc] initWithFile:metaData atPath:path scraper:siteScraper] autorelease];
702        [siteScraper release];
703       
704        [state setLookupName:searchStr];
705        [state setTranslation:tran];
706        SapphireTVShow *show = tran.tvShow;
707        [state setShow:show];
708        state->episode = ep;
709        state->season = season;
710        state->secondEp = otherEp;
711        if(ep == 0)
712        {
713                NSString *epTitle = nil;
714                [scanner scanUpToCharactersFromSet:[NSCharacterSet alphanumericCharacterSet] intoString:nil];
715                if([scanner scanUpToString:@"." intoString:&epTitle])
716                        [state setEpisodeTitle:epTitle];
717        }
718        if([tran itemID] == nil)
719        {
720                BOOL nfoPathIsDir = NO;
721                NSString *nfoFilePath=[extLessPath stringByAppendingPathExtension:@"nfo"];
722                NSString *showURL = nil;
723                NSString *showID = nil;
724                if([[NSFileManager defaultManager] fileExistsAtPath:nfoFilePath isDirectory:&nfoPathIsDir] && !nfoPathIsDir)
725                        showURL = [self showURLFromNfoFilePath:nfoFilePath withID:&showID];
726               
727                if([showURL length])
728                {
729                        [tran setUrl:showURL];
730                        if([showID length])
731                                [tran setItemID:showID];
732                }
733                else
734                {
735                        SapphireLog(SapphireLogTypeImport, SapphireLogLevelDebug, @"Conducting search");
736                        if(![delegate canDisplayChooser])
737                        /*There is no data menu, background import. So we can't ask user, skip*/
738                                return ImportStateNotUpdated;
739                        /*Ask the user what show this is*/
740                        [siteScraper setObject:state];
741                        [siteScraper searchForShowNamed:searchStr];
742                        return ImportStateMultipleSuspend;
743                }
744        }
745       
746        [self getTVShowResultsForState:state];
747        return ImportStateMultipleSuspend;
748}
749
750/*!
751 * @brief Write our setings out
752 */
753- (void)writeSettingsForContext:(NSManagedObjectContext *)moc
754{
755        [SapphireMetaDataSupport save:moc];
756}
757
758/*- (ImportState)importMetaData:(SapphireFileMetaData *)metaData path:(NSString *)path
759{
760        [showInfoClearTimer invalidate];
761        showInfoClearTimer = nil;
762        ImportState ret = [self doRealImportMetaData:metaData path:path];
763        showInfoClearTimer = [NSTimer scheduledTimerWithTimeInterval:60 target:self selector:@selector(clearCache) userInfo:nil repeats:NO];
764        return ret;
765}*/
766
767- (NSString *)completionText
768{
769        return BRLocalizedString(@"All available TV Show data has been imported", @"The TV Show import complete");
770}
771
772- (NSString *)initialText
773{
774        return BRLocalizedString(@"Fetch TV Show Data", @"Title");
775}
776
777- (NSString *)informativeText
778{
779        return BRLocalizedString(@"This tool will attempt to fetch information about your TV shows files from the Internet (TVRage).  This procedure may take quite some time and could ask you questions.  You may cancel at any time.", @"Description of the movie import");
780}
781
782- (NSString *)buttonTitle
783{
784        return BRLocalizedString(@"Start Fetching Data", @"Button");
785}
786
787- (BOOL)stillNeedsDisplayOfChooser:(BRLayerController <SapphireChooser> *)chooser withContext:(id)context
788{
789        SapphireTVShowImportStateData *state = (SapphireTVShowImportStateData *)context;
790        /*Check for a match done earlier*/
791        NSManagedObjectContext *moc = [state->file managedObjectContext];
792        SapphireTVTranslation *tran = [SapphireTVTranslation tvTranslationForName:state->lookupName inContext:moc];
793        if([tran itemID])
794        {
795                [state setTranslation:tran];
796                [self getTVShowResultsForState:state];
797                return NO;
798        }
799        return YES;
800}
801
802- (void)exhumedChooser:(BRLayerController <SapphireChooser> *)aChooser withContext:(id)context
803{
804        SapphireTVShowImportStateData *state = (SapphireTVShowImportStateData *)context;
805        if(![aChooser isKindOfClass:[SapphireShowChooser class]])
806                return;
807        SapphireShowChooser *chooser = (SapphireShowChooser *)aChooser;
808        SapphireFileMetaData *currentData = state->file;
809       
810        /*Get the user's selection*/
811        int selection = [chooser selection];
812        if(selection == SapphireChooserChoiceCancel)
813                /*They aborted, skip*/
814                [self completeWithState:state withStatus:ImportStateUserSkipped importComplete:NO];
815        else if(selection == SapphireChooserChoiceNotType)
816                /*They said it is not a show, so put in empty data so they are not asked again*/
817                [self completeWithState:state withStatus:ImportStateNotUpdated importComplete:YES];
818        else
819        {
820                /*They selected a show, save the translation and write it*/
821                NSDictionary *show = [[chooser shows] objectAtIndex:selection];
822                NSManagedObjectContext *moc = [currentData managedObjectContext];
823                NSString *showURL = [show objectForKey:tvShowTranslationLinkKey];
824                NSString *showID = [show objectForKey:tvShowTranslationItemIDKey];
825                SapphireTVTranslation *tran = [SapphireTVTranslation createTVTranslationForName:state->lookupName withURL:showURL itemID:showID importer:[[state->siteScraper scraper] name] inContext:moc];
826                [state setTranslation:tran];
827                [self writeSettingsForContext:moc];
828                [self getTVShowResultsForState:state];
829        }
830}
831
832@end
Note: See TracBrowser for help on using the repository browser.