Read feed directory data from disk.

This commit is contained in:
Brent Simmons 2017-11-04 12:19:34 -07:00
parent 0960477be6
commit 44461af07a
6 changed files with 218 additions and 5 deletions

View File

@ -79,6 +79,9 @@
84B06FFE1ED3818D00F0B54B /* RSTree.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84B06FFA1ED3818000F0B54B /* RSTree.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 84B06FFE1ED3818D00F0B54B /* RSTree.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84B06FFA1ED3818000F0B54B /* RSTree.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
84B0700A1ED3822600F0B54B /* RSTextDrawing.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84B070071ED3821900F0B54B /* RSTextDrawing.framework */; }; 84B0700A1ED3822600F0B54B /* RSTextDrawing.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84B070071ED3821900F0B54B /* RSTextDrawing.framework */; };
84B0700B1ED3822600F0B54B /* RSTextDrawing.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84B070071ED3821900F0B54B /* RSTextDrawing.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 84B0700B1ED3822600F0B54B /* RSTextDrawing.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84B070071ED3821900F0B54B /* RSTextDrawing.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
84B99C671FAE35E600ECDEDB /* FeedListTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C661FAE35E600ECDEDB /* FeedListTreeControllerDelegate.swift */; };
84B99C691FAE36B800ECDEDB /* FeedListFolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C681FAE36B800ECDEDB /* FeedListFolder.swift */; };
84B99C6B1FAE370B00ECDEDB /* FeedListFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C6A1FAE370B00ECDEDB /* FeedListFeed.swift */; };
84BB4B771F11753300858766 /* Data.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84BB4B681F1174D400858766 /* Data.framework */; }; 84BB4B771F11753300858766 /* Data.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84BB4B681F1174D400858766 /* Data.framework */; };
84BB4B781F11753300858766 /* Data.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84BB4B681F1174D400858766 /* Data.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 84BB4B781F11753300858766 /* Data.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84BB4B681F1174D400858766 /* Data.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
84DAEE301F86CAFE0058304B /* OPMLImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DAEE2F1F86CAFE0058304B /* OPMLImporter.swift */; }; 84DAEE301F86CAFE0058304B /* OPMLImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DAEE2F1F86CAFE0058304B /* OPMLImporter.swift */; };
@ -446,6 +449,9 @@
84B06FE01ED3803200F0B54B /* RSFeedFinder.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSFeedFinder.xcodeproj; path = Frameworks/RSFeedFinder/RSFeedFinder.xcodeproj; sourceTree = "<group>"; }; 84B06FE01ED3803200F0B54B /* RSFeedFinder.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSFeedFinder.xcodeproj; path = Frameworks/RSFeedFinder/RSFeedFinder.xcodeproj; sourceTree = "<group>"; };
84B06FF41ED3818000F0B54B /* RSTree.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSTree.xcodeproj; path = Frameworks/RSTree/RSTree.xcodeproj; sourceTree = "<group>"; }; 84B06FF41ED3818000F0B54B /* RSTree.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSTree.xcodeproj; path = Frameworks/RSTree/RSTree.xcodeproj; sourceTree = "<group>"; };
84B070011ED3821800F0B54B /* RSTextDrawing.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSTextDrawing.xcodeproj; path = Frameworks/RSTextDrawing/RSTextDrawing.xcodeproj; sourceTree = "<group>"; }; 84B070011ED3821800F0B54B /* RSTextDrawing.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSTextDrawing.xcodeproj; path = Frameworks/RSTextDrawing/RSTextDrawing.xcodeproj; sourceTree = "<group>"; };
84B99C661FAE35E600ECDEDB /* FeedListTreeControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedListTreeControllerDelegate.swift; sourceTree = "<group>"; };
84B99C681FAE36B800ECDEDB /* FeedListFolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedListFolder.swift; sourceTree = "<group>"; };
84B99C6A1FAE370B00ECDEDB /* FeedListFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedListFeed.swift; sourceTree = "<group>"; };
84BB4B611F1174D400858766 /* Data.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Data.xcodeproj; path = Frameworks/Data/Data.xcodeproj; sourceTree = "<group>"; }; 84BB4B611F1174D400858766 /* Data.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Data.xcodeproj; path = Frameworks/Data/Data.xcodeproj; sourceTree = "<group>"; };
84DAEE2F1F86CAFE0058304B /* OPMLImporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OPMLImporter.swift; sourceTree = "<group>"; }; 84DAEE2F1F86CAFE0058304B /* OPMLImporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OPMLImporter.swift; sourceTree = "<group>"; };
84DAEE311F870B390058304B /* DockBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DockBadge.swift; path = Evergreen/DockBadge.swift; sourceTree = "<group>"; }; 84DAEE311F870B390058304B /* DockBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DockBadge.swift; path = Evergreen/DockBadge.swift; sourceTree = "<group>"; };
@ -644,6 +650,9 @@
84F204CD1FAACB660076E152 /* FeedListViewController.swift */, 84F204CD1FAACB660076E152 /* FeedListViewController.swift */,
84F204DD1FAACB8B0076E152 /* FeedListTimelineViewController.swift */, 84F204DD1FAACB8B0076E152 /* FeedListTimelineViewController.swift */,
84513F8F1FAA63950023A1A9 /* FeedListControlsView.swift */, 84513F8F1FAA63950023A1A9 /* FeedListControlsView.swift */,
84B99C661FAE35E600ECDEDB /* FeedListTreeControllerDelegate.swift */,
84B99C681FAE36B800ECDEDB /* FeedListFolder.swift */,
84B99C6A1FAE370B00ECDEDB /* FeedListFeed.swift */,
84E95CF61FABB3C800552D99 /* FeedList.plist */, 84E95CF61FABB3C800552D99 /* FeedList.plist */,
); );
name = "Feed List"; name = "Feed List";
@ -1224,11 +1233,14 @@
849A976E1ED9EBC8007D329B /* TimelineViewController.swift in Sources */, 849A976E1ED9EBC8007D329B /* TimelineViewController.swift in Sources */,
849A978D1ED9EE4D007D329B /* FeedListWindowController.swift in Sources */, 849A978D1ED9EE4D007D329B /* FeedListWindowController.swift in Sources */,
849A97771ED9EC04007D329B /* TimelineCellData.swift in Sources */, 849A97771ED9EC04007D329B /* TimelineCellData.swift in Sources */,
84B99C6B1FAE370B00ECDEDB /* FeedListFeed.swift in Sources */,
849A97781ED9EC04007D329B /* TimelineCellLayout.swift in Sources */, 849A97781ED9EC04007D329B /* TimelineCellLayout.swift in Sources */,
849A976C1ED9EBC8007D329B /* TimelineTableRowView.swift in Sources */, 849A976C1ED9EBC8007D329B /* TimelineTableRowView.swift in Sources */,
849A977B1ED9EC04007D329B /* UnreadIndicatorView.swift in Sources */, 849A977B1ED9EC04007D329B /* UnreadIndicatorView.swift in Sources */,
849A97541ED9EAC0007D329B /* AddFeedWindowController.swift in Sources */, 849A97541ED9EAC0007D329B /* AddFeedWindowController.swift in Sources */,
849A976D1ED9EBC8007D329B /* TimelineTableView.swift in Sources */, 849A976D1ED9EBC8007D329B /* TimelineTableView.swift in Sources */,
84B99C671FAE35E600ECDEDB /* FeedListTreeControllerDelegate.swift in Sources */,
84B99C691FAE36B800ECDEDB /* FeedListFolder.swift in Sources */,
84F204DE1FAACB8B0076E152 /* FeedListTimelineViewController.swift in Sources */, 84F204DE1FAACB8B0076E152 /* FeedListTimelineViewController.swift in Sources */,
849A97A31ED9F180007D329B /* FolderTreeControllerDelegate.swift in Sources */, 849A97A31ED9F180007D329B /* FolderTreeControllerDelegate.swift in Sources */,
849A97851ED9ECCD007D329B /* PreferencesWindowController.swift in Sources */, 849A97851ED9ECCD007D329B /* PreferencesWindowController.swift in Sources */,

View File

@ -5,7 +5,7 @@
<key>Mac and iOS</key> <key>Mac and iOS</key>
<array> <array>
<dict> <dict>
<key>editedName</key> <key>name</key>
<string>TidBITS</string> <string>TidBITS</string>
<key>homePageURL</key> <key>homePageURL</key>
<string>http://tidbits.org/</string> <string>http://tidbits.org/</string>
@ -13,7 +13,7 @@
<string>http://tidbits.org/feeds/tidbits_blurb.rss</string> <string>http://tidbits.org/feeds/tidbits_blurb.rss</string>
</dict> </dict>
<dict> <dict>
<key>editedName</key> <key>name</key>
<string>Michael Tsai</string> <string>Michael Tsai</string>
<key>homePageURL</key> <key>homePageURL</key>
<string>https://mjtsai.com/blog/</string> <string>https://mjtsai.com/blog/</string>
@ -24,7 +24,7 @@
<key>Blogs</key> <key>Blogs</key>
<array> <array>
<dict> <dict>
<key>editedName</key> <key>name</key>
<string>Jason Kottke</string> <string>Jason Kottke</string>
<key>homePageURL</key> <key>homePageURL</key>
<string>https://kottke.org/</string> <string>https://kottke.org/</string>
@ -35,7 +35,7 @@
<key>News</key> <key>News</key>
<array> <array>
<dict> <dict>
<key>editedName</key> <key>name</key>
<string>Talking Points Memo</string> <string>Talking Points Memo</string>
<key>homePageURL</key> <key>homePageURL</key>
<string>https://talkingpointsmemo.com/</string> <string>https://talkingpointsmemo.com/</string>

View File

@ -0,0 +1,46 @@
//
// FeedListFeed.swift
// Evergreen
//
// Created by Brent Simmons on 11/4/17.
// Copyright © 2017 Ranchero Software. All rights reserved.
//
import Foundation
final class FeedListFeed: Hashable {
let name: String
let url: String
let homePageURL: String
let hashValue: Int
init(name: String, url: String, homePageURL: String) {
self.name = name
self.url = url
self.homePageURL = homePageURL
self.hashValue = url.hashValue
}
private struct Key {
static let name = "name"
static let editedName = "editedName" // Used in DefaultFeeds.plist
static let url = "url"
static let homePageURL = "homePageURL"
}
convenience init(dictionary: [String: String]) {
let name = (dictionary[Key.name] ?? dictionary[Key.editedName])!
let url = dictionary[Key.url]!
let homePageURL = dictionary[Key.homePageURL]!
self.init(name: name, url: url, homePageURL: homePageURL)
}
static func ==(lhs: FeedListFeed, rhs: FeedListFeed) -> Bool {
return lhs.hashValue == rhs.hashValue && lhs.url == rhs.url && lhs.name == rhs.name && lhs.homePageURL == rhs.homePageURL
}
}

View File

@ -0,0 +1,28 @@
//
// FeedListFolder.swift
// Evergreen
//
// Created by Brent Simmons on 11/4/17.
// Copyright © 2017 Ranchero Software. All rights reserved.
//
import Foundation
final class FeedListFolder: Hashable {
let name: String
let feeds: Set<FeedListFeed>
let hashValue: Int
init(name: String, feeds: Set<FeedListFeed>) {
self.name = name
self.feeds = feeds
self.hashValue = name.hashValue
}
static func ==(lhs: FeedListFolder, rhs: FeedListFolder) -> Bool {
return lhs === rhs
}
}

View File

@ -0,0 +1,93 @@
//
// FeedListTreeControllerDelegate.swift
// Evergreen
//
// Created by Brent Simmons on 11/4/17.
// Copyright © 2017 Ranchero Software. All rights reserved.
//
import Foundation
import RSTree
// Folders and feeds that appear in the Feed Directory are pulled from three sources:
// 1. Feeds added in code here. (Evergreen News should be the only one.)
// 2. Default feeds for new users see DefaultFeeds.plist.
// 3. FeedList.plist the main directory. Its top level is all folders. There are no sub-folders.
// Its okay if theres overlap: a feed may appear in multiple places.
// If theres any problem with the data (wrong types), this will crash. By design.
final class FeedListTreeControllerDelegate: TreeControllerDelegate {
let topLevelFeeds: Set<FeedListFeed>
let defaultFeeds: Set<FeedListFeed>
let folders: Set<FeedListFolder>
init() {
let evergreenNewsFeed = FeedListFeed(name: "Evergreen News", url: "https://ranchero.com/evergreen/feed.json", homePageURL: "https://ranchero.com/evergreen/blog/")
self.topLevelFeeds = Set([evergreenNewsFeed])
self.defaultFeeds = FeedListReader.defaultFeeds()
self.folders = FeedListReader.folders()
}
func treeController(treeController: TreeController, childNodesFor node: Node) -> [Node]? {
// if node.isRoot {
// return childNodesForRootNode(node)
// }
// if node.representedObject is FeedListFolder {
// return childNodesForFolderNode(node)
// }
return nil
}
}
//private extension FeedListTreeControllerDelegate {
//
// func childNodesForRootNode(_ rootNode: Node) -> [Node]? {
//
// return childNodesForContainerNode(rootNode, AccountManager.shared.localAccount.children)
// }
//
//}
// MARK: - Loading from Disk
private struct FeedListReader {
static func folders() -> Set<FeedListFolder> {
return Set(foldersDictionary().map { (arg: (key: String, value: [[String : String]])) -> FeedListFolder in
let (name, feedDictionaries) = arg
return FeedListFolder(name: name, feeds: feeds(with: feedDictionaries))
})
}
static func defaultFeeds() -> Set<FeedListFeed> {
return feeds(with: defaultFeedDictionaries())
}
private static func defaultFeedDictionaries() -> [[String: String]] {
let f = Bundle.main.path(forResource: "DefaultFeeds", ofType: "plist")!
return NSArray(contentsOfFile: f)! as! [[String: String]]
}
private static func foldersDictionary() -> [String: [[String: String]]] {
let f = Bundle.main.path(forResource: "FeedList", ofType: "plist")!
return NSDictionary(contentsOfFile: f)! as! [String: [[String: String]]]
}
private static func feeds(with dictionaries: [[String: String]]) -> Set<FeedListFeed> {
return Set(dictionaries.map { FeedListFeed(dictionary: $0) })
}
}

View File

@ -7,8 +7,42 @@
// //
import Cocoa import Cocoa
import RSTree
final class FeedListViewController: NSViewController { final class FeedListViewController: NSViewController {
@IBOutlet var outlineView: NSOutlineView!
private let treeControllerDelegate = FeedListTreeControllerDelegate()
lazy var treeController: TreeController = {
TreeController(delegate: treeControllerDelegate)
}()
} }
// MARK: - NSOutlineViewDataSource
extension FeedListViewController: NSOutlineViewDataSource {
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
return nodeForItem(item as AnyObject?).numberOfChildNodes
}
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
return nodeForItem(item as AnyObject?).childNodes![index]
}
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
return nodeForItem(item as AnyObject?).canHaveChildNodes
}
private func nodeForItem(_ item: AnyObject?) -> Node {
if item == nil {
return treeController.rootNode
}
return item as! Node
}
}