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
// DataModel
// NetNewsWire
//
// Created by Brent Simmons on 7/1/17.
// 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) {
switch type {
case .onMyMac:
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(unreadCountDidChange(_:)), name: .UnreadCountDidChange, 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(childrenDidChange(_:)), name: .ChildrenDidChange, object: nil)
pullObjectsFromDisk()
DispatchQueue.main.async {
@ -285,7 +282,6 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
self.delegate.accountDidInitialize(self)
startingUp = false
}
// MARK: - API
@ -307,7 +303,6 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
try CredentialsManager.storeCredentials(credentials, server: server)
delegate.credentials = 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) {
guard !delegate.isOPMLImportInProgress else {
completion(.failure(AccountError.opmlImportInProgress))
return
@ -398,7 +392,6 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
@discardableResult
public func ensureFolder(with name: String) -> Folder? {
// TODO: support subfolders, maybe, some day
if name.isEmpty {
@ -418,7 +411,6 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
}
public func ensureFolder(withFolderNames folderNames: [String]) -> Folder? {
// TODO: support subfolders, maybe, some day.
// 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 {
let metadata = feedMetadata(feedURL: url, feedID: feedID)
let feed = Feed(account: self, url: url, metadata: metadata)
feed.name = name
feed.homePageURL = homePageURL
return feed
}
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) {
guard let children = opmlDocument.children else {
return
}
@ -516,13 +505,11 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
}
public func updateUnreadCounts(for feeds: Set<Feed>) {
if feeds.isEmpty {
return
}
database.fetchUnreadCounts(for: feeds.feedIDs()) { (unreadCountDictionary) in
for feed in feeds {
if let unreadCount = unreadCountDictionary[feed.feedID] {
feed.unreadCount = unreadCount
@ -569,15 +556,12 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
}
}
public func fetchUnreadCountForToday(_ callback: @escaping (Int) -> Void) {
let startOfToday = NSCalendar.startOfToday()
database.fetchUnreadCount(for: flattenedFeeds().feedIDs(), since: startOfToday, callback: callback)
}
public func fetchUnreadCountForStarredArticles(_ callback: @escaping (Int) -> Void) {
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)) {
database.update(feedID: feed.feedID, parsedItems: parsedItems, defaultRead: defaultRead) { (newArticles, updatedArticles) in
var userInfo = [String: Any]()
if let newArticles = newArticles, !newArticles.isEmpty {
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)
}
}
@discardableResult
@ -675,7 +656,6 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
noteStatusesForArticlesDidChange(updatedArticles)
return updatedArticles
}
func ensureStatuses(_ articleIDs: Set<String>, _ statusKey: ArticleStatus.Key, _ flag: Bool) {
@ -721,7 +701,6 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
// MARK: - Debug
public func debugDropConditionalGetInfo() {
#if DEBUG
flattenedFeeds().forEach{ $0.debugDropConditionalGetInfo() }
#endif
@ -740,7 +719,6 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
// MARK: - Notifications
@objc func downloadProgressDidChange(_ note: Notification) {
guard let noteObject = note.object as? DownloadProgress, noteObject === refreshProgress else {
return
}
@ -756,14 +734,12 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
}
@objc func batchUpdateDidPerform(_ note: Notification) {
flattenedFeedsNeedUpdate = true
rebuildFeedDictionaries()
updateUnreadCount()
}
@objc func childrenDidChange(_ note: Notification) {
guard let object = note.object else {
return
}
@ -777,14 +753,12 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
}
@objc func displayNameDidChange(_ note: Notification) {
if let folder = note.object as? Folder, folder.account === self {
structureDidChange()
}
}
@objc func saveToDiskIfNeeded() {
if dirty && !isDeleted {
saveToDisk()
}
@ -811,7 +785,6 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
// MARK: - Equatable
public class func ==(lhs: Account, rhs: Account) -> Bool {
return lhs === rhs
}
}
@ -948,7 +921,6 @@ private extension Account {
}
func validateUnreadCount(_ feed: Feed, _ articles: Set<Article>) {
// articles must contain all the unread articles for the feed.
// The unread number should match the feeds unread count.
@ -1029,11 +1001,9 @@ private extension Account {
BatchUpdate.shared.perform {
loadOPMLItems(children, parentFolder: nil)
}
}
func saveToDisk() {
dirty = false
let opmlDocumentString = opmlDocument()
@ -1131,7 +1101,6 @@ private extension Account {
}
func loadOPMLItems(_ items: [RSOPMLItem], parentFolder: Folder?) {
var feedsToAdd = Set<Feed>()
items.forEach { (item) in
@ -1181,7 +1150,6 @@ private extension Account {
}
func noteStatusesForArticlesDidChange(_ articles: Set<Article>) {
let feeds = Set(articles.compactMap { $0.feed })
let statuses = Set(articles.map { $0.status })
@ -1193,10 +1161,9 @@ private extension Account {
}
func fetchAllUnreadCounts() {
fetchingAllUnreadCounts = true
database.fetchAllNonZeroUnreadCounts { (unreadCountDictionary) in
database.fetchAllNonZeroUnreadCounts { (unreadCountDictionary) in
if unreadCountDictionary.isEmpty {
self.fetchingAllUnreadCounts = false
self.updateUnreadCount()
@ -1205,7 +1172,6 @@ private extension Account {
}
self.flattenedFeeds().forEach{ (feed) in
// When the unread count is zero, it wont appear in unreadCountDictionary.
if let unreadCount = unreadCountDictionary[feed.feedID] {
@ -1227,10 +1193,8 @@ private extension Account {
extension Account {
public func existingFeed(with feedID: String) -> Feed? {
return idToFeedDictionary[feedID]
}
}
// MARK: - OPMLRepresentable
@ -1238,7 +1202,6 @@ extension Account {
extension Account: OPMLRepresentable {
public func OPMLString(indentLevel: Int) -> String {
var s = ""
for feed in topLevelFeeds {
s += feed.OPMLString(indentLevel: indentLevel + 1)

View File

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

View File

@ -1,6 +1,6 @@
//
// AccountError.swift
// Account
// NetNewsWire
//
// Created by Maurice Parker on 5/26/19.
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
@ -19,9 +19,9 @@ public enum AccountError: LocalizedError {
public var errorDescription: String? {
switch self {
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:
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:
return NSLocalizedString("An OPML import for this account is already running.", comment: "Import running")
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")
return NSString.localizedStringWithFormat(localizedText as NSString, account.nameForDisplay, error.localizedDescription) as String
}
}

View File

@ -13,12 +13,9 @@ import Articles
// Main thread only.
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 static let shared = AccountManager()
@ -27,6 +24,9 @@ public final class AccountManager: UnreadCountProvider {
private let accountsFolder = RSDataSubfolder(nil, "Accounts")!
private var accountsDictionary = [String: Account]()
private let defaultAccountFolderName = "OnMyMac"
private let defaultAccountIdentifier = "OnMyMac"
public var isUnreadCountsInitialized: Bool {
for account in activeAccounts {
if !account.isUnreadCountsInitialized {
@ -76,9 +76,7 @@ public final class AccountManager: UnreadCountProvider {
}
public init() {
// The local "On My Mac" account must always exist, even if it's empty.
let localAccountFolder = (accountsFolder as NSString).appendingPathComponent("OnMyMac")
do {
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 {
let accountID = UUID().uuidString
let accountFolder = (accountsFolder as NSString).appendingPathComponent("\(type.rawValue)_\(accountID)")
@ -124,7 +121,6 @@ public final class AccountManager: UnreadCountProvider {
}
public func deleteAccount(_ account: Account) {
guard !account.refreshInProgress else {
return
}
@ -142,16 +138,13 @@ public final class AccountManager: UnreadCountProvider {
updateUnreadCount()
NotificationCenter.default.post(name: .AccountsDidChange, object: self)
}
public func existingAccount(with accountID: String) -> Account? {
return accountsDictionary[accountID]
}
public func refreshAll(errorHandler: @escaping (Error) -> Void) {
activeAccounts.forEach { account in
account.refreshAll() { result in
switch result {
@ -162,7 +155,6 @@ public final class AccountManager: UnreadCountProvider {
}
}
}
}
public func syncArticleStatusAll(completion: (() -> Void)? = nil) {
@ -181,7 +173,6 @@ public final class AccountManager: UnreadCountProvider {
}
public func anyAccountHasAtLeastOneFeed() -> Bool {
for account in activeAccounts {
if account.hasAtLeastOneFeed() {
return true
@ -192,7 +183,6 @@ public final class AccountManager: UnreadCountProvider {
}
public func anyAccountHasFeedWithURL(_ urlString: String) -> Bool {
for account in activeAccounts {
if let _ = account.existingFeed(withURL: urlString) {
return true
@ -200,11 +190,6 @@ public final class AccountManager: UnreadCountProvider {
}
return false
}
func updateUnreadCount() {
unreadCount = calculateUnreadCount(activeAccounts)
}
// MARK: - Fetching Articles
@ -238,10 +223,9 @@ public final class AccountManager: UnreadCountProvider {
}
}
// MARK: Notifications
// MARK: - Notifications
@objc dynamic func unreadCountDidChange(_ notification: Notification) {
guard let _ = notification.object as? Account else {
return
}
@ -251,15 +235,21 @@ public final class AccountManager: UnreadCountProvider {
@objc func accountStateDidChange(_ notification: Notification) {
updateUnreadCount()
}
// MARK: Private
}
private func loadAccount(_ accountSpecifier: AccountSpecifier) -> Account? {
// MARK: - Private
private extension AccountManager {
func updateUnreadCount() {
unreadCount = calculateUnreadCount(activeAccounts)
}
func loadAccount(_ accountSpecifier: AccountSpecifier) -> Account? {
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)
if let accountSpecifier = AccountSpecifier(folderPath: folderPath) {
return loadAccount(accountSpecifier)
@ -267,8 +257,7 @@ public final class AccountManager: UnreadCountProvider {
return nil
}
private func readAccountsFromDisk() {
func readAccountsFromDisk() {
var filenames: [String]?
do {
@ -280,7 +269,6 @@ public final class AccountManager: UnreadCountProvider {
}
filenames?.forEach { (oneFilename) in
guard oneFilename != defaultAccountFolderName else {
return
}
@ -290,12 +278,10 @@ public final class AccountManager: UnreadCountProvider {
}
}
private func sortByName(_ accounts: [Account]) -> [Account] {
func sortByName(_ accounts: [Account]) -> [Account] {
// LocalAccount is first.
return accounts.sorted { (account1, account2) -> Bool in
if account1 === defaultAccount {
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 {
let type: AccountType
@ -322,8 +301,8 @@ private struct AccountSpecifier {
let folderName: String
let dataFilePath: String
init?(folderPath: String) {
init?(folderPath: String) {
if !FileManager.default.rs_fileIsFolder(folderPath) {
return nil
}
@ -335,18 +314,21 @@ private struct AccountSpecifier {
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
}
self.folderPath = folderPath
self.folderName = name
self.type = acctType
self.type = accountType
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) {
delegate?.valueDidChange(self, key: key)
}
}

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
//
// ContainerPath.swift
// Account
// NetNewsWire
//
// Created by Brent Simmons on 11/4/17.
// 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"]
public init(account: Account, folders: [Folder]) {
self.account = account
self.names = folders.map { $0.nameForDisplay }
self.isTopLevel = folders.isEmpty
@ -31,7 +30,6 @@ public struct ContainerPath {
}
public func resolveContainer() -> Container? {
// 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.

View File

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

View File

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

View File

@ -1,6 +1,6 @@
//
// FeedFinder.swift
// FeedFinder
// NetNewsWire
//
// Created by Brent Simmons on 8/2/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
@ -14,9 +14,7 @@ import RSCore
class FeedFinder {
static func find(url: URL, completion: @escaping (Result<Set<FeedSpecifier>, Error>) -> Void) {
downloadUsingCache(url) { (data, response, error) in
if response?.forcedStatusCode == 404 {
completion(.failure(AccountError.createErrorNotFound))
return
@ -49,17 +47,13 @@ class FeedFinder {
}
FeedFinder.findFeedsInHTMLPage(htmlData: data, urlString: url.absoluteString, completion: completion)
}
}
}
private extension FeedFinder {
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 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) {
// Feeds in the <head> section we automatically assume are feeds.
// If there are none from the <head> section,
// then possible feeds in <body> section are downloaded individually
@ -99,16 +92,17 @@ private extension FeedFinder {
if didFindFeedInHTMLHead {
completion(.success(Set(feedSpecifiers.values)))
return
} else if feedSpecifiersToDownload.isEmpty {
}
else if feedSpecifiersToDownload.isEmpty {
completion(.failure(AccountError.createErrorNotFound))
return
} else {
}
else {
downloadFeedSpecifiers(feedSpecifiersToDownload, feedSpecifiers: feedSpecifiers, completion: completion)
}
}
static func possibleFeedsInHTMLPage(htmlData: Data, urlString: String) -> Set<FeedSpecifier> {
let parserData = ParserData(url: urlString, data: htmlData)
var feedSpecifiers = HTMLFeedFinder(parserData: parserData).feedSpecifiers
@ -139,7 +133,6 @@ private extension FeedFinder {
let group = DispatchGroup()
for downloadFeedSpecifier in downloadFeedSpecifiers {
guard let url = URL(string: downloadFeedSpecifier.urlString) else {
continue
}
@ -159,12 +152,10 @@ private extension FeedFinder {
group.notify(queue: DispatchQueue.main) {
completion(.success(Set(resultFeedSpecifiers.values)))
}
}
static func isFeed(_ data: Data, _ urlString: String) -> Bool {
let parserData = ParserData(url: urlString, data: data)
return FeedParser.canParse(parserData)
}
}

View File

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

View File

@ -1,6 +1,6 @@
//
// HTMLFeedFinder.swift
// FeedFinder
// NetNewsWire
//
// Created by Brent Simmons on 8/7/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
@ -20,7 +20,6 @@ class HTMLFeedFinder {
private var feedSpecifiersDictionary = [String: FeedSpecifier]()
init(parserData: ParserData) {
let metadata = RSHTMLMetadataParser.htmlMetadata(with: parserData)
for oneFeedLink in metadata.feedLinks {
@ -46,7 +45,6 @@ class HTMLFeedFinder {
private extension HTMLFeedFinder {
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 let existingFeedSpecifier = feedSpecifiersDictionary[feedSpecifier.urlString] {
@ -59,7 +57,6 @@ private extension HTMLFeedFinder {
}
func urlStringMightBeFeed(_ urlString: String) -> Bool {
let massagedURLString = urlString.replacingOccurrences(of: "buzzfeed", with: "_")
for oneMatch in feedURLWordsToMatch {
@ -73,7 +70,6 @@ private extension HTMLFeedFinder {
}
func linkMightBeFeed(_ link: RSHTMLLink) -> Bool {
if let linkURLString = link.urlString, urlStringMightBeFeed(linkURLString) {
return true
}

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
//
// LocalAccountDelegate.swift
// Account
// NetNewsWire
//
// Created by Brent Simmons on 9/16/17.
// 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) {
var fileData: Data?
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) {
guard let url = URL(string: urlString) else {
completion(.failure(LocalAccountDelegateError.invalidParameter))
return
@ -99,8 +97,6 @@ final class LocalAccountDelegate: AccountDelegate {
switch result {
case .success(let feedSpecifiers):
guard let bestFeedSpecifier = FeedSpecifier.bestFeed(in: feedSpecifiers),
let url = URL(string: bestFeedSpecifier.urlString) else {
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)
InitialFeedDownloader.download(url) { parsedFeed in
self.refreshProgress.completeTask()
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) {
return completion(.success(nil))
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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