Made launch performance *much* faster for large (thousands of feeds) subscriptions list. Also: split container.children in container.topLevelFeeds and container.folders. This simplifies a bunch of things, and makes some things faster.
This commit is contained in:
parent
f88c58a130
commit
a914b3949b
|
@ -37,6 +37,7 @@ public enum AccountType: Int {
|
|||
|
||||
public final class Account: DisplayNameProvider, UnreadCountProvider, Container, Hashable {
|
||||
|
||||
|
||||
public struct UserInfoKey {
|
||||
public static let newArticles = "newArticles" // AccountDidDownloadArticles
|
||||
public static let updatedArticles = "updatedArticles" // AccountDidDownloadArticles
|
||||
|
@ -48,8 +49,20 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||
public let accountID: String
|
||||
public let type: AccountType
|
||||
public var nameForDisplay = ""
|
||||
public var children = [AnyObject]()
|
||||
var idToFeedDictionary = [String: Feed]()
|
||||
public var topLevelFeeds = Set<Feed>()
|
||||
public var folders: Set<Folder>? = Set<Folder>()
|
||||
|
||||
private var feedDictionaryNeedsUpdate = true
|
||||
private var _idToFeedDictionary = [String: Feed]()
|
||||
var idToFeedDictionary: [String: Feed] {
|
||||
if feedDictionaryNeedsUpdate {
|
||||
rebuildFeedDictionaries()
|
||||
}
|
||||
return _idToFeedDictionary
|
||||
}
|
||||
|
||||
private var fetchingAllUnreadCounts = false
|
||||
|
||||
let settingsFile: String
|
||||
let dataFolder: String
|
||||
let database: ArticlesDatabase
|
||||
|
@ -65,6 +78,9 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||
private var unreadCounts = [String: Int]() // [feedID: Int]
|
||||
private let opmlFilePath: String
|
||||
|
||||
private var _flattenedFeeds = Set<Feed>()
|
||||
private var flattenedFeedsNeedUpdate = true
|
||||
|
||||
private struct SettingsKey {
|
||||
static let unreadCount = "unreadCount"
|
||||
}
|
||||
|
@ -204,7 +220,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||
}
|
||||
|
||||
let folder = Folder(account: self, name: name)
|
||||
children += [folder]
|
||||
folders!.insert(folder)
|
||||
dirty = true
|
||||
|
||||
postChildrenDidChangeNotification()
|
||||
|
@ -244,21 +260,30 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||
didAddFeed = folder.addFeed(feed)
|
||||
}
|
||||
else {
|
||||
if !topLevelObjectsContainsFeed(feed) {
|
||||
children += [feed]
|
||||
if !topLevelFeeds.contains(feed) {
|
||||
topLevelFeeds.insert(feed)
|
||||
postChildrenDidChangeNotification()
|
||||
didAddFeed = true
|
||||
}
|
||||
didAddFeed = true
|
||||
}
|
||||
|
||||
if didAddFeed {
|
||||
addToFeedDictionaries(feed)
|
||||
dirty = true
|
||||
structureDidChange()
|
||||
}
|
||||
|
||||
|
||||
return didAddFeed
|
||||
}
|
||||
|
||||
public func addFeeds(_ feeds: Set<Feed>, to folder: Folder?) {
|
||||
if let folder = folder {
|
||||
folder.addFeeds(feeds)
|
||||
}
|
||||
else {
|
||||
topLevelFeeds.formUnion(feeds)
|
||||
}
|
||||
structureDidChange()
|
||||
}
|
||||
|
||||
public func createFeed(with name: String?, editedName: String?, url: String) -> Feed? {
|
||||
|
||||
// For syncing, this may need to be an async method with a callback,
|
||||
|
@ -285,13 +310,12 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||
|
||||
// TODO: support subfolders, maybe, some day, if one of the sync systems
|
||||
// supports subfolders. But, for now, parentFolder is ignored.
|
||||
|
||||
if objectIsChild(folder) {
|
||||
if folders!.contains(folder) {
|
||||
return true
|
||||
}
|
||||
children += [folder]
|
||||
folders!.insert(folder)
|
||||
postChildrenDidChangeNotification()
|
||||
rebuildFeedDictionaries()
|
||||
structureDidChange()
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -300,9 +324,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||
guard let children = opmlDocument.children else {
|
||||
return
|
||||
}
|
||||
rebuildFeedDictionaries()
|
||||
importOPMLItems(children, parentFolder: nil)
|
||||
saveToDisk()
|
||||
structureDidChange()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.refreshAll()
|
||||
|
@ -450,6 +473,35 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||
unreadCounts[feed.feedID] = unreadCount
|
||||
}
|
||||
|
||||
public func structureDidChange() {
|
||||
// Feeds were added or deleted. Or folders added or deleted.
|
||||
// Or feeds inside folders were added or deleted.
|
||||
dirty = true
|
||||
flattenedFeedsNeedUpdate = true
|
||||
feedDictionaryNeedsUpdate = true
|
||||
}
|
||||
|
||||
// MARK: - Container
|
||||
|
||||
public func flattenedFeeds() -> Set<Feed> {
|
||||
if flattenedFeedsNeedUpdate {
|
||||
updateFlattenedFeeds()
|
||||
}
|
||||
return _flattenedFeeds
|
||||
}
|
||||
|
||||
public func deleteFeed(_ feed: Feed) {
|
||||
topLevelFeeds.remove(feed)
|
||||
structureDidChange()
|
||||
postChildrenDidChangeNotification()
|
||||
}
|
||||
|
||||
public func deleteFolder(_ folder: Folder) {
|
||||
folders?.remove(folder)
|
||||
structureDidChange()
|
||||
postChildrenDidChangeNotification()
|
||||
}
|
||||
|
||||
// MARK: - Debug
|
||||
|
||||
public func debugDropConditionalGetInfo() {
|
||||
|
@ -482,6 +534,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||
|
||||
@objc func batchUpdateDidPerform(_ note: Notification) {
|
||||
|
||||
flattenedFeedsNeedUpdate = true
|
||||
rebuildFeedDictionaries()
|
||||
updateUnreadCount()
|
||||
}
|
||||
|
@ -492,17 +545,17 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||
return
|
||||
}
|
||||
if let account = object as? Account, account === self {
|
||||
dirty = true
|
||||
structureDidChange()
|
||||
}
|
||||
if let folder = object as? Folder, folder.account === self {
|
||||
dirty = true
|
||||
structureDidChange()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func displayNameDidChange(_ note: Notification) {
|
||||
|
||||
if let folder = note.object as? Folder, folder.account === self {
|
||||
dirty = true
|
||||
structureDidChange()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -582,11 +635,20 @@ private extension Account {
|
|||
guard let childrenArray = d[Key.children] as? [[String: Any]] else {
|
||||
return
|
||||
}
|
||||
children = objects(with: childrenArray)
|
||||
rebuildFeedDictionaries()
|
||||
|
||||
let userInfo = d[Key.userInfo] as? NSDictionary
|
||||
delegate.update(account: self, withUserInfo: userInfo)
|
||||
let children = objects(with: childrenArray)
|
||||
var feeds = Set<Feed>()
|
||||
var folders = Set<Folder>()
|
||||
for oneChild in children {
|
||||
if let feed = oneChild as? Feed {
|
||||
feeds.insert(feed)
|
||||
}
|
||||
else if let folder = oneChild as? Folder {
|
||||
folders.insert(folder)
|
||||
}
|
||||
}
|
||||
self.topLevelFeeds = feeds
|
||||
self.folders = folders
|
||||
structureDidChange()
|
||||
|
||||
// Rename plist file so we don’t see it next time.
|
||||
let renamedFilePath = (dataFolder as NSString).appendingPathComponent("AccountData-old.plist")
|
||||
|
@ -624,11 +686,13 @@ private extension Account {
|
|||
NSApplication.shared.presentError(error)
|
||||
return
|
||||
}
|
||||
guard let parsedOPML = opmlDocument else {
|
||||
guard let parsedOPML = opmlDocument, let children = parsedOPML.children else {
|
||||
return
|
||||
}
|
||||
|
||||
importOPML(parsedOPML)
|
||||
BatchUpdate.shared.perform {
|
||||
importOPMLItems(children, parentFolder: nil)
|
||||
}
|
||||
}
|
||||
|
||||
func saveToDisk() {
|
||||
|
@ -650,50 +714,47 @@ private extension Account {
|
|||
|
||||
private extension Account {
|
||||
|
||||
func updateFlattenedFeeds() {
|
||||
var feeds = Set<Feed>()
|
||||
feeds.formUnion(topLevelFeeds)
|
||||
for folder in folders! {
|
||||
feeds.formUnion(folder.flattenedFeeds())
|
||||
}
|
||||
|
||||
_flattenedFeeds = feeds
|
||||
flattenedFeedsNeedUpdate = false
|
||||
}
|
||||
|
||||
func rebuildFeedDictionaries() {
|
||||
|
||||
var urlDictionary = [String: Feed]()
|
||||
var idDictionary = [String: Feed]()
|
||||
|
||||
flattenedFeeds().forEach { (feed) in
|
||||
urlDictionary[feed.url] = feed
|
||||
idDictionary[feed.feedID] = feed
|
||||
}
|
||||
|
||||
idToFeedDictionary = idDictionary
|
||||
}
|
||||
|
||||
func addToFeedDictionaries(_ feed: Feed) {
|
||||
|
||||
idToFeedDictionary[feed.feedID] = feed
|
||||
}
|
||||
|
||||
func topLevelObjectsContainsFeed(_ 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
|
||||
})
|
||||
_idToFeedDictionary = idDictionary
|
||||
feedDictionaryNeedsUpdate = false
|
||||
}
|
||||
|
||||
func createFeed(with opmlFeedSpecifier: RSOPMLFeedSpecifier) -> Feed {
|
||||
|
||||
let feed = Feed(account: self, url: opmlFeedSpecifier.feedURL, feedID: opmlFeedSpecifier.feedURL)
|
||||
feed.editedName = opmlFeedSpecifier.title
|
||||
if let feedTitle = opmlFeedSpecifier.title, feed.editedName == nil {
|
||||
feed.editedName = feedTitle
|
||||
}
|
||||
return feed
|
||||
}
|
||||
|
||||
func importOPMLItems(_ items: [RSOPMLItem], parentFolder: Folder?) {
|
||||
|
||||
var feedsToAdd = Set<Feed>()
|
||||
|
||||
items.forEach { (item) in
|
||||
|
||||
if let feedSpecifier = item.feedSpecifier {
|
||||
let feed = createFeed(with: feedSpecifier)
|
||||
addFeed(feed, to: parentFolder)
|
||||
feedsToAdd.insert(feed)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -713,11 +774,21 @@ private extension Account {
|
|||
importOPMLItems(itemChildren, parentFolder: folder)
|
||||
}
|
||||
}
|
||||
|
||||
if !feedsToAdd.isEmpty {
|
||||
addFeeds(feedsToAdd, to: parentFolder)
|
||||
}
|
||||
}
|
||||
|
||||
func updateUnreadCount() {
|
||||
|
||||
unreadCount = calculateUnreadCount(flattenedFeeds())
|
||||
if fetchingAllUnreadCounts {
|
||||
return
|
||||
}
|
||||
var updatedUnreadCount = 0
|
||||
for feed in flattenedFeeds() {
|
||||
updatedUnreadCount += feed.unreadCount
|
||||
}
|
||||
unreadCount = updatedUnreadCount
|
||||
}
|
||||
|
||||
func noteStatusesForArticlesDidChange(_ articles: Set<Article>) {
|
||||
|
@ -734,6 +805,7 @@ private extension Account {
|
|||
|
||||
func fetchAllUnreadCounts() {
|
||||
|
||||
fetchingAllUnreadCounts = true
|
||||
database.fetchAllNonZeroUnreadCounts { (unreadCountDictionary) in
|
||||
|
||||
if unreadCountDictionary.isEmpty {
|
||||
|
@ -751,6 +823,7 @@ private extension Account {
|
|||
feed.unreadCount = 0
|
||||
}
|
||||
}
|
||||
self.fetchingAllUnreadCounts = false
|
||||
self.updateUnreadCount()
|
||||
}
|
||||
}
|
||||
|
@ -764,6 +837,7 @@ extension Account {
|
|||
|
||||
return idToFeedDictionary[feedID]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - OPMLRepresentable
|
||||
|
@ -773,10 +847,11 @@ extension Account: OPMLRepresentable {
|
|||
public func OPMLString(indentLevel: Int) -> String {
|
||||
|
||||
var s = ""
|
||||
for oneObject in children {
|
||||
if let oneOPMLObject = oneObject as? OPMLRepresentable {
|
||||
s += oneOPMLObject.OPMLString(indentLevel: indentLevel + 1)
|
||||
}
|
||||
for feed in topLevelFeeds {
|
||||
s += feed.OPMLString(indentLevel: indentLevel + 1)
|
||||
}
|
||||
for folder in folders! {
|
||||
s += folder.OPMLString(indentLevel: indentLevel + 1)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
|
|
@ -18,7 +18,8 @@ extension Notification.Name {
|
|||
|
||||
public protocol Container: class {
|
||||
|
||||
var children: [AnyObject] { get set }
|
||||
var topLevelFeeds: Set<Feed> { get set }
|
||||
var folders: Set<Folder>? { get set }
|
||||
|
||||
func hasAtLeastOneFeed() -> Bool
|
||||
func objectIsChild(_ object: AnyObject) -> Bool
|
||||
|
@ -29,7 +30,7 @@ public protocol Container: class {
|
|||
func deleteFeed(_ feed: Feed)
|
||||
func deleteFolder(_ folder: Folder)
|
||||
|
||||
//Recursive
|
||||
//Recursive — checks subfolders
|
||||
func flattenedFeeds() -> Set<Feed>
|
||||
func hasFeed(with feedID: String) -> Bool
|
||||
func hasFeed(withURL url: String) -> Bool
|
||||
|
@ -44,43 +45,31 @@ public protocol Container: class {
|
|||
public extension Container {
|
||||
|
||||
func hasAtLeastOneFeed() -> Bool {
|
||||
|
||||
for child in children {
|
||||
if child is Feed {
|
||||
return true
|
||||
}
|
||||
if let folder = child as? Folder {
|
||||
if folder.hasAtLeastOneFeed() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return topLevelFeeds.count > 0
|
||||
}
|
||||
|
||||
func hasChildFolder(with name: String) -> Bool {
|
||||
|
||||
return childFolder(with: name) != nil
|
||||
}
|
||||
|
||||
func childFolder(with name: String) -> Folder? {
|
||||
|
||||
for child in children {
|
||||
if let folder = child as? Folder, folder.name == name {
|
||||
guard let folders = folders else {
|
||||
return nil
|
||||
}
|
||||
for folder in folders {
|
||||
if folder.name == name {
|
||||
return folder
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func objectIsChild(_ object: AnyObject) -> Bool {
|
||||
|
||||
for child in children {
|
||||
if object === child {
|
||||
return true
|
||||
}
|
||||
if let feed = object as? Feed {
|
||||
return topLevelFeeds.contains(feed)
|
||||
}
|
||||
if let folder = object as? Folder {
|
||||
return folders?.contains(folder) ?? false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -88,123 +77,74 @@ public extension Container {
|
|||
func flattenedFeeds() -> Set<Feed> {
|
||||
|
||||
var feeds = Set<Feed>()
|
||||
|
||||
for object in children {
|
||||
if let feed = object as? Feed {
|
||||
feeds.insert(feed)
|
||||
}
|
||||
else if let container = object as? Container {
|
||||
feeds.formUnion(container.flattenedFeeds())
|
||||
feeds.formUnion(topLevelFeeds)
|
||||
if let folders = folders {
|
||||
for folder in folders {
|
||||
feeds.formUnion(folder.flattenedFeeds())
|
||||
}
|
||||
}
|
||||
|
||||
return feeds
|
||||
}
|
||||
|
||||
func hasFeed(with feedID: String) -> Bool {
|
||||
|
||||
return existingFeed(with: feedID) != nil
|
||||
}
|
||||
|
||||
func hasFeed(withURL url: String) -> Bool {
|
||||
|
||||
return existingFeed(withURL: url) != nil
|
||||
}
|
||||
|
||||
func existingFeed(with feedID: String) -> Feed? {
|
||||
|
||||
for child in children {
|
||||
|
||||
if let feed = child as? Feed, feed.feedID == feedID {
|
||||
return feed
|
||||
}
|
||||
if let container = child as? Container, let feed = container.existingFeed(with: feedID) {
|
||||
for feed in flattenedFeeds() {
|
||||
if feed.feedID == feedID {
|
||||
return feed
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func existingFeed(withURL url: String) -> Feed? {
|
||||
|
||||
for child in children {
|
||||
|
||||
if let feed = child as? Feed, feed.url == url {
|
||||
return feed
|
||||
}
|
||||
if let container = child as? Container, let feed = container.existingFeed(withURL: url) {
|
||||
for feed in flattenedFeeds() {
|
||||
if feed.url == url {
|
||||
return feed
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func existingFolder(with name: String) -> Folder? {
|
||||
|
||||
for child in children {
|
||||
|
||||
if let folder = child as? Folder {
|
||||
if folder.name == name {
|
||||
return folder
|
||||
}
|
||||
if let subFolder = folder.existingFolder(with: name) {
|
||||
return subFolder
|
||||
}
|
||||
}
|
||||
guard let folders = folders else {
|
||||
return nil
|
||||
}
|
||||
|
||||
for folder in folders {
|
||||
if folder.name == name {
|
||||
return folder
|
||||
}
|
||||
if let subFolder = folder.existingFolder(with: name) {
|
||||
return subFolder
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func existingFolder(withID folderID: Int) -> Folder? {
|
||||
|
||||
for child in children {
|
||||
|
||||
if let folder = child as? Folder {
|
||||
if folder.folderID == folderID {
|
||||
return folder
|
||||
}
|
||||
if let subFolder = folder.existingFolder(withID: folderID) {
|
||||
return subFolder
|
||||
}
|
||||
}
|
||||
guard let folders = folders else {
|
||||
return nil
|
||||
}
|
||||
|
||||
for folder in folders {
|
||||
if folder.folderID == folderID {
|
||||
return folder
|
||||
}
|
||||
if let subFolder = folder.existingFolder(withID: folderID) {
|
||||
return subFolder
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func indexOf<T: Equatable>(_ object: T) -> Int? {
|
||||
|
||||
return children.index(where: { (child) -> Bool in
|
||||
if let oneObject = child as? T {
|
||||
return oneObject == object
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
func delete<T: Equatable>(_ object: T) {
|
||||
|
||||
if let index = indexOf(object) {
|
||||
children.remove(at: index)
|
||||
postChildrenDidChangeNotification()
|
||||
}
|
||||
}
|
||||
|
||||
func deleteFeed(_ feed: Feed) {
|
||||
|
||||
return delete(feed)
|
||||
}
|
||||
|
||||
func deleteFolder(_ folder: Folder) {
|
||||
|
||||
return delete(folder)
|
||||
}
|
||||
|
||||
func postChildrenDidChangeNotification() {
|
||||
|
||||
NotificationCenter.default.post(name: .ChildrenDidChange, object: self)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,11 +10,13 @@ import Foundation
|
|||
import Articles
|
||||
import RSCore
|
||||
|
||||
public final class Folder: DisplayNameProvider, Container, UnreadCountProvider, Hashable {
|
||||
public final class Folder: DisplayNameProvider, Container, UnreadCountProvider, Hashable {
|
||||
|
||||
|
||||
public weak var account: Account?
|
||||
public var children = [AnyObject]()
|
||||
|
||||
public var topLevelFeeds: Set<Feed> = Set<Feed>()
|
||||
public var folders: Set<Folder>? = nil // subfolders are not supported, so this is always nil
|
||||
|
||||
public var name: String? {
|
||||
didSet {
|
||||
postDisplayNameDidChangeNotification()
|
||||
|
@ -69,21 +71,28 @@ public final class Folder: DisplayNameProvider, Container, UnreadCountProvider,
|
|||
self.init(account: account, name: name)
|
||||
|
||||
if let childrenArray = dictionary[Key.children] as? [[String: Any]] {
|
||||
self.children = Folder.objects(with: childrenArray, account: account)
|
||||
self.topLevelFeeds = Folder.feedsOnly(with: childrenArray, account: account)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Feeds
|
||||
|
||||
|
||||
/// Add a single feed. Return true if number of feeds in folder changes.
|
||||
func addFeed(_ feed: Feed) -> Bool {
|
||||
|
||||
// Return true in the case where the feed is already a child.
|
||||
|
||||
if !childrenContain(feed) {
|
||||
children += [feed]
|
||||
return addFeeds(Set([feed]))
|
||||
}
|
||||
|
||||
/// Add one or more feeds. Return true if number of feeds in folder changes.
|
||||
@discardableResult
|
||||
func addFeeds(_ feedsToAdd: Set<Feed>) -> Bool {
|
||||
let feedCount = topLevelFeeds.count
|
||||
topLevelFeeds.formUnion(feedsToAdd)
|
||||
|
||||
if feedCount != topLevelFeeds.count {
|
||||
postChildrenDidChangeNotification()
|
||||
return true
|
||||
}
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
// MARK: - Notifications
|
||||
|
@ -102,6 +111,30 @@ public final class Folder: DisplayNameProvider, Container, UnreadCountProvider,
|
|||
updateUnreadCount()
|
||||
}
|
||||
|
||||
// MARK: Container
|
||||
|
||||
public func flattenedFeeds() -> Set<Feed> {
|
||||
// Since sub-folders are not supported, it’s always the top-level feeds.
|
||||
return topLevelFeeds
|
||||
}
|
||||
|
||||
public func objectIsChild(_ object: AnyObject) -> Bool {
|
||||
// Folders contain Feed objects only, at least for now.
|
||||
guard let feed = object as? Feed else {
|
||||
return false
|
||||
}
|
||||
return topLevelFeeds.contains(feed)
|
||||
}
|
||||
|
||||
public func deleteFeed(_ feed: Feed) {
|
||||
topLevelFeeds.remove(feed)
|
||||
postChildrenDidChangeNotification()
|
||||
}
|
||||
|
||||
public func deleteFolder(_ folder: Folder) {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
// MARK: - Hashable
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
|
@ -121,17 +154,15 @@ public final class Folder: DisplayNameProvider, Container, UnreadCountProvider,
|
|||
private extension Folder {
|
||||
|
||||
func updateUnreadCount() {
|
||||
|
||||
unreadCount = calculateUnreadCount(children)
|
||||
var updatedUnreadCount = 0
|
||||
for feed in topLevelFeeds {
|
||||
updatedUnreadCount += feed.unreadCount
|
||||
}
|
||||
unreadCount = updatedUnreadCount
|
||||
}
|
||||
|
||||
func childrenContain(_ feed: Feed) -> Bool {
|
||||
return children.contains(where: { (object) -> Bool in
|
||||
if let oneFeed = object as? Feed {
|
||||
return oneFeed == feed
|
||||
}
|
||||
return false
|
||||
})
|
||||
return topLevelFeeds.contains(feed)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -139,17 +170,6 @@ private extension Folder {
|
|||
|
||||
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 doesn’t support subfolders, but they might exist on disk.
|
||||
|
@ -187,11 +207,9 @@ extension Folder: OPMLRepresentable {
|
|||
|
||||
var hasAtLeastOneChild = false
|
||||
|
||||
for child in children {
|
||||
if let opmlObject = child as? OPMLRepresentable {
|
||||
s += opmlObject.OPMLString(indentLevel: indentLevel + 1)
|
||||
hasAtLeastOneChild = true
|
||||
}
|
||||
for feed in topLevelFeeds {
|
||||
s += feed.OPMLString(indentLevel: indentLevel + 1)
|
||||
hasAtLeastOneChild = true
|
||||
}
|
||||
|
||||
if !hasAtLeastOneChild {
|
||||
|
|
|
@ -27,15 +27,10 @@ private extension FolderTreeControllerDelegate {
|
|||
// Root node is “Top Level” and children are folders. Folders can’t have subfolders.
|
||||
// This will have to be revised later.
|
||||
|
||||
var folderNodes = [Node]()
|
||||
|
||||
for oneRepresentedObject in AccountManager.shared.localAccount.children {
|
||||
|
||||
if let folder = oneRepresentedObject as? Folder {
|
||||
folderNodes += [createNode(folder, parent: node)]
|
||||
}
|
||||
guard let folders = AccountManager.shared.localAccount.folders else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let folderNodes = folders.map { createNode($0, parent: node) }
|
||||
return folderNodes.sortedAlphabetically()
|
||||
}
|
||||
|
||||
|
|
|
@ -51,9 +51,15 @@ private extension SidebarTreeControllerDelegate {
|
|||
|
||||
let container = containerNode.representedObject as! Container
|
||||
|
||||
var children = [AnyObject]()
|
||||
children.append(contentsOf: Array(container.topLevelFeeds))
|
||||
if let folders = container.folders {
|
||||
children.append(contentsOf: Array(folders))
|
||||
}
|
||||
|
||||
var updatedChildNodes = [Node]()
|
||||
|
||||
container.children.forEach { (representedObject) in
|
||||
children.forEach { (representedObject) in
|
||||
|
||||
if let existingNode = containerNode.childNodeRepresentingObject(representedObject) {
|
||||
if !updatedChildNodes.contains(existingNode) {
|
||||
|
|
|
@ -68,34 +68,35 @@ class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
|
|||
|
||||
@objc(feeds)
|
||||
var feeds:NSArray {
|
||||
let feeds = account.children.compactMap { $0 as? Feed }
|
||||
return feeds.map { ScriptableFeed($0, container:self) } as NSArray
|
||||
return account.topLevelFeeds.map { ScriptableFeed($0, container:self) } as NSArray
|
||||
}
|
||||
|
||||
@objc(valueInFeedsWithUniqueID:)
|
||||
func valueInFeeds(withUniqueID id:String) -> ScriptableFeed? {
|
||||
let feeds = account.children.compactMap { $0 as? Feed }
|
||||
let feeds = Array(account.topLevelFeeds)
|
||||
guard let feed = feeds.first(where:{$0.feedID == id}) else { return nil }
|
||||
return ScriptableFeed(feed, container:self)
|
||||
}
|
||||
|
||||
@objc(valueInFeedsWithName:)
|
||||
func valueInFeeds(withName name:String) -> ScriptableFeed? {
|
||||
let feeds = account.children.compactMap { $0 as? Feed }
|
||||
let feeds = Array(account.topLevelFeeds)
|
||||
guard let feed = feeds.first(where:{$0.name == name}) else { return nil }
|
||||
return ScriptableFeed(feed, container:self)
|
||||
}
|
||||
|
||||
@objc(folders)
|
||||
var folders:NSArray {
|
||||
let folders = account.children.compactMap { $0 as? Folder }
|
||||
return folders.map { ScriptableFolder($0, container:self) } as NSArray
|
||||
let foldersSet = account.folders ?? Set<Folder>()
|
||||
let folders = Array(foldersSet)
|
||||
return folders.map { ScriptableFolder($0, container:self) } as NSArray
|
||||
}
|
||||
|
||||
@objc(valueInFoldersWithUniqueID:)
|
||||
func valueInFolders(withUniqueID id:NSNumber) -> ScriptableFolder? {
|
||||
let folderId = id.intValue
|
||||
let folders = account.children.compactMap { $0 as? Folder }
|
||||
let foldersSet = account.folders ?? Set<Folder>()
|
||||
let folders = Array(foldersSet)
|
||||
guard let folder = folders.first(where:{$0.folderID == folderId}) else { return nil }
|
||||
return ScriptableFolder(folder, container:self)
|
||||
}
|
||||
|
@ -105,14 +106,15 @@ class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
|
|||
@objc(contents)
|
||||
var contents:NSArray {
|
||||
var contentsArray:[AnyObject] = []
|
||||
for child in account.children {
|
||||
if let aFeed = child as? Feed {
|
||||
contentsArray.append(ScriptableFeed(aFeed, container:self))
|
||||
} else if let aFolder = child as? Folder {
|
||||
contentsArray.append(ScriptableFolder(aFolder, container:self))
|
||||
}
|
||||
}
|
||||
return contentsArray as NSArray
|
||||
for feed in account.topLevelFeeds {
|
||||
contentsArray.append(ScriptableFeed(feed, container: self))
|
||||
}
|
||||
if let folders = account.folders {
|
||||
for folder in folders {
|
||||
contentsArray.append(ScriptableFolder(folder, container:self))
|
||||
}
|
||||
}
|
||||
return contentsArray as NSArray
|
||||
}
|
||||
|
||||
@objc(opmlRepresentation)
|
||||
|
|
|
@ -92,7 +92,7 @@ class ScriptableFolder: NSObject, UniqueIdScriptingObject, ScriptingObjectContai
|
|||
|
||||
@objc(feeds)
|
||||
var feeds:NSArray {
|
||||
let feeds = folder.children.compactMap { $0 as? Feed }
|
||||
let feeds = Array(folder.topLevelFeeds)
|
||||
return feeds.map { ScriptableFeed($0, container:self) } as NSArray
|
||||
}
|
||||
|
||||
|
|
|
@ -77,7 +77,7 @@ extension NSApplication : ScriptingObjectContainer {
|
|||
let accounts = AccountManager.shared.accounts
|
||||
let emptyFeeds:[Feed] = []
|
||||
return accounts.reduce(emptyFeeds) { (result, nthAccount) -> [Feed] in
|
||||
let accountFeeds = nthAccount.children.compactMap { $0 as? Feed }
|
||||
let accountFeeds = Array(nthAccount.topLevelFeeds)
|
||||
return result + accountFeeds
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue