source: trunk/SapphireFrappliance/SapphireMovieImporter.m @ 660

Revision 660, 34.0 KB checked in by gbooker, 6 years ago (diff)

Corrected for IMDB's changed format.
Fixes #228

Line 
1/*
2 * SapphireMovieImporter.m
3 * Sapphire
4 *
5 * Created by Patrick Merrill on Sep. 10, 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 "SapphireMovieImporter.h"
22#import "SapphireMetaData.h"
23#import "NSString-Extensions.h"
24#import "NSFileManager-Extensions.h"
25#import "SapphireMovieChooser.h"
26#import "SapphirePosterChooser.h"
27#import <SapphireCompatClasses/SapphireFrontRowCompat.h>
28#import "SapphireShowChooser.h"
29#import "SapphireSettings.h"
30#import "NSArray-Extensions.h"
31
32#define VERSION_KEY                                     @"Version"
33#define CURRENT_VERSION                         2
34/* Translation Keys */
35#define TRANSLATIONS_KEY                        @"Translations"
36#define IMDB_LINK_KEY                           @"IMDB Link"
37#define IMP_LINK_KEY                            @"IMP Link"
38#define IMP_POSTERS_KEY                         @"IMP Posters"
39#define SELECTED_POSTER_KEY                     @"Selected Poster"
40#define AUTO_SELECT_POSTER_KEY          @"Default Poster"
41 /* IMDB XPATHS */
42#define IMDB_SEARCH_XPATH                               @"//td[starts-with(a/@href,'/title')]"
43#define IMDB_UNIQUE_SEARCH_XPATH                @"//a[@class='tn15more inline']/@href"
44#define IMDB_RESULT_LINK_XPATH                  @"a/@href"
45#define IMDB_POSTER_LINK_XPATH                  @"//ul/li/a/@href"
46#define IMDB_RESULT_NAME_XPATH                  @"normalize-space(string())"
47#define IMDB_RESULT_TITLE_YEAR_XPATH    @"//div[@id='tn15title']/h1/replace(string(), '\n', '')"
48#define IMDB_RESULT_INFO_XPATH  @"//div[@class='info']"
49#define IMDB_RESTULT_CAST_NAMES_XPATH   @"//div[@class='info']/table/tr/td/a"
50/* IMP XPATHS */
51#define IMP_POSTER_CANDIDATES_XPATH             @"//img/@src"
52#define IMP_LINK_REDIRECT_XPATH                         @"//head/meta/@content/string()"
53
54
55
56
57/*Delegate class to download cover art*/
58@interface SapphireMovieDataMenuDownloadDelegate : NSObject
59{
60        NSString *destination;
61        NSArray *requestList ;
62        NSMutableArray *delegates ;
63        long downloadsLeft ;
64        id delegate;
65}
66- (id)initWithRequest:(NSArray*)reqList withDestination:(NSString *)dest delegate:(id)aDelegate;
67- (void) downloadDidFinish: (NSURLDownload *) download;
68- (void)downloadMoviePosters ;
69-(void)downloadSingleMoviePoster;
70@end
71
72@interface NSObject (MovieDataDownloadDelegateDelegate)
73- (void)downloadCompleted:(NSURLDownload *)download atIndex:(int)index;
74@end
75
76@implementation SapphireMovieDataMenuDownloadDelegate
77/*!
78* @brief Initialize a cover art downloader
79 *
80 * @param reqList The list of url requests to try
81 * @param dest The path to save the file
82 */
83- (id)initWithRequest:(NSArray*)reqList withDestination:(NSString *)dest delegate:(id)aDelegate;
84{
85        self = [super init];
86        if(!self)
87                return nil;
88        delegates = [NSMutableArray new];
89        destination = [dest retain];
90        requestList = [reqList retain];
91        downloadsLeft=[requestList count];
92        delegate = aDelegate;
93        return self;   
94}
95
96- (void)dealloc
97{
98        [destination release];
99        [requestList release];
100        [delegates release];
101        [super dealloc];
102}
103
104/*!
105 * @brief Fire the delegate to start downloading the posters
106 *
107 */
108-(void)downloadMoviePosters
109{
110        NSEnumerator *reqEnum = [requestList objectEnumerator] ;
111        NSString *req = nil ;
112        while((req = [reqEnum nextObject]) !=nil)
113        {
114                NSURL *posterURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://www.IMPAwards.com%@",req]];
115                NSString *fullDestination = [NSString stringWithFormat:@"%@/%@", destination, [req lastPathComponent]];
116                NSURLRequest *request = [NSURLRequest requestWithURL:posterURL];
117                NSURLDownload *currentDownload = [[NSURLDownload alloc] initWithRequest:request delegate:self] ;
118                [currentDownload setDestination:fullDestination allowOverwrite:YES];
119                [delegates addObject:currentDownload];
120                [currentDownload release];
121        }
122}
123
124/*!
125 * @brief Fire the delegate to start downloading a single poster
126 *
127 */
128-(void)downloadSingleMoviePoster
129{
130        NSURL *posterURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://www.IMPAwards.com%@",[requestList objectAtIndex:0]]];
131        NSString *fullDestination = destination;
132        NSURLRequest *request = [NSURLRequest requestWithURL:posterURL];
133        NSURLDownload *currentDownload = [[NSURLDownload alloc] initWithRequest:request delegate:self] ;
134        [currentDownload setDestination:fullDestination allowOverwrite:YES];
135        [delegates addObject:currentDownload];
136        [currentDownload release];
137}
138
139- (void) downloadDidFinish: (NSURLDownload *) download
140{
141        downloadsLeft--;
142        if([delegate respondsToSelector:@selector(downloadCompleted:atIndex:)])
143                [delegate downloadCompleted:download atIndex:[delegates indexOfObject:download]];
144}
145
146@end
147
148@interface SapphireMovieImporter (private)
149- (void)writeSettings;
150@end
151
152@implementation SapphireMovieImporter
153
154- (int)upgradeFromVersion1
155{
156        NSEnumerator *keyEnum = [[movieTranslations allKeys] objectEnumerator];
157        NSString *key = nil;
158        while((key = [keyEnum nextObject]) != nil)
159        {
160                NSString *newKey = [key stringByDeletingPathExtension];
161                if(![newKey isEqualToString:key])
162                {
163                        [movieTranslations setObject:[movieTranslations objectForKey:key] forKey:newKey];
164                        [movieTranslations removeObjectForKey:key];
165                }
166        }
167        return 2;
168}
169
170- (id) initWithSavedSetting:(NSString *)path
171{
172        self = [super init];
173        if(!self)
174                return nil;
175       
176        /*Get the settings*/
177        settingsPath = [path retain];
178        NSDictionary *settings = [NSDictionary dictionaryWithContentsOfFile:path];
179        /*Get or create the show translation dict*/
180        movieTranslations = [[settings objectForKey:TRANSLATIONS_KEY] mutableCopy];
181        if(movieTranslations == nil)
182                movieTranslations = [NSMutableDictionary new];
183       
184        int version = [[settings objectForKey:VERSION_KEY] intValue];
185        if(version < CURRENT_VERSION)
186        {
187                if(version < 2)
188                        version = [self upgradeFromVersion1];
189                [self writeSettings];
190        }
191       
192        return self;
193}
194
195- (void)dealloc
196{
197        [movieTranslations release];
198        [settingsPath release];
199        [super dealloc];
200}
201
202- (void)setImporterDataMenu:(SapphireImporterDataMenu *)theDataMenu
203{
204        dataMenu = theDataMenu;
205}
206
207/*!
208* @brief Gets IMPAwards.com Poster page link
209 *
210 * @param candidateIMDBLink The functions IMDB Posters Path
211 */
212- (NSString *)getPosterPath:(NSString *)candidateIMDBLink
213{
214        NSError *error = nil ;
215        NSURL * url=[NSURL URLWithString:[NSString stringWithFormat:@"http://www.imdb.com%@/posters",candidateIMDBLink]] ;
216        NSXMLDocument *document = [[NSXMLDocument alloc] initWithContentsOfURL:url options:NSXMLDocumentTidyHTML error:&error];
217        NSXMLElement *root = [document rootElement];
218
219        /*Get the results list*/
220        NSArray *results = [root objectsForXQuery:IMDB_POSTER_LINK_XPATH error:&error];
221        if([results count])
222        {
223                /*Get each result*/
224                NSEnumerator *resultEnum = [results objectEnumerator];
225                NSXMLElement *result = nil;
226                while((result = [resultEnum nextObject]) != nil)
227                {
228                        /*Add the result to the list*/                 
229                        NSString *resultURL =[[result stringValue] lowercaseString];
230                        if(resultURL == nil)
231                                continue;
232                        else if([resultURL hasPrefix:@"http://www.impawards.com"])/* See if the link is to IMP */
233                        {
234                                NSString * foundPosterLink =[resultURL stringByReplacingAllOccurancesOf:@"http://www.impawards.com" withString:@""];
235                                return foundPosterLink;
236                        }
237                }               
238        }
239        return nil;
240}
241
242/*!
243* @brief Compile IMPAwards.com Poster link list
244 *
245 * @param posterPageLink The Movie's IMP Poster link extention
246 * @return An array of canidate poster images
247 */
248- (NSArray *)getPosterLinks:(NSString *)posterPageLink
249{
250        NSError *error = nil ;
251        NSURL * url=[NSURL URLWithString:[NSString stringWithFormat:@"http://www.IMPAwards.com%@",posterPageLink]] ;
252        NSXMLDocument *document = [[NSXMLDocument alloc] initWithContentsOfURL:url options:NSXMLDocumentTidyHTML error:&error];
253        NSXMLElement *root = [document rootElement];
254        NSMutableArray * candidatePosterLinks=[NSMutableArray arrayWithObjects:nil] ;
255        NSString * yearPathComponent=[posterPageLink stringByDeletingLastPathComponent];
256       
257        /*Get the results list*/
258        NSArray *results = [root objectsForXQuery:IMP_POSTER_CANDIDATES_XPATH error:&error];
259        if([results count]<1)
260        {
261                /* IMDB had the wrong release year link, see if IMP Tried to redirect*/
262                NSArray *newPosterPageLinkArray = [root objectsForXQuery:IMP_LINK_REDIRECT_XPATH error:&error];
263                if([newPosterPageLinkArray count])
264                {
265                        NSString * newPosterPageLink=[newPosterPageLinkArray objectAtIndex:0] ;
266                        NSScanner *trimmer=[NSScanner scannerWithString:newPosterPageLink];
267                        [trimmer scanUpToString:@"URL=.." intoString:&yearPathComponent];
268                        newPosterPageLink=[newPosterPageLink substringFromIndex:[yearPathComponent length]+6];
269                        yearPathComponent=[newPosterPageLink stringByDeletingLastPathComponent];
270                        url=[NSURL URLWithString:[NSString stringWithFormat:@"http://www.IMPAwards.com%@",newPosterPageLink]] ;
271                        document = [[NSXMLDocument alloc] initWithContentsOfURL:url options:NSXMLDocumentTidyHTML error:&error];
272                        root = [document rootElement];
273                        results = [root objectsForXQuery:IMP_POSTER_CANDIDATES_XPATH error:&error];                     
274                }
275        }
276
277        if([results count])
278        {
279                /*Get each result*/
280                NSEnumerator *resultEnum = [results objectEnumerator];
281                NSXMLElement *result = nil;
282                while((result = [resultEnum nextObject]) != nil)
283                {
284                        /*Add the result to the list*/                 
285                        NSString *resultURL =[[result stringValue] lowercaseString];
286                        if(resultURL == nil)
287                                continue;
288                        if([resultURL hasPrefix:@"posters/"]) /* get the displayed poster link */
289                        {
290                                NSString * subPath=[resultURL substringFromIndex:7];
291                                subPath=[NSString stringWithFormat:[NSString stringWithFormat:@"%@/posters%@",yearPathComponent,subPath]];
292                                [candidatePosterLinks addObject:subPath];
293                        }
294                        else if([resultURL hasPrefix:@"thumbs/"]) /* get the displayed poster link */
295                        {
296                                NSString * subPath=[resultURL substringFromIndex:11];
297                                subPath=[NSString stringWithFormat:[NSString stringWithFormat:@"%@/posters/%@",yearPathComponent,subPath]];
298                                [candidatePosterLinks addObject:subPath];
299                        }
300                }
301        }
302        [candidatePosterLinks uniqueObjects];
303        return [[candidatePosterLinks copy] autorelease];
304}
305
306- (void)downloadPosterCandidates:(NSArray *)posterCandidates
307{
308        /* download all posters to the scratch folder */
309        NSString *posterBuffer = [NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/Sapphire/Poster_Buffer"];
310        [[NSFileManager defaultManager] constructPath:posterBuffer];
311        SapphireMovieDataMenuDownloadDelegate *myDelegate = [[SapphireMovieDataMenuDownloadDelegate alloc] initWithRequest:posterCandidates withDestination:posterBuffer delegate:self];
312        [myDelegate downloadMoviePosters] ;
313        [myDelegate autorelease];
314}
315
316/*!
317 * @brief A download completed
318 *
319 * @param download The download which completed
320 * @param index The index of this poster
321 */
322- (void)downloadCompleted:(NSURLDownload *)download atIndex:(int)index;
323{
324        id controller = [[dataMenu stack] peekController];
325        if([controller isKindOfClass:[SapphirePosterChooser class]])
326                [posterChooser reloadPoster:index];
327}
328
329/*!
330* @brief Fetch information for a movie
331 *
332 * @param movieTitleLink The IMDB link extention (part of the show's URL)
333 * @param moviePath The movie file's location
334 * @return A cached dictionary of the movie info
335 */
336- (NSMutableDictionary *)getMetaForMovie:(NSString *)movieTitleLink withPath:(NSString*)moviePath
337{
338        NSError *error = nil;
339        NSMutableDictionary *ret = [NSMutableDictionary dictionary];
340       
341        /* Gather IMDB Data */
342        /*Get the movie html*/
343        NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://www.IMDB.com%@",movieTitleLink]];
344        NSXMLDocument *document = [[NSXMLDocument alloc] initWithContentsOfURL:url options:NSXMLDocumentTidyHTML error:&error];
345       
346        /* Get the movie title */
347        NSString *movieTitle= [[document objectsForXQuery:IMDB_RESULT_TITLE_YEAR_XPATH error:&error] objectAtIndex:0];
348        NSScanner *metaTrimmer=[NSScanner scannerWithString:movieTitle];
349        [metaTrimmer scanUpToString:@"(" intoString:&movieTitle];
350        movieTitle=[movieTitle substringToIndex:[movieTitle length]-1];
351       
352        /* Get the User Rating (IMDB) */
353        NSArray *ratingCandidates=[document objectsForXQuery:@"(//b | //h5)/string()" error:&error];
354        int ratingIndex = [ratingCandidates indexOfObject:@"User Rating:\n"];
355        NSString *usrRating=nil;
356        if(ratingIndex != NSNotFound)
357        {
358                usrRating = [ratingCandidates objectAtIndex:ratingIndex+1];
359                metaTrimmer=[NSScanner scannerWithString:usrRating];
360                [metaTrimmer scanUpToString:@"/" intoString:&usrRating];               
361        }
362        /* Check for IMDB top 250 */
363        NSNumber * top250=nil ;
364        NSArray *top250Candidate=[document objectsForXQuery:@"//div[@class='left']/a/string()" error:&error];
365       
366        if([top250Candidate count])
367        {
368                NSString *top250Str=[top250Candidate objectAtIndex:0];
369                if([top250Str hasPrefix:@"Top 250:"])
370                        top250=[NSNumber numberWithInt:[[top250Str substringFromIndex:10] intValue]];
371        }
372       
373        /* Get the release date */
374        NSArray *rawData=[document objectsForXQuery:IMDB_RESULT_INFO_XPATH error:&error];
375        NSDate * releaseDate=nil ;
376        NSString * plot=nil;
377        NSString * mpaaRating=nil;
378        NSNumber * oscarsWon=nil ;
379        NSArray * directors=nil;
380        NSArray * writers=nil;
381        NSArray * genres=nil;
382        if([rawData count])
383        {
384                NSEnumerator *resultEnum = [rawData objectEnumerator];
385                NSXMLElement *result = nil;
386                while((result = [resultEnum nextObject]) != nil)
387                {
388                        NSString *dataCandidate=[result stringValue];
389                       
390                        if([dataCandidate length])
391                        {
392                                NSString * dataType=nil;
393                                NSScanner * trimmer=[NSScanner scannerWithString:dataCandidate];
394                                [trimmer scanUpToString:@"\n" intoString:&dataType];
395                                if([dataType hasPrefix:@"Release"])
396                                {
397                                        [trimmer scanUpToString:@"(" intoString:&dataCandidate];
398                                        releaseDate=[NSDate dateWithNaturalLanguageString:dataCandidate];
399                                       
400                                }
401                                else if([dataType hasPrefix:@"Writer"])
402                                {
403                                        NSString *writersStr = [[trimmer string] substringFromIndex:[trimmer scanLocation]+1];
404                                        NSMutableArray *mutDirs = [[writersStr componentsSeparatedByString:@"\n"] mutableCopy];
405                                        [mutDirs removeObject:@""];
406                                        int i, count = [mutDirs count];
407                                        for(i=0; i<count; i++)
408                                        {
409                                                NSString *tdirector;
410                                                NSScanner *typeTrimmer = [[NSScanner alloc] initWithString:[mutDirs objectAtIndex:i]];
411                                                [typeTrimmer scanUpToString:@" (" intoString:&tdirector];
412                                                [mutDirs replaceObjectAtIndex:i withObject:tdirector];
413                                                [typeTrimmer release];
414                                        }
415                                        writers = [[mutDirs copy] autorelease];
416                                        [mutDirs release];
417                                }
418                                else if([dataType hasPrefix:@"Director"])
419                                {
420                                        NSString *directorsStr = [[trimmer string] substringFromIndex:[trimmer scanLocation]+1];
421                                        NSMutableArray *mutDirs = [[directorsStr componentsSeparatedByString:@"\n"] mutableCopy];
422                                        [mutDirs removeObject:@""];
423                                        int i, count = [mutDirs count];
424                                        for(i=0; i<count; i++)
425                                        {
426                                                NSString *tdirector;
427                                                NSScanner *typeTrimmer = [[NSScanner alloc] initWithString:[mutDirs objectAtIndex:i]];
428                                                [typeTrimmer scanUpToString:@" (" intoString:&tdirector];
429                                                [mutDirs replaceObjectAtIndex:i withObject:tdirector];
430                                                [typeTrimmer release];
431                                        }
432                                        directors = [[mutDirs copy] autorelease];
433                                        [mutDirs release];
434                                }
435                                else if([dataType hasPrefix:@"Awards"])
436                                {
437                                        NSString *awardsStr = [[trimmer string] substringFromIndex:[trimmer scanLocation]+1];
438                                        trimmer=[NSScanner scannerWithString:awardsStr];
439                                        [trimmer scanUpToString:@" Oscars." intoString:&awardsStr];
440                                        if([awardsStr length]<[[trimmer string] length])
441                                        {
442                                                awardsStr=[awardsStr substringFromIndex:3];
443                                                oscarsWon=[NSNumber numberWithInt:[awardsStr intValue]];
444                                        }
445                                        else if([awardsStr hasPrefix:@"Won Oscar"])
446                                                oscarsWon=[[NSNumber alloc] initWithInt:1];
447                                       
448                                }
449                                else if([dataType hasPrefix:@"MPAA"])
450                                {
451                                        NSString *mpaaStr = [[trimmer string] substringFromIndex:[trimmer scanLocation]+1];
452                                        if([mpaaStr hasPrefix:@"Rated"])
453                                        {
454                                                trimmer=[NSScanner scannerWithString:[mpaaStr substringFromIndex:6]] ;
455                                                [trimmer scanUpToString:@" " intoString:&mpaaRating];
456                                        }
457                                }
458                                else if([dataType hasPrefix:@"Genre"])
459                                {
460                                       
461                                        NSMutableArray *myGenres=[NSMutableArray array];
462                                        NSCharacterSet *seperators = [NSCharacterSet characterSetWithCharactersInString:@"/|"];
463                                        while(![trimmer isAtEnd])
464                                        {
465                                                NSString *aGenre=nil;
466                                                [trimmer scanUpToCharactersFromSet:seperators intoString:&aGenre];
467                                                if(aGenre)
468                                                {
469                                                        if([aGenre rangeOfCharacterFromSet:seperators options:0].length == [aGenre length])
470                                                                continue ;
471                                                        else if([aGenre hasSuffix:@"more\n"])
472                                                                aGenre=[aGenre substringToIndex:[aGenre length]-6];
473                                                        else if([aGenre hasSuffix:@" "])
474                                                                aGenre=[aGenre substringToIndex:[aGenre length]-1];
475                                                        else if([aGenre hasSuffix:@"\n"])
476                                                                aGenre=[aGenre substringToIndex:[aGenre length]-1];
477                                                        [myGenres addObject:aGenre];
478                                                }
479                                                else
480                                                {
481                                                        [trimmer scanUpToString:@" " intoString:&aGenre];
482                                                }
483                                        }
484                                        genres = [[myGenres copy] autorelease];
485                                }
486                                else if([dataType hasPrefix:@"Plot:"])
487                                {
488                                        [trimmer scanUpToString:@"full summary" intoString:&plot];
489                                        NSMutableString *mutStr = [plot mutableCopy];
490                                        [mutStr replaceOccurrencesOfString:@" | add synopsis" withString:@"" options:0 range:NSMakeRange(0, [mutStr length])];
491                                        plot = [NSString stringWithString:mutStr];
492                                        [mutStr release];
493                                }
494                                else
495                                        continue ;
496                        }
497                        else
498                                continue ;
499                }
500               
501               
502        }
503       
504        /* Get the cast list */
505        NSArray *rawCast=[document objectsForXQuery:IMDB_RESTULT_CAST_NAMES_XPATH error:&error];
506        NSArray *completeCast=nil ;
507        if([rawCast count])
508        {
509                NSMutableArray *results=nil;
510                NSEnumerator *resultEnum = [rawCast objectEnumerator];
511                NSXMLElement *result = nil;
512                while((result = [resultEnum nextObject]) != nil)
513                {
514                        NSString *castName=nil;
515                        castName=[result stringValue];
516                        if([castName length])
517                        {
518                                NSString * castURL=[[[result attributeForName:@"href"]stringValue]lowercaseString];
519                                if([castURL hasPrefix:@"/name/"])
520                                {
521                                        if(!results)
522                                                results=[NSMutableArray arrayWithObject:castName];
523                                        else
524                                                [results addObject:castName];
525                                }
526                                else continue ;
527                        }
528                        else
529                                continue ;
530                }
531                completeCast=[[results copy] autorelease] ;
532        }
533       
534        /* populate metadata to return */
535        [ret setObject:movieTitleLink forKey:META_MOVIE_IDENTIFIER_KEY];
536        if(oscarsWon)
537                [ret setObject:oscarsWon forKey:META_MOVIE_OSCAR_KEY];
538        else
539                [ret setObject:[NSNumber numberWithInt:0] forKey:META_MOVIE_OSCAR_KEY];
540        if(top250)
541                [ret setObject:top250 forKey:META_MOVIE_IMDB_250_KEY];
542        if([usrRating length]>0)
543                [ret setObject:[NSNumber numberWithFloat:[usrRating floatValue]] forKey:META_MOVIE_IMDB_RATING_KEY];
544        if(mpaaRating)
545                [ret setObject:mpaaRating forKey:META_MOVIE_MPAA_RATING_KEY];
546        else
547                [ret setObject:@"N/A" forKey:META_MOVIE_MPAA_RATING_KEY];
548        if(directors)
549                [ret setObject:directors forKey:META_MOVIE_DIRECTOR_KEY];
550        if(plot)
551                [ret setObject:plot forKey:META_MOVIE_PLOT_KEY];
552        if(releaseDate)
553                [ret setObject:releaseDate forKey:META_MOVIE_RELEASE_DATE_KEY];
554        if(genres)
555                [ret setObject:genres forKey:META_MOVIE_GENRES_KEY];
556        if(completeCast)
557                [ret setObject:completeCast forKey:META_MOVIE_CAST_KEY];
558        if(movieTitle)
559                [ret setObject:movieTitle forKey:META_MOVIE_TITLE_KEY];
560        return ret;
561}
562
563
564
565/*!
566* @brief Searches for a movie based on the filename
567 *
568 * @param searchStr Part of the filename to use in the show search
569 * @return An array of possible results
570 */
571- (NSArray *)searchResultsForMovie:(NSString *)searchStr
572{
573        /* prep the search string */
574        searchStr = [searchStr stringByDeletingPathExtension];
575        searchStr = [searchStr stringByReplacingAllOccurancesOf:@"_" withString:@" "];
576        searchStr = [searchStr stringByReplacingAllOccurancesOf:@"." withString:@" "];
577        searchStr = [searchStr stringByReplacingAllOccurancesOf:@"-" withString:@" "];
578        NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://www.imdb.com/find?s=tt&site=aka&q=%@", [searchStr URLEncode]]];
579        NSError * error = nil;
580        BOOL uniqueResult=NO ;
581        NSArray * results = nil;
582        NSMutableArray *ret=nil;
583        NSXMLDocument *document = [[NSXMLDocument alloc] initWithContentsOfURL:url options:NSXMLDocumentTidyHTML error:&error];
584        NSXMLElement *root = [document rootElement];   
585        NSString *resultTitle=[[[root objectsForXQuery:@"//title" error:&error]objectAtIndex:0] stringValue];
586       
587        if([resultTitle isEqualToString:@"IMDb Title Search"])/*Make sure we didn't get back a unique result */
588        {
589                results = [root objectsForXQuery:IMDB_SEARCH_XPATH error:&error];
590                ret = [NSMutableArray arrayWithCapacity:[results count]];
591        }
592        else /* IMDB directly linked to a unique movie title */
593        {
594                uniqueResult=YES ;
595                ret = [NSMutableArray arrayWithCapacity:1];
596                results = [root objectsForXQuery:IMDB_UNIQUE_SEARCH_XPATH error:&error];               
597        }       
598               
599        if([results count])
600        {
601                /*Get each result*/
602                NSEnumerator *resultEnum = [results objectEnumerator];
603                NSXMLElement *result = nil;
604                while((result = [resultEnum nextObject]) != nil)
605                {
606                        if(uniqueResult)/*Check for a unique title link*/
607                        {
608                                NSURL *resultURL = [NSURL URLWithString:[[[result objectsForXQuery:IMDB_UNIQUE_SEARCH_XPATH error:&error] objectAtIndex:0] stringValue]] ;
609                                if(resultURL == nil)
610                                        continue;
611                                NSString *URLSubPath =[resultURL path] ;
612                                if([URLSubPath hasSuffix:@"/releaseinfo"])
613                                {
614                                        URLSubPath=[URLSubPath stringByReplacingAllOccurancesOf:@"/releaseinfo" withString:@""];
615                                        [ret addObject:[NSDictionary dictionaryWithObjectsAndKeys:
616                                                resultTitle, @"name",
617                                                URLSubPath, IMDB_LINK_KEY,
618                                                nil]];
619                                        return ret ;
620                                }
621                        }
622                        else
623                        {
624                                /*Add the result to the list*/
625                                NSURL *resultURL = [NSURL URLWithString:[[[result objectsForXQuery:IMDB_RESULT_LINK_XPATH error:&error] objectAtIndex:0] stringValue]] ;
626                                NSString * resultTitleValue=[result stringValue];
627                                /* Deal with AKA titles */
628                                if([resultTitleValue hasPrefix:@"\n"])
629                                {
630                                        resultTitleValue=[resultTitleValue substringFromIndex:3];
631                                        resultTitle=[resultTitle stringByReplacingAllOccurancesOf:@"\n" withString:@" "];
632                                }
633                                /* Skip image links */
634                                else if(resultURL == nil || [resultTitleValue characterAtIndex:0] == 160)
635                                        continue;
636                                /*Skip Video Game titles (VG) */
637                                if([resultTitleValue rangeOfString:@"(VG)"].location != NSNotFound)
638                                        continue ;
639                                [ret addObject:[NSDictionary dictionaryWithObjectsAndKeys:
640                                        [[result objectsForXQuery:IMDB_RESULT_NAME_XPATH error:&error] objectAtIndex:0], @"name",
641                                        [resultURL path], IMDB_LINK_KEY,
642                                        nil]];
643                        }
644                }
645                if(!uniqueResult && [ret count]>0)return ret;
646        }
647        return nil ;
648}
649
650
651/*!
652* @brief Write our setings out
653 */
654- (void)writeSettings
655{
656        NSDictionary *settings = [NSDictionary dictionaryWithObjectsAndKeys:
657                [NSNumber numberWithInt:CURRENT_VERSION], VERSION_KEY,
658                movieTranslations, TRANSLATIONS_KEY,
659                nil];
660        [settings writeToFile:settingsPath atomically:YES];
661}
662
663/*!
664* @brief verify file extention of a file
665 *
666 * @param metaData The file's metadata
667 * @return YES if candidate, NO otherwise
668 */
669- (BOOL)isMovieCandidate:(id <SapphireFileMetaDataProtocol>)metaData;
670{
671        NSString *fileExt = [[metaData path] pathExtension];
672        if([[SapphireMetaData videoExtensions] member:fileExt])
673                return YES;
674        if([metaData fileContainerType] == FILE_CONTAINER_TYPE_VIDEO_TS)
675                return YES;
676        return NO ;
677}
678
679- (ImportState) importMetaData:(id <SapphireFileMetaDataProtocol>)metaData
680{
681        currentData = metaData;
682        /*Check to see if it is already imported*/
683        if([metaData importedTimeFromSource:META_IMDB_IMPORT_KEY])
684                return IMPORT_STATE_NOT_UPDATED;
685        id controller = [[dataMenu stack] peekController];
686        /* Check to see if we are waiting on the user to select a show title */
687        if(![controller isKindOfClass:[SapphireImporterDataMenu class]])
688        {
689                /* Another chooser is on the screen - delay further processing */
690                return IMPORT_STATE_NOT_UPDATED;
691        }
692        /*Get path*/
693        if(![self isMovieCandidate:metaData])
694                return IMPORT_STATE_NOT_UPDATED;
695        NSString *path = [metaData path];
696        NSString *fileName = [path lastPathComponent];
697        /*choose between file or directory name for lookup */
698        NSString *lookupName;
699        if([[SapphireSettings sharedSettings] dirLookup])
700                lookupName = [[path stringByDeletingLastPathComponent] lastPathComponent];
701        else
702                lookupName = fileName;
703        if([metaData fileClass]==FILE_CLASS_TV_SHOW) /* File is a TV Show - skip it */
704                return IMPORT_STATE_NOT_UPDATED;
705       
706        /*Get the movie title*/
707        NSString *movieDataLink = nil ;
708        /*Check to see if we know this movie*/
709        NSMutableDictionary *dict=[movieTranslations objectForKey:[[lookupName lowercaseString] stringByDeletingPathExtension]];
710        if(dict == nil)
711        {
712                if(dataMenu == nil)
713                /*There is no data menu, background import. So we can't ask user, skip*/
714                        return IMPORT_STATE_NOT_UPDATED;
715                /*Ask the user what movie this is*/
716                NSArray *movies = [self searchResultsForMovie:lookupName];
717                /* No need to prompt the user for an empty set */
718                if(movies==nil)
719                {
720                        /* We tried to import but found nothing - mark this file to be skipped on future imports */
721                        [metaData importInfo:[NSMutableDictionary dictionary] fromSource:META_IMDB_IMPORT_KEY withTime:[[NSDate date] timeIntervalSince1970]];
722                        [metaData setFileClass:FILE_CLASS_OTHER];
723                        return IMPORT_STATE_UPDATED;
724                }
725                /*Bring up the prompt*/
726                SapphireMovieChooser *chooser = [[SapphireMovieChooser alloc] initWithScene:[dataMenu scene]];
727                [chooser setMovies:movies];
728                [chooser setFileName:lookupName];               
729                [chooser setListTitle:BRLocalizedString(@"Select Movie Title", @"Prompt the user for title of movie")];
730                /*And display prompt*/
731                [[dataMenu stack] pushController:chooser];
732                [chooser release];
733                return IMPORT_STATE_NEEDS_SUSPEND;
734                //Data will be ready for access on the next call
735        }
736
737        NSString * selectedPoster=nil ;
738        NSString * autoSelectPoster=nil ;
739        selectedPoster =        [dict objectForKey:SELECTED_POSTER_KEY] ;
740        autoSelectPoster=       [dict objectForKey:AUTO_SELECT_POSTER_KEY] ;
741        if(!selectedPoster)
742        {
743                if(dataMenu == nil)
744                /*There is no data menu, background import. So we can't ask user, skip*/
745                        return IMPORT_STATE_NOT_UPDATED;
746                /* Posters will be downloaded, let the user choose one */
747                [SapphireFrontRowCompat renderScene:[dataMenu scene]];
748                NSArray *posters=[dict objectForKey:IMP_POSTERS_KEY];
749                if(![posters count])
750                {
751                        NSString *posterPath=nil ;
752                        /* Get the IMP Key with the IMDB Posters page */
753                        posterPath=[self getPosterPath:[dict objectForKey:IMDB_LINK_KEY]] ;
754                        if(posterPath!=nil)
755                        {
756                                [dict setObject:posterPath forKey:IMP_LINK_KEY];
757                                /*We got a posterPath, get the posterLinks */
758                                posters = [self getPosterLinks:posterPath];
759                                if(posters != nil)
760                                {
761                                        /* Add the poster links */
762                                        [dict setObject:posters forKey:IMP_POSTERS_KEY];
763                                        [self writeSettings];
764                                }
765                                /* Add another method via chooser incase IMDB doesn't have an IMP link */
766                        }
767                        else posters=nil ;
768                }
769                if([posters count])
770                {
771                        posterChooser=[[SapphirePosterChooser alloc] initWithScene:[dataMenu scene]];
772                        if(![posterChooser okayToDisplay])
773                        {
774                                if(autoSelectPoster==nil)
775                                {
776                                        /* Auto Select the first poster */
777                                        autoSelectPoster=[posters objectAtIndex:0];
778                                        [dict setObject:autoSelectPoster forKey:AUTO_SELECT_POSTER_KEY];
779                                        [self writeSettings];
780                                }
781                                [posterChooser release];
782                        }
783                        else
784                        {
785                                [self downloadPosterCandidates:posters];
786                                [posterChooser setPosters:posters] ;
787                                [posterChooser setFileName:lookupName];
788                                [posterChooser setFile:(SapphireFileMetaData *)metaData];
789                                [posterChooser setListTitle:BRLocalizedString(@"Select Movie Poster", @"Prompt the user for poster selection")];
790                                [[dataMenu stack] pushController:posterChooser];
791                                [posterChooser release];
792                                return IMPORT_STATE_NEEDS_SUSPEND;
793                        }
794                        [dataMenu resume];
795                }
796        }
797
798        NSFileManager *fileAgent=[NSFileManager defaultManager];
799        NSString * coverart=[[SapphireMetaData collectionArtPath]stringByAppendingPathComponent:@"@MOVIES"];
800        [fileAgent constructPath:coverart];
801        coverart=[coverart stringByAppendingPathComponent:[fileName stringByDeletingPathExtension]];
802       
803        if(selectedPoster && [dict objectForKey:IMP_POSTERS_KEY])
804        {
805                /* Lets move the selected poster to the corresponding Cover Art Directory */
806                NSString * poster=[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/Sapphire/Poster_Buffer"];
807                poster=[poster stringByAppendingPathComponent:[selectedPoster lastPathComponent]];
808                coverart=[coverart stringByAppendingPathExtension:[poster pathExtension]];
809                if([fileAgent fileExistsAtPath:poster])/* See if we need to clean up */
810                {
811                        if([fileAgent fileExistsAtPath:coverart])/* Remove old poster */
812                                [fileAgent removeFileAtPath:coverart handler:self];
813                        [fileAgent movePath:poster toPath:coverart handler:self] ;
814                        /* Lets clean up the Poster_Buffer */
815                        NSArray *oldPosters = [dict objectForKey:IMP_POSTERS_KEY];
816                        if([oldPosters count])
817                        {
818                                NSEnumerator *resultEnum = [oldPosters objectEnumerator];
819                                NSString *result = nil;
820                                while((result = [resultEnum nextObject]) != nil)
821                                {
822                                        BOOL isDir=NO ;
823                                        NSString *removeFile=[NSString stringWithFormat:@"%@/%@",[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/Sapphire/Poster_Buffer"],[result lastPathComponent]];
824                                        [fileAgent fileExistsAtPath:removeFile isDirectory:&isDir];
825                                        if(!isDir)[fileAgent removeFileAtPath:removeFile handler:self] ;
826                                }
827                        }
828                }
829                else if(![fileAgent fileExistsAtPath:coverart])
830                {
831                        /* We have seen this file before, but in a different location */
832                        /* - OR - the coverart has been deleted */
833                        NSArray * posterList=[NSArray arrayWithObject:selectedPoster];
834                        SapphireMovieDataMenuDownloadDelegate *myDelegate = [[SapphireMovieDataMenuDownloadDelegate alloc] initWithRequest:posterList withDestination:coverart delegate:self];
835                        [myDelegate downloadSingleMoviePoster] ;
836                        [myDelegate autorelease];
837                }
838        }
839        else if(autoSelectPoster)
840        {
841                /* The poster chooser wasn't loaded - ATV 1.0 */
842                NSArray * posterList=[NSArray arrayWithObject:autoSelectPoster];
843                coverart=[coverart stringByAppendingPathExtension:[autoSelectPoster pathExtension]];
844                SapphireMovieDataMenuDownloadDelegate *myDelegate = [[SapphireMovieDataMenuDownloadDelegate alloc] initWithRequest:posterList withDestination:coverart delegate:self];
845                [myDelegate downloadSingleMoviePoster] ;
846                [myDelegate autorelease];       
847        }
848       
849        /* If we have JPEG art and content is a ripped DVD we provide Preview.jpg coverart in the film folder,
850         * To allow for updates the preview.jpg is not a copy, but instead a symbolic link to the cover
851         * art in the Collection Art/@MOVIES folder */
852        if( ([[coverart pathExtension] caseInsensitiveCompare:@"jpg" ] == NSOrderedSame ||
853             [[coverart pathExtension] caseInsensitiveCompare:@"jpeg"] == NSOrderedSame  ) &&
854            [metaData fileContainerType] == FILE_CONTAINER_TYPE_VIDEO_TS )
855        {
856                /* This is non-critical code, just adding fluff, ignore returned value */
857                [fileAgent createSymbolicLinkAtPath:[[metaData path] stringByAppendingPathComponent:@"Preview.jpg"] pathContent:coverart];
858        }
859       
860        /*Import the info*/
861        /*IMDB Data */
862        NSMutableDictionary *infoIMDB = nil;
863        movieDataLink=[dict objectForKey:IMDB_LINK_KEY];
864        infoIMDB = [self getMetaForMovie:movieDataLink withPath:path];
865        if(!infoIMDB)
866                return IMPORT_STATE_NOT_UPDATED;
867        [infoIMDB removeObjectForKey:IMDB_LINK_KEY];
868        [metaData importInfo:infoIMDB fromSource:META_IMDB_IMPORT_KEY withTime:[[NSDate date] timeIntervalSince1970]];
869        [metaData setFileClass:FILE_CLASS_MOVIE];
870        /*We imported something*/
871        return IMPORT_STATE_UPDATED;
872}
873
874
875- (NSString *)completionText
876{
877        return BRLocalizedString(@"All availble Movie data has been imported", @"The Movie import is complete");
878}
879
880- (NSString *)initialText
881{
882        return BRLocalizedString(@"Fetch Movie Data", @"Title");
883}
884
885- (NSString *)informativeText
886{
887        return BRLocalizedString(@"This tool will attempt to fetch information about your Movie files from the Internet (IMDB/IMPAwards).  This procedure may take quite some time and could ask you questions.  You may cancel at any time.", @"Description of the movie import");
888}
889
890- (NSString *)buttonTitle
891{
892        return BRLocalizedString(@"Start Fetching Data", @"Button");
893}
894
895- (void) wasExhumedByPoppingController: (BRLayerController *) controller
896{
897        /*See if it was a movie chooser*/
898        if([controller isKindOfClass:[SapphireMovieChooser class]])
899        {
900                /*Get the user's selection*/
901                SapphireMovieChooser *chooser = (SapphireMovieChooser *)controller;
902                int selection = [chooser selection];
903                if(selection == MOVIE_CHOOSE_CANCEL)
904                {
905                        /*They aborted, skip*/
906                        [dataMenu skipNextItem];
907                }
908                else if(selection == MOVIE_CHOOSE_NOT_MOVIE)
909                {
910                        /*They said it is not a movie, so put in empty data so they are not asked again*/
911                        [currentData importInfo:[NSMutableDictionary dictionary] fromSource:META_IMDB_IMPORT_KEY withTime:[[NSDate date] timeIntervalSince1970]];
912                        if ([currentData fileClass] != FILE_CLASS_TV_SHOW)
913                                [currentData setFileClass:FILE_CLASS_UNKNOWN];
914                }
915                else
916                {
917                        /*They selected a movie title, save the translation and write it*/
918                        NSDictionary *movie = [[chooser movies] objectAtIndex:selection];
919                        NSString *filename = [[[chooser fileName] lowercaseString] stringByDeletingPathExtension];
920                        NSMutableDictionary * transDict = [movieTranslations objectForKey:filename];
921                        if(transDict == nil)
922                        {
923                                transDict=[NSMutableDictionary new] ;
924                                [movieTranslations setObject:transDict forKey:filename];
925                                [transDict release];
926                        }
927                        /* Add IMDB Key */
928                        [transDict setObject:[movie objectForKey:IMDB_LINK_KEY] forKey:IMDB_LINK_KEY];
929                }
930                [self writeSettings];
931                /*We can resume now*/
932                [dataMenu resume];
933        }
934        else if([controller isKindOfClass:[SapphirePosterChooser class]])
935        {
936                int selectedPoster = [posterChooser selectedPoster];
937                if(selectedPoster == POSTER_CHOOSE_CANCEL)
938                        /*They aborted, skip*/
939                        [dataMenu skipNextItem];
940                else
941                {
942                        NSString *selected = [[posterChooser posters] objectAtIndex:selectedPoster];
943                        NSString *filename = [[[posterChooser fileName] lowercaseString] stringByDeletingPathExtension];
944                        NSMutableDictionary * transDict = [movieTranslations objectForKey:filename];
945                        if(transDict == nil)
946                        {
947                                transDict=[NSMutableDictionary new] ;
948                                [movieTranslations setObject:transDict forKey:filename];
949                                [transDict release];
950                        }
951                        [transDict setObject:selected forKey:SELECTED_POSTER_KEY];
952                }
953                posterChooser = nil;
954                [self writeSettings];
955                /*We can resume now*/
956                [dataMenu resume];
957        }
958        else
959                return;
960}
961
962@end
Note: See TracBrowser for help on using the repository browser.