NetNewsWire/Frameworks/Account/Folder.swift

209 lines
4.5 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// Folder.swift
// DataModel
//
// Created by Brent Simmons on 7/1/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved.
//
import Foundation
import Data
public final class Folder: DisplayNameProvider, Container, UnreadCountProvider {
public let account: Account
public var children = [AnyObject]()
var name: String?
static let untitledName = NSLocalizedString("Untitled ƒ", comment: "Folder name")
// MARK: - Fetching Articles
public func fetchArticles() -> Set<Article> {
return account.fetchArticles(folder: self)
}
// MARK: - DisplayNameProvider
public var nameForDisplay: String {
get {
return name ?? Folder.untitledName
}
}
// MARK: - UnreadCountProvider
public var unreadCount = 0 {
didSet {
if unreadCount != oldValue {
postUnreadCountDidChangeNotification()
}
}
}
// MARK: - Init
init(account: Account, name: String?) {
self.account = account
self.name = name
}
// MARK: - Disk Dictionary
private struct Key {
static let name = "name"
static let unreadCount = "unreadCount"
static let children = "children"
}
convenience init?(account: Account, dictionary: [String: Any]) {
let name = dictionary[Key.name] as? String
self.init(account: account, name: name)
if let childrenArray = dictionary[Key.children] as? [[String: Any]] {
self.children = Folder.objects(with: childrenArray, account: account)
}
if let savedUnreadCount = dictionary[Key.unreadCount] as? Int {
self.unreadCount = savedUnreadCount
}
}
var dictionary: [String: Any] {
get {
var d = [String: Any]()
if let name = name {
d[Key.name] = name
}
if unreadCount > 0 {
d[Key.unreadCount] = unreadCount
}
let childObjects = children.flatMap { (child) -> [String: Any]? in
if let feed = child as? Feed {
return feed.dictionary
}
if let folder = child as? Folder, account.supportsSubFolders {
return folder.dictionary
}
assertionFailure("Expected a feed or a folder.");
return nil
}
if !childObjects.isEmpty {
d[Key.children] = childObjects
}
return d
}
}
// MARK: Feeds
func addFeed(_ feed: Feed) -> Bool {
// The feed has been uniqued at this point.
// Return true in the case where the feed is already a child.
if childrenContainsFeed(feed) {
return true
}
children += [feed]
return true
}
}
// MARK: - Private
private extension Folder {
func childrenContainsFeed(_ feed: Feed) -> Bool {
return children.contains(where: { (object) -> Bool in
if let oneFeed = object as? Feed {
if oneFeed.feedID == feed.feedID {
return true
}
}
return false
})
}
}
// MARK: - Disk
private extension Folder {
static func objects(with diskObjects: [[String: Any]], account: Account) -> [AnyObject] {
if account.supportsSubFolders {
return account.objects(with: diskObjects)
}
else {
let flattenedFeeds = feedsOnly(with: diskObjects, account: account)
return Array(flattenedFeeds) as [AnyObject]
}
}
static func feedsOnly(with diskObjects: [[String: Any]], account: Account) -> Set<Feed> {
// This Folder doesnt support subfolders, but they might exist on disk.
// (For instance: a user might manually edit the plist to add subfolders.)
// Create a flattened version of the feeds.
var feeds = Set<Feed>()
for diskObject in diskObjects {
if Feed.isFeedDictionary(diskObject) {
if let feed = Feed(accountID: account.accountID, dictionary: diskObject) {
feeds.insert(feed)
}
}
else { // Folder
if let subFolderChildren = diskObject[Key.children] as? [[String: Any]] {
let subFolderFeeds = feedsOnly(with: subFolderChildren, account: account)
feeds.formUnion(subFolderFeeds)
}
}
}
return feeds
}
}
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)
var hasAtLeastOneChild = false
for child in children {
if let opmlObject = child as? OPMLRepresentable {
s += opmlObject.OPMLString(indentLevel: indentLevel + 1)
hasAtLeastOneChild = true
}
}
if !hasAtLeastOneChild {
s = "<outline text=\"\(escapedTitle)\" title=\"\(escapedTitle)\"/>\n"
s = s.rs_string(byPrependingNumberOfTabs: indentLevel)
return s
}
s = s + NSString.rs_string(withNumberOfTabs: indentLevel) + "</outline>\n"
return s
}
}