2017-07-04 00:04:31 +02:00
|
|
|
|
//
|
|
|
|
|
// Article+Database.swift
|
2019-07-09 07:20:57 +02:00
|
|
|
|
// NetNewsWire
|
2017-07-04 00:04:31 +02:00
|
|
|
|
//
|
|
|
|
|
// Created by Brent Simmons on 7/3/17.
|
|
|
|
|
// Copyright © 2017 Ranchero Software. All rights reserved.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
import Foundation
|
|
|
|
|
import RSDatabase
|
2018-07-24 03:29:08 +02:00
|
|
|
|
import Articles
|
2017-09-05 02:10:02 +02:00
|
|
|
|
import RSParser
|
2017-07-04 00:04:31 +02:00
|
|
|
|
|
|
|
|
|
extension Article {
|
|
|
|
|
|
2017-12-19 03:20:13 +01:00
|
|
|
|
init(databaseArticle: DatabaseArticle, accountID: String, authors: Set<Author>?, attachments: Set<Attachment>?) {
|
|
|
|
|
self.init(accountID: accountID, articleID: databaseArticle.articleID, feedID: databaseArticle.feedID, uniqueID: databaseArticle.uniqueID, title: databaseArticle.title, contentHTML: databaseArticle.contentHTML, contentText: databaseArticle.contentText, url: databaseArticle.url, externalURL: databaseArticle.externalURL, summary: databaseArticle.summary, imageURL: databaseArticle.imageURL, bannerImageURL: databaseArticle.bannerImageURL, datePublished: databaseArticle.datePublished, dateModified: databaseArticle.dateModified, authors: authors, attachments: attachments, status: databaseArticle.status)
|
2017-09-19 22:36:13 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-02-09 06:54:45 +01:00
|
|
|
|
init(parsedItem: ParsedItem, maximumDateAllowed: Date, accountID: String, feedID: String, status: ArticleStatus) {
|
2017-09-14 06:41:01 +02:00
|
|
|
|
let authors = Author.authorsWithParsedAuthors(parsedItem.authors)
|
2017-09-05 02:10:02 +02:00
|
|
|
|
let attachments = Attachment.attachmentsWithParsedAttachments(parsedItem.attachments)
|
|
|
|
|
|
2019-02-09 06:54:45 +01:00
|
|
|
|
// Deal with future datePublished and dateModified dates.
|
|
|
|
|
var datePublished = parsedItem.datePublished
|
|
|
|
|
if datePublished == nil {
|
|
|
|
|
datePublished = parsedItem.dateModified
|
|
|
|
|
}
|
|
|
|
|
if datePublished != nil, datePublished! > maximumDateAllowed {
|
|
|
|
|
datePublished = nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var dateModified = parsedItem.dateModified
|
|
|
|
|
if dateModified != nil, dateModified! > maximumDateAllowed {
|
|
|
|
|
dateModified = nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.init(accountID: accountID, articleID: parsedItem.syncServiceID, feedID: feedID, uniqueID: parsedItem.uniqueID, title: parsedItem.title, contentHTML: parsedItem.contentHTML, contentText: parsedItem.contentText, url: parsedItem.url, externalURL: parsedItem.externalURL, summary: parsedItem.summary, imageURL: parsedItem.imageURL, bannerImageURL: parsedItem.bannerImageURL, datePublished: datePublished, dateModified: dateModified, authors: authors, attachments: attachments, status: status)
|
2017-07-04 00:04:31 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-03-03 01:17:06 +01:00
|
|
|
|
private func addPossibleStringChangeWithKeyPath(_ comparisonKeyPath: KeyPath<Article,String?>, _ otherArticle: Article, _ key: String, _ dictionary: inout DatabaseDictionary) {
|
2017-09-09 21:09:48 +02:00
|
|
|
|
if self[keyPath: comparisonKeyPath] != otherArticle[keyPath: comparisonKeyPath] {
|
2019-03-03 01:17:06 +01:00
|
|
|
|
dictionary[key] = self[keyPath: comparisonKeyPath] ?? ""
|
2017-09-09 21:09:48 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-03 01:17:06 +01:00
|
|
|
|
func changesFrom(_ existingArticle: Article) -> DatabaseDictionary? {
|
2019-02-09 05:41:46 +01:00
|
|
|
|
if self == existingArticle {
|
2017-09-09 21:09:48 +02:00
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-03 01:17:06 +01:00
|
|
|
|
var d = DatabaseDictionary()
|
2019-02-09 05:41:46 +01:00
|
|
|
|
if uniqueID != existingArticle.uniqueID {
|
|
|
|
|
d[DatabaseKey.uniqueID] = uniqueID
|
2017-09-09 21:57:24 +02:00
|
|
|
|
}
|
2019-02-09 05:41:46 +01:00
|
|
|
|
|
2019-03-03 01:17:06 +01:00
|
|
|
|
addPossibleStringChangeWithKeyPath(\Article.title, existingArticle, DatabaseKey.title, &d)
|
|
|
|
|
addPossibleStringChangeWithKeyPath(\Article.contentHTML, existingArticle, DatabaseKey.contentHTML, &d)
|
|
|
|
|
addPossibleStringChangeWithKeyPath(\Article.contentText, existingArticle, DatabaseKey.contentText, &d)
|
|
|
|
|
addPossibleStringChangeWithKeyPath(\Article.url, existingArticle, DatabaseKey.url, &d)
|
|
|
|
|
addPossibleStringChangeWithKeyPath(\Article.externalURL, existingArticle, DatabaseKey.externalURL, &d)
|
|
|
|
|
addPossibleStringChangeWithKeyPath(\Article.summary, existingArticle, DatabaseKey.summary, &d)
|
|
|
|
|
addPossibleStringChangeWithKeyPath(\Article.imageURL, existingArticle, DatabaseKey.imageURL, &d)
|
|
|
|
|
addPossibleStringChangeWithKeyPath(\Article.bannerImageURL, existingArticle, DatabaseKey.bannerImageURL, &d)
|
2017-09-09 21:09:48 +02:00
|
|
|
|
|
|
|
|
|
// If updated versions of dates are nil, and we have existing dates, keep the existing dates.
|
|
|
|
|
// This is data that’s good to have, and it’s likely that a feed removing dates is doing so in error.
|
2019-02-09 05:41:46 +01:00
|
|
|
|
if datePublished != existingArticle.datePublished {
|
|
|
|
|
if let updatedDatePublished = datePublished {
|
2017-09-09 21:09:48 +02:00
|
|
|
|
d[DatabaseKey.datePublished] = updatedDatePublished
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-02-09 05:41:46 +01:00
|
|
|
|
if dateModified != existingArticle.dateModified {
|
|
|
|
|
if let updatedDateModified = dateModified {
|
2017-09-09 21:09:48 +02:00
|
|
|
|
d[DatabaseKey.dateModified] = updatedDateModified
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-02-09 05:41:46 +01:00
|
|
|
|
|
|
|
|
|
return d.count < 1 ? nil : d
|
2017-09-09 21:09:48 +02:00
|
|
|
|
}
|
2017-09-05 02:10:02 +02:00
|
|
|
|
|
2017-09-19 07:00:35 +02:00
|
|
|
|
static func articlesWithParsedItems(_ parsedItems: Set<ParsedItem>, _ accountID: String, _ feedID: String, _ statusesDictionary: [String: ArticleStatus]) -> Set<Article> {
|
2019-02-09 06:54:45 +01:00
|
|
|
|
let maximumDateAllowed = Date().addingTimeInterval(60 * 60 * 24) // Allow dates up to about 24 hours ahead of now
|
|
|
|
|
return Set(parsedItems.map{ Article(parsedItem: $0, maximumDateAllowed: maximumDateAllowed, accountID: accountID, feedID: feedID, status: statusesDictionary[$0.articleID]!) })
|
2017-09-05 02:10:02 +02:00
|
|
|
|
}
|
2017-09-05 17:53:45 +02:00
|
|
|
|
|
2017-07-04 00:04:31 +02:00
|
|
|
|
}
|
2017-08-07 06:16:13 +02:00
|
|
|
|
|
2017-08-21 02:46:15 +02:00
|
|
|
|
extension Article: DatabaseObject {
|
2017-09-13 22:29:52 +02:00
|
|
|
|
|
2019-03-03 01:17:06 +01:00
|
|
|
|
public func databaseDictionary() -> DatabaseDictionary? {
|
|
|
|
|
var d = DatabaseDictionary()
|
2017-09-13 22:29:52 +02:00
|
|
|
|
|
|
|
|
|
d[DatabaseKey.articleID] = articleID
|
|
|
|
|
d[DatabaseKey.feedID] = feedID
|
|
|
|
|
d[DatabaseKey.uniqueID] = uniqueID
|
|
|
|
|
|
2019-03-03 01:17:06 +01:00
|
|
|
|
if let title = title {
|
|
|
|
|
d[DatabaseKey.title] = title
|
|
|
|
|
}
|
|
|
|
|
if let contentHTML = contentHTML {
|
|
|
|
|
d[DatabaseKey.contentHTML] = contentHTML
|
|
|
|
|
}
|
|
|
|
|
if let contentText = contentText {
|
|
|
|
|
d[DatabaseKey.contentText] = contentText
|
|
|
|
|
}
|
|
|
|
|
if let url = url {
|
|
|
|
|
d[DatabaseKey.url] = url
|
|
|
|
|
}
|
|
|
|
|
if let externalURL = externalURL {
|
|
|
|
|
d[DatabaseKey.externalURL] = externalURL
|
|
|
|
|
}
|
|
|
|
|
if let summary = summary {
|
|
|
|
|
d[DatabaseKey.summary] = summary
|
|
|
|
|
}
|
|
|
|
|
if let imageURL = imageURL {
|
|
|
|
|
d[DatabaseKey.imageURL] = imageURL
|
|
|
|
|
}
|
|
|
|
|
if let bannerImageURL = bannerImageURL {
|
|
|
|
|
d[DatabaseKey.bannerImageURL] = bannerImageURL
|
|
|
|
|
}
|
|
|
|
|
if let datePublished = datePublished {
|
|
|
|
|
d[DatabaseKey.datePublished] = datePublished
|
|
|
|
|
}
|
|
|
|
|
if let dateModified = dateModified {
|
|
|
|
|
d[DatabaseKey.dateModified] = dateModified
|
|
|
|
|
}
|
|
|
|
|
return d
|
2017-09-13 22:29:52 +02:00
|
|
|
|
}
|
2017-08-21 02:46:15 +02:00
|
|
|
|
|
2017-08-21 07:43:46 +02:00
|
|
|
|
public var databaseID: String {
|
2018-02-14 22:14:25 +01:00
|
|
|
|
return articleID
|
2017-08-21 02:46:15 +02:00
|
|
|
|
}
|
2017-12-02 22:20:27 +01:00
|
|
|
|
|
|
|
|
|
public func relatedObjectsWithName(_ name: String) -> [DatabaseObject]? {
|
|
|
|
|
switch name {
|
|
|
|
|
case RelationshipName.authors:
|
|
|
|
|
return databaseObjectArray(with: authors)
|
|
|
|
|
case RelationshipName.attachments:
|
|
|
|
|
return databaseObjectArray(with: attachments)
|
|
|
|
|
default:
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func databaseObjectArray<T: DatabaseObject>(with objects: Set<T>?) -> [DatabaseObject]? {
|
|
|
|
|
guard let objects = objects else {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
return Array(objects)
|
|
|
|
|
}
|
2017-08-21 02:46:15 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-07 06:16:13 +02:00
|
|
|
|
extension Set where Element == Article {
|
|
|
|
|
|
2017-09-18 22:17:30 +02:00
|
|
|
|
func statuses() -> Set<ArticleStatus> {
|
|
|
|
|
return Set<ArticleStatus>(map { $0.status })
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-02 23:19:42 +02:00
|
|
|
|
func dictionary() -> [String: Article] {
|
|
|
|
|
var d = [String: Article]()
|
|
|
|
|
for article in self {
|
|
|
|
|
d[article.articleID] = article
|
|
|
|
|
}
|
|
|
|
|
return d
|
|
|
|
|
}
|
2017-09-05 02:10:02 +02:00
|
|
|
|
|
|
|
|
|
func databaseObjects() -> [DatabaseObject] {
|
|
|
|
|
return self.map{ $0 as DatabaseObject }
|
|
|
|
|
}
|
2017-09-13 22:29:52 +02:00
|
|
|
|
|
2019-03-03 01:17:06 +01:00
|
|
|
|
func databaseDictionaries() -> [DatabaseDictionary]? {
|
2018-01-28 03:50:48 +01:00
|
|
|
|
return self.compactMap { $0.databaseDictionary() }
|
2017-09-13 22:29:52 +02:00
|
|
|
|
}
|
2017-09-05 02:10:02 +02:00
|
|
|
|
}
|