Javier Cicchelli 9bcdaa697b [Setup] Basic project structure (#1)
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
2023-04-08 18:37:13 +00:00

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
}
}