#import #import #import @interface NSManagedObjectContext (WMFArticle_Private) - (NSUInteger)countOfSavedArticleVariantsWithKey:(nullable NSString *)key error:(NSError **)error; - (nullable WMFArticle *)savedArticleVariantWithKey:(nullable NSString *)key error:(NSError **)error; @end @implementation WMFArticle (Extensions) - (NSString *)capitalizedWikidataDescription { return [self.wikidataDescription wmf_stringByCapitalizingFirstCharacterUsingWikipediaLanguageCode:self.URL.wmf_languageCode]; } - (nullable NSURL *)URL { NSString *key = self.key; if (!key) { return nil; } NSURL *value = [NSURL URLWithString:key]; value.wmf_languageVariantCode = self.variant; return value; } - (nullable WMFInMemoryURLKey *)inMemoryKey { return self.URL.wmf_inMemoryKey; } - (BOOL)isAnyVariantSaved { NSUInteger savedCount = [self.managedObjectContext countOfSavedArticleVariantsWithKey:self.key error:nil]; NSAssert(savedCount < 2, @"More than one article variant marked as saved for key '%@'", self.key); return savedCount > 0; } - (nullable WMFArticle *)savedVariant { // If the article has a savedDate, it is the saved variant if (self.savedDate != nil) { return self; // If the article does not have a variant, no further checking is needed. Return nil. } else if (!self.variant) { return nil; } else { return [self.managedObjectContext savedArticleVariantWithKey:self.key error:nil]; } } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" // this is the only section of code where the "deprecated" but not really deprecated displayTitleHTMLString should be used - (void)setDisplayTitleHTML:(NSString *)displayTitleHTML { self.displayTitleHTMLString = displayTitleHTML; self.displayTitle = displayTitleHTML.wmf_stringByRemovingHTML; } - (NSString *)displayTitleHTML { return self.displayTitleHTMLString ?: self.displayTitle ?: self.URL.wmf_title ?: @""; } #pragma clang diagnostic pop - (nullable NSURL *)thumbnailURL { NSString *thumbnailURLString = self.thumbnailURLString; if (!thumbnailURLString) { return [self imageURLForWidth:240]; //hardcoded to not rely on UIScreen in a model object } return [NSURL URLWithString:thumbnailURLString]; } + (nullable NSURL *)imageURLForTargetImageWidth:(NSInteger)width fromImageSource:(NSString *)imageSource withOriginalWidth:(NSInteger)originalWidth { NSAssert(width > 0, @"Width must be > 0"); if (width <= 0) { return nil; } NSString *lowercasePathExtension = [[imageSource pathExtension] lowercaseString]; if (width >= originalWidth && ![lowercasePathExtension isEqualToString:@"svg"] && ![lowercasePathExtension isEqualToString:@"pdf"]) { return [NSURL URLWithString:imageSource]; } return [NSURL URLWithString:WMFChangeImageSourceURLSizePrefix(imageSource, width)]; } - (nullable NSURL *)imageURLForWidth:(NSInteger)width { NSAssert(width > 0, @"Width must be > 0"); if (width <= 0) { return nil; } NSString *imageURLString = self.imageURLString; NSNumber *imageWidth = self.imageWidth; if (!imageURLString || !imageWidth) { NSString *thumbnailURLString = self.thumbnailURLString; if (!thumbnailURLString) { return nil; } NSInteger sizePrefix = WMFParseSizePrefixFromSourceURL(thumbnailURLString); if (sizePrefix == NSNotFound || width >= sizePrefix) { return [NSURL URLWithString:thumbnailURLString]; } return [NSURL URLWithString:WMFChangeImageSourceURLSizePrefix(thumbnailURLString, width)]; } return [WMFArticle imageURLForTargetImageWidth:width fromImageSource:imageURLString withOriginalWidth:[imageWidth integerValue]]; } - (void)setThumbnailURL:(NSURL *)thumbnailURL { self.thumbnailURLString = thumbnailURL.absoluteString; } - (NSArray *)pageViewsSortedByDate { return self.pageViews.wmf_pageViewsSortedByDate; } - (void)updateViewedDateWithoutTime { NSDate *viewedDate = self.viewedDate; if (viewedDate) { NSCalendar *calendar = [NSCalendar wmf_gregorianCalendar]; NSDateComponents *components = [calendar components:(NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay) fromDate:viewedDate]; self.viewedDateWithoutTime = [calendar dateFromComponents:components]; } else { self.viewedDateWithoutTime = nil; } } - (void)updateWithSearchResult:(nullable MWKSearchResult *)searchResult { if ([searchResult.displayTitleHTML length] > 0) { self.displayTitleHTML = searchResult.displayTitleHTML; } else if ([searchResult.displayTitle length] > 0) { self.displayTitleHTML = searchResult.displayTitle; } if ([searchResult.wikidataDescription length] > 0) { self.wikidataDescription = searchResult.wikidataDescription; } if ([searchResult.extract length] > 0) { self.snippet = searchResult.extract; } if (searchResult.thumbnailURL != nil) { self.thumbnailURL = searchResult.thumbnailURL; } if (searchResult.location != nil) { self.location = searchResult.location; } if (searchResult.geoDimension != nil) { self.geoDimensionNumber = searchResult.geoDimension; } if (searchResult.geoType != nil) { self.geoTypeNumber = searchResult.geoType; } } @end #pragma mark - NSManagedObjectContext Extensions @implementation NSManagedObjectContext (WMFArticle) - (nullable WMFArticle *)fetchArticleWithURL:(nullable NSURL *)articleURL { return [self fetchArticleWithKey:articleURL.wmf_databaseKey variant:articleURL.wmf_languageVariantCode]; } - (nullable NSArray *)fetchArticlesWithURL:(nullable NSURL *)url error:(NSError **)error { return [self fetchArticlesWithKey:url.wmf_databaseKey variant:url.wmf_languageVariantCode error:error]; } - (nullable NSArray *)fetchArticlesWithKey:(nullable NSString *)key variant:(nullable NSString *)variant error:(NSError **)error { if (!key) { return @[]; } NSFetchRequest *request = [WMFArticle fetchRequest]; request.predicate = [NSPredicate predicateWithFormat:@"key == %@ && variant == %@", key, variant]; return [self executeFetchRequest:request error:error]; } - (nullable NSArray *)fetchArticlesWithInMemoryURLKeys:(NSArray *)urlKeys error:(NSError **)error { if (urlKeys.count == 0) { return @[]; } NSFetchRequest *request = [WMFArticle fetchRequest]; request.predicate = [self articlePredicateForInMemoryURLKeys:urlKeys]; return [self executeFetchRequest:request error:error]; } - (NSPredicate *)articlePredicateForInMemoryURLKeys:(NSArray *)urlKeys { NSMutableArray *subpredicates = [[NSMutableArray alloc] init]; for (WMFInMemoryURLKey *urlKey in urlKeys) { NSPredicate *predicate = [NSPredicate predicateWithFormat:@"key == %@ && variant == %@", urlKey.databaseKey, urlKey.languageVariantCode]; [subpredicates addObject:predicate]; } return [NSCompoundPredicate orPredicateWithSubpredicates:subpredicates]; } - (nullable WMFArticle *)fetchArticleWithKey:(nullable NSString *)key variant:(nullable NSString *)variant { if (!key) { return nil; } WMFArticle *article = nil; NSFetchRequest *request = [WMFArticle fetchRequest]; request.fetchLimit = 1; request.predicate = [NSPredicate predicateWithFormat:@"key == %@ && variant == %@", key, variant]; article = [[self executeFetchRequest:request error:nil] firstObject]; return article; } - (nullable WMFArticle *)fetchArticleWithWikidataID:(nullable NSString *)wikidataID { if (!wikidataID) { return nil; } WMFArticle *article = nil; NSFetchRequest *request = [WMFArticle fetchRequest]; request.fetchLimit = 1; request.predicate = [NSPredicate predicateWithFormat:@"wikidataID == %@", wikidataID]; article = [[self executeFetchRequest:request error:nil] firstObject]; return article; } - (nullable WMFArticle *)createArticleWithURL:(nullable NSURL *)url { return [self createArticleWithKey:url.wmf_databaseKey variant:url.wmf_languageVariantCode]; } - (nullable WMFArticle *)createArticleWithKey:(nullable NSString *)key variant:(nullable NSString *)variant { WMFArticle *article = [[WMFArticle alloc] initWithContext:self]; article.key = key; article.variant = variant; return article; } - (nullable WMFArticle *)fetchOrCreateArticleWithKey:(nullable NSString *)key variant:(nullable NSString *)variant { if (!key) { return nil; } WMFArticle *article = [self fetchArticleWithKey:key variant:variant]; if (!article) { article = [self createArticleWithKey:key variant:variant]; } return article; } - (nullable WMFArticle *)fetchOrCreateArticleWithURL:(nullable NSURL *)articleURL { return [self fetchOrCreateArticleWithKey:articleURL.wmf_databaseKey variant:articleURL.wmf_languageVariantCode]; } - (nullable WMFArticle *)fetchOrCreateArticleWithURL:(nullable NSURL *)articleURL updatedWithSearchResult:(nullable MWKSearchResult *)searchResult { NSParameterAssert(articleURL); WMFArticle *article = [self fetchOrCreateArticleWithURL:articleURL]; [article updateWithSearchResult:searchResult]; return article; } - (nullable WMFArticle *)fetchOrCreateArticleWithURL:(nullable NSURL *)articleURL updatedWithFeedPreview:(nullable WMFFeedArticlePreview *)feedPreview pageViews:(nullable NSDictionary *)pageViews isFeatured:(BOOL)isFeatured { NSParameterAssert(articleURL); if (!articleURL) { return nil; } WMFArticle *article = [self fetchOrCreateArticleWithURL:articleURL]; if (isFeatured) { WMFFeedArticlePreview *oldFeedPreview = [article feedArticlePreview]; if (![oldFeedPreview isEqual:feedPreview]) { [SharedContainerCacheClearFeaturedArticleWrapper clearOutFeaturedArticleWidgetCache]; } } if ([feedPreview.displayTitleHTML length] > 0) { article.displayTitleHTML = feedPreview.displayTitleHTML; } else if ([feedPreview.displayTitle length] > 0) { article.displayTitleHTML = feedPreview.displayTitle; } if ([feedPreview.wikidataDescription length] > 0) { article.wikidataDescription = feedPreview.wikidataDescription; } if ([feedPreview.snippet length] > 0) { article.snippet = feedPreview.snippet; } if (feedPreview.thumbnailURL != nil) { article.thumbnailURL = feedPreview.thumbnailURL; } if (pageViews != nil) { if (article.pageViews == nil) { article.pageViews = pageViews; } else { article.pageViews = [article.pageViews mtl_dictionaryByAddingEntriesFromDictionary:pageViews]; } } if (feedPreview.imageURLString != nil) { article.imageURLString = feedPreview.imageURLString; } if (feedPreview.imageWidth != nil) { article.imageWidth = feedPreview.imageWidth; } if (feedPreview.imageHeight != nil) { article.imageHeight = feedPreview.imageHeight; } return article; } @end @implementation NSManagedObjectContext (WMFArticle_Private) - (NSUInteger)countOfSavedArticleVariantsWithKey:(nullable NSString *)key error:(NSError **)error { if (!key) { return 0; } NSFetchRequest *request = [WMFArticle fetchRequest]; request.predicate = [NSPredicate predicateWithFormat:@"key == %@ && savedDate != NULL", key]; NSUInteger count = [self countForFetchRequest:request error:error]; return count == NSNotFound ? 0 : count; } - (nullable WMFArticle *)savedArticleVariantWithKey:(nullable NSString *)key error:(NSError **)error { if (!key) { return nil; } NSFetchRequest *request = [WMFArticle fetchRequest]; request.predicate = [NSPredicate predicateWithFormat:@"key == %@ && savedDate != NULL", key]; return (WMFArticle *)[[self executeFetchRequest:request error:error] firstObject]; } @end