| 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 "SapphireFileMetaData.h" |
|---|
| 23 | #import "NSFileManager-Extensions.h" |
|---|
| 24 | #import "SapphireMovieChooser.h" |
|---|
| 25 | #import "SapphirePosterChooser.h" |
|---|
| 26 | #import "SapphireSettings.h" |
|---|
| 27 | #import "SapphireMetaDataSupport.h" |
|---|
| 28 | #import "SapphireMovie.h" |
|---|
| 29 | #import "SapphireMovieTranslation.h" |
|---|
| 30 | #import "SapphireMoviePoster.h" |
|---|
| 31 | #import "SapphireApplianceController.h" |
|---|
| 32 | #import "SapphireURLLoader.h" |
|---|
| 33 | #import "SapphireScraper.h" |
|---|
| 34 | #import "NSString-Extensions.h" |
|---|
| 35 | |
|---|
| 36 | @interface SapphireMovieImportStateData : SapphireImportStateData |
|---|
| 37 | { |
|---|
| 38 | @public |
|---|
| 39 | SapphireSiteMovieScraper *siteScraper; |
|---|
| 40 | SapphireMovieTranslation *translation; |
|---|
| 41 | } |
|---|
| 42 | - (id)initWithFile:(SapphireFileMetaData *)aFile atPath:(NSString *)aPath scraper:(SapphireSiteMovieScraper *)siteScaper; |
|---|
| 43 | - (void)setTranslation:(SapphireMovieTranslation *)aTranslation; |
|---|
| 44 | - (SapphireMovieTranslation *)createTranslationInContext:(NSManagedObjectContext *)moc; |
|---|
| 45 | @end |
|---|
| 46 | |
|---|
| 47 | @implementation SapphireMovieImportStateData |
|---|
| 48 | |
|---|
| 49 | - (id)initWithFile:(SapphireFileMetaData *)aFile atPath:(NSString *)aPath scraper:(SapphireSiteMovieScraper *)aSiteScaper |
|---|
| 50 | { |
|---|
| 51 | self = [super initWithFile:aFile atPath:aPath]; |
|---|
| 52 | if(!self) |
|---|
| 53 | return self; |
|---|
| 54 | |
|---|
| 55 | siteScraper = [aSiteScaper retain]; |
|---|
| 56 | |
|---|
| 57 | return self; |
|---|
| 58 | } |
|---|
| 59 | |
|---|
| 60 | - (void)dealloc |
|---|
| 61 | { |
|---|
| 62 | [siteScraper release]; |
|---|
| 63 | [translation release]; |
|---|
| 64 | [super dealloc]; |
|---|
| 65 | } |
|---|
| 66 | |
|---|
| 67 | - (void)setTranslation:(SapphireMovieTranslation *)aTranslation |
|---|
| 68 | { |
|---|
| 69 | [translation autorelease]; |
|---|
| 70 | translation = [aTranslation retain]; |
|---|
| 71 | } |
|---|
| 72 | |
|---|
| 73 | - (SapphireMovieTranslation *)createTranslationInContext:(NSManagedObjectContext *)moc |
|---|
| 74 | { |
|---|
| 75 | SapphireMovieTranslation *tran = [SapphireMovieTranslation createMovieTranslationWithName:lookupName inContext:moc]; |
|---|
| 76 | [self setTranslation:tran]; |
|---|
| 77 | return tran; |
|---|
| 78 | } |
|---|
| 79 | |
|---|
| 80 | @end |
|---|
| 81 | |
|---|
| 82 | |
|---|
| 83 | |
|---|
| 84 | @interface SapphireMovieImporter () |
|---|
| 85 | - (void)getMovieResultsForState:(SapphireMovieImportStateData *)state; |
|---|
| 86 | - (void)getMoviePostersForState:(SapphireMovieImportStateData *)state thumbElements:(NSArray *)thumbElements; |
|---|
| 87 | - (void)saveMoviePosterAtURL:(NSString *)url forTranslation:(SapphireMovieTranslation *)tran; |
|---|
| 88 | - (void)completeWithState:(SapphireMovieImportStateData *)state withStatus:(ImportState)status importComplete:(BOOL)importComplete; |
|---|
| 89 | @end |
|---|
| 90 | |
|---|
| 91 | @implementation SapphireMovieImporter |
|---|
| 92 | |
|---|
| 93 | - (id)init |
|---|
| 94 | { |
|---|
| 95 | self = [super init]; |
|---|
| 96 | if(!self) |
|---|
| 97 | return self; |
|---|
| 98 | |
|---|
| 99 | scraper = [[SapphireScraper scrapperWithName:@"IMDb.com"] retain]; |
|---|
| 100 | |
|---|
| 101 | return self; |
|---|
| 102 | } |
|---|
| 103 | |
|---|
| 104 | - (void)dealloc |
|---|
| 105 | { |
|---|
| 106 | [scraper release]; |
|---|
| 107 | [super dealloc]; |
|---|
| 108 | } |
|---|
| 109 | |
|---|
| 110 | |
|---|
| 111 | - (void)setDelegate:(id <SapphireImporterDelegate>)aDelegate |
|---|
| 112 | { |
|---|
| 113 | delegate = aDelegate; |
|---|
| 114 | } |
|---|
| 115 | |
|---|
| 116 | - (void)cancelImports |
|---|
| 117 | { |
|---|
| 118 | cancelled = YES; |
|---|
| 119 | } |
|---|
| 120 | |
|---|
| 121 | - (void)retrievedSearchResuls:(NSXMLDocument *)results forObject:(id)stateObj |
|---|
| 122 | { |
|---|
| 123 | SapphireMovieImportStateData *state = (SapphireMovieImportStateData *)stateObj; |
|---|
| 124 | [state->siteScraper setObject:nil]; //Avoid retain loop |
|---|
| 125 | if(cancelled) |
|---|
| 126 | return; |
|---|
| 127 | |
|---|
| 128 | if(results == nil) |
|---|
| 129 | { |
|---|
| 130 | /*Failed to get data, network likely, don't mark this as imported*/ |
|---|
| 131 | [self completeWithState:state withStatus:ImportStateNotUpdated importComplete:NO]; |
|---|
| 132 | return; |
|---|
| 133 | } |
|---|
| 134 | NSXMLElement *root = [results rootElement]; |
|---|
| 135 | NSArray *entities = [root elementsForName:@"entity"]; |
|---|
| 136 | NSMutableArray *movies = [[NSMutableArray alloc] initWithCapacity:[entities count]]; |
|---|
| 137 | NSEnumerator *entityEnum = [entities objectEnumerator]; |
|---|
| 138 | NSXMLElement *entity; |
|---|
| 139 | while((entity = [entityEnum nextObject]) != nil) |
|---|
| 140 | { |
|---|
| 141 | NSString *kind = stringValueOfChild(entity, @"kind"); |
|---|
| 142 | |
|---|
| 143 | /*Skip video games and tv series*/ |
|---|
| 144 | if([kind isEqualToString:@"VG"] || [kind isEqualToString:@"TV series"]) |
|---|
| 145 | continue; |
|---|
| 146 | |
|---|
| 147 | NSString *title = stringValueOfChild(entity, @"title"); |
|---|
| 148 | NSString *itemID = stringValueOfChild(entity, @"id"); |
|---|
| 149 | NSString *url = stringValueOfChild(entity, @"url"); |
|---|
| 150 | if([url length]) |
|---|
| 151 | { |
|---|
| 152 | NSURL *trimmer = [NSURL URLWithString:url]; |
|---|
| 153 | url = [trimmer path]; |
|---|
| 154 | } |
|---|
| 155 | NSString *year = stringValueOfChild(entity, @"year"); |
|---|
| 156 | if([year length]) |
|---|
| 157 | title = [title stringByAppendingFormat:@" (%@)", year]; |
|---|
| 158 | |
|---|
| 159 | if([title length] && [url length]) |
|---|
| 160 | [movies addObject:[NSDictionary dictionaryWithObjectsAndKeys: |
|---|
| 161 | title, movieTranslationNameKey, |
|---|
| 162 | url, movieTranslationLinkKey, |
|---|
| 163 | itemID, movieTranslationIDKey, |
|---|
| 164 | nil]]; |
|---|
| 165 | } |
|---|
| 166 | |
|---|
| 167 | SapphireLog(SAPPHIRE_LOG_IMPORT, SAPPHIRE_LOG_LEVEL_DETAIL, @"Found results: %@", movies); |
|---|
| 168 | |
|---|
| 169 | /* No need to prompt the user for an empty set */ |
|---|
| 170 | if(![movies count]) |
|---|
| 171 | { |
|---|
| 172 | /* We tried to import but found nothing - mark this file to be skipped on future imports */ |
|---|
| 173 | [self completeWithState:state withStatus:ImportStateNotUpdated importComplete:YES]; |
|---|
| 174 | } |
|---|
| 175 | else if([[SapphireSettings sharedSettings] autoSelection]) |
|---|
| 176 | { |
|---|
| 177 | SapphireFileMetaData *metaData = state->file; |
|---|
| 178 | NSManagedObjectContext *moc = [metaData managedObjectContext]; |
|---|
| 179 | SapphireMovieTranslation *tran = [state createTranslationInContext:moc]; |
|---|
| 180 | tran.url = [[movies objectAtIndex:0] objectForKey:movieTranslationLinkKey]; |
|---|
| 181 | [self getMovieResultsForState:state]; |
|---|
| 182 | } |
|---|
| 183 | else |
|---|
| 184 | { |
|---|
| 185 | /*Bring up the prompt*/ |
|---|
| 186 | SapphireMovieChooser *chooser = [[SapphireMovieChooser alloc] initWithScene:[delegate chooserScene]]; |
|---|
| 187 | [chooser setMovies:movies]; |
|---|
| 188 | [chooser setFileName:[NSString stringByCroppingDirectoryPath:state->path toLength:3]]; |
|---|
| 189 | [chooser setListTitle:BRLocalizedString(@"Select Movie Title", @"Prompt the user for title of movie")]; |
|---|
| 190 | /*And display prompt*/ |
|---|
| 191 | [delegate displayChooser:chooser forImporter:self withContext:state]; |
|---|
| 192 | [chooser release]; |
|---|
| 193 | } |
|---|
| 194 | [movies release]; |
|---|
| 195 | } |
|---|
| 196 | |
|---|
| 197 | - (void)getMovieResultsForState:(SapphireMovieImportStateData *)state |
|---|
| 198 | { |
|---|
| 199 | SapphireMovieTranslation *tran = state->translation; |
|---|
| 200 | NSString *link = tran.url; |
|---|
| 201 | SapphireSiteMovieScraper *siteScraper = state->siteScraper; |
|---|
| 202 | [siteScraper setObject:state]; |
|---|
| 203 | [siteScraper getMovieDetailsAtURL:link forMovieID:tran.itemID]; |
|---|
| 204 | } |
|---|
| 205 | |
|---|
| 206 | - (void)retrievedMovieDetails:(NSXMLDocument *)details forObject:(id)stateObj |
|---|
| 207 | { |
|---|
| 208 | SapphireMovieImportStateData *state = (SapphireMovieImportStateData *)stateObj; |
|---|
| 209 | [state->siteScraper setObject:nil]; //Avoid retain loop |
|---|
| 210 | |
|---|
| 211 | if(cancelled) |
|---|
| 212 | return; |
|---|
| 213 | |
|---|
| 214 | if(details == nil) |
|---|
| 215 | { |
|---|
| 216 | /*Failed to get data, network likely, don't mark this as imported*/ |
|---|
| 217 | [self completeWithState:state withStatus:ImportStateNotUpdated importComplete:NO]; |
|---|
| 218 | return; |
|---|
| 219 | } |
|---|
| 220 | NSXMLElement *root = [details rootElement]; |
|---|
| 221 | |
|---|
| 222 | NSString *movieTitleLink = stringValueOfChild(root, @"id"); |
|---|
| 223 | NSNumber *oscarsWon = intValueOfChild(root, @"oscars"); |
|---|
| 224 | NSDate *releaseDate = dateValueOfChild(root, @"releasedate"); |
|---|
| 225 | NSString *plot = stringValueOfChild(root, @"plot"); |
|---|
| 226 | NSString *mpaaStr = stringValueOfChild(root, @"mpaa"); |
|---|
| 227 | NSArray *directors = arrayStringValueOfChild(root, @"director"); |
|---|
| 228 | NSArray *genres = arrayStringValueOfChild(root, @"genre"); |
|---|
| 229 | NSNumber *top250 = intValueOfChild(root, @"top250"); |
|---|
| 230 | NSString *usrRating = stringValueOfChild(root, @"rating"); |
|---|
| 231 | NSArray *completeCast = arrayStringValueOfXPath(root, @"actor/name"); |
|---|
| 232 | NSString *movieTitle = stringValueOfChild(root, @"title"); |
|---|
| 233 | |
|---|
| 234 | if([movieTitle rangeOfString:@"Request Limit Reached"].location != NSNotFound) |
|---|
| 235 | { |
|---|
| 236 | /*IMDB said we hit them too much; abort this data*/ |
|---|
| 237 | [self completeWithState:state withStatus:ImportStateNotUpdated importComplete:NO]; |
|---|
| 238 | return; |
|---|
| 239 | } |
|---|
| 240 | |
|---|
| 241 | NSString *mpaaRating = nil; |
|---|
| 242 | if([mpaaStr hasPrefix:@"Rated"]) |
|---|
| 243 | { |
|---|
| 244 | NSScanner *trimmer=[NSScanner scannerWithString:[mpaaStr substringFromIndex:6]] ; |
|---|
| 245 | [trimmer scanUpToString:@" " intoString:&mpaaRating]; |
|---|
| 246 | } |
|---|
| 247 | else |
|---|
| 248 | { |
|---|
| 249 | NSArray *certifications = arrayStringValueOfXPath(root, @"certification"); |
|---|
| 250 | NSString *certification; |
|---|
| 251 | NSEnumerator *certEnum = [certifications objectEnumerator]; |
|---|
| 252 | while((certification = [certEnum nextObject]) != nil) |
|---|
| 253 | { |
|---|
| 254 | if([certification hasPrefix:@"USA:"]) |
|---|
| 255 | { |
|---|
| 256 | NSScanner *trimmer=[NSScanner scannerWithString:[certification substringFromIndex:4]]; |
|---|
| 257 | [trimmer scanUpToString:@" " intoString:&mpaaRating]; |
|---|
| 258 | } |
|---|
| 259 | } |
|---|
| 260 | } |
|---|
| 261 | |
|---|
| 262 | |
|---|
| 263 | NSMutableDictionary *infoIMDB = [NSMutableDictionary dictionary]; |
|---|
| 264 | /* populate metadata to return */ |
|---|
| 265 | [infoIMDB setObject:movieTitleLink forKey:META_MOVIE_IDENTIFIER_KEY]; |
|---|
| 266 | if(oscarsWon) |
|---|
| 267 | [infoIMDB setObject:oscarsWon forKey:META_MOVIE_OSCAR_KEY]; |
|---|
| 268 | else |
|---|
| 269 | [infoIMDB setObject:[NSNumber numberWithInt:0] forKey:META_MOVIE_OSCAR_KEY]; |
|---|
| 270 | if(top250) |
|---|
| 271 | [infoIMDB setObject:top250 forKey:META_MOVIE_IMDB_250_KEY]; |
|---|
| 272 | if([usrRating length]>0) |
|---|
| 273 | [infoIMDB setObject:[NSNumber numberWithFloat:[usrRating floatValue]] forKey:META_MOVIE_IMDB_RATING_KEY]; |
|---|
| 274 | if(mpaaRating) |
|---|
| 275 | [infoIMDB setObject:mpaaRating forKey:META_MOVIE_MPAA_RATING_KEY]; |
|---|
| 276 | else |
|---|
| 277 | [infoIMDB setObject:@"N/A" forKey:META_MOVIE_MPAA_RATING_KEY]; |
|---|
| 278 | if(directors) |
|---|
| 279 | [infoIMDB setObject:directors forKey:META_MOVIE_DIRECTOR_KEY]; |
|---|
| 280 | if(plot) |
|---|
| 281 | [infoIMDB setObject:plot forKey:META_MOVIE_PLOT_KEY]; |
|---|
| 282 | if(releaseDate) |
|---|
| 283 | [infoIMDB setObject:releaseDate forKey:META_MOVIE_RELEASE_DATE_KEY]; |
|---|
| 284 | if(genres) |
|---|
| 285 | [infoIMDB setObject:genres forKey:META_MOVIE_GENRES_KEY]; |
|---|
| 286 | if(completeCast) |
|---|
| 287 | [infoIMDB setObject:completeCast forKey:META_MOVIE_CAST_KEY]; |
|---|
| 288 | if(movieTitle) |
|---|
| 289 | [infoIMDB setObject:movieTitle forKey:META_MOVIE_TITLE_KEY]; |
|---|
| 290 | |
|---|
| 291 | SapphireFileMetaData *metaData = state->file; |
|---|
| 292 | NSManagedObjectContext *moc = [metaData managedObjectContext]; |
|---|
| 293 | SapphireMovie *movie = [SapphireMovie movieWithDictionary:infoIMDB inContext:moc]; |
|---|
| 294 | if(movie == nil) |
|---|
| 295 | { |
|---|
| 296 | SapphireLog(SAPPHIRE_LOG_IMPORT, SAPPHIRE_LOG_LEVEL_ERROR, @"Failed to import movie for %@", state->path); |
|---|
| 297 | [self completeWithState:state withStatus:ImportStateNotUpdated importComplete:NO]; |
|---|
| 298 | return; |
|---|
| 299 | } |
|---|
| 300 | [state->translation setMovie:movie]; |
|---|
| 301 | [metaData setMovie:movie]; |
|---|
| 302 | |
|---|
| 303 | NSArray *thumbs = [root elementsForName:@"thumb"]; |
|---|
| 304 | NSXMLElement *fanart = [[root elementsForName:@"fanart"] lastObject]; |
|---|
| 305 | if(fanart) |
|---|
| 306 | thumbs = [thumbs arrayByAddingObjectsFromArray:[fanart elementsForName:@"thumb"]]; |
|---|
| 307 | |
|---|
| 308 | BOOL canDisplay = [delegate canDisplayChooser]; |
|---|
| 309 | if(canDisplay && [thumbs count]) |
|---|
| 310 | [self getMoviePostersForState:state thumbElements:thumbs]; |
|---|
| 311 | else |
|---|
| 312 | [self completeWithState:state withStatus:ImportStateUpdated importComplete:canDisplay]; |
|---|
| 313 | } |
|---|
| 314 | |
|---|
| 315 | - (void)getMoviePostersForState:(SapphireMovieImportStateData *)state thumbElements:(NSArray *)thumbElements; |
|---|
| 316 | { |
|---|
| 317 | NSMutableArray *previews = [NSMutableArray arrayWithCapacity:[thumbElements count]]; |
|---|
| 318 | SapphireMovieTranslation *tran = state->translation; |
|---|
| 319 | if([thumbElements count]) |
|---|
| 320 | { |
|---|
| 321 | int index = 0; |
|---|
| 322 | NSManagedObjectContext *moc = [tran managedObjectContext]; |
|---|
| 323 | //Redoing posters, get rid of old ones |
|---|
| 324 | [tran setSelectedPosterIndex:nil]; |
|---|
| 325 | NSEnumerator *posterEnum = [[tran postersSet] objectEnumerator]; |
|---|
| 326 | SapphireMoviePoster *poster; |
|---|
| 327 | while((poster = [posterEnum nextObject]) != nil) |
|---|
| 328 | [moc deleteObject:poster]; |
|---|
| 329 | |
|---|
| 330 | NSEnumerator *thumbEnum = [thumbElements objectEnumerator]; |
|---|
| 331 | NSXMLElement *thumb; |
|---|
| 332 | NSMutableSet *posterSet = [NSMutableSet setWithCapacity:[thumbElements count]]; |
|---|
| 333 | while((thumb = [thumbEnum nextObject]) != nil) |
|---|
| 334 | { |
|---|
| 335 | NSString *preview = [[thumb attributeForName:@"preview"] stringValue]; |
|---|
| 336 | NSString *url = [thumb stringValue]; |
|---|
| 337 | if(![preview length]) |
|---|
| 338 | preview = url; |
|---|
| 339 | |
|---|
| 340 | [previews addObject:preview]; |
|---|
| 341 | [posterSet addObject:[SapphireMoviePoster createPosterWithLink:url index:index translation:tran inContext:moc]]; |
|---|
| 342 | index++; |
|---|
| 343 | } |
|---|
| 344 | [[tran postersSet] setSet:posterSet]; |
|---|
| 345 | } |
|---|
| 346 | else |
|---|
| 347 | { |
|---|
| 348 | NSArray *posters = [tran orderedPosters]; |
|---|
| 349 | NSEnumerator *posterEnum = [posters objectEnumerator]; |
|---|
| 350 | SapphireMoviePoster *poster; |
|---|
| 351 | while((poster = [posterEnum nextObject]) != nil) |
|---|
| 352 | { |
|---|
| 353 | NSString *link = [poster link]; |
|---|
| 354 | [previews addObject:link]; |
|---|
| 355 | } |
|---|
| 356 | } |
|---|
| 357 | if([tran selectedPoster]) |
|---|
| 358 | { |
|---|
| 359 | [self saveMoviePosterAtURL:[[tran selectedPoster] link] forTranslation:tran]; |
|---|
| 360 | [self completeWithState:state withStatus:ImportStateUpdated importComplete:YES]; |
|---|
| 361 | } |
|---|
| 362 | else if([previews count]) |
|---|
| 363 | { |
|---|
| 364 | SapphirePosterChooser *posterChooser = [[SapphirePosterChooser alloc] initWithScene:[delegate chooserScene]]; |
|---|
| 365 | if(![posterChooser okayToDisplay] || [[SapphireSettings sharedSettings] autoSelection]) |
|---|
| 366 | { |
|---|
| 367 | /* Auto Select the first poster */ |
|---|
| 368 | [self saveMoviePosterAtURL:[[tran posterAtIndex:0] link] forTranslation:tran]; |
|---|
| 369 | [posterChooser release]; |
|---|
| 370 | } |
|---|
| 371 | else |
|---|
| 372 | { |
|---|
| 373 | [posterChooser setPosters:previews]; |
|---|
| 374 | [posterChooser setFileName:[NSString stringByCroppingDirectoryPath:state->path toLength:3]]; |
|---|
| 375 | [posterChooser setFile:state->file]; |
|---|
| 376 | [posterChooser setListTitle:BRLocalizedString(@"Select Movie Poster", @"Prompt the user for poster selection")]; |
|---|
| 377 | [delegate displayChooser:posterChooser forImporter:self withContext:state]; |
|---|
| 378 | [posterChooser release]; |
|---|
| 379 | } |
|---|
| 380 | } |
|---|
| 381 | else |
|---|
| 382 | [self completeWithState:state withStatus:ImportStateUpdated importComplete:YES]; |
|---|
| 383 | } |
|---|
| 384 | |
|---|
| 385 | - (void)saveMoviePosterAtURL:(NSString *)url forTranslation:(SapphireMovieTranslation *)tran |
|---|
| 386 | { |
|---|
| 387 | NSString *coverart = [[SapphireMetaDataSupport collectionArtPath] stringByAppendingPathComponent:@"@MOVIES"]; |
|---|
| 388 | [[NSFileManager defaultManager] constructPath:coverart]; |
|---|
| 389 | int imdbNumber = [SapphireMovie imdbNumberFromString:tran.itemID]; |
|---|
| 390 | coverart = [coverart stringByAppendingPathComponent:[NSString stringWithFormat:@"%d.jpg", imdbNumber]]; |
|---|
| 391 | [[SapphireApplianceController urlLoader] saveDataAtURL:url toFile:coverart]; |
|---|
| 392 | } |
|---|
| 393 | |
|---|
| 394 | - (void)completeWithState:(SapphireMovieImportStateData *)state withStatus:(ImportState)status importComplete:(BOOL)importComplete; |
|---|
| 395 | { |
|---|
| 396 | SapphireFileMetaData *currentData = state->file; |
|---|
| 397 | if(importComplete) |
|---|
| 398 | { |
|---|
| 399 | [currentData didImportType:IMPORT_TYPE_MOVIE_MASK]; |
|---|
| 400 | if (status == ImportStateNotUpdated && [currentData fileClassValue] != FILE_CLASS_TV_SHOW) |
|---|
| 401 | [currentData setFileClassValue:FILE_CLASS_UNKNOWN]; |
|---|
| 402 | } |
|---|
| 403 | [delegate backgroundImporter:self completedImportOnPath:state->path withState:status]; |
|---|
| 404 | } |
|---|
| 405 | |
|---|
| 406 | - (NSString *)movieURLFromNfoFilePath:(NSString *)filepath withID:(NSString * *)movieID |
|---|
| 407 | { |
|---|
| 408 | NSString *nfoContent = [NSString stringWithContentsOfFile:filepath]; |
|---|
| 409 | |
|---|
| 410 | if(![nfoContent length]) |
|---|
| 411 | return nil; |
|---|
| 412 | |
|---|
| 413 | NSString *results = [scraper searchResultsForNfoContent:nfoContent]; |
|---|
| 414 | if(![results length]) |
|---|
| 415 | return nil; |
|---|
| 416 | |
|---|
| 417 | NSString *fullResults = [NSString stringWithFormat:@"<results>%@</results>", results]; |
|---|
| 418 | NSError *error = nil; |
|---|
| 419 | NSXMLDocument *doc = [[[NSXMLDocument alloc] initWithXMLString:fullResults options:0 error:&error] autorelease]; |
|---|
| 420 | if(!doc) |
|---|
| 421 | return nil; |
|---|
| 422 | |
|---|
| 423 | NSXMLElement *root = [doc rootElement]; |
|---|
| 424 | NSString *urlStr = stringValueOfChild(root, @"url"); |
|---|
| 425 | *movieID = stringValueOfChild(root, @"id"); |
|---|
| 426 | return urlStr; |
|---|
| 427 | } |
|---|
| 428 | |
|---|
| 429 | /*! |
|---|
| 430 | * @brief verify file extention of a file |
|---|
| 431 | * |
|---|
| 432 | * @param metaData The file's metadata |
|---|
| 433 | * @return YES if candidate, NO otherwise |
|---|
| 434 | */ |
|---|
| 435 | - (BOOL)isMovieCandidate:(SapphireFileMetaData *)metaData; |
|---|
| 436 | { |
|---|
| 437 | NSString *path = [metaData path]; |
|---|
| 438 | BOOL ret = [[NSFileManager defaultManager] acceptFilePath:path]; |
|---|
| 439 | if([metaData fileContainerType] == FILE_CONTAINER_TYPE_QT_MOVIE) |
|---|
| 440 | ret &= [[NSFileManager videoExtensions] containsObject:[path pathExtension]]; |
|---|
| 441 | if([metaData fileClassValue]==FILE_CLASS_TV_SHOW) /* File is a TV Show - skip it */ |
|---|
| 442 | ret = NO; |
|---|
| 443 | return ret; |
|---|
| 444 | } |
|---|
| 445 | |
|---|
| 446 | - (ImportState)importMetaData:(SapphireFileMetaData *)metaData path:(NSString *)path |
|---|
| 447 | { |
|---|
| 448 | cancelled = NO; |
|---|
| 449 | /*Check to see if it is already imported*/ |
|---|
| 450 | if([metaData importTypeValue] & IMPORT_TYPE_MOVIE_MASK) |
|---|
| 451 | return ImportStateNotUpdated; |
|---|
| 452 | /*Get path*/ |
|---|
| 453 | if(![self isMovieCandidate:metaData]) |
|---|
| 454 | return ImportStateNotUpdated; |
|---|
| 455 | SapphireLog(SAPPHIRE_LOG_IMPORT, SAPPHIRE_LOG_LEVEL_DEBUG, @"Going to movie import %@", path); |
|---|
| 456 | NSString *extLessPath = path; |
|---|
| 457 | if([metaData fileContainerTypeValue] != FILE_CONTAINER_TYPE_VIDEO_TS) |
|---|
| 458 | extLessPath = [extLessPath stringByDeletingPathExtension]; |
|---|
| 459 | |
|---|
| 460 | /*choose between file or directory name for lookup */ |
|---|
| 461 | NSString *lookupName; |
|---|
| 462 | if([[SapphireSettings sharedSettings] dirLookup]) |
|---|
| 463 | lookupName = [[[path stringByDeletingLastPathComponent] lastPathComponent] lowercaseString]; |
|---|
| 464 | else |
|---|
| 465 | lookupName = [[extLessPath lastPathComponent] lowercaseString]; |
|---|
| 466 | |
|---|
| 467 | SapphireSiteMovieScraper *siteScraper = [[[SapphireSiteMovieScraper alloc] initWithMovieScraper:scraper delegate:self loader:[SapphireApplianceController urlLoader]] autorelease]; |
|---|
| 468 | SapphireMovieImportStateData *state = [[[SapphireMovieImportStateData alloc] initWithFile:metaData atPath:path scraper:siteScraper] autorelease]; |
|---|
| 469 | [state setLookupName:lookupName]; |
|---|
| 470 | /*Check to see if we know this movie*/ |
|---|
| 471 | |
|---|
| 472 | /*Look for a year in the title*/ |
|---|
| 473 | NSScanner *titleYearScanner = [NSScanner scannerWithString:state->lookupName]; |
|---|
| 474 | NSString *normalTitle = nil; |
|---|
| 475 | int year = 0; |
|---|
| 476 | BOOL success = YES; |
|---|
| 477 | success &= [titleYearScanner scanUpToString:@"(" intoString:&normalTitle]; |
|---|
| 478 | success &= [titleYearScanner scanString:@"(" intoString:nil]; |
|---|
| 479 | success &= [titleYearScanner scanInt:&year]; |
|---|
| 480 | success &= [titleYearScanner scanString:@")" intoString:nil]; |
|---|
| 481 | |
|---|
| 482 | NSString *yearStr = nil; |
|---|
| 483 | if(success) |
|---|
| 484 | { |
|---|
| 485 | yearStr = [NSString stringWithFormat:@"%d", year]; |
|---|
| 486 | if([normalTitle hasSuffix:@" "]) |
|---|
| 487 | normalTitle = [normalTitle substringToIndex:[normalTitle length]-1]; |
|---|
| 488 | [state setLookupName:normalTitle]; |
|---|
| 489 | } |
|---|
| 490 | |
|---|
| 491 | SapphireLog(SAPPHIRE_LOG_IMPORT, SAPPHIRE_LOG_LEVEL_DETAIL, @"Searching for movie \"%@\"", state->lookupName); |
|---|
| 492 | NSManagedObjectContext *moc = [metaData managedObjectContext]; |
|---|
| 493 | SapphireMovieTranslation *tran = [SapphireMovieTranslation movieTranslationWithName:state->lookupName inContext:moc]; |
|---|
| 494 | if(tran == nil) |
|---|
| 495 | //Check for translation with full title |
|---|
| 496 | tran = [SapphireMovieTranslation movieTranslationWithName:[titleYearScanner string] inContext:moc]; |
|---|
| 497 | [state setTranslation:tran]; |
|---|
| 498 | int searchIMDBNumber = [metaData searchIMDBNumber]; |
|---|
| 499 | if(searchIMDBNumber > 0) |
|---|
| 500 | { |
|---|
| 501 | if(!tran) |
|---|
| 502 | tran = [state createTranslationInContext:moc]; |
|---|
| 503 | tran.url = [NSString stringWithFormat:@"http://%@/title/tt%d/", [[[siteScraper scraper] settings] objectForKey:@"url"], searchIMDBNumber]; |
|---|
| 504 | } |
|---|
| 505 | if(tran.url == nil) |
|---|
| 506 | { |
|---|
| 507 | BOOL nfoPathIsDir = NO; |
|---|
| 508 | NSString *nfoFilePath=[extLessPath stringByAppendingPathExtension:@"nfo"]; |
|---|
| 509 | NSString *movieURL = nil; |
|---|
| 510 | NSString *movieID = nil; |
|---|
| 511 | if([[NSFileManager defaultManager] fileExistsAtPath:nfoFilePath isDirectory:&nfoPathIsDir] && !nfoPathIsDir) |
|---|
| 512 | movieURL = [self movieURLFromNfoFilePath:nfoFilePath withID:&movieID]; |
|---|
| 513 | |
|---|
| 514 | if([movieURL length]) |
|---|
| 515 | { |
|---|
| 516 | if(tran == nil) |
|---|
| 517 | tran = [state createTranslationInContext:moc]; |
|---|
| 518 | tran.url = movieURL; |
|---|
| 519 | if([movieID length]) |
|---|
| 520 | [tran setItemID:movieID]; |
|---|
| 521 | } |
|---|
| 522 | else |
|---|
| 523 | { |
|---|
| 524 | if(![delegate canDisplayChooser]) |
|---|
| 525 | /*There is no data menu, background import. So we can't ask user, skip*/ |
|---|
| 526 | return ImportStateNotUpdated; |
|---|
| 527 | |
|---|
| 528 | SapphireLog(SAPPHIRE_LOG_IMPORT, SAPPHIRE_LOG_LEVEL_DEBUG, @"Searching for %@ with year %@", state->lookupName, yearStr); |
|---|
| 529 | |
|---|
| 530 | /*Ask the user what movie this is*/ |
|---|
| 531 | [siteScraper setObject:state]; |
|---|
| 532 | [siteScraper searchForMovieName:state->lookupName year:yearStr]; |
|---|
| 533 | return ImportStateBackground; |
|---|
| 534 | } |
|---|
| 535 | } |
|---|
| 536 | |
|---|
| 537 | SapphireMovie *movie = [tran movie]; |
|---|
| 538 | if(movie != nil) |
|---|
| 539 | { |
|---|
| 540 | [metaData setMovie:movie]; |
|---|
| 541 | if([tran selectedPoster] != nil) |
|---|
| 542 | return ImportStateUpdated; |
|---|
| 543 | [self getMoviePostersForState:state thumbElements:[NSArray array]]; |
|---|
| 544 | return ImportStateBackground; |
|---|
| 545 | } |
|---|
| 546 | [self getMovieResultsForState:state]; |
|---|
| 547 | return ImportStateBackground; |
|---|
| 548 | } |
|---|
| 549 | |
|---|
| 550 | |
|---|
| 551 | - (NSString *)completionText |
|---|
| 552 | { |
|---|
| 553 | return BRLocalizedString(@"All available Movie data has been imported", @"The Movie import is complete"); |
|---|
| 554 | } |
|---|
| 555 | |
|---|
| 556 | - (NSString *)initialText |
|---|
| 557 | { |
|---|
| 558 | return BRLocalizedString(@"Fetch Movie Data", @"Title"); |
|---|
| 559 | } |
|---|
| 560 | |
|---|
| 561 | - (NSString *)informativeText |
|---|
| 562 | { |
|---|
| 563 | 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"); |
|---|
| 564 | } |
|---|
| 565 | |
|---|
| 566 | - (NSString *)buttonTitle |
|---|
| 567 | { |
|---|
| 568 | return BRLocalizedString(@"Start Fetching Data", @"Button"); |
|---|
| 569 | } |
|---|
| 570 | |
|---|
| 571 | - (BOOL)stillNeedsDisplayOfChooser:(BRLayerController <SapphireChooser> *)chooser withContext:(id)context |
|---|
| 572 | { |
|---|
| 573 | SapphireMovieImportStateData *state = (SapphireMovieImportStateData *)context; |
|---|
| 574 | if([chooser isKindOfClass:[SapphireMovieChooser class]]) |
|---|
| 575 | { |
|---|
| 576 | NSManagedObjectContext *moc = [state->file managedObjectContext]; |
|---|
| 577 | SapphireMovieTranslation *tran = [SapphireMovieTranslation movieTranslationWithName:state->lookupName inContext:moc]; |
|---|
| 578 | if(tran) |
|---|
| 579 | [state setTranslation:tran]; |
|---|
| 580 | if(tran.url) |
|---|
| 581 | { |
|---|
| 582 | [self getMovieResultsForState:state]; |
|---|
| 583 | return NO; |
|---|
| 584 | } |
|---|
| 585 | } |
|---|
| 586 | else if([chooser isKindOfClass:[SapphirePosterChooser class]]) |
|---|
| 587 | { |
|---|
| 588 | SapphireMovieTranslation *tran = state->translation; |
|---|
| 589 | if([[tran selectedPoster] link]) |
|---|
| 590 | { |
|---|
| 591 | [self saveMoviePosterAtURL:[[tran selectedPoster] link] forTranslation:tran]; |
|---|
| 592 | [self completeWithState:state withStatus:ImportStateUpdated importComplete:YES]; |
|---|
| 593 | return NO; |
|---|
| 594 | } |
|---|
| 595 | } |
|---|
| 596 | return YES; |
|---|
| 597 | } |
|---|
| 598 | |
|---|
| 599 | - (void)exhumedChooser:(BRLayerController <SapphireChooser> *)chooser withContext:(id)context |
|---|
| 600 | { |
|---|
| 601 | SapphireMovieImportStateData *state = (SapphireMovieImportStateData *)context; |
|---|
| 602 | /*See if it was a movie chooser*/ |
|---|
| 603 | if([chooser isKindOfClass:[SapphireMovieChooser class]]) |
|---|
| 604 | { |
|---|
| 605 | /*Get the user's selection*/ |
|---|
| 606 | SapphireMovieChooser *movieChooser = (SapphireMovieChooser *)chooser; |
|---|
| 607 | SapphireFileMetaData *currentData = state->file; |
|---|
| 608 | NSManagedObjectContext *moc = [currentData managedObjectContext]; |
|---|
| 609 | int selection = [movieChooser selection]; |
|---|
| 610 | if(selection == SapphireChooserChoiceCancel) |
|---|
| 611 | { |
|---|
| 612 | /*They aborted, skip*/ |
|---|
| 613 | [self completeWithState:state withStatus:ImportStateNotUpdated importComplete:NO]; |
|---|
| 614 | } |
|---|
| 615 | else if(selection == SapphireChooserChoiceNotType) |
|---|
| 616 | { |
|---|
| 617 | /*They said it is not a movie, so put in empty data so they are not asked again*/ |
|---|
| 618 | [self completeWithState:state withStatus:ImportStateNotUpdated importComplete:YES]; |
|---|
| 619 | } |
|---|
| 620 | else |
|---|
| 621 | { |
|---|
| 622 | /*They selected a movie title, save the translation and write it*/ |
|---|
| 623 | NSDictionary *movie = [[movieChooser movies] objectAtIndex:selection]; |
|---|
| 624 | SapphireMovieTranslation *tran = [state createTranslationInContext:moc]; |
|---|
| 625 | tran.url = [movie objectForKey:movieTranslationLinkKey]; |
|---|
| 626 | /*We can resume now*/ |
|---|
| 627 | [self getMovieResultsForState:state]; |
|---|
| 628 | } |
|---|
| 629 | [SapphireMetaDataSupport save:moc]; |
|---|
| 630 | } |
|---|
| 631 | else if([chooser isKindOfClass:[SapphirePosterChooser class]]) |
|---|
| 632 | { |
|---|
| 633 | SapphirePosterChooser *posterChooser = (SapphirePosterChooser *)chooser; |
|---|
| 634 | SapphireFileMetaData *currentData = state->file; |
|---|
| 635 | NSManagedObjectContext *moc = [currentData managedObjectContext]; |
|---|
| 636 | SapphireChooserChoice selectedPoster = [posterChooser selection]; |
|---|
| 637 | if(selectedPoster == SapphireChooserChoiceCancel) |
|---|
| 638 | /*They aborted, skip*/ |
|---|
| 639 | [self completeWithState:state withStatus:ImportStateNotUpdated importComplete:NO]; |
|---|
| 640 | else |
|---|
| 641 | { |
|---|
| 642 | SapphireMovieTranslation *tran = state->translation; |
|---|
| 643 | [tran setSelectedPosterIndexValue:selectedPoster]; |
|---|
| 644 | [self saveMoviePosterAtURL:[[tran selectedPoster] link] forTranslation:tran]; |
|---|
| 645 | [self completeWithState:state withStatus:ImportStateUpdated importComplete:YES]; |
|---|
| 646 | } |
|---|
| 647 | [SapphireMetaDataSupport save:moc]; |
|---|
| 648 | } |
|---|
| 649 | else |
|---|
| 650 | return; |
|---|
| 651 | } |
|---|
| 652 | |
|---|
| 653 | @end |
|---|