NetNewsWire/Frameworks/Account/Folder.swift

209 lines
5.0 KiB
Swift
Raw Normal View History

//
// Folder.swift
// NetNewsWire
//
// Created by Brent Simmons on 7/1/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved.
//
import Foundation
import Articles
2017-11-04 22:53:21 +01:00
import RSCore
2019-11-15 13:19:14 +01:00
public final class Folder: Feed, Renamable, Container, Hashable {
public var defaultReadFilterType: ReadFilterType {
2019-11-22 01:22:43 +01:00
return .read
}
public var containerID: ContainerIdentifier? {
guard let accountID = account?.accountID else {
assertionFailure("Expected feed.account, but got nil.")
return nil
}
return ContainerIdentifier.folder(accountID, nameForDisplay)
}
2019-11-15 13:19:14 +01:00
public var feedID: FeedIdentifier? {
guard let accountID = account?.accountID else {
assertionFailure("Expected feed.account, but got nil.")
return nil
}
return FeedIdentifier.folder(accountID, nameForDisplay)
}
public weak var account: Account?
public var topLevelWebFeeds: Set<WebFeed> = Set<WebFeed>()
public var folders: Set<Folder>? = nil // subfolders are not supported, so this is always nil
public var name: String? {
didSet {
postDisplayNameDidChangeNotification()
}
}
2017-09-28 22:16:47 +02:00
static let untitledName = NSLocalizedString("Untitled ƒ", comment: "Folder name")
public let folderID: Int // not saved: per-run only
public var externalID: String? = nil
static var incrementingID = 0
// MARK: - DisplayNameProvider
public var nameForDisplay: String {
return name ?? Folder.untitledName
}
2019-10-03 16:53:21 +02:00
// MARK: - UnreadCountProvider
public var unreadCount = 0 {
didSet {
if unreadCount != oldValue {
postUnreadCountDidChangeNotification()
}
}
}
// MARK: - Renamable
2019-05-06 17:53:20 +02:00
public func rename(to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
guard let account = account else { return }
account.renameFolder(self, to: name, completion: completion)
}
// MARK: - Init
init(account: Account, name: String?) {
self.account = account
self.name = name
let folderID = Folder.incrementingID
Folder.incrementingID += 1
self.folderID = folderID
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(childrenDidChange(_:)), name: .ChildrenDidChange, object: self)
}
// MARK: - Notifications
@objc func unreadCountDidChange(_ note: Notification) {
if let object = note.object {
if objectIsChild(object as AnyObject) {
updateUnreadCount()
}
}
}
@objc func childrenDidChange(_ note: Notification) {
updateUnreadCount()
}
// MARK: Container
public func flattenedWebFeeds() -> Set<WebFeed> {
// Since sub-folders are not supported, its always the top-level feeds.
return topLevelWebFeeds
}
public func objectIsChild(_ object: AnyObject) -> Bool {
// Folders contain Feed objects only, at least for now.
guard let feed = object as? WebFeed else {
return false
}
return topLevelWebFeeds.contains(feed)
}
public func addWebFeed(_ feed: WebFeed) {
topLevelWebFeeds.insert(feed)
postChildrenDidChangeNotification()
}
public func addFeeds(_ feeds: Set<WebFeed>) {
guard !feeds.isEmpty else {
return
}
topLevelWebFeeds.formUnion(feeds)
postChildrenDidChangeNotification()
}
public func removeWebFeed(_ feed: WebFeed) {
topLevelWebFeeds.remove(feed)
postChildrenDidChangeNotification()
}
public func removeFeeds(_ feeds: Set<WebFeed>) {
guard !feeds.isEmpty else {
return
}
topLevelWebFeeds.subtract(feeds)
postChildrenDidChangeNotification()
}
// MARK: - Hashable
public func hash(into hasher: inout Hasher) {
hasher.combine(folderID)
}
// MARK: - Equatable
static public func ==(lhs: Folder, rhs: Folder) -> Bool {
return lhs === rhs
}
2017-10-01 19:59:35 +02:00
}
// MARK: - Private
private extension Folder {
func updateUnreadCount() {
var updatedUnreadCount = 0
for feed in topLevelWebFeeds {
updatedUnreadCount += feed.unreadCount
}
unreadCount = updatedUnreadCount
}
func childrenContain(_ feed: WebFeed) -> Bool {
return topLevelWebFeeds.contains(feed)
2017-10-01 19:59:35 +02:00
}
}
// MARK: - OPMLRepresentable
extension Folder: OPMLRepresentable {
public func OPMLString(indentLevel: Int, strictConformance: Bool) -> String {
let attrExternalID: String = {
if !strictConformance, let externalID = externalID {
return " nnw_externalID=\"\(externalID)\""
} else {
return ""
}
}()
let escapedTitle = nameForDisplay.rs_stringByEscapingSpecialXMLCharacters()
var s = "<outline text=\"\(escapedTitle)\" title=\"\(escapedTitle)\"\(attrExternalID)>\n"
s = s.rs_string(byPrependingNumberOfTabs: indentLevel)
var hasAtLeastOneChild = false
for feed in topLevelWebFeeds.sorted(by: { $0.nameForDisplay < $1.nameForDisplay }) {
s += feed.OPMLString(indentLevel: indentLevel + 1, strictConformance: strictConformance)
hasAtLeastOneChild = true
}
if !hasAtLeastOneChild {
s = "<outline text=\"\(escapedTitle)\" title=\"\(escapedTitle)\"\(attrExternalID)/>\n"
s = s.rs_string(byPrependingNumberOfTabs: indentLevel)
return s
}
s = s + NSString.rs_string(withNumberOfTabs: indentLevel) + "</outline>\n"
return s
}
}