Merge branch 'mac-candidate'
This commit is contained in:
commit
0d5ebad782
|
@ -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 don’t, just take the last name and make sure there’s 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 feed’s 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 won’t 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)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// AccountDelegate.swift
|
||||
// Account
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Brent Simmons on 9/16/17.
|
||||
// Copyright © 2017 Ranchero Software, LLC. All rights reserved.
|
||||
|
|
|
@ -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 couldn’t be found and can’t 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 can’t 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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
@ -201,11 +191,6 @@ public final class AccountManager: UnreadCountProvider {
|
|||
return false
|
||||
}
|
||||
|
||||
func updateUnreadCount() {
|
||||
|
||||
unreadCount = calculateUnreadCount(activeAccounts)
|
||||
}
|
||||
|
||||
// MARK: - Fetching Articles
|
||||
|
||||
// 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) {
|
||||
|
||||
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
|
||||
// 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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -77,5 +77,4 @@ final class AccountMetadata: Codable {
|
|||
func valueDidChange(_ key: CodingKeys) {
|
||||
delegate?.valueDidChange(self, key: key)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// ArticleFetcher.swift
|
||||
// Account
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Brent Simmons on 2/4/18.
|
||||
// Copyright © 2018 Ranchero Software, LLC. All rights reserved.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -76,7 +76,6 @@ public extension Container {
|
|||
}
|
||||
|
||||
func flattenedFeeds() -> Set<Feed> {
|
||||
|
||||
var feeds = Set<Feed>()
|
||||
feeds.formUnion(topLevelFeeds)
|
||||
if let folders = folders {
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// DataExtensions.swift
|
||||
// Account
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Brent Simmons on 10/7/17.
|
||||
// Copyright © 2017 Ranchero Software, LLC. All rights reserved.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// Feed.swift
|
||||
// DataModel
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Brent Simmons on 7/1/17.
|
||||
// Copyright © 2017 Ranchero Software, LLC. All rights reserved.
|
||||
|
|
|
@ -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 there’s an existing feed specifier, merge the two so that we have the best data. If one has a title and one doesn’t, 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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 there’s an existing feed specifier, merge the two so that we have the best data. If one has a title and one doesn’t, 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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// Article.swift
|
||||
// Data
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Brent Simmons on 7/1/17.
|
||||
// Copyright © 2017 Ranchero Software, LLC. All rights reserved.
|
||||
|
|
|
@ -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,7 +79,6 @@ public final class ArticleStatus: Hashable {
|
|||
public extension Set where Element == ArticleStatus {
|
||||
|
||||
func articleIDs() -> Set<String> {
|
||||
|
||||
return Set<String>(map { $0.articleID })
|
||||
}
|
||||
}
|
||||
|
@ -92,7 +86,6 @@ public extension Set where Element == ArticleStatus {
|
|||
public extension Array where Element == ArticleStatus {
|
||||
|
||||
func articleIDs() -> [String] {
|
||||
|
||||
return map { $0.articleID }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// Attachment.swift
|
||||
// DataModel
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Brent Simmons on 7/1/17.
|
||||
// Copyright © 2017 Ranchero Software, LLC. All rights reserved.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// Author.swift
|
||||
// DataModel
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Brent Simmons on 7/1/17.
|
||||
// Copyright © 2017 Ranchero Software, LLC. All rights reserved.
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue