diff --git a/Frameworks/Data/Article.swift b/Frameworks/Data/Article.swift index cd19ba858..34e240ffb 100644 --- a/Frameworks/Data/Article.swift +++ b/Frameworks/Data/Article.swift @@ -12,9 +12,7 @@ public final class Article: Hashable { weak var account: Account? - public let feedID: String - public let articleID: String //Calculated: unique per account - public var uniqueID: String //guid: unique per feed + public let articleID: ArticleID public var title: String? public var contentHTML: String? public var contentText: String? @@ -35,15 +33,14 @@ public final class Article: Hashable { var feed: Feed? { get { - return account?.existingFeed(with: feedID) + return account?.existingFeed(with: articleID.feedID) } } init(account: Account, feedID: String, uniqueID: String, title: String?, contentHTML: String?, contentText: String?, url: String?, externalURL: String?, summary: String?, imageURL: String?, bannerImageURL: String?, datePublished: Date?, dateModified: Date?, authors: [Author]?, tags: Set?, attachments: [Attachment]?, accountInfo: AccountInfo?) { - + self.account = account - self.feedID = feedID - self.uniqueID = uniqueID + self.articleID = ArticleID(feedID: feedID, uniqueID: uniqueID) self.title = title self.contentHTML = contentHTML self.contentText = contentText @@ -59,8 +56,7 @@ public final class Article: Hashable { self.attachments = attachments self.accountInfo = accountInfo - self.articleID = "\(feedID) \(uniqueID)" - self.hashValue = account.hashValue + feedID.hashValue + uniqueID.hashValue + self.hashValue = account.hashValue ^ self.articleID.hashValue } public class func ==(lhs: Article, rhs: Article) -> Bool { diff --git a/Frameworks/Data/ArticleID.swift b/Frameworks/Data/ArticleID.swift new file mode 100644 index 000000000..7199ac278 --- /dev/null +++ b/Frameworks/Data/ArticleID.swift @@ -0,0 +1,39 @@ +// +// ArticleID.swift +// Data +// +// Created by Brent Simmons on 7/10/17. +// Copyright © 2017 Ranchero Software. All rights reserved. +// + +import Foundation + +// Any given article’s unique ID is unique only for the feed it appears in. +// We can’t rely on feed authors to produce globally unique identifiers. +// So ArticleID includes the feedID as well as the uniqueID. +// +// While we could use a compound primary key in the database (feedID, articleID), +// that complicates things more than a bit. So ArticleID.stringValue provides +// a single value that can be used as a primary key. + +public struct ArticleID: Hashable { + + public let feedID: String + public let uniqueID: String + public let stringValue: String // Stored in database + public let hashValue: Int + + public init(feedID: String, uniqueID: String) { + + self.feedID = feedID + self.uniqueID = uniqueID + self.stringValue = "\(feedID) \(uniqueID)" + self.hashValue = stringValue.hashValue + } + + public static func ==(lhs: ArticleID, rhs: ArticleID) -> Bool { + + return lhs.hashValue == rhs.hashValue && lhs.feedID == rhs.uniqueID && lhs.feedID == rhs.feedID + } +} + diff --git a/Frameworks/Data/Data.xcodeproj/project.pbxproj b/Frameworks/Data/Data.xcodeproj/project.pbxproj index d152730ac..1855cd24e 100644 --- a/Frameworks/Data/Data.xcodeproj/project.pbxproj +++ b/Frameworks/Data/Data.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 843079FA1F0AB57F00B4B7F7 /* RSCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 844BEEA31F0AB512004AB7CD /* RSCore.framework */; }; + 843A08BA1F148A4400D9C438 /* ArticleID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 843A08B91F148A4400D9C438 /* ArticleID.swift */; }; 844BEE651F0AB3C9004AB7CD /* Data.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 844BEE5B1F0AB3C8004AB7CD /* Data.framework */; }; 844BEE6A1F0AB3C9004AB7CD /* DataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844BEE691F0AB3C9004AB7CD /* DataTests.swift */; }; 844BEE791F0AB4B8004AB7CD /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844BEE781F0AB4B8004AB7CD /* Account.swift */; }; @@ -61,6 +62,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 843A08B91F148A4400D9C438 /* ArticleID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleID.swift; sourceTree = ""; }; 844BEE5B1F0AB3C8004AB7CD /* Data.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Data.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 844BEE641F0AB3C9004AB7CD /* DataTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DataTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 844BEE691F0AB3C9004AB7CD /* DataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataTests.swift; sourceTree = ""; }; @@ -112,6 +114,7 @@ 844BEE781F0AB4B8004AB7CD /* Account.swift */, 844BEE7A1F0AB4BE004AB7CD /* Folder.swift */, 844BEE7C1F0AB4C4004AB7CD /* Feed.swift */, + 843A08B91F148A4400D9C438 /* ArticleID.swift */, 844BEE7E1F0AB4CA004AB7CD /* Article.swift */, 844BEE801F0AB4D0004AB7CD /* Author.swift */, 844BEE821F0AB4D6004AB7CD /* Attachment.swift */, @@ -336,6 +339,7 @@ 844BEE9A1F0AB4F8004AB7CD /* DisplayNameProvider.swift in Sources */, 844BEE8F1F0AB4EF004AB7CD /* Account+OPMLRepresentable.swift in Sources */, 844BEE991F0AB4F8004AB7CD /* Container.swift in Sources */, + 843A08BA1F148A4400D9C438 /* ArticleID.swift in Sources */, 844BEE7B1F0AB4BE004AB7CD /* Folder.swift in Sources */, 844BEE981F0AB4F8004AB7CD /* AccountDelegate.swift in Sources */, 844BEE831F0AB4D6004AB7CD /* Attachment.swift in Sources */, diff --git a/Frameworks/Database/CreateStatements.sql b/Frameworks/Database/CreateStatements.sql index 1fec87e80..bcbf5f23b 100644 --- a/Frameworks/Database/CreateStatements.sql +++ b/Frameworks/Database/CreateStatements.sql @@ -1,4 +1,4 @@ -CREATE TABLE if not EXISTS articles (articleID TEXT NOT NULL PRIMARY KEY, feedID TEXT NOT NULL, uniqueID TEXT, title TEXT, contentHTML TEXT, contentText TEXT, url TEXT, externalURL TEXT, summary TEXT, imageURL TEXT, bannerImageURL TEXT, datePublished DATE, dateModified DATE, accountInfo BLOB); +CREATE TABLE if not EXISTS articles (articleID TEXT NOT NULL PRIMARY KEY, feedID TEXT NOT NULL, uniqueID TEXT NOT NULL, title TEXT, contentHTML TEXT, contentText TEXT, url TEXT, externalURL TEXT, summary TEXT, imageURL TEXT, bannerImageURL TEXT, datePublished DATE, dateModified DATE, accountInfo BLOB); CREATE TABLE if not EXISTS statuses (articleID TEXT NOT NULL PRIMARY KEY, read BOOL NOT NULL DEFAULT 0, starred BOOL NOT NULL DEFAULT 0, userDeleted BOOL NOT NULL DEFAULT 0, dateArrived DATE NOT NULL DEFAULT 0, accountInfo BLOB); @@ -10,3 +10,6 @@ CREATE TABLE if not EXISTS tags(tagName TEXT NOT NULL, articleID TEXT NOT NULL, CREATE TABLE if not EXISTS attachments(articleID TEXT NOT NULL, url TEXT NOT NULL, mimeType TEXT, title TEXT, sizeInBytes INTEGER, durationInSeconds INTEGER, PRIMARY KEY(articleID, url)); CREATE INDEX if not EXISTS feedIndex on articles (feedID); + +CREATE INDEX if not EXISTS tags_tagName_index on tags(tagName COLLATE NOCASE); +