Merge branch 'mac-candidate'

This commit is contained in:
Brent Simmons 2019-07-08 23:10:41 -07:00
commit 0d5ebad782
25 changed files with 57 additions and 181 deletions

View File

@ -1,6 +1,6 @@
// //
// Account.swift // Account.swift
// DataModel // NetNewsWire
// //
// Created by Brent Simmons on 7/1/17. // Created by Brent Simmons on 7/1/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved. // Copyright © 2017 Ranchero Software, LLC. All rights reserved.
@ -230,7 +230,6 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
} }
init?(dataFolder: String, type: AccountType, accountID: String, transport: Transport? = nil) { init?(dataFolder: String, type: AccountType, accountID: String, transport: Transport? = nil) {
switch type { switch type {
case .onMyMac: case .onMyMac:
self.delegate = LocalAccountDelegate() self.delegate = LocalAccountDelegate()
@ -271,12 +270,10 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
NotificationCenter.default.addObserver(self, selector: #selector(downloadProgressDidChange(_:)), name: .DownloadProgressDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(downloadProgressDidChange(_:)), name: .DownloadProgressDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(batchUpdateDidPerform(_:)), name: .BatchUpdateDidPerform, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(batchUpdateDidPerform(_:)), name: .BatchUpdateDidPerform, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(displayNameDidChange(_:)), name: .DisplayNameDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(displayNameDidChange(_:)), name: .DisplayNameDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(childrenDidChange(_:)), name: .ChildrenDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(childrenDidChange(_:)), name: .ChildrenDidChange, object: nil)
pullObjectsFromDisk() pullObjectsFromDisk()
DispatchQueue.main.async { DispatchQueue.main.async {
@ -285,7 +282,6 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
self.delegate.accountDidInitialize(self) self.delegate.accountDidInitialize(self)
startingUp = false startingUp = false
} }
// MARK: - API // MARK: - API
@ -307,7 +303,6 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
try CredentialsManager.storeCredentials(credentials, server: server) try CredentialsManager.storeCredentials(credentials, server: server)
delegate.credentials = credentials delegate.credentials = credentials
} }
public func retrieveCredentials() throws -> Credentials? { public func retrieveCredentials() throws -> Credentials? {
@ -372,7 +367,6 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
} }
public func importOPML(_ opmlFile: URL, completion: @escaping (Result<Void, Error>) -> Void) { public func importOPML(_ opmlFile: URL, completion: @escaping (Result<Void, Error>) -> Void) {
guard !delegate.isOPMLImportInProgress else { guard !delegate.isOPMLImportInProgress else {
completion(.failure(AccountError.opmlImportInProgress)) completion(.failure(AccountError.opmlImportInProgress))
return return
@ -398,7 +392,6 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
@discardableResult @discardableResult
public func ensureFolder(with name: String) -> Folder? { public func ensureFolder(with name: String) -> Folder? {
// TODO: support subfolders, maybe, some day // TODO: support subfolders, maybe, some day
if name.isEmpty { if name.isEmpty {
@ -418,7 +411,6 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
} }
public func ensureFolder(withFolderNames folderNames: [String]) -> Folder? { public func ensureFolder(withFolderNames folderNames: [String]) -> Folder? {
// TODO: support subfolders, maybe, some day. // TODO: support subfolders, maybe, some day.
// Since we dont, just take the last name and make sure theres a Folder. // Since we dont, just take the last name and make sure theres a Folder.
@ -449,14 +441,12 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
} }
func createFeed(with name: String?, url: String, feedID: String, homePageURL: String?) -> Feed { func createFeed(with name: String?, url: String, feedID: String, homePageURL: String?) -> Feed {
let metadata = feedMetadata(feedURL: url, feedID: feedID) let metadata = feedMetadata(feedURL: url, feedID: feedID)
let feed = Feed(account: self, url: url, metadata: metadata) let feed = Feed(account: self, url: url, metadata: metadata)
feed.name = name feed.name = name
feed.homePageURL = homePageURL feed.homePageURL = homePageURL
return feed return feed
} }
public func removeFeed(_ feed: Feed, from container: Container?, completion: @escaping (Result<Void, Error>) -> Void) { public func removeFeed(_ feed: Feed, from container: Container?, completion: @escaping (Result<Void, Error>) -> Void) {
@ -502,7 +492,6 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
} }
func loadOPML(_ opmlDocument: RSOPMLDocument) { func loadOPML(_ opmlDocument: RSOPMLDocument) {
guard let children = opmlDocument.children else { guard let children = opmlDocument.children else {
return return
} }
@ -516,13 +505,11 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
} }
public func updateUnreadCounts(for feeds: Set<Feed>) { public func updateUnreadCounts(for feeds: Set<Feed>) {
if feeds.isEmpty { if feeds.isEmpty {
return return
} }
database.fetchUnreadCounts(for: feeds.feedIDs()) { (unreadCountDictionary) in database.fetchUnreadCounts(for: feeds.feedIDs()) { (unreadCountDictionary) in
for feed in feeds { for feed in feeds {
if let unreadCount = unreadCountDictionary[feed.feedID] { if let unreadCount = unreadCountDictionary[feed.feedID] {
feed.unreadCount = unreadCount feed.unreadCount = unreadCount
@ -569,15 +556,12 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
} }
} }
public func fetchUnreadCountForToday(_ callback: @escaping (Int) -> Void) { public func fetchUnreadCountForToday(_ callback: @escaping (Int) -> Void) {
let startOfToday = NSCalendar.startOfToday() let startOfToday = NSCalendar.startOfToday()
database.fetchUnreadCount(for: flattenedFeeds().feedIDs(), since: startOfToday, callback: callback) database.fetchUnreadCount(for: flattenedFeeds().feedIDs(), since: startOfToday, callback: callback)
} }
public func fetchUnreadCountForStarredArticles(_ callback: @escaping (Int) -> Void) { public func fetchUnreadCountForStarredArticles(_ callback: @escaping (Int) -> Void) {
database.fetchStarredAndUnreadCount(for: flattenedFeeds().feedIDs(), callback: callback) database.fetchStarredAndUnreadCount(for: flattenedFeeds().feedIDs(), callback: callback)
} }
@ -643,9 +627,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
} }
func update(_ feed: Feed, parsedItems: Set<ParsedItem>, defaultRead: Bool = false, _ completion: @escaping (() -> Void)) { func update(_ feed: Feed, parsedItems: Set<ParsedItem>, defaultRead: Bool = false, _ completion: @escaping (() -> Void)) {
database.update(feedID: feed.feedID, parsedItems: parsedItems, defaultRead: defaultRead) { (newArticles, updatedArticles) in database.update(feedID: feed.feedID, parsedItems: parsedItems, defaultRead: defaultRead) { (newArticles, updatedArticles) in
var userInfo = [String: Any]() var userInfo = [String: Any]()
if let newArticles = newArticles, !newArticles.isEmpty { if let newArticles = newArticles, !newArticles.isEmpty {
self.updateUnreadCounts(for: Set([feed])) self.updateUnreadCounts(for: Set([feed]))
@ -660,7 +642,6 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
NotificationCenter.default.post(name: .AccountDidDownloadArticles, object: self, userInfo: userInfo) NotificationCenter.default.post(name: .AccountDidDownloadArticles, object: self, userInfo: userInfo)
} }
} }
@discardableResult @discardableResult
@ -675,7 +656,6 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
noteStatusesForArticlesDidChange(updatedArticles) noteStatusesForArticlesDidChange(updatedArticles)
return updatedArticles return updatedArticles
} }
func ensureStatuses(_ articleIDs: Set<String>, _ statusKey: ArticleStatus.Key, _ flag: Bool) { func ensureStatuses(_ articleIDs: Set<String>, _ statusKey: ArticleStatus.Key, _ flag: Bool) {
@ -721,7 +701,6 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
// MARK: - Debug // MARK: - Debug
public func debugDropConditionalGetInfo() { public func debugDropConditionalGetInfo() {
#if DEBUG #if DEBUG
flattenedFeeds().forEach{ $0.debugDropConditionalGetInfo() } flattenedFeeds().forEach{ $0.debugDropConditionalGetInfo() }
#endif #endif
@ -740,7 +719,6 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
// MARK: - Notifications // MARK: - Notifications
@objc func downloadProgressDidChange(_ note: Notification) { @objc func downloadProgressDidChange(_ note: Notification) {
guard let noteObject = note.object as? DownloadProgress, noteObject === refreshProgress else { guard let noteObject = note.object as? DownloadProgress, noteObject === refreshProgress else {
return return
} }
@ -756,14 +734,12 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
} }
@objc func batchUpdateDidPerform(_ note: Notification) { @objc func batchUpdateDidPerform(_ note: Notification) {
flattenedFeedsNeedUpdate = true flattenedFeedsNeedUpdate = true
rebuildFeedDictionaries() rebuildFeedDictionaries()
updateUnreadCount() updateUnreadCount()
} }
@objc func childrenDidChange(_ note: Notification) { @objc func childrenDidChange(_ note: Notification) {
guard let object = note.object else { guard let object = note.object else {
return return
} }
@ -777,14 +753,12 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
} }
@objc func displayNameDidChange(_ note: Notification) { @objc func displayNameDidChange(_ note: Notification) {
if let folder = note.object as? Folder, folder.account === self { if let folder = note.object as? Folder, folder.account === self {
structureDidChange() structureDidChange()
} }
} }
@objc func saveToDiskIfNeeded() { @objc func saveToDiskIfNeeded() {
if dirty && !isDeleted { if dirty && !isDeleted {
saveToDisk() saveToDisk()
} }
@ -811,7 +785,6 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
// MARK: - Equatable // MARK: - Equatable
public class func ==(lhs: Account, rhs: Account) -> Bool { public class func ==(lhs: Account, rhs: Account) -> Bool {
return lhs === rhs return lhs === rhs
} }
} }
@ -948,7 +921,6 @@ private extension Account {
} }
func validateUnreadCount(_ feed: Feed, _ articles: Set<Article>) { func validateUnreadCount(_ feed: Feed, _ articles: Set<Article>) {
// articles must contain all the unread articles for the feed. // articles must contain all the unread articles for the feed.
// The unread number should match the feeds unread count. // The unread number should match the feeds unread count.
@ -1029,11 +1001,9 @@ private extension Account {
BatchUpdate.shared.perform { BatchUpdate.shared.perform {
loadOPMLItems(children, parentFolder: nil) loadOPMLItems(children, parentFolder: nil)
} }
} }
func saveToDisk() { func saveToDisk() {
dirty = false dirty = false
let opmlDocumentString = opmlDocument() let opmlDocumentString = opmlDocument()
@ -1131,7 +1101,6 @@ private extension Account {
} }
func loadOPMLItems(_ items: [RSOPMLItem], parentFolder: Folder?) { func loadOPMLItems(_ items: [RSOPMLItem], parentFolder: Folder?) {
var feedsToAdd = Set<Feed>() var feedsToAdd = Set<Feed>()
items.forEach { (item) in items.forEach { (item) in
@ -1181,7 +1150,6 @@ private extension Account {
} }
func noteStatusesForArticlesDidChange(_ articles: Set<Article>) { func noteStatusesForArticlesDidChange(_ articles: Set<Article>) {
let feeds = Set(articles.compactMap { $0.feed }) let feeds = Set(articles.compactMap { $0.feed })
let statuses = Set(articles.map { $0.status }) let statuses = Set(articles.map { $0.status })
@ -1193,10 +1161,9 @@ private extension Account {
} }
func fetchAllUnreadCounts() { func fetchAllUnreadCounts() {
fetchingAllUnreadCounts = true fetchingAllUnreadCounts = true
database.fetchAllNonZeroUnreadCounts { (unreadCountDictionary) in
database.fetchAllNonZeroUnreadCounts { (unreadCountDictionary) in
if unreadCountDictionary.isEmpty { if unreadCountDictionary.isEmpty {
self.fetchingAllUnreadCounts = false self.fetchingAllUnreadCounts = false
self.updateUnreadCount() self.updateUnreadCount()
@ -1205,7 +1172,6 @@ private extension Account {
} }
self.flattenedFeeds().forEach{ (feed) in self.flattenedFeeds().forEach{ (feed) in
// When the unread count is zero, it wont appear in unreadCountDictionary. // When the unread count is zero, it wont appear in unreadCountDictionary.
if let unreadCount = unreadCountDictionary[feed.feedID] { if let unreadCount = unreadCountDictionary[feed.feedID] {
@ -1227,10 +1193,8 @@ private extension Account {
extension Account { extension Account {
public func existingFeed(with feedID: String) -> Feed? { public func existingFeed(with feedID: String) -> Feed? {
return idToFeedDictionary[feedID] return idToFeedDictionary[feedID]
} }
} }
// MARK: - OPMLRepresentable // MARK: - OPMLRepresentable
@ -1238,7 +1202,6 @@ extension Account {
extension Account: OPMLRepresentable { extension Account: OPMLRepresentable {
public func OPMLString(indentLevel: Int) -> String { public func OPMLString(indentLevel: Int) -> String {
var s = "" var s = ""
for feed in topLevelFeeds { for feed in topLevelFeeds {
s += feed.OPMLString(indentLevel: indentLevel + 1) s += feed.OPMLString(indentLevel: indentLevel + 1)

View File

@ -1,6 +1,6 @@
// //
// AccountDelegate.swift // AccountDelegate.swift
// Account // NetNewsWire
// //
// Created by Brent Simmons on 9/16/17. // Created by Brent Simmons on 9/16/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved. // Copyright © 2017 Ranchero Software, LLC. All rights reserved.

View File

@ -1,6 +1,6 @@
// //
// AccountError.swift // AccountError.swift
// Account // NetNewsWire
// //
// Created by Maurice Parker on 5/26/19. // Created by Maurice Parker on 5/26/19.
// Copyright © 2019 Ranchero Software, LLC. All rights reserved. // Copyright © 2019 Ranchero Software, LLC. All rights reserved.
@ -19,9 +19,9 @@ public enum AccountError: LocalizedError {
public var errorDescription: String? { public var errorDescription: String? {
switch self { switch self {
case .createErrorNotFound: case .createErrorNotFound:
return NSLocalizedString("The feed couldn't be found and can't be added.", comment: "Not found") return NSLocalizedString("The feed couldnt be found and cant be added.", comment: "Not found")
case .createErrorAlreadySubscribed: case .createErrorAlreadySubscribed:
return NSLocalizedString("You are already subscribed to this feed and can't add it again.", comment: "Already subscribed") return NSLocalizedString("You are already subscribed to this feed and cant add it again.", comment: "Already subscribed")
case .opmlImportInProgress: case .opmlImportInProgress:
return NSLocalizedString("An OPML import for this account is already running.", comment: "Import running") return NSLocalizedString("An OPML import for this account is already running.", comment: "Import running")
case .wrappedError(let error, let account): case .wrappedError(let error, let account):
@ -65,5 +65,4 @@ public enum AccountError: LocalizedError {
let localizedText = NSLocalizedString("An error occurred while processing the \"%@\" account: %@", comment: "Unknown error") let localizedText = NSLocalizedString("An error occurred while processing the \"%@\" account: %@", comment: "Unknown error")
return NSString.localizedStringWithFormat(localizedText as NSString, account.nameForDisplay, error.localizedDescription) as String return NSString.localizedStringWithFormat(localizedText as NSString, account.nameForDisplay, error.localizedDescription) as String
} }
} }

View File

@ -13,12 +13,9 @@ import Articles
// Main thread only. // Main thread only.
public extension Notification.Name { public extension Notification.Name {
static let AccountsDidChange = Notification.Name(rawValue: "AccountsDidChange") static let AccountsDidChange = Notification.Name("AccountsDidChange")
} }
private let defaultAccountFolderName = "OnMyMac"
private let defaultAccountIdentifier = "OnMyMac"
public final class AccountManager: UnreadCountProvider { public final class AccountManager: UnreadCountProvider {
public static let shared = AccountManager() public static let shared = AccountManager()
@ -27,6 +24,9 @@ public final class AccountManager: UnreadCountProvider {
private let accountsFolder = RSDataSubfolder(nil, "Accounts")! private let accountsFolder = RSDataSubfolder(nil, "Accounts")!
private var accountsDictionary = [String: Account]() private var accountsDictionary = [String: Account]()
private let defaultAccountFolderName = "OnMyMac"
private let defaultAccountIdentifier = "OnMyMac"
public var isUnreadCountsInitialized: Bool { public var isUnreadCountsInitialized: Bool {
for account in activeAccounts { for account in activeAccounts {
if !account.isUnreadCountsInitialized { if !account.isUnreadCountsInitialized {
@ -76,9 +76,7 @@ public final class AccountManager: UnreadCountProvider {
} }
public init() { public init() {
// The local "On My Mac" account must always exist, even if it's empty. // The local "On My Mac" account must always exist, even if it's empty.
let localAccountFolder = (accountsFolder as NSString).appendingPathComponent("OnMyMac") let localAccountFolder = (accountsFolder as NSString).appendingPathComponent("OnMyMac")
do { do {
try FileManager.default.createDirectory(atPath: localAccountFolder, withIntermediateDirectories: true, attributes: nil) try FileManager.default.createDirectory(atPath: localAccountFolder, withIntermediateDirectories: true, attributes: nil)
@ -101,10 +99,9 @@ public final class AccountManager: UnreadCountProvider {
} }
} }
// MARK: API // MARK: - API
public func createAccount(type: AccountType) -> Account { public func createAccount(type: AccountType) -> Account {
let accountID = UUID().uuidString let accountID = UUID().uuidString
let accountFolder = (accountsFolder as NSString).appendingPathComponent("\(type.rawValue)_\(accountID)") let accountFolder = (accountsFolder as NSString).appendingPathComponent("\(type.rawValue)_\(accountID)")
@ -124,7 +121,6 @@ public final class AccountManager: UnreadCountProvider {
} }
public func deleteAccount(_ account: Account) { public func deleteAccount(_ account: Account) {
guard !account.refreshInProgress else { guard !account.refreshInProgress else {
return return
} }
@ -142,16 +138,13 @@ public final class AccountManager: UnreadCountProvider {
updateUnreadCount() updateUnreadCount()
NotificationCenter.default.post(name: .AccountsDidChange, object: self) NotificationCenter.default.post(name: .AccountsDidChange, object: self)
} }
public func existingAccount(with accountID: String) -> Account? { public func existingAccount(with accountID: String) -> Account? {
return accountsDictionary[accountID] return accountsDictionary[accountID]
} }
public func refreshAll(errorHandler: @escaping (Error) -> Void) { public func refreshAll(errorHandler: @escaping (Error) -> Void) {
activeAccounts.forEach { account in activeAccounts.forEach { account in
account.refreshAll() { result in account.refreshAll() { result in
switch result { switch result {
@ -162,7 +155,6 @@ public final class AccountManager: UnreadCountProvider {
} }
} }
} }
} }
public func syncArticleStatusAll(completion: (() -> Void)? = nil) { public func syncArticleStatusAll(completion: (() -> Void)? = nil) {
@ -181,7 +173,6 @@ public final class AccountManager: UnreadCountProvider {
} }
public func anyAccountHasAtLeastOneFeed() -> Bool { public func anyAccountHasAtLeastOneFeed() -> Bool {
for account in activeAccounts { for account in activeAccounts {
if account.hasAtLeastOneFeed() { if account.hasAtLeastOneFeed() {
return true return true
@ -192,7 +183,6 @@ public final class AccountManager: UnreadCountProvider {
} }
public func anyAccountHasFeedWithURL(_ urlString: String) -> Bool { public func anyAccountHasFeedWithURL(_ urlString: String) -> Bool {
for account in activeAccounts { for account in activeAccounts {
if let _ = account.existingFeed(withURL: urlString) { if let _ = account.existingFeed(withURL: urlString) {
return true return true
@ -201,11 +191,6 @@ public final class AccountManager: UnreadCountProvider {
return false return false
} }
func updateUnreadCount() {
unreadCount = calculateUnreadCount(activeAccounts)
}
// MARK: - Fetching Articles // MARK: - Fetching Articles
// These fetch articles from active accounts and return a merged Set<Article>. // These fetch articles from active accounts and return a merged Set<Article>.
@ -238,10 +223,9 @@ public final class AccountManager: UnreadCountProvider {
} }
} }
// MARK: Notifications // MARK: - Notifications
@objc dynamic func unreadCountDidChange(_ notification: Notification) { @objc dynamic func unreadCountDidChange(_ notification: Notification) {
guard let _ = notification.object as? Account else { guard let _ = notification.object as? Account else {
return return
} }
@ -251,15 +235,21 @@ public final class AccountManager: UnreadCountProvider {
@objc func accountStateDidChange(_ notification: Notification) { @objc func accountStateDidChange(_ notification: Notification) {
updateUnreadCount() updateUnreadCount()
} }
}
// MARK: Private // MARK: - Private
private func loadAccount(_ accountSpecifier: AccountSpecifier) -> Account? { private extension AccountManager {
func updateUnreadCount() {
unreadCount = calculateUnreadCount(activeAccounts)
}
func loadAccount(_ accountSpecifier: AccountSpecifier) -> Account? {
return Account(dataFolder: accountSpecifier.folderPath, type: accountSpecifier.type, accountID: accountSpecifier.identifier) return Account(dataFolder: accountSpecifier.folderPath, type: accountSpecifier.type, accountID: accountSpecifier.identifier)
} }
private func loadAccount(_ filename: String) -> Account? { func loadAccount(_ filename: String) -> Account? {
let folderPath = (accountsFolder as NSString).appendingPathComponent(filename) let folderPath = (accountsFolder as NSString).appendingPathComponent(filename)
if let accountSpecifier = AccountSpecifier(folderPath: folderPath) { if let accountSpecifier = AccountSpecifier(folderPath: folderPath) {
return loadAccount(accountSpecifier) return loadAccount(accountSpecifier)
@ -267,8 +257,7 @@ public final class AccountManager: UnreadCountProvider {
return nil return nil
} }
private func readAccountsFromDisk() { func readAccountsFromDisk() {
var filenames: [String]? var filenames: [String]?
do { do {
@ -280,7 +269,6 @@ public final class AccountManager: UnreadCountProvider {
} }
filenames?.forEach { (oneFilename) in filenames?.forEach { (oneFilename) in
guard oneFilename != defaultAccountFolderName else { guard oneFilename != defaultAccountFolderName else {
return return
} }
@ -290,12 +278,10 @@ public final class AccountManager: UnreadCountProvider {
} }
} }
private func sortByName(_ accounts: [Account]) -> [Account] { func sortByName(_ accounts: [Account]) -> [Account] {
// LocalAccount is first. // LocalAccount is first.
return accounts.sorted { (account1, account2) -> Bool in return accounts.sorted { (account1, account2) -> Bool in
if account1 === defaultAccount { if account1 === defaultAccount {
return true return true
} }
@ -307,13 +293,6 @@ public final class AccountManager: UnreadCountProvider {
} }
} }
private let accountDataFileName = "AccountData.plist"
private func accountFilePathWithFolder(_ folderPath: String) -> String {
return NSString(string: folderPath).appendingPathComponent(accountDataFileName)
}
private struct AccountSpecifier { private struct AccountSpecifier {
let type: AccountType let type: AccountType
@ -322,8 +301,8 @@ private struct AccountSpecifier {
let folderName: String let folderName: String
let dataFilePath: String let dataFilePath: String
init?(folderPath: String) {
init?(folderPath: String) {
if !FileManager.default.rs_fileIsFolder(folderPath) { if !FileManager.default.rs_fileIsFolder(folderPath) {
return nil return nil
} }
@ -335,18 +314,21 @@ private struct AccountSpecifier {
let nameComponents = name.components(separatedBy: "_") let nameComponents = name.components(separatedBy: "_")
guard nameComponents.count == 2, let rawType = Int(nameComponents[0]), let acctType = AccountType(rawValue: rawType) else { guard nameComponents.count == 2, let rawType = Int(nameComponents[0]), let accountType = AccountType(rawValue: rawType) else {
return nil return nil
} }
self.folderPath = folderPath self.folderPath = folderPath
self.folderName = name self.folderName = name
self.type = acctType self.type = accountType
self.identifier = nameComponents[1] self.identifier = nameComponents[1]
self.dataFilePath = accountFilePathWithFolder(self.folderPath) self.dataFilePath = AccountSpecifier.accountFilePathWithFolder(self.folderPath)
}
private static let accountDataFileName = "AccountData.plist"
private static func accountFilePathWithFolder(_ folderPath: String) -> String {
return NSString(string: folderPath).appendingPathComponent(accountDataFileName)
} }
} }

View File

@ -77,5 +77,4 @@ final class AccountMetadata: Codable {
func valueDidChange(_ key: CodingKeys) { func valueDidChange(_ key: CodingKeys) {
delegate?.valueDidChange(self, key: key) delegate?.valueDidChange(self, key: key)
} }
} }

View File

@ -1,6 +1,6 @@
// //
// ArticleFetcher.swift // ArticleFetcher.swift
// Account // NetNewsWire
// //
// Created by Brent Simmons on 2/4/18. // Created by Brent Simmons on 2/4/18.
// Copyright © 2018 Ranchero Software, LLC. All rights reserved. // Copyright © 2018 Ranchero Software, LLC. All rights reserved.

View File

@ -1,6 +1,6 @@
// //
// CombinedRefreshProgress.swift // CombinedRefreshProgress.swift
// Account // NetNewsWire
// //
// Created by Brent Simmons on 10/7/17. // Created by Brent Simmons on 10/7/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved. // Copyright © 2017 Ranchero Software, LLC. All rights reserved.
@ -20,7 +20,6 @@ public struct CombinedRefreshProgress {
public let isComplete: Bool public let isComplete: Bool
init(numberOfTasks: Int, numberRemaining: Int, numberCompleted: Int) { init(numberOfTasks: Int, numberRemaining: Int, numberCompleted: Int) {
self.numberOfTasks = max(numberOfTasks, 0) self.numberOfTasks = max(numberOfTasks, 0)
self.numberRemaining = max(numberRemaining, 0) self.numberRemaining = max(numberRemaining, 0)
self.numberCompleted = max(numberCompleted, 0) self.numberCompleted = max(numberCompleted, 0)
@ -28,7 +27,6 @@ public struct CombinedRefreshProgress {
} }
public init(downloadProgressArray: [DownloadProgress]) { public init(downloadProgressArray: [DownloadProgress]) {
var numberOfTasks = 0 var numberOfTasks = 0
var numberRemaining = 0 var numberRemaining = 0
var numberCompleted = 0 var numberCompleted = 0

View File

@ -76,7 +76,6 @@ public extension Container {
} }
func flattenedFeeds() -> Set<Feed> { func flattenedFeeds() -> Set<Feed> {
var feeds = Set<Feed>() var feeds = Set<Feed>()
feeds.formUnion(topLevelFeeds) feeds.formUnion(topLevelFeeds)
if let folders = folders { if let folders = folders {

View File

@ -1,6 +1,6 @@
// //
// ContainerPath.swift // ContainerPath.swift
// Account // NetNewsWire
// //
// Created by Brent Simmons on 11/4/17. // Created by Brent Simmons on 11/4/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved. // Copyright © 2017 Ranchero Software, LLC. All rights reserved.
@ -22,7 +22,6 @@ public struct ContainerPath {
// folders should be from top-level down, as in ["Cats", "Tabbies"] // folders should be from top-level down, as in ["Cats", "Tabbies"]
public init(account: Account, folders: [Folder]) { public init(account: Account, folders: [Folder]) {
self.account = account self.account = account
self.names = folders.map { $0.nameForDisplay } self.names = folders.map { $0.nameForDisplay }
self.isTopLevel = folders.isEmpty self.isTopLevel = folders.isEmpty
@ -31,7 +30,6 @@ public struct ContainerPath {
} }
public func resolveContainer() -> Container? { public func resolveContainer() -> Container? {
// The only time it should fail is if the account no longer exists. // The only time it should fail is if the account no longer exists.
// Otherwise the worst-case scenario is that it will create Folders if needed. // Otherwise the worst-case scenario is that it will create Folders if needed.

View File

@ -1,6 +1,6 @@
// //
// DataExtensions.swift // DataExtensions.swift
// Account // NetNewsWire
// //
// Created by Brent Simmons on 10/7/17. // Created by Brent Simmons on 10/7/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved. // Copyright © 2017 Ranchero Software, LLC. All rights reserved.

View File

@ -1,6 +1,6 @@
// //
// Feed.swift // Feed.swift
// DataModel // NetNewsWire
// //
// Created by Brent Simmons on 7/1/17. // Created by Brent Simmons on 7/1/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved. // Copyright © 2017 Ranchero Software, LLC. All rights reserved.

View File

@ -1,6 +1,6 @@
// //
// FeedFinder.swift // FeedFinder.swift
// FeedFinder // NetNewsWire
// //
// Created by Brent Simmons on 8/2/16. // Created by Brent Simmons on 8/2/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved. // Copyright © 2016 Ranchero Software, LLC. All rights reserved.
@ -14,9 +14,7 @@ import RSCore
class FeedFinder { class FeedFinder {
static func find(url: URL, completion: @escaping (Result<Set<FeedSpecifier>, Error>) -> Void) { static func find(url: URL, completion: @escaping (Result<Set<FeedSpecifier>, Error>) -> Void) {
downloadUsingCache(url) { (data, response, error) in downloadUsingCache(url) { (data, response, error) in
if response?.forcedStatusCode == 404 { if response?.forcedStatusCode == 404 {
completion(.failure(AccountError.createErrorNotFound)) completion(.failure(AccountError.createErrorNotFound))
return return
@ -49,17 +47,13 @@ class FeedFinder {
} }
FeedFinder.findFeedsInHTMLPage(htmlData: data, urlString: url.absoluteString, completion: completion) FeedFinder.findFeedsInHTMLPage(htmlData: data, urlString: url.absoluteString, completion: completion)
} }
} }
} }
private extension FeedFinder { private extension FeedFinder {
static func addFeedSpecifier(_ feedSpecifier: FeedSpecifier, feedSpecifiers: inout [String: FeedSpecifier]) { static func addFeedSpecifier(_ feedSpecifier: FeedSpecifier, feedSpecifiers: inout [String: FeedSpecifier]) {
// If theres an existing feed specifier, merge the two so that we have the best data. If one has a title and one doesnt, use that non-nil title. Use the better source. // If theres an existing feed specifier, merge the two so that we have the best data. If one has a title and one doesnt, use that non-nil title. Use the better source.
if let existingFeedSpecifier = feedSpecifiers[feedSpecifier.urlString] { if let existingFeedSpecifier = feedSpecifiers[feedSpecifier.urlString] {
@ -72,7 +66,6 @@ private extension FeedFinder {
} }
static func findFeedsInHTMLPage(htmlData: Data, urlString: String, completion: @escaping (Result<Set<FeedSpecifier>, Error>) -> Void) { static func findFeedsInHTMLPage(htmlData: Data, urlString: String, completion: @escaping (Result<Set<FeedSpecifier>, Error>) -> Void) {
// Feeds in the <head> section we automatically assume are feeds. // Feeds in the <head> section we automatically assume are feeds.
// If there are none from the <head> section, // If there are none from the <head> section,
// then possible feeds in <body> section are downloaded individually // then possible feeds in <body> section are downloaded individually
@ -99,16 +92,17 @@ private extension FeedFinder {
if didFindFeedInHTMLHead { if didFindFeedInHTMLHead {
completion(.success(Set(feedSpecifiers.values))) completion(.success(Set(feedSpecifiers.values)))
return return
} else if feedSpecifiersToDownload.isEmpty { }
else if feedSpecifiersToDownload.isEmpty {
completion(.failure(AccountError.createErrorNotFound)) completion(.failure(AccountError.createErrorNotFound))
return return
} else { }
else {
downloadFeedSpecifiers(feedSpecifiersToDownload, feedSpecifiers: feedSpecifiers, completion: completion) downloadFeedSpecifiers(feedSpecifiersToDownload, feedSpecifiers: feedSpecifiers, completion: completion)
} }
} }
static func possibleFeedsInHTMLPage(htmlData: Data, urlString: String) -> Set<FeedSpecifier> { static func possibleFeedsInHTMLPage(htmlData: Data, urlString: String) -> Set<FeedSpecifier> {
let parserData = ParserData(url: urlString, data: htmlData) let parserData = ParserData(url: urlString, data: htmlData)
var feedSpecifiers = HTMLFeedFinder(parserData: parserData).feedSpecifiers var feedSpecifiers = HTMLFeedFinder(parserData: parserData).feedSpecifiers
@ -139,7 +133,6 @@ private extension FeedFinder {
let group = DispatchGroup() let group = DispatchGroup()
for downloadFeedSpecifier in downloadFeedSpecifiers { for downloadFeedSpecifier in downloadFeedSpecifiers {
guard let url = URL(string: downloadFeedSpecifier.urlString) else { guard let url = URL(string: downloadFeedSpecifier.urlString) else {
continue continue
} }
@ -159,12 +152,10 @@ private extension FeedFinder {
group.notify(queue: DispatchQueue.main) { group.notify(queue: DispatchQueue.main) {
completion(.success(Set(resultFeedSpecifiers.values))) completion(.success(Set(resultFeedSpecifiers.values)))
} }
} }
static func isFeed(_ data: Data, _ urlString: String) -> Bool { static func isFeed(_ data: Data, _ urlString: String) -> Bool {
let parserData = ParserData(url: urlString, data: data) let parserData = ParserData(url: urlString, data: data)
return FeedParser.canParse(parserData) return FeedParser.canParse(parserData)
} }
} }

View File

@ -1,6 +1,6 @@
// //
// FeedSpecifier.swift // FeedSpecifier.swift
// FeedFinder // NetNewsWire
// //
// Created by Brent Simmons on 8/7/16. // Created by Brent Simmons on 8/7/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved. // Copyright © 2016 Ranchero Software, LLC. All rights reserved.
@ -11,11 +11,9 @@ import Foundation
struct FeedSpecifier: Hashable { struct FeedSpecifier: Hashable {
enum Source: Int { enum Source: Int {
case UserEntered = 0, HTMLHead, HTMLLink case UserEntered = 0, HTMLHead, HTMLLink
func equalToOrBetterThan(_ otherSource: Source) -> Bool { func equalToOrBetterThan(_ otherSource: Source) -> Bool {
return self.rawValue <= otherSource.rawValue return self.rawValue <= otherSource.rawValue
} }
} }
@ -28,7 +26,6 @@ struct FeedSpecifier: Hashable {
} }
func feedSpecifierByMerging(_ feedSpecifier: FeedSpecifier) -> FeedSpecifier { func feedSpecifierByMerging(_ feedSpecifier: FeedSpecifier) -> FeedSpecifier {
// Take the best data (non-nil title, better source) to create a new feed specifier; // Take the best data (non-nil title, better source) to create a new feed specifier;
let mergedTitle = title ?? feedSpecifier.title let mergedTitle = title ?? feedSpecifier.title
@ -38,7 +35,6 @@ struct FeedSpecifier: Hashable {
} }
public static func bestFeed(in feedSpecifiers: Set<FeedSpecifier>) -> FeedSpecifier? { public static func bestFeed(in feedSpecifiers: Set<FeedSpecifier>) -> FeedSpecifier? {
if feedSpecifiers.isEmpty { if feedSpecifiers.isEmpty {
return nil return nil
} }
@ -64,7 +60,6 @@ struct FeedSpecifier: Hashable {
private extension FeedSpecifier { private extension FeedSpecifier {
func calculatedScore() -> Int { func calculatedScore() -> Int {
var score = 0 var score = 0
if source == .UserEntered { if source == .UserEntered {

View File

@ -1,6 +1,6 @@
// //
// HTMLFeedFinder.swift // HTMLFeedFinder.swift
// FeedFinder // NetNewsWire
// //
// Created by Brent Simmons on 8/7/16. // Created by Brent Simmons on 8/7/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved. // Copyright © 2016 Ranchero Software, LLC. All rights reserved.
@ -20,7 +20,6 @@ class HTMLFeedFinder {
private var feedSpecifiersDictionary = [String: FeedSpecifier]() private var feedSpecifiersDictionary = [String: FeedSpecifier]()
init(parserData: ParserData) { init(parserData: ParserData) {
let metadata = RSHTMLMetadataParser.htmlMetadata(with: parserData) let metadata = RSHTMLMetadataParser.htmlMetadata(with: parserData)
for oneFeedLink in metadata.feedLinks { for oneFeedLink in metadata.feedLinks {
@ -46,7 +45,6 @@ class HTMLFeedFinder {
private extension HTMLFeedFinder { private extension HTMLFeedFinder {
func addFeedSpecifier(_ feedSpecifier: FeedSpecifier) { func addFeedSpecifier(_ feedSpecifier: FeedSpecifier) {
// If theres an existing feed specifier, merge the two so that we have the best data. If one has a title and one doesnt, use that non-nil title. Use the better source. // If theres an existing feed specifier, merge the two so that we have the best data. If one has a title and one doesnt, use that non-nil title. Use the better source.
if let existingFeedSpecifier = feedSpecifiersDictionary[feedSpecifier.urlString] { if let existingFeedSpecifier = feedSpecifiersDictionary[feedSpecifier.urlString] {
@ -59,7 +57,6 @@ private extension HTMLFeedFinder {
} }
func urlStringMightBeFeed(_ urlString: String) -> Bool { func urlStringMightBeFeed(_ urlString: String) -> Bool {
let massagedURLString = urlString.replacingOccurrences(of: "buzzfeed", with: "_") let massagedURLString = urlString.replacingOccurrences(of: "buzzfeed", with: "_")
for oneMatch in feedURLWordsToMatch { for oneMatch in feedURLWordsToMatch {
@ -73,7 +70,6 @@ private extension HTMLFeedFinder {
} }
func linkMightBeFeed(_ link: RSHTMLLink) -> Bool { func linkMightBeFeed(_ link: RSHTMLLink) -> Bool {
if let linkURLString = link.urlString, urlStringMightBeFeed(linkURLString) { if let linkURLString = link.urlString, urlStringMightBeFeed(linkURLString) {
return true return true
} }

View File

@ -1,6 +1,6 @@
// //
// FeedMetadata.swift // FeedMetadata.swift
// Account // NetNewsWire
// //
// Created by Brent Simmons on 3/12/19. // Created by Brent Simmons on 3/12/19.
// Copyright © 2019 Ranchero Software, LLC. All rights reserved. // Copyright © 2019 Ranchero Software, LLC. All rights reserved.
@ -128,5 +128,4 @@ final class FeedMetadata: Codable {
func valueDidChange(_ key: CodingKeys) { func valueDidChange(_ key: CodingKeys) {
delegate?.valueDidChange(self, key: key) delegate?.valueDidChange(self, key: key)
} }
} }

View File

@ -1,6 +1,6 @@
// //
// Folder.swift // Folder.swift
// DataModel // NetNewsWire
// //
// Created by Brent Simmons on 7/1/17. // Created by Brent Simmons on 7/1/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved. // Copyright © 2017 Ranchero Software, LLC. All rights reserved.
@ -52,7 +52,6 @@ public final class Folder: DisplayNameProvider, Renamable, Container, UnreadCoun
// MARK: - Init // MARK: - Init
init(account: Account, name: String?) { init(account: Account, name: String?) {
self.account = account self.account = account
self.name = name self.name = name
@ -67,7 +66,6 @@ public final class Folder: DisplayNameProvider, Renamable, Container, UnreadCoun
// MARK: - Notifications // MARK: - Notifications
@objc func unreadCountDidChange(_ note: Notification) { @objc func unreadCountDidChange(_ note: Notification) {
if let object = note.object { if let object = note.object {
if objectIsChild(object as AnyObject) { if objectIsChild(object as AnyObject) {
updateUnreadCount() updateUnreadCount()
@ -76,7 +74,6 @@ public final class Folder: DisplayNameProvider, Renamable, Container, UnreadCoun
} }
@objc func childrenDidChange(_ note: Notification) { @objc func childrenDidChange(_ note: Notification) {
updateUnreadCount() updateUnreadCount()
} }
@ -114,7 +111,6 @@ public final class Folder: DisplayNameProvider, Renamable, Container, UnreadCoun
// MARK: - Equatable // MARK: - Equatable
static public func ==(lhs: Folder, rhs: Folder) -> Bool { static public func ==(lhs: Folder, rhs: Folder) -> Bool {
return lhs === rhs return lhs === rhs
} }
} }
@ -141,7 +137,6 @@ private extension Folder {
extension Folder: OPMLRepresentable { extension Folder: OPMLRepresentable {
public func OPMLString(indentLevel: Int) -> String { public func OPMLString(indentLevel: Int) -> String {
let escapedTitle = nameForDisplay.rs_stringByEscapingSpecialXMLCharacters() let escapedTitle = nameForDisplay.rs_stringByEscapingSpecialXMLCharacters()
var s = "<outline text=\"\(escapedTitle)\" title=\"\(escapedTitle)\">\n" var s = "<outline text=\"\(escapedTitle)\" title=\"\(escapedTitle)\">\n"
s = s.rs_string(byPrependingNumberOfTabs: indentLevel) s = s.rs_string(byPrependingNumberOfTabs: indentLevel)

View File

@ -15,7 +15,6 @@ struct InitialFeedDownloader {
static func download(_ url: URL,_ completionHandler: @escaping (_ parsedFeed: ParsedFeed?) -> Void) { static func download(_ url: URL,_ completionHandler: @escaping (_ parsedFeed: ParsedFeed?) -> Void) {
downloadUsingCache(url) { (data, response, error) in downloadUsingCache(url) { (data, response, error) in
guard let data = data else { guard let data = data else {
completionHandler(nil) completionHandler(nil)
return return

View File

@ -1,6 +1,6 @@
// //
// LocalAccountDelegate.swift // LocalAccountDelegate.swift
// Account // NetNewsWire
// //
// Created by Brent Simmons on 9/16/17. // Created by Brent Simmons on 9/16/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved. // Copyright © 2017 Ranchero Software, LLC. All rights reserved.
@ -48,7 +48,6 @@ final class LocalAccountDelegate: AccountDelegate {
} }
func importOPML(for account:Account, opmlFile: URL, completion: @escaping (Result<Void, Error>) -> Void) { func importOPML(for account:Account, opmlFile: URL, completion: @escaping (Result<Void, Error>) -> Void) {
var fileData: Data? var fileData: Data?
do { do {
@ -88,7 +87,6 @@ final class LocalAccountDelegate: AccountDelegate {
} }
func createFeed(for account: Account, url urlString: String, name: String?, container: Container, completion: @escaping (Result<Feed, Error>) -> Void) { func createFeed(for account: Account, url urlString: String, name: String?, container: Container, completion: @escaping (Result<Feed, Error>) -> Void) {
guard let url = URL(string: urlString) else { guard let url = URL(string: urlString) else {
completion(.failure(LocalAccountDelegateError.invalidParameter)) completion(.failure(LocalAccountDelegateError.invalidParameter))
return return
@ -99,8 +97,6 @@ final class LocalAccountDelegate: AccountDelegate {
switch result { switch result {
case .success(let feedSpecifiers): case .success(let feedSpecifiers):
guard let bestFeedSpecifier = FeedSpecifier.bestFeed(in: feedSpecifiers), guard let bestFeedSpecifier = FeedSpecifier.bestFeed(in: feedSpecifiers),
let url = URL(string: bestFeedSpecifier.urlString) else { let url = URL(string: bestFeedSpecifier.urlString) else {
self.refreshProgress.completeTask() self.refreshProgress.completeTask()
@ -117,7 +113,6 @@ final class LocalAccountDelegate: AccountDelegate {
let feed = account.createFeed(with: nil, url: url.absoluteString, feedID: url.absoluteString, homePageURL: nil) let feed = account.createFeed(with: nil, url: url.absoluteString, feedID: url.absoluteString, homePageURL: nil)
InitialFeedDownloader.download(url) { parsedFeed in InitialFeedDownloader.download(url) { parsedFeed in
self.refreshProgress.completeTask() self.refreshProgress.completeTask()
if let parsedFeed = parsedFeed { if let parsedFeed = parsedFeed {
@ -199,5 +194,4 @@ final class LocalAccountDelegate: AccountDelegate {
static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL? = nil, completion: (Result<Credentials?, Error>) -> Void) { static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL? = nil, completion: (Result<Credentials?, Error>) -> Void) {
return completion(.success(nil)) return completion(.success(nil))
} }
} }

View File

@ -1,6 +1,6 @@
// //
// LocalAccountRefresher.swift // LocalAccountRefresher.swift
// LocalAccount // NetNewsWire
// //
// Created by Brent Simmons on 9/6/16. // Created by Brent Simmons on 9/6/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved. // Copyright © 2016 Ranchero Software, LLC. All rights reserved.
@ -23,7 +23,6 @@ final class LocalAccountRefresher {
} }
public func refreshFeeds(_ feeds: Set<Feed>) { public func refreshFeeds(_ feeds: Set<Feed>) {
downloadSession.downloadObjects(feeds as NSSet) downloadSession.downloadObjects(feeds as NSSet)
} }
} }
@ -33,11 +32,9 @@ final class LocalAccountRefresher {
extension LocalAccountRefresher: DownloadSessionDelegate { extension LocalAccountRefresher: DownloadSessionDelegate {
func downloadSession(_ downloadSession: DownloadSession, requestForRepresentedObject representedObject: AnyObject) -> URLRequest? { func downloadSession(_ downloadSession: DownloadSession, requestForRepresentedObject representedObject: AnyObject) -> URLRequest? {
guard let feed = representedObject as? Feed else { guard let feed = representedObject as? Feed else {
return nil return nil
} }
guard let url = URL(string: feed.url) else { guard let url = URL(string: feed.url) else {
return nil return nil
} }
@ -51,7 +48,6 @@ extension LocalAccountRefresher: DownloadSessionDelegate {
} }
func downloadSession(_ downloadSession: DownloadSession, downloadDidCompleteForRepresentedObject representedObject: AnyObject, response: URLResponse?, data: Data, error: NSError?) { func downloadSession(_ downloadSession: DownloadSession, downloadDidCompleteForRepresentedObject representedObject: AnyObject, response: URLResponse?, data: Data, error: NSError?) {
guard let feed = representedObject as? Feed, !data.isEmpty else { guard let feed = representedObject as? Feed, !data.isEmpty else {
return return
} }
@ -63,18 +59,15 @@ extension LocalAccountRefresher: DownloadSessionDelegate {
let dataHash = (data as NSData).rs_md5HashString() let dataHash = (data as NSData).rs_md5HashString()
if dataHash == feed.contentHash { if dataHash == feed.contentHash {
// print("Hashed content of \(feed.url) has not changed.")
return return
} }
let parserData = ParserData(url: feed.url, data: data) let parserData = ParserData(url: feed.url, data: data)
FeedParser.parse(parserData) { (parsedFeed, error) in FeedParser.parse(parserData) { (parsedFeed, error) in
guard let account = feed.account, let parsedFeed = parsedFeed, error == nil else { guard let account = feed.account, let parsedFeed = parsedFeed, error == nil else {
return return
} }
account.update(feed, with: parsedFeed) { account.update(feed, with: parsedFeed) {
if let httpResponse = response as? HTTPURLResponse { if let httpResponse = response as? HTTPURLResponse {
feed.conditionalGetInfo = HTTPConditionalGetInfo(urlResponse: httpResponse) feed.conditionalGetInfo = HTTPConditionalGetInfo(urlResponse: httpResponse)
} }
@ -85,7 +78,6 @@ extension LocalAccountRefresher: DownloadSessionDelegate {
} }
func downloadSession(_ downloadSession: DownloadSession, shouldContinueAfterReceivingData data: Data, representedObject: AnyObject) -> Bool { func downloadSession(_ downloadSession: DownloadSession, shouldContinueAfterReceivingData data: Data, representedObject: AnyObject) -> Bool {
guard let feed = representedObject as? Feed else { guard let feed = representedObject as? Feed else {
return false return false
} }
@ -106,21 +98,9 @@ extension LocalAccountRefresher: DownloadSessionDelegate {
} }
func downloadSession(_ downloadSession: DownloadSession, didReceiveUnexpectedResponse response: URLResponse, representedObject: AnyObject) { func downloadSession(_ downloadSession: DownloadSession, didReceiveUnexpectedResponse response: URLResponse, representedObject: AnyObject) {
// guard let feed = representedObject as? Feed else {
// return
// }
//
// print("Unexpected response \(response) for \(feed.url).")
} }
func downloadSession(_ downloadSession: DownloadSession, didReceiveNotModifiedResponse: URLResponse, representedObject: AnyObject) { func downloadSession(_ downloadSession: DownloadSession, didReceiveNotModifiedResponse: URLResponse, representedObject: AnyObject) {
// guard let feed = representedObject as? Feed else {
// return
// }
//
// print("Not modified response for \(feed.url).")
} }
} }
@ -129,7 +109,6 @@ extension LocalAccountRefresher: DownloadSessionDelegate {
private extension Data { private extension Data {
func isDefinitelyNotFeed() -> Bool { func isDefinitelyNotFeed() -> Bool {
// We only detect a few image types for now. This should get fleshed-out at some later date. // We only detect a few image types for now. This should get fleshed-out at some later date.
return (self as NSData).rs_dataIsImage() return (self as NSData).rs_dataIsImage()
} }

View File

@ -25,12 +25,10 @@ public protocol UnreadCountProvider {
public extension UnreadCountProvider { public extension UnreadCountProvider {
func postUnreadCountDidChangeNotification() { func postUnreadCountDidChangeNotification() {
NotificationCenter.default.post(name: .UnreadCountDidChange, object: self, userInfo: nil) NotificationCenter.default.post(name: .UnreadCountDidChange, object: self, userInfo: nil)
} }
func calculateUnreadCount<T: Collection>(_ children: T) -> Int { func calculateUnreadCount<T: Collection>(_ children: T) -> Int {
let updatedUnreadCount = children.reduce(0) { (result, oneChild) -> Int in let updatedUnreadCount = children.reduce(0) { (result, oneChild) -> Int in
if let oneUnreadCountProvider = oneChild as? UnreadCountProvider { if let oneUnreadCountProvider = oneChild as? UnreadCountProvider {
return result + oneUnreadCountProvider.unreadCount return result + oneUnreadCountProvider.unreadCount

View File

@ -1,6 +1,6 @@
// //
// Article.swift // Article.swift
// Data // NetNewsWire
// //
// Created by Brent Simmons on 7/1/17. // Created by Brent Simmons on 7/1/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved. // Copyright © 2017 Ranchero Software, LLC. All rights reserved.

View File

@ -1,6 +1,6 @@
// //
// ArticleStatus.swift // ArticleStatus.swift
// DataModel // NetNewsWire
// //
// Created by Brent Simmons on 7/1/17. // Created by Brent Simmons on 7/1/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved. // Copyright © 2017 Ranchero Software, LLC. All rights reserved.
@ -30,7 +30,6 @@ public final class ArticleStatus: Hashable {
public var userDeleted = false public var userDeleted = false
public init(articleID: String, read: Bool, starred: Bool, userDeleted: Bool, dateArrived: Date) { public init(articleID: String, read: Bool, starred: Bool, userDeleted: Bool, dateArrived: Date) {
self.articleID = articleID self.articleID = articleID
self.read = read self.read = read
self.starred = starred self.starred = starred
@ -39,12 +38,10 @@ public final class ArticleStatus: Hashable {
} }
public convenience init(articleID: String, read: Bool, dateArrived: Date) { public convenience init(articleID: String, read: Bool, dateArrived: Date) {
self.init(articleID: articleID, read: read, starred: false, userDeleted: false, dateArrived: dateArrived) self.init(articleID: articleID, read: read, starred: false, userDeleted: false, dateArrived: dateArrived)
} }
public func boolStatus(forKey key: ArticleStatus.Key) -> Bool { public func boolStatus(forKey key: ArticleStatus.Key) -> Bool {
switch key { switch key {
case .read: case .read:
return read return read
@ -56,7 +53,6 @@ public final class ArticleStatus: Hashable {
} }
public func setBoolStatus(_ status: Bool, forKey key: ArticleStatus.Key) { public func setBoolStatus(_ status: Bool, forKey key: ArticleStatus.Key) {
switch key { switch key {
case .read: case .read:
read = status read = status
@ -76,7 +72,6 @@ public final class ArticleStatus: Hashable {
// MARK: - Equatable // MARK: - Equatable
public static func ==(lhs: ArticleStatus, rhs: ArticleStatus) -> Bool { public static func ==(lhs: ArticleStatus, rhs: ArticleStatus) -> Bool {
return lhs.articleID == rhs.articleID && lhs.dateArrived == rhs.dateArrived && lhs.read == rhs.read && lhs.starred == rhs.starred && lhs.userDeleted == rhs.userDeleted return lhs.articleID == rhs.articleID && lhs.dateArrived == rhs.dateArrived && lhs.read == rhs.read && lhs.starred == rhs.starred && lhs.userDeleted == rhs.userDeleted
} }
} }
@ -84,7 +79,6 @@ public final class ArticleStatus: Hashable {
public extension Set where Element == ArticleStatus { public extension Set where Element == ArticleStatus {
func articleIDs() -> Set<String> { func articleIDs() -> Set<String> {
return Set<String>(map { $0.articleID }) return Set<String>(map { $0.articleID })
} }
} }
@ -92,7 +86,6 @@ public extension Set where Element == ArticleStatus {
public extension Array where Element == ArticleStatus { public extension Array where Element == ArticleStatus {
func articleIDs() -> [String] { func articleIDs() -> [String] {
return map { $0.articleID } return map { $0.articleID }
} }
} }

View File

@ -1,6 +1,6 @@
// //
// Attachment.swift // Attachment.swift
// DataModel // NetNewsWire
// //
// Created by Brent Simmons on 7/1/17. // Created by Brent Simmons on 7/1/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved. // Copyright © 2017 Ranchero Software, LLC. All rights reserved.

View File

@ -1,6 +1,6 @@
// //
// Author.swift // Author.swift
// DataModel // NetNewsWire
// //
// Created by Brent Simmons on 7/1/17. // Created by Brent Simmons on 7/1/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved. // Copyright © 2017 Ranchero Software, LLC. All rights reserved.

View File

@ -1,6 +1,6 @@
// //
// DatabaseID.swift // DatabaseID.swift
// Data // NetNewsWire
// //
// Created by Brent Simmons on 7/15/17. // Created by Brent Simmons on 7/15/17.
// Copyright © 2017 Ranchero Software. All rights reserved. // Copyright © 2017 Ranchero Software. All rights reserved.
@ -17,7 +17,6 @@ private var databaseIDCache = [String: String]()
private var databaseIDCacheLock = os_unfair_lock_s() private var databaseIDCacheLock = os_unfair_lock_s()
public func databaseIDWithString(_ s: String) -> String { public func databaseIDWithString(_ s: String) -> String {
os_unfair_lock_lock(&databaseIDCacheLock) os_unfair_lock_lock(&databaseIDCacheLock)
defer { defer {
os_unfair_lock_unlock(&databaseIDCacheLock) os_unfair_lock_unlock(&databaseIDCacheLock)