| 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 "SapphireTVShowImporter.h" |
|---|
| 22 | #import "SapphireFileMetaData.h" |
|---|
| 23 | #import "NSString-Extensions.h" |
|---|
| 24 | #import "NSFileManager-Extensions.h" |
|---|
| 25 | #import "SapphireShowChooser.h" |
|---|
| 26 | #import "SapphireMovieChooser.h" |
|---|
| 27 | #import "SapphireMetaDataSupport.h" |
|---|
| 28 | #import "SapphireTVTranslation.h" |
|---|
| 29 | #import "SapphireEpisode.h" |
|---|
| 30 | |
|---|
| 31 | /* TVRage XPATHS */ |
|---|
| 32 | #define TVRAGE_SHOWNAME_XPATH @".//h3/text()" |
|---|
| 33 | #define TVRAGE_EPLIST_XPATH @"//*[@class='b']" |
|---|
| 34 | #define TVRAGE_EP_INFO @".//*[@class='b2']/*" |
|---|
| 35 | #define TVRAGE_EP_TEXT @".//*[@class='b2']/text()" |
|---|
| 36 | #define TVRAGE_SCREEN_CAP_XPATH @".//img[contains(@src, 'screencap')]" |
|---|
| 37 | #define TVRAGE_SEARCH_XPATH @"//*[@class='b1']/a" |
|---|
| 38 | #define TVRAGE_UNKNOWN_XPATH @"//*[contains(text(), 'Unknown Page')]" |
|---|
| 39 | |
|---|
| 40 | #define TRANSLATIONS_KEY @"Translations" |
|---|
| 41 | #define LINK_KEY @"Link" |
|---|
| 42 | #define IMG_URL @"imgURL" |
|---|
| 43 | |
|---|
| 44 | /*Delegate class to download cover art*/ |
|---|
| 45 | @interface SapphireTVShowDataMenuDownloadDelegate : NSObject |
|---|
| 46 | { |
|---|
| 47 | NSString *destination; |
|---|
| 48 | } |
|---|
| 49 | - (id)initWithDest:(NSString *)dest; |
|---|
| 50 | @end |
|---|
| 51 | |
|---|
| 52 | @implementation SapphireTVShowDataMenuDownloadDelegate |
|---|
| 53 | /*! |
|---|
| 54 | * @brief Initialize a cover art downloader |
|---|
| 55 | * |
|---|
| 56 | * @param dest The path to save the file |
|---|
| 57 | */ |
|---|
| 58 | - (id)initWithDest:(NSString *)dest |
|---|
| 59 | { |
|---|
| 60 | self = [super init]; |
|---|
| 61 | if(!self) |
|---|
| 62 | return nil; |
|---|
| 63 | |
|---|
| 64 | destination = [dest retain]; |
|---|
| 65 | |
|---|
| 66 | return self; |
|---|
| 67 | } |
|---|
| 68 | |
|---|
| 69 | - (void)dealloc |
|---|
| 70 | { |
|---|
| 71 | [destination release]; |
|---|
| 72 | [super dealloc]; |
|---|
| 73 | } |
|---|
| 74 | |
|---|
| 75 | /*! |
|---|
| 76 | * @brief Delegate Method which prompts for location to save file. Override and set new |
|---|
| 77 | * destination |
|---|
| 78 | * |
|---|
| 79 | * @param download The downloader |
|---|
| 80 | * @param filename The suggested filename |
|---|
| 81 | */ |
|---|
| 82 | - (void)download:(NSURLDownload *)download decideDestinationWithSuggestedFilename:(NSString *)filename |
|---|
| 83 | { |
|---|
| 84 | [download setDestination:destination allowOverwrite:YES]; |
|---|
| 85 | } |
|---|
| 86 | @end |
|---|
| 87 | |
|---|
| 88 | @interface SapphireTVShowImporter (private) |
|---|
| 89 | - (void)writeSettings; |
|---|
| 90 | @end |
|---|
| 91 | |
|---|
| 92 | @implementation SapphireTVShowImporter |
|---|
| 93 | |
|---|
| 94 | - (id) initWithContext:(NSManagedObjectContext *)context |
|---|
| 95 | { |
|---|
| 96 | self = [super init]; |
|---|
| 97 | if(!self) |
|---|
| 98 | return nil; |
|---|
| 99 | |
|---|
| 100 | /*Get the settings*/ |
|---|
| 101 | moc = [context retain]; |
|---|
| 102 | /*Cached show info*/ |
|---|
| 103 | showInfo = [NSMutableDictionary new]; |
|---|
| 104 | |
|---|
| 105 | /*Initialize the regexes*/ |
|---|
| 106 | regcomp(&letterMarking, "[\\. -]?S[0-9]+E[S0-9]+(-E[0-9]+)?", REG_EXTENDED | REG_ICASE); |
|---|
| 107 | regcomp(&seasonByEpisode, "[\\. -]?[0-9]+x[S0-9]+(-[0-9]+)?", REG_EXTENDED | REG_ICASE); |
|---|
| 108 | regcomp(&seasonEpisodeTriple, "[\\. -][0-9]{1,3}[S0-9]{2}[\\. -]", REG_EXTENDED | REG_ICASE); |
|---|
| 109 | return self; |
|---|
| 110 | } |
|---|
| 111 | |
|---|
| 112 | - (void)dealloc |
|---|
| 113 | { |
|---|
| 114 | [moc release]; |
|---|
| 115 | [showInfo release]; |
|---|
| 116 | regfree(&letterMarking); |
|---|
| 117 | regfree(&seasonByEpisode); |
|---|
| 118 | regfree(&seasonEpisodeTriple); |
|---|
| 119 | [super dealloc]; |
|---|
| 120 | } |
|---|
| 121 | |
|---|
| 122 | - (void)setImporterDataMenu:(SapphireImporterDataMenu *)theDataMenu |
|---|
| 123 | { |
|---|
| 124 | dataMenu = theDataMenu; |
|---|
| 125 | } |
|---|
| 126 | |
|---|
| 127 | /*! |
|---|
| 128 | * @brief Add an episode's info into our cache dict |
|---|
| 129 | * |
|---|
| 130 | * @param showName The TV Show's name |
|---|
| 131 | * @param epTitle The episode's title |
|---|
| 132 | * @param season The episode's season |
|---|
| 133 | * @param ep The episodes's episode number within the season |
|---|
| 134 | * @param summary The episodes's summary |
|---|
| 135 | * @param eplink The episode's info URL |
|---|
| 136 | * @param epNumber The absolute episode number |
|---|
| 137 | * @param airDate The episode's air date |
|---|
| 138 | * @param imgURL The episode's screenshot URL |
|---|
| 139 | * @param dict The cache dictionary |
|---|
| 140 | */ |
|---|
| 141 | - (void)addEp:(NSString *)showName title:(NSString *)epTitle season:(int)season epNum:(int)ep summary:(NSString *)summary link:(NSString *)epLink absEpNum:(int)epNumber airDate:(NSDate *)airDate showID:(NSString *)showID imgURL:(NSString *)imgURL toDict:(NSMutableDictionary *)dict |
|---|
| 142 | { |
|---|
| 143 | /*Set the key by which to store this. Either by season/ep or season/title*/ |
|---|
| 144 | NSNumber *epNum = [NSNumber numberWithInt:ep]; |
|---|
| 145 | id key = epNum; |
|---|
| 146 | if(ep == 0) |
|---|
| 147 | key = [epTitle lowercaseString]; |
|---|
| 148 | |
|---|
| 149 | /*Get the ep dict*/ |
|---|
| 150 | NSNumber *seasonNum = [NSNumber numberWithInt:season]; |
|---|
| 151 | NSMutableDictionary *epDict = [dict objectForKey:key]; |
|---|
| 152 | if(epDict == nil) |
|---|
| 153 | { |
|---|
| 154 | epDict = [NSMutableDictionary new]; |
|---|
| 155 | [dict setObject:epDict forKey:key]; |
|---|
| 156 | [epDict release]; |
|---|
| 157 | } |
|---|
| 158 | /*Add info*/ |
|---|
| 159 | if(showName) |
|---|
| 160 | { |
|---|
| 161 | [epDict setObject:showName forKey:META_SHOW_NAME_KEY] ; |
|---|
| 162 | } |
|---|
| 163 | if(ep != 0) |
|---|
| 164 | [epDict setObject:epNum forKey:META_EPISODE_NUMBER_KEY]; |
|---|
| 165 | if(season != 0) |
|---|
| 166 | { |
|---|
| 167 | [epDict setObject:seasonNum forKey:META_SEASON_NUMBER_KEY]; |
|---|
| 168 | } |
|---|
| 169 | if(epTitle != nil) |
|---|
| 170 | [epDict setObject:epTitle forKey:META_TITLE_KEY]; |
|---|
| 171 | if(epLink != nil) |
|---|
| 172 | [epDict setObject:epLink forKey:LINK_KEY]; |
|---|
| 173 | if(showID != nil) |
|---|
| 174 | [epDict setObject:showID forKey:META_SHOW_IDENTIFIER_KEY]; |
|---|
| 175 | if(summary != nil) |
|---|
| 176 | [epDict setObject:summary forKey:META_DESCRIPTION_KEY]; |
|---|
| 177 | if(epNumber != nil) |
|---|
| 178 | [epDict setObject:[NSNumber numberWithInt:epNumber] forKey:META_ABSOLUTE_EP_NUMBER_KEY]; |
|---|
| 179 | if(airDate != nil) |
|---|
| 180 | [epDict setObject:airDate forKey:META_SHOW_AIR_DATE]; |
|---|
| 181 | if(imgURL != nil) |
|---|
| 182 | [epDict setObject:imgURL forKey:IMG_URL]; |
|---|
| 183 | } |
|---|
| 184 | |
|---|
| 185 | /*! |
|---|
| 186 | * @brief Fetch information about a season of a show |
|---|
| 187 | * |
|---|
| 188 | * @param seriesName The tvrage series name (part of the show's URL) |
|---|
| 189 | * @param season The season to fech |
|---|
| 190 | * @return A cached dictionary of the season's episodes |
|---|
| 191 | */ |
|---|
| 192 | - (NSMutableDictionary *)getMetaForSeries:(NSString *)seriesName inSeason:(int)season |
|---|
| 193 | { |
|---|
| 194 | NSMutableDictionary *ret = [NSMutableDictionary dictionary]; |
|---|
| 195 | NSCharacterSet *decimalSet = [NSCharacterSet decimalDigitCharacterSet]; |
|---|
| 196 | /*Get the season's html*/ |
|---|
| 197 | NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://www.tvrage.com%@/episode_guide/%d", seriesName, season]]; |
|---|
| 198 | NSError *error = nil; |
|---|
| 199 | NSXMLDocument *document = [[NSXMLDocument alloc] initWithContentsOfURL:url options:NSXMLDocumentTidyHTML error:&error]; |
|---|
| 200 | /* Dump XML document to disk (Dev Only) */ |
|---|
| 201 | /* NSString *documentPath =[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/Sapphire/XML"]; |
|---|
| 202 | [[document XMLDataWithOptions:NSXMLNodePrettyPrint] writeToFile:[NSString stringWithFormat:@"/%@%@.xml",documentPath,seriesName] atomically:YES] ;*/ |
|---|
| 203 | /*Get the episode list*/ |
|---|
| 204 | NSString *showName= nil; |
|---|
| 205 | |
|---|
| 206 | |
|---|
| 207 | NSXMLElement *html = [document rootElement]; |
|---|
| 208 | |
|---|
| 209 | NSArray *titleArray=[html objectsForXQuery:TVRAGE_SHOWNAME_XPATH error:&error]; |
|---|
| 210 | if([titleArray count]) |
|---|
| 211 | { |
|---|
| 212 | showName=[[titleArray objectAtIndex:0] stringValue]; |
|---|
| 213 | int length = [showName length]; |
|---|
| 214 | if([showName characterAtIndex:length - 1] == '\n') |
|---|
| 215 | showName = [showName substringToIndex:length - 1]; |
|---|
| 216 | /* int index = [showName rangeOfString:@"Season" options:0].location; |
|---|
| 217 | if(index != NSNotFound) |
|---|
| 218 | showName = [showName substringToIndex:index - 1];*/ |
|---|
| 219 | } |
|---|
| 220 | |
|---|
| 221 | NSArray *eps = [html objectsForXQuery:TVRAGE_EPLIST_XPATH error:&error]; |
|---|
| 222 | NSEnumerator *epEnum = [eps objectEnumerator]; |
|---|
| 223 | NSXMLNode *epNode = nil; |
|---|
| 224 | while((epNode = [epEnum nextObject]) != nil) |
|---|
| 225 | { |
|---|
| 226 | /*Parse the episode's info*/ |
|---|
| 227 | NSString *epTitle = nil; |
|---|
| 228 | NSString *link = nil; |
|---|
| 229 | int seasonNum = 0; |
|---|
| 230 | int ep = 0; |
|---|
| 231 | int epNumber = 0; |
|---|
| 232 | NSMutableString *summary = nil; |
|---|
| 233 | NSDate *airDate = nil; |
|---|
| 234 | NSString *imageURL = nil; |
|---|
| 235 | |
|---|
| 236 | /*Get the info pieces*/ |
|---|
| 237 | NSArray *epInfos = [epNode objectsForXQuery:TVRAGE_EP_INFO error:&error]; |
|---|
| 238 | NSEnumerator *epInfoEnum = [epInfos objectEnumerator]; |
|---|
| 239 | NSXMLNode *epInfo = nil; |
|---|
| 240 | while((epInfo = [epInfoEnum nextObject]) != nil) |
|---|
| 241 | { |
|---|
| 242 | NSString *nodeName = [epInfo name]; |
|---|
| 243 | NSArray *summaryObjects = [epInfo objectsForXQuery:@".//font" error:&error]; |
|---|
| 244 | if([summaryObjects count] && ![nodeName isEqualToString:@"font"]) |
|---|
| 245 | { |
|---|
| 246 | /*Sometimes, the summary is inside formatting, strip*/ |
|---|
| 247 | epInfo = [summaryObjects objectAtIndex:0]; |
|---|
| 248 | nodeName = [epInfo name]; |
|---|
| 249 | } |
|---|
| 250 | if(link == nil && [nodeName isEqualToString:@"a"]) |
|---|
| 251 | { |
|---|
| 252 | /*Get the URL*/ |
|---|
| 253 | link = [[(NSXMLElement *)epInfo attributeForName:@"href"] stringValue]; |
|---|
| 254 | link = [NSString stringWithFormat:@"http://www.tvrage.com%@", link]; |
|---|
| 255 | /*Parse the name*/ |
|---|
| 256 | NSString *epInfoStr = [[epInfo childAtIndex:0] stringValue]; |
|---|
| 257 | if(epInfoStr != nil) |
|---|
| 258 | { |
|---|
| 259 | /*Get the season number and ep numbers*/ |
|---|
| 260 | NSScanner *scanner = [NSScanner scannerWithString:epInfoStr]; |
|---|
| 261 | NSRange range = [epInfoStr rangeOfString:@" - " options:0]; |
|---|
| 262 | if(range.location != NSNotFound) |
|---|
| 263 | { |
|---|
| 264 | [scanner scanInt:&epNumber]; |
|---|
| 265 | [scanner scanUpToCharactersFromSet:decimalSet intoString:nil]; |
|---|
| 266 | [scanner scanInt:&seasonNum]; |
|---|
| 267 | [scanner scanUpToCharactersFromSet:decimalSet intoString:nil]; |
|---|
| 268 | [scanner scanInt:&ep]; |
|---|
| 269 | [scanner setScanLocation:range.length + range.location]; |
|---|
| 270 | if(seasonNum == 0) |
|---|
| 271 | seasonNum = season; |
|---|
| 272 | } |
|---|
| 273 | else |
|---|
| 274 | seasonNum = season; |
|---|
| 275 | epTitle = [epInfoStr substringFromIndex:[scanner scanLocation]]; |
|---|
| 276 | } |
|---|
| 277 | } |
|---|
| 278 | else if(summary == nil && [nodeName isEqualToString:@"font"]) |
|---|
| 279 | { |
|---|
| 280 | /*Get the summary*/ |
|---|
| 281 | NSArray *summaries = [epInfo objectsForXQuery:@"replace(string(), '\n\n', '\n')" error:&error]; |
|---|
| 282 | summary = [NSMutableString string]; |
|---|
| 283 | NSEnumerator *sumEnum = [summaries objectEnumerator]; |
|---|
| 284 | NSXMLNode *sum = nil; |
|---|
| 285 | while((sum = [sumEnum nextObject]) != nil) |
|---|
| 286 | [summary appendFormat:@"\n%@", sum]; |
|---|
| 287 | if([summary length] > 3 && [[summary substringFromIndex:3] isEqualToString:@"No Summary (Add Here)"]) |
|---|
| 288 | summary = nil; |
|---|
| 289 | if([summary length]) |
|---|
| 290 | [summary deleteCharactersInRange:NSMakeRange(0,1)]; |
|---|
| 291 | else |
|---|
| 292 | summary = nil; |
|---|
| 293 | } |
|---|
| 294 | else if(imageURL == nil) |
|---|
| 295 | { |
|---|
| 296 | NSArray *images = [epInfo objectsForXQuery:TVRAGE_SCREEN_CAP_XPATH error:&error]; |
|---|
| 297 | if([images count]) |
|---|
| 298 | { |
|---|
| 299 | imageURL = [[(NSXMLElement *)[images objectAtIndex:0] attributeForName:@"src"] stringValue]; |
|---|
| 300 | } |
|---|
| 301 | } |
|---|
| 302 | } |
|---|
| 303 | epInfos = [epNode objectsForXQuery:TVRAGE_EP_TEXT error:&error]; |
|---|
| 304 | epInfoEnum = [epInfos objectEnumerator]; |
|---|
| 305 | epInfo = nil; |
|---|
| 306 | while((epInfo = [epInfoEnum nextObject]) != nil) |
|---|
| 307 | { |
|---|
| 308 | /*Get the air date*/ |
|---|
| 309 | NSString *nodeName = [epInfo stringValue]; |
|---|
| 310 | if ([nodeName hasPrefix:@" ("] && [nodeName hasSuffix:@") "]) |
|---|
| 311 | { |
|---|
| 312 | NSString *subStr = [nodeName substringWithRange:NSMakeRange(2, [nodeName length] - 4)]; |
|---|
| 313 | |
|---|
| 314 | airDate = [NSDate dateWithNaturalLanguageString:subStr]; |
|---|
| 315 | if([airDate timeIntervalSince1970] == 0) |
|---|
| 316 | airDate = nil; |
|---|
| 317 | else |
|---|
| 318 | break; |
|---|
| 319 | } |
|---|
| 320 | } |
|---|
| 321 | /*Add to cache*/ |
|---|
| 322 | [self addEp:showName title:epTitle season:seasonNum epNum:ep summary:summary link:link absEpNum:epNumber airDate:airDate showID:seriesName imgURL:imageURL toDict:ret]; |
|---|
| 323 | } |
|---|
| 324 | return ret; |
|---|
| 325 | } |
|---|
| 326 | |
|---|
| 327 | /*! |
|---|
| 328 | * @brief Searches for a show based on the filename |
|---|
| 329 | * |
|---|
| 330 | * @param searchStr Part of the filename to use in the show search |
|---|
| 331 | * @return An array of possible results |
|---|
| 332 | */ |
|---|
| 333 | - (NSArray *)searchResultsForSeries:(NSString *)searchStr |
|---|
| 334 | { |
|---|
| 335 | /*Load the search info*/ |
|---|
| 336 | NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://www.tvrage.com/search.php?search=%@&sonly=1", [searchStr URLEncode]]]; |
|---|
| 337 | NSError *error = nil; |
|---|
| 338 | NSXMLDocument *document = [[NSXMLDocument alloc] initWithContentsOfURL:url options:NSXMLDocumentTidyHTML error:&error]; |
|---|
| 339 | |
|---|
| 340 | /*Get the results list*/ |
|---|
| 341 | NSXMLElement *root = [document rootElement]; |
|---|
| 342 | NSArray *results = [root objectsForXQuery:TVRAGE_SEARCH_XPATH error:&error]; |
|---|
| 343 | NSMutableArray *ret = [NSMutableArray arrayWithCapacity:[results count]]; |
|---|
| 344 | if([results count]) |
|---|
| 345 | { |
|---|
| 346 | /*Get each result*/ |
|---|
| 347 | NSEnumerator *resultEnum = [results objectEnumerator]; |
|---|
| 348 | NSXMLElement *result = nil; |
|---|
| 349 | while((result = [resultEnum nextObject]) != nil) |
|---|
| 350 | { |
|---|
| 351 | /*Add the result to the list*/ |
|---|
| 352 | NSURL *resultURL = [NSURL URLWithString:[[result attributeForName:@"href"] stringValue]]; |
|---|
| 353 | if(resultURL == nil) |
|---|
| 354 | continue; |
|---|
| 355 | [ret addObject:[NSDictionary dictionaryWithObjectsAndKeys: |
|---|
| 356 | [[result childAtIndex:0] stringValue], @"name", |
|---|
| 357 | [resultURL path], LINK_KEY, |
|---|
| 358 | nil]]; |
|---|
| 359 | } |
|---|
| 360 | return ret; |
|---|
| 361 | } |
|---|
| 362 | /*No results found*/ |
|---|
| 363 | return nil; |
|---|
| 364 | } |
|---|
| 365 | |
|---|
| 366 | /*! |
|---|
| 367 | * @brief Fetch cached information about a show's season, and if none, fetch it |
|---|
| 368 | * |
|---|
| 369 | * @param show The tvrage series name |
|---|
| 370 | * @param season The season to fetch |
|---|
| 371 | * @return A dictionary with info about the season |
|---|
| 372 | */ |
|---|
| 373 | - (NSMutableDictionary *)getInfo:(NSString *)show forSeason:(int)season |
|---|
| 374 | { |
|---|
| 375 | /*Get the show's info*/ |
|---|
| 376 | NSMutableDictionary *showDict = [showInfo objectForKey:show]; |
|---|
| 377 | NSMutableDictionary *seasonDict = nil; |
|---|
| 378 | NSNumber *seasonNum = [NSNumber numberWithInt:season]; |
|---|
| 379 | if(!showDict) |
|---|
| 380 | { |
|---|
| 381 | showDict = [NSMutableDictionary new]; |
|---|
| 382 | [showInfo setObject:showDict forKey:show]; |
|---|
| 383 | [showDict release]; |
|---|
| 384 | } |
|---|
| 385 | else |
|---|
| 386 | /*Get the season's info*/ |
|---|
| 387 | seasonDict = [showDict objectForKey:seasonNum]; |
|---|
| 388 | if(!seasonDict) |
|---|
| 389 | { |
|---|
| 390 | /*Not in cache, so fetch it*/ |
|---|
| 391 | seasonDict = [self getMetaForSeries:show inSeason:season]; |
|---|
| 392 | if(seasonDict != nil) |
|---|
| 393 | /*Put the result in cache*/ |
|---|
| 394 | [showDict setObject:seasonDict forKey:seasonNum]; |
|---|
| 395 | } |
|---|
| 396 | /*Return the info*/ |
|---|
| 397 | return seasonDict; |
|---|
| 398 | } |
|---|
| 399 | |
|---|
| 400 | /*! |
|---|
| 401 | * @brief Get info about a show's episode |
|---|
| 402 | * |
|---|
| 403 | * @param show The tvrage show name |
|---|
| 404 | * @param season The episode's season |
|---|
| 405 | * @param ep The episode's episode number |
|---|
| 406 | * @return The episode's info |
|---|
| 407 | */ |
|---|
| 408 | - (NSMutableDictionary *)getInfo:(NSString *)show forSeason:(int)season episode:(int)ep |
|---|
| 409 | { |
|---|
| 410 | NSNumber *epNum = [NSNumber numberWithInt:ep]; |
|---|
| 411 | return [NSMutableDictionary dictionaryWithDictionary:[[self getInfo:show forSeason:season] objectForKey:epNum]]; |
|---|
| 412 | } |
|---|
| 413 | |
|---|
| 414 | /*! |
|---|
| 415 | * @brief Get info about a show's episode which doesn't have an episode number (specials) |
|---|
| 416 | * |
|---|
| 417 | * @param show The tvrage show name |
|---|
| 418 | * @param season The episode's season |
|---|
| 419 | * @param epTitle The episode's title |
|---|
| 420 | * @return The episode's info |
|---|
| 421 | */ |
|---|
| 422 | - (NSMutableDictionary *)getInfo:(NSString *)show forSeason:(int)season episodeTitle:(NSString *)epTitle |
|---|
| 423 | { |
|---|
| 424 | return [NSMutableDictionary dictionaryWithDictionary:[[self getInfo:show forSeason:season] objectForKey:[epTitle lowercaseString]]]; |
|---|
| 425 | } |
|---|
| 426 | |
|---|
| 427 | /*! |
|---|
| 428 | * @brief Write our setings out |
|---|
| 429 | */ |
|---|
| 430 | - (void)writeSettings |
|---|
| 431 | { |
|---|
| 432 | NSError *error = nil; |
|---|
| 433 | [moc save:&error]; |
|---|
| 434 | } |
|---|
| 435 | |
|---|
| 436 | - (void)combine:(NSMutableDictionary *)info with:(NSMutableDictionary *)info2 |
|---|
| 437 | { |
|---|
| 438 | NSMutableDictionary *tdict = [info2 mutableCopy]; |
|---|
| 439 | [tdict addEntriesFromDictionary:info]; |
|---|
| 440 | NSString *secname = [info2 objectForKey:META_TITLE_KEY]; |
|---|
| 441 | NSString *origname = [info objectForKey:META_TITLE_KEY]; |
|---|
| 442 | if(secname != nil && origname != nil) |
|---|
| 443 | [tdict setObject:[NSString stringWithFormat:@"%@ / %@", origname, secname] forKey:META_TITLE_KEY]; |
|---|
| 444 | |
|---|
| 445 | if([info objectForKey:META_EPISODE_NUMBER_KEY] != nil) |
|---|
| 446 | { |
|---|
| 447 | NSNumber *secondEp = [info2 objectForKey:META_EPISODE_NUMBER_KEY]; |
|---|
| 448 | [tdict setObject:secondEp forKey:META_EPISODE_2_NUMBER_KEY]; |
|---|
| 449 | } |
|---|
| 450 | NSString *secdesc = [info2 objectForKey:META_DESCRIPTION_KEY]; |
|---|
| 451 | NSString *origdesc = [info objectForKey:META_DESCRIPTION_KEY]; |
|---|
| 452 | if(secdesc != nil && origdesc != nil) |
|---|
| 453 | [tdict setObject:[NSString stringWithFormat:@"%@ / %@", origdesc, secdesc] forKey:META_DESCRIPTION_KEY]; |
|---|
| 454 | |
|---|
| 455 | if([info objectForKey:META_ABSOLUTE_EP_NUMBER_KEY] != nil) |
|---|
| 456 | { |
|---|
| 457 | NSNumber *secondEp = [info2 objectForKey:META_ABSOLUTE_EP_NUMBER_KEY]; |
|---|
| 458 | [tdict setObject:secondEp forKey:META_ABSOLUTE_EP_2_NUMBER_KEY]; |
|---|
| 459 | } |
|---|
| 460 | |
|---|
| 461 | [info addEntriesFromDictionary:tdict]; |
|---|
| 462 | [tdict release]; |
|---|
| 463 | } |
|---|
| 464 | |
|---|
| 465 | - (ImportState) importMetaData:(SapphireFileMetaData *)metaData |
|---|
| 466 | { |
|---|
| 467 | currentData = metaData; |
|---|
| 468 | /*Check to see if it is already imported*/ |
|---|
| 469 | if([metaData importTypeValue] & IMPORT_TYPE_TVSHOW_MASK) |
|---|
| 470 | return IMPORT_STATE_NOT_UPDATED; |
|---|
| 471 | id controller = [[dataMenu stack] peekController]; |
|---|
| 472 | /* Check to see if we are waiting on the user to select a movie title */ |
|---|
| 473 | if(controller != nil && ![controller isKindOfClass:[SapphireImporterDataMenu class]]) |
|---|
| 474 | { |
|---|
| 475 | /* Another chooser is on the screen - delay further processing */ |
|---|
| 476 | return IMPORT_STATE_NOT_UPDATED; |
|---|
| 477 | } |
|---|
| 478 | /*Get path*/ |
|---|
| 479 | NSString *path = [metaData path]; |
|---|
| 480 | // NSArray *pathComponents = [path pathComponents]; |
|---|
| 481 | NSString *fileName = [path lastPathComponent]; |
|---|
| 482 | |
|---|
| 483 | /*Check regexes to see if this is a tv show*/ |
|---|
| 484 | int index = NSNotFound; |
|---|
| 485 | int secondEp = -1; |
|---|
| 486 | regmatch_t matches[3]; |
|---|
| 487 | NSData *fileNameData = [fileName dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES]; |
|---|
| 488 | const char *theFileName = [fileNameData bytes]; |
|---|
| 489 | NSString *scanString = nil; |
|---|
| 490 | if(!regexec(&letterMarking, theFileName, 3, matches, 0)) |
|---|
| 491 | { |
|---|
| 492 | index = matches[0].rm_so; |
|---|
| 493 | scanString = [fileName substringFromIndex:index]; |
|---|
| 494 | secondEp = matches[1].rm_so; |
|---|
| 495 | } |
|---|
| 496 | else if(!regexec(&seasonByEpisode, theFileName, 3, matches, 0)) |
|---|
| 497 | { |
|---|
| 498 | index = matches[0].rm_so; |
|---|
| 499 | scanString = [fileName substringFromIndex:index]; |
|---|
| 500 | secondEp = matches[1].rm_so; |
|---|
| 501 | } |
|---|
| 502 | else if(!regexec(&seasonEpisodeTriple, theFileName, 3, matches, 0)) |
|---|
| 503 | { |
|---|
| 504 | index = matches[0].rm_so; |
|---|
| 505 | /*Insert an artificial season/ep seperator so things are easier later*/ |
|---|
| 506 | NSMutableString *tempStr = [fileName mutableCopy]; |
|---|
| 507 | // if(index > [tempStr length] || index <= 0 )return NO; |
|---|
| 508 | [tempStr deleteCharactersInRange:NSMakeRange(0, index+1)]; |
|---|
| 509 | [tempStr insertString:@"x" atIndex:matches[0].rm_eo - index - 4]; |
|---|
| 510 | scanString = [tempStr autorelease]; |
|---|
| 511 | } |
|---|
| 512 | |
|---|
| 513 | /*See if we found a match*/ |
|---|
| 514 | if(index == NSNotFound) |
|---|
| 515 | return IMPORT_STATE_NOT_UPDATED; |
|---|
| 516 | |
|---|
| 517 | /*Get the show title*/ |
|---|
| 518 | NSString *searchStr = [fileName substringToIndex:index]; |
|---|
| 519 | /*Check to see if we know this title*/ |
|---|
| 520 | SapphireTVTranslation *tran = [SapphireTVTranslation tvTranslationForName:[searchStr lowercaseString] inContext:moc]; |
|---|
| 521 | if([tran showPath] == nil) |
|---|
| 522 | { |
|---|
| 523 | if(dataMenu == nil) |
|---|
| 524 | /*There is no data menu, background import. So we can't ask user, skip*/ |
|---|
| 525 | return IMPORT_STATE_NOT_UPDATED; |
|---|
| 526 | /*Ask the user what show this is*/ |
|---|
| 527 | NSArray *shows = [self searchResultsForSeries:searchStr]; |
|---|
| 528 | /*Bring up the prompt*/ |
|---|
| 529 | SapphireShowChooser *chooser = [[SapphireShowChooser alloc] initWithScene:[dataMenu scene]]; |
|---|
| 530 | [chooser setShows:shows]; |
|---|
| 531 | [chooser setFileName:fileName]; |
|---|
| 532 | [chooser setListTitle:BRLocalizedString(@"Select Show Title", @"Prompt the user for showname with a file")]; |
|---|
| 533 | [chooser setSearchStr:searchStr]; |
|---|
| 534 | /*And display prompt*/ |
|---|
| 535 | [[dataMenu stack] pushController:chooser]; |
|---|
| 536 | [chooser release]; |
|---|
| 537 | return IMPORT_STATE_NEEDS_SUSPEND; |
|---|
| 538 | } |
|---|
| 539 | |
|---|
| 540 | int season = 0; |
|---|
| 541 | int ep = 0; |
|---|
| 542 | /*Get the season*/ |
|---|
| 543 | NSScanner *scanner = [NSScanner scannerWithString:scanString]; |
|---|
| 544 | NSCharacterSet *digits = [NSCharacterSet decimalDigitCharacterSet]; |
|---|
| 545 | [scanner scanUpToCharactersFromSet:digits intoString:nil]; |
|---|
| 546 | [scanner scanInt:&season]; |
|---|
| 547 | /*Get the episode number*/ |
|---|
| 548 | NSString *skipped = nil; |
|---|
| 549 | [scanner scanUpToCharactersFromSet:digits intoString:&skipped]; |
|---|
| 550 | [scanner scanInt:&ep]; |
|---|
| 551 | /*Was there an S before the episode number?*/ |
|---|
| 552 | if([skipped hasSuffix:@"S"]) |
|---|
| 553 | ep = 0; |
|---|
| 554 | |
|---|
| 555 | int overriddenSeason = [metaData searchSeasonNumber]; |
|---|
| 556 | if(overriddenSeason != -1) |
|---|
| 557 | season = overriddenSeason; |
|---|
| 558 | |
|---|
| 559 | int overriddenEpisode = [metaData searchEpisodeNumber]; |
|---|
| 560 | if(overriddenEpisode != -1) |
|---|
| 561 | ep = overriddenEpisode; |
|---|
| 562 | |
|---|
| 563 | /*No season, no info*/ |
|---|
| 564 | if(season == 0) |
|---|
| 565 | return IMPORT_STATE_NOT_UPDATED; |
|---|
| 566 | |
|---|
| 567 | int otherEp = 0; |
|---|
| 568 | if(secondEp != -1) |
|---|
| 569 | { |
|---|
| 570 | [scanner setScanLocation:secondEp - index]; |
|---|
| 571 | [scanner scanUpToCharactersFromSet:digits intoString:nil]; |
|---|
| 572 | [scanner scanInt:&otherEp]; |
|---|
| 573 | } |
|---|
| 574 | |
|---|
| 575 | overriddenEpisode = [metaData searchLastEpisodeNumber]; |
|---|
| 576 | if(overriddenEpisode != -1) |
|---|
| 577 | otherEp = overriddenEpisode; |
|---|
| 578 | |
|---|
| 579 | /*Get the episode's info*/ |
|---|
| 580 | NSMutableDictionary *info = nil, *info2 = nil; |
|---|
| 581 | if(ep != 0) |
|---|
| 582 | { |
|---|
| 583 | /*Match on s/e*/ |
|---|
| 584 | info = [self getInfo:[tran showPath] forSeason:season episode:ep]; |
|---|
| 585 | if(otherEp != 0) |
|---|
| 586 | info2 = [self getInfo:[tran showPath] forSeason:season episode:otherEp]; |
|---|
| 587 | } |
|---|
| 588 | else |
|---|
| 589 | { |
|---|
| 590 | /*Match on show title*/ |
|---|
| 591 | NSString *showTitle = nil; |
|---|
| 592 | [scanner scanUpToCharactersFromSet:[NSCharacterSet letterCharacterSet] intoString:nil]; |
|---|
| 593 | if([scanner scanUpToString:@"." intoString:&showTitle]) |
|---|
| 594 | info = [self getInfo:[tran showPath] forSeason:season episodeTitle:showTitle]; |
|---|
| 595 | } |
|---|
| 596 | /*No info, well, no info*/ |
|---|
| 597 | if(!info) |
|---|
| 598 | return IMPORT_STATE_NOT_UPDATED; |
|---|
| 599 | |
|---|
| 600 | /* Lets process the cover art directory structure */ |
|---|
| 601 | NSString * previewArtPath=[NSString stringWithFormat:@"%@/@TV/%@/%@", |
|---|
| 602 | [SapphireMetaDataSupport collectionArtPath], |
|---|
| 603 | [info objectForKey:META_SHOW_NAME_KEY], |
|---|
| 604 | [NSString stringWithFormat:@"Season %d",[[info objectForKey:META_SEASON_NUMBER_KEY] intValue]]]; |
|---|
| 605 | |
|---|
| 606 | [[NSFileManager defaultManager] constructPath:previewArtPath]; |
|---|
| 607 | /*Check for screen cap locally and on server*/ |
|---|
| 608 | NSString *imgURL = [info objectForKey:IMG_URL]; |
|---|
| 609 | NSString *newPath = nil; |
|---|
| 610 | if(ep != 0) |
|---|
| 611 | newPath = [previewArtPath stringByAppendingFormat:@"/Episode %d", ep]; |
|---|
| 612 | else |
|---|
| 613 | newPath = [previewArtPath stringByAppendingPathComponent:[info objectForKey:META_TITLE_KEY]]; |
|---|
| 614 | NSString *imageDestination = [newPath stringByAppendingPathExtension:@"jpg"]; |
|---|
| 615 | BOOL isDir = NO; |
|---|
| 616 | BOOL imageExists = [[NSFileManager defaultManager] fileExistsAtPath:imageDestination isDirectory:&isDir] && !isDir; |
|---|
| 617 | if(imgURL && !imageExists) |
|---|
| 618 | { |
|---|
| 619 | /*Download the screen cap*/ |
|---|
| 620 | NSURL *imageURL = [NSURL URLWithString:imgURL]; |
|---|
| 621 | NSURLRequest *request = [NSURLRequest requestWithURL:imageURL]; |
|---|
| 622 | SapphireTVShowDataMenuDownloadDelegate *myDelegate = [[SapphireTVShowDataMenuDownloadDelegate alloc] initWithDest:imageDestination]; |
|---|
| 623 | [[NSURLDownload alloc] initWithRequest:request delegate:myDelegate]; |
|---|
| 624 | [myDelegate release]; |
|---|
| 625 | } |
|---|
| 626 | |
|---|
| 627 | if(info2 != nil) |
|---|
| 628 | [self combine:info with:info2]; |
|---|
| 629 | /*Import the info*/ |
|---|
| 630 | [info removeObjectForKey:LINK_KEY]; |
|---|
| 631 | SapphireEpisode *episode = [SapphireEpisode episodeWithDictionary:info inContext:moc]; |
|---|
| 632 | [metaData setTvEpisode:episode]; |
|---|
| 633 | [tran setTvShow:[episode tvShow]]; |
|---|
| 634 | |
|---|
| 635 | /*We imported something*/ |
|---|
| 636 | return IMPORT_STATE_UPDATED; |
|---|
| 637 | } |
|---|
| 638 | |
|---|
| 639 | - (NSString *)completionText |
|---|
| 640 | { |
|---|
| 641 | return BRLocalizedString(@"All availble TV Show data has been imported", @"The TV Show import complete"); |
|---|
| 642 | } |
|---|
| 643 | |
|---|
| 644 | - (NSString *)initialText |
|---|
| 645 | { |
|---|
| 646 | return BRLocalizedString(@"Fetch TV Show Data", @"Title"); |
|---|
| 647 | } |
|---|
| 648 | |
|---|
| 649 | - (NSString *)informativeText |
|---|
| 650 | { |
|---|
| 651 | 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"); |
|---|
| 652 | } |
|---|
| 653 | |
|---|
| 654 | - (NSString *)buttonTitle |
|---|
| 655 | { |
|---|
| 656 | return BRLocalizedString(@"Start Fetching Data", @"Button"); |
|---|
| 657 | } |
|---|
| 658 | |
|---|
| 659 | - (void) wasExhumedByPoppingController: (BRLayerController *) controller |
|---|
| 660 | { |
|---|
| 661 | /*See if it was a show chooser*/ |
|---|
| 662 | if(![controller isKindOfClass:[SapphireShowChooser class]]) |
|---|
| 663 | return; |
|---|
| 664 | |
|---|
| 665 | /*Get the user's selection*/ |
|---|
| 666 | SapphireShowChooser *chooser = (SapphireShowChooser *)controller; |
|---|
| 667 | int selection = [chooser selection]; |
|---|
| 668 | if(selection == SHOW_CHOOSE_CANCEL) |
|---|
| 669 | /*They aborted, skip*/ |
|---|
| 670 | [dataMenu skipNextItem]; |
|---|
| 671 | else if(selection == SHOW_CHOOSE_NOT_SHOW) |
|---|
| 672 | { |
|---|
| 673 | /*They said it is not a show, so put in empty data so they are not asked again*/ |
|---|
| 674 | [currentData importInfo:[NSMutableDictionary dictionary] fromSource:IMPORT_TYPE_TVSHOW_MASK withTime:[[NSDate date] timeIntervalSince1970]]; |
|---|
| 675 | if ([currentData fileClassValue] == FILE_CLASS_TV_SHOW) |
|---|
| 676 | [currentData setFileClassValue:FILE_CLASS_UNKNOWN]; |
|---|
| 677 | } |
|---|
| 678 | else |
|---|
| 679 | { |
|---|
| 680 | /*They selected a show, save the translation and write it*/ |
|---|
| 681 | NSDictionary *show = [[chooser shows] objectAtIndex:selection]; |
|---|
| 682 | [SapphireTVTranslation createTVTranslationForName:[[chooser searchStr] lowercaseString] withPath:[show objectForKey:LINK_KEY] inContext:moc]; |
|---|
| 683 | [self writeSettings]; |
|---|
| 684 | } |
|---|
| 685 | /*We can resume now*/ |
|---|
| 686 | [dataMenu resume]; |
|---|
| 687 | } |
|---|
| 688 | |
|---|
| 689 | @end |
|---|