This PR contains all the work related to setting up this project as required to implement the [Assignment](https://repo.rock-n-code.com/rock-n-code/deep-linking-assignment/wiki/Assignment) on top, as intended. To summarise this work: - [x] created a new **Xcode** project; - [x] cloned the `Wikipedia` app and inserted it into the **Xcode** project; - [x] created the `Locations` app and also, its `Libraries` package; - [x] created the `Shared` package to share dependencies between the apps; - [x] added a `Makefile` file and implemented some **environment** and **help** commands. Co-authored-by: Javier Cicchelli <javier@rock-n-code.com> Reviewed-on: rock-n-code/deep-linking-assignment#1
163 lines
6.2 KiB
Swift
163 lines
6.2 KiB
Swift
import CocoaLumberjackSwift
|
|
|
|
extension WMFArticle {
|
|
func merge(_ article: WMFArticle) {
|
|
guard article.objectID != objectID else {
|
|
return
|
|
}
|
|
// merge important keys not set by the summary
|
|
let keysToMerge = [#keyPath(WMFArticle.savedDate), #keyPath(WMFArticle.placesSortOrder), #keyPath(WMFArticle.pageViews)]
|
|
for key in keysToMerge {
|
|
guard let valueToMerge = article.value(forKey: key) else {
|
|
continue
|
|
}
|
|
// keep the later date when both have date values
|
|
if let dateValueToMerge = valueToMerge as? Date, let dateValue = value(forKey: key) as? Date, dateValue > dateValueToMerge {
|
|
continue
|
|
}
|
|
// prefer existing values
|
|
if value(forKey: key) != nil {
|
|
continue
|
|
}
|
|
setValue(valueToMerge, forKey: key)
|
|
}
|
|
|
|
if let articleReadingLists = article.readingLists {
|
|
addReadingLists(articleReadingLists)
|
|
}
|
|
|
|
if let articlePreviewReadingLists = article.previewReadingLists {
|
|
addPreviewReadingLists(articlePreviewReadingLists)
|
|
}
|
|
|
|
if article.isExcludedFromFeed {
|
|
isExcludedFromFeed = true
|
|
}
|
|
|
|
let mergeViewedProperties: Bool
|
|
if let viewedDateToMerge = article.viewedDate {
|
|
if let existingViewedDate = viewedDate, existingViewedDate > viewedDateToMerge {
|
|
mergeViewedProperties = false
|
|
} else {
|
|
mergeViewedProperties = true
|
|
}
|
|
} else {
|
|
mergeViewedProperties = false
|
|
}
|
|
|
|
if mergeViewedProperties {
|
|
viewedDate = article.viewedDate
|
|
viewedFragment = article.viewedFragment
|
|
viewedScrollPosition = article.viewedScrollPosition
|
|
wasSignificantlyViewed = article.wasSignificantlyViewed
|
|
}
|
|
}
|
|
|
|
@objc public func update(withSummary summary: ArticleSummary) {
|
|
if let original = summary.original {
|
|
imageURLString = original.source
|
|
imageWidth = NSNumber(value: original.width)
|
|
imageHeight = NSNumber(value: original.height)
|
|
} else {
|
|
imageURLString = nil
|
|
imageWidth = NSNumber(value: 0)
|
|
imageHeight = NSNumber(value: 0)
|
|
}
|
|
|
|
if let thumbnail = summary.thumbnail {
|
|
thumbnailURLString = thumbnail.source
|
|
thumbnailURL = thumbnail.url
|
|
}
|
|
|
|
wikidataDescription = summary.articleDescription
|
|
wikidataID = summary.wikidataID
|
|
displayTitleHTML = summary.displayTitle ?? summary.title ?? ""
|
|
snippet = summary.extract?.wmf_summaryFromText()
|
|
|
|
if let summaryCoordinate = summary.coordinates {
|
|
coordinate = CLLocationCoordinate2D(latitude: summaryCoordinate.lat, longitude: summaryCoordinate.lon)
|
|
} else {
|
|
coordinate = nil
|
|
}
|
|
if let id = summary.id {
|
|
pageID = NSNumber(value: id)
|
|
} else {
|
|
pageID = nil
|
|
}
|
|
if let timestamp = summary.timestamp {
|
|
lastModifiedDate = DateFormatter.wmf_iso8601()?.date(from: timestamp)
|
|
} else {
|
|
lastModifiedDate = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
extension NSManagedObjectContext {
|
|
@objc public func wmf_createOrUpdateArticleSummmaries(withSummaryResponses summaryResponses: [WMFInMemoryURLKey: ArticleSummary]) throws -> [WMFInMemoryURLKey: WMFArticle] {
|
|
guard !summaryResponses.isEmpty else {
|
|
return [:]
|
|
}
|
|
var keys: [WMFInMemoryURLKey] = []
|
|
var reverseRedirectedKeys: [WMFInMemoryURLKey: WMFInMemoryURLKey] = [:]
|
|
keys.reserveCapacity(summaryResponses.count)
|
|
for (key, summary) in summaryResponses {
|
|
guard
|
|
let summaryKey = summary.key,
|
|
key != summaryKey // find the mismatched keys
|
|
else {
|
|
keys.append(key)
|
|
continue
|
|
}
|
|
reverseRedirectedKeys[summaryKey] = key
|
|
keys.append(summaryKey)
|
|
do {
|
|
let articlesWithKey = try fetchArticles(with: key.url)
|
|
let articlesWithSummaryKey = try fetchArticles(with: summaryKey.url)
|
|
guard let canonicalArticle = articlesWithSummaryKey.first ?? articlesWithKey.first else {
|
|
continue
|
|
}
|
|
for article in articlesWithKey {
|
|
canonicalArticle.merge(article)
|
|
delete(article)
|
|
}
|
|
for article in articlesWithSummaryKey {
|
|
canonicalArticle.merge(article)
|
|
delete(article)
|
|
}
|
|
canonicalArticle.key = summaryKey.databaseKey
|
|
canonicalArticle.variant = summaryKey.languageVariantCode
|
|
} catch let error {
|
|
DDLogError("Error fetching articles for merge: \(error)")
|
|
}
|
|
}
|
|
var keysToCreate = Set(keys)
|
|
var articles: [WMFInMemoryURLKey: WMFArticle] = [:]
|
|
articles.reserveCapacity(keys.count)
|
|
let fetchedArticles = try self.fetchArticlesWithInMemoryURLKeys(keys)
|
|
for articleToUpdate in fetchedArticles {
|
|
guard let articleKey = articleToUpdate.inMemoryKey else {
|
|
continue
|
|
}
|
|
let requestedKey = reverseRedirectedKeys[articleKey] ?? articleKey
|
|
guard let result = summaryResponses[requestedKey] else {
|
|
articles[requestedKey] = articleToUpdate
|
|
continue
|
|
}
|
|
articleToUpdate.update(withSummary: result)
|
|
articles[requestedKey] = articleToUpdate
|
|
keysToCreate.remove(articleKey)
|
|
}
|
|
for key in keysToCreate {
|
|
let requestedKey = reverseRedirectedKeys[key] ?? key
|
|
guard let result = summaryResponses[requestedKey], // responses are by requested key
|
|
let article = self.createArticle(with: key.url) else { // article should have redirected key
|
|
continue
|
|
}
|
|
article.update(withSummary: result)
|
|
articles[requestedKey] = article
|
|
}
|
|
try self.save()
|
|
return articles
|
|
}
|
|
}
|