Commit major surgery but leave it unfinished. Everything is broken.

This commit is contained in:
Brent Simmons 2017-07-01 17:22:19 -07:00
parent 683d5aaf71
commit 944f05c71e
38 changed files with 1241 additions and 212 deletions

View File

@ -11,7 +11,7 @@ import DB5
import DataModel
import RSTextDrawing
import RSTree
import RSXML
import RSParser
import RSWeb
var currentTheme: VSTheme!
@ -353,7 +353,7 @@ private extension AppDelegate {
return
}
let xmlData = RSXMLData(data: opmlData, urlString: url.absoluteString)
let parserData = ParserData(data: opmlData, urlString: url.absoluteString)
RSParseOPML(xmlData) { (opmlDocument, error) in
if let error = error {

View File

@ -7,7 +7,7 @@
//
import Foundation
import RSXML
import RSParser
import RSWeb
func downloadTitleForFeed(_ url: URL, _ completionHandler: @escaping (_ title: String?) -> ()) {
@ -19,9 +19,8 @@ func downloadTitleForFeed(_ url: URL, _ completionHandler: @escaping (_ title: S
return
}
let xmlData = RSXMLData(data: data, urlString: url.absoluteString)
RSParseFeed(xmlData) { (parsedFeed : RSParsedFeed?, error: Error?) in
let parserData = ParserData(url: url.absoluteString, data: data)
FeedParser.parse(parserData) { (parsedFeed, error) in
completionHandler(parsedFeed?.title)
}
}

View File

@ -0,0 +1,35 @@
//
// Account.swift
// DataModel
//
// Created by Brent Simmons on 7/1/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved.
//
import Foundation
public enum AccountType: Int {
case onMyMac = 1
case feedly = 16
case feedbin
case feedWrangler
case newsBlur
}
public final class Account: Container, PlistProvider {
public let identifier: String
public let type: AccountType
public var nameForDisplay: String
public weak var delegate: AccountDelegate
init(settingsFile: String, type: AccountType, dataFolder: String, identifier: String, delegate: AccountDelegate) {
self.identifier = identifier
self.type = type
self.settingsFile = settingsFile
self.dataFolder = dataFolder
self.delegate = delegate
}
}

View File

@ -0,0 +1,16 @@
//
// AccountDelegate.swift
// DataModel
//
// Created by Brent Simmons on 7/1/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved.
//
import Foundation
public protocol AccountDelegate {
}

View File

@ -0,0 +1,77 @@
//
// Article.swift
// DataModel
//
// Created by Brent Simmons on 7/1/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved.
//
import Foundation
public final class Article: Hashable {
weak var account: Account?
let feedID: String
let articleID: String //Calculated: unique per account
var uniqueID: String //guid: unique per feed
var title: String?
var contentHTML: String?
var contentText: String?
var url: String?
var externalURL: String?
var summary: String?
var imageURL: String?
var bannerImageURL: String?
var datePublished: Date?
var dateModified: Date?
var authors: [Authors]?
var tags: [String]?
var attachments: [Attachment]?
var status: ArticleStatus?
public var accountInfo: [String: Any]? //If account needs to store more data
var feed: Feed? {
get {
return account?.existingFeed(with: feedID)
}
}
init(account: Account, feedID: String, uniqueID: String, title: String?, contentHTML: String?, contentText: String?, url: String?, externalURL: String?, summary: String?, imageURL: String?, bannerImageURL: String?, datePublished: Date?, dateModified: Date?, authors: [Authors]?, tags: [String]?, attachments: [Attachment]?) {
self.account = account
self.feedID = feedID
self.uniqueID = uniqueID
self.title = title
self.contentHTML = contentHTML
self.contentText = contentText
self.url = url
self.externalURL = externalURL
self.summary = summary
self.imageURL = imageURL
self.bannerImageURL = bannerImageURL
self.datePublished = datePublished
self.dateModified = dateModified
self.dateArrived = dateArrived
self.authors = authors
self.tags = tags
self.attachments = attachments
self.hashValue = account.hashValue + feedID.hashValue + uniqueID.hashValue
}
public class func ==(lhs: Article, rhs: Article) -> Bool {
return lhs === rhs
}
}
public extension Article {
public var logicalDatePublished: Date? {
get {
return (datePublished ?? dateModified) && status?.dateArrived
}
}
}

View File

@ -1,68 +0,0 @@
//
// ArticleProtocol.swift
// Evergreen
//
// Created by Brent Simmons on 4/23/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
//
import Foundation
public protocol Article: class {
var account: Account? {get}
var feedID: String {get}
var feed: Feed? {get}
var articleID: String {get}
var status: ArticleStatus! {get}
var guid: String? {get}
var title: String? {get}
var body: String? {get}
var link: String? {get}
var permalink: String? {get}
var author: String? {get}
var datePublished: Date? {get}
var logicalDatePublished: Date {get} //datePublished or something reasonable.
var dateModified: Date? {get}
}
public extension Article {
var feed: Feed? {
get {
return account?.existingFeedWithID(feedID)
}
}
var logicalDatePublished: Date {
get {
if let d = datePublished {
return d
}
if let d = dateModified {
return d
}
return status.dateArrived as Date
}
}
}
public func articleArraysAreIdentical(array1: [Article], array2: [Article]) -> Bool {
if array1.count != array2.count {
return false
}
var index = 0
for oneItem in array1 {
if oneItem !== array2[index] {
return false
}
index = index + 1
}
return true
}

View File

@ -0,0 +1,32 @@
//
// Attachment.swift
// DataModel
//
// Created by Brent Simmons on 7/1/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved.
//
import Foundation
public struct Attachment: Equatable {
public let url: String?
public let mimeType: String?
public let title: String?
public let sizeInBytes: Int?
public let durationInSeconds: Int?
init(url: String?, mimeType: String?, title: String?, sizeInBytes: Int?, durationInSeconds: Int?) {
self.url = url
self.mimeType = mimeType
self.title = title
self.sizeInBytes = sizeInBytes
self.durationInSeconds = durationInSeconds
}
public class func ==(lhs: Attachment, rhs: Attachment) -> Bool {
return lhs.url == rhs.url && lhs.mimeType == rhs.mimeType && lhs.title == rhs.title && lhs.sizeInBytes == rhs.sizeInBytes && lhs.durationInSeconds == rhs.durationInSeconds
}
}

View File

@ -0,0 +1,39 @@
//
// Author.swift
// DataModel
//
// Created by Brent Simmons on 7/1/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved.
//
import Foundation
public struct Author: Hashable {
public let name: String?
public let url: String?
public let avatarURL: String?
public let emailAddress: String?
public init?(name: String?, url: String?, avatarURL: String?, emailAddress: String?) {
if name == nil && url == nil && emailAddress == nil {
return nil
}
self.name = name
self.url = url
self.avatarURL = avatarURL
self.emailAddress = emailAddress
let s = name ?? ""
s += url ?? ""
s += avatarURL ?? ""
s += emailAddres ?? ""
self.hashValue = s.hashValue
}
public class func ==(lhs: Author, rhs: Author) {
return lhs.hashValue == rhs.hashValue && lhs.name == rhs.name && lhs.url == rhs.url && lhs.avatarURL == rhs.avatarURL && lhs.emailAddress == rhs.emailAddress
}
}

View File

@ -18,11 +18,8 @@ public func FolderPostChildrenDidChangeNotification(_ folder: Folder) {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: FolderChildrenDidChangeNotification), object: folder)
}
public protocol Folder: class, UnreadCountProvider, DisplayNameProvider {
public protocol Container: class {
// TODO: get rid of children, flattenedFeeds, and some default implementations in favor of faster, specific ones.
// var children: NSSet {get}
var account: Account? {get}
var hasAtLeastOneFeed: Bool {get} //Recursive

View File

@ -9,29 +9,41 @@
/* Begin PBXBuildFile section */
8439773E1DA07F2400F0FCBD /* RSCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8439773D1DA07F2400F0FCBD /* RSCore.framework */; };
8471A2CC1ED4CEEE008F099E /* AccountProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8471A2C81ED4CEEE008F099E /* AccountProtocol.swift */; };
8471A2CD1ED4CEEE008F099E /* ArticleProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8471A2C91ED4CEEE008F099E /* ArticleProtocol.swift */; };
8471A2CE1ED4CEEE008F099E /* ArticleStatusProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8471A2CA1ED4CEEE008F099E /* ArticleStatusProtocol.swift */; };
8471A2CF1ED4CEEE008F099E /* BatchUpdates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8471A2CB1ED4CEEE008F099E /* BatchUpdates.swift */; };
8471A2D51ED4CEFA008F099E /* DisplayNameProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8471A2D01ED4CEFA008F099E /* DisplayNameProviderProtocol.swift */; };
8471A2D61ED4CEFA008F099E /* FeedProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8471A2D11ED4CEFA008F099E /* FeedProtocol.swift */; };
8471A2D71ED4CEFA008F099E /* FolderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8471A2D21ED4CEFA008F099E /* FolderProtocol.swift */; };
8471A2D71ED4CEFA008F099E /* Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8471A2D21ED4CEFA008F099E /* Container.swift */; };
8471A2D81ED4CEFA008F099E /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8471A2D31ED4CEFA008F099E /* Notifications.swift */; };
8471A2D91ED4CEFA008F099E /* UnreadCountProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8471A2D41ED4CEFA008F099E /* UnreadCountProviderProtocol.swift */; };
84F466A61F08389400225386 /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F466A51F08389400225386 /* Account.swift */; };
84F466A81F083A2A00225386 /* Feed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F466A71F083A2A00225386 /* Feed.swift */; };
84F466AA1F083B7A00225386 /* OPMLRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F466A91F083B7A00225386 /* OPMLRepresentable.swift */; };
84F466AC1F083F2600225386 /* Article.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F466AB1F083F2600225386 /* Article.swift */; };
84F466AE1F08435800225386 /* Author.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F466AD1F08435800225386 /* Author.swift */; };
84F466B01F0862AF00225386 /* Attachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F466AF1F0862AF00225386 /* Attachment.swift */; };
84F466B21F0863D700225386 /* AccountDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F466B11F0863D700225386 /* AccountDelegate.swift */; };
84F466B41F08725700225386 /* Folder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F466B31F08725700225386 /* Folder.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
8439773D1DA07F2400F0FCBD /* RSCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RSCore.framework; path = "../../../../../Library/Developer/Xcode/DerivedData/Rainier-cidsoqwawkdqqphkdtrqrojskege/Build/Products/Debug/RSCore.framework"; sourceTree = "<group>"; };
8471A2C81ED4CEEE008F099E /* AccountProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountProtocol.swift; sourceTree = "<group>"; };
8471A2C91ED4CEEE008F099E /* ArticleProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArticleProtocol.swift; sourceTree = "<group>"; };
8471A2CA1ED4CEEE008F099E /* ArticleStatusProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArticleStatusProtocol.swift; sourceTree = "<group>"; };
8471A2CB1ED4CEEE008F099E /* BatchUpdates.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BatchUpdates.swift; sourceTree = "<group>"; };
8471A2D01ED4CEFA008F099E /* DisplayNameProviderProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayNameProviderProtocol.swift; sourceTree = "<group>"; };
8471A2D11ED4CEFA008F099E /* FeedProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedProtocol.swift; sourceTree = "<group>"; };
8471A2D21ED4CEFA008F099E /* FolderProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FolderProtocol.swift; sourceTree = "<group>"; };
8471A2D21ED4CEFA008F099E /* Container.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Container.swift; sourceTree = "<group>"; };
8471A2D31ED4CEFA008F099E /* Notifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Notifications.swift; sourceTree = "<group>"; };
8471A2D41ED4CEFA008F099E /* UnreadCountProviderProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnreadCountProviderProtocol.swift; sourceTree = "<group>"; };
8471A2DA1ED4CF01008F099E /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
84C7AE921D68C558009FB883 /* DataModel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DataModel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
84F466A51F08389400225386 /* Account.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Account.swift; sourceTree = "<group>"; };
84F466A71F083A2A00225386 /* Feed.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Feed.swift; sourceTree = "<group>"; };
84F466A91F083B7A00225386 /* OPMLRepresentable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OPMLRepresentable.swift; sourceTree = "<group>"; };
84F466AB1F083F2600225386 /* Article.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Article.swift; sourceTree = "<group>"; };
84F466AD1F08435800225386 /* Author.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Author.swift; sourceTree = "<group>"; };
84F466AF1F0862AF00225386 /* Attachment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Attachment.swift; sourceTree = "<group>"; };
84F466B11F0863D700225386 /* AccountDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountDelegate.swift; sourceTree = "<group>"; };
84F466B31F08725700225386 /* Folder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Folder.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -57,13 +69,19 @@
84C7AE881D68C558009FB883 = {
isa = PBXGroup;
children = (
84F466A51F08389400225386 /* Account.swift */,
84F466B11F0863D700225386 /* AccountDelegate.swift */,
84F466B31F08725700225386 /* Folder.swift */,
84F466A71F083A2A00225386 /* Feed.swift */,
84F466AB1F083F2600225386 /* Article.swift */,
84F466AD1F08435800225386 /* Author.swift */,
84F466AF1F0862AF00225386 /* Attachment.swift */,
8471A2C81ED4CEEE008F099E /* AccountProtocol.swift */,
8471A2C91ED4CEEE008F099E /* ArticleProtocol.swift */,
8471A2CA1ED4CEEE008F099E /* ArticleStatusProtocol.swift */,
8471A2CB1ED4CEEE008F099E /* BatchUpdates.swift */,
8471A2D01ED4CEFA008F099E /* DisplayNameProviderProtocol.swift */,
8471A2D11ED4CEFA008F099E /* FeedProtocol.swift */,
8471A2D21ED4CEFA008F099E /* FolderProtocol.swift */,
84F466A91F083B7A00225386 /* OPMLRepresentable.swift */,
8471A2D21ED4CEFA008F099E /* Container.swift */,
8471A2D31ED4CEFA008F099E /* Notifications.swift */,
8471A2D41ED4CEFA008F099E /* UnreadCountProviderProtocol.swift */,
8471A2DA1ED4CF01008F099E /* Info.plist */,
@ -159,15 +177,21 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
84F466AC1F083F2600225386 /* Article.swift in Sources */,
8471A2CC1ED4CEEE008F099E /* AccountProtocol.swift in Sources */,
8471A2D61ED4CEFA008F099E /* FeedProtocol.swift in Sources */,
84F466A81F083A2A00225386 /* Feed.swift in Sources */,
84F466A61F08389400225386 /* Account.swift in Sources */,
8471A2CF1ED4CEEE008F099E /* BatchUpdates.swift in Sources */,
8471A2CE1ED4CEEE008F099E /* ArticleStatusProtocol.swift in Sources */,
84F466B01F0862AF00225386 /* Attachment.swift in Sources */,
8471A2D91ED4CEFA008F099E /* UnreadCountProviderProtocol.swift in Sources */,
8471A2CD1ED4CEEE008F099E /* ArticleProtocol.swift in Sources */,
84F466B21F0863D700225386 /* AccountDelegate.swift in Sources */,
8471A2D51ED4CEFA008F099E /* DisplayNameProviderProtocol.swift in Sources */,
84F466AE1F08435800225386 /* Author.swift in Sources */,
84F466B41F08725700225386 /* Folder.swift in Sources */,
8471A2D81ED4CEFA008F099E /* Notifications.swift in Sources */,
8471A2D71ED4CEFA008F099E /* FolderProtocol.swift in Sources */,
84F466AA1F083B7A00225386 /* OPMLRepresentable.swift in Sources */,
8471A2D71ED4CEFA008F099E /* Container.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -0,0 +1,66 @@
//
// Feed.swift
// DataModel
//
// Created by Brent Simmons on 7/1/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved.
//
import Foundation
import RSCore
public final class Feed: UnreadCountProvider, DisplayNameProvider, Hashable {
public let account: Account
public let url: String
public let feedID: String
public var homePageURL: String?
public var name: String?
public var editedName: String?
public var nameForDisplay: String {
get {
return (editedName ?? name) ?? NSLocalizedString("Untitled", comment: "Feed name")
}
}
public var articles = Set<Article>()
public var accountInfo: [String: Any]? //If account needs to store more data
public init(account: Account, url: String, feedID: String) {
self.account = account
self.url = url
self.feedID = feedID
self.hashValue = account.hashValue + url.hashValue + feedID.hashValue
}
public fetchArticles() -> Set<Article> {
articles = account.fetchArticles(self)
return articles
}
public class func ==(lhs: Feed, rhs: Feed) -> Bool {
return lhs === rhs
}
}
public extension Feed: OPMLRepresentable {
func OPMLString(indentLevel: Int) -> String {
let escapedName = nameForDisplay.rs_stringByEscapingSpecialXMLCharacters()
var escapedHomePageURL = ""
if let homePageURL = homePageURL {
escapedHomePageURL = homePageURL.rs_stringByEscapingSpecialXMLCharacters()
}
let escapedFeedURL = url.rs_stringByEscapingSpecialXMLCharacters()
var s = "<outline text=\"\(escapedName)\" title=\"\(escapedName)\" description=\"\" type=\"rss\" version=\"RSS\" htmlUrl=\"\(escapedHomePageURL)\" xmlUrl=\"\(escapedFeedURL)\"/>\n"
s = s.rs_string(byPrependingNumberOfTabs: indentLevel)
return s
}
}

View File

@ -1,45 +0,0 @@
//
// FeedProtocol.swift
// Evergreen
//
// Created by Brent Simmons on 4/23/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
//
import Foundation
import RSCore
public protocol Feed: class, UnreadCountProvider, DisplayNameProvider {
var account: Account {get}
var url: String {get}
var feedID: String {get}
var homePageURL: String? {get}
var name: String? {get}
var editedName: String? {get}
var nameForDisplay: String {get}
// var articles: NSSet {get}
init(account: Account, url: String, feedID: String)
// Exporting OPML.
func opmlString(indentLevel: Int) -> String
}
public extension Feed {
func opmlString(indentLevel: Int) -> String {
let escapedName = nameForDisplay.rs_stringByEscapingSpecialXMLCharacters()
var escapedHomePageURL = ""
if let homePageURL = homePageURL {
escapedHomePageURL = homePageURL.rs_stringByEscapingSpecialXMLCharacters()
}
let escapedFeedURL = url.rs_stringByEscapingSpecialXMLCharacters()
var s = "<outline text=\"\(escapedName)\" title=\"\(escapedName)\" description=\"\" type=\"rss\" version=\"RSS\" htmlUrl=\"\(escapedHomePageURL)\" xmlUrl=\"\(escapedFeedURL)\"/>\n"
s = s.rs_string(byPrependingNumberOfTabs: indentLevel)
return s
}
}

View File

@ -0,0 +1,14 @@
//
// Folder.swift
// DataModel
//
// Created by Brent Simmons on 7/1/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved.
//
import Foundation
public class Folder: Container {
}

View File

@ -0,0 +1,14 @@
//
// OPMLRepresentable.swift
// DataModel
//
// Created by Brent Simmons on 7/1/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved.
//
import Foundation
public protocol OPMLRepresentable {
func OPMLString(indentLevel: Int) -> String
}

View File

@ -0,0 +1,585 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
84F466721F08255100225386 /* Feedbin.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84F466681F08255100225386 /* Feedbin.framework */; };
84F466771F08255100225386 /* FeedbinTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F466761F08255100225386 /* FeedbinTests.swift */; };
84F466791F08255100225386 /* Feedbin.h in Headers */ = {isa = PBXBuildFile; fileRef = 84F4666B1F08255100225386 /* Feedbin.h */; settings = {ATTRIBUTES = (Public, ); }; };
84F466A01F0825A000225386 /* DataModel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84F4669F1F08258300225386 /* DataModel.framework */; };
84F466A11F0825A000225386 /* RSCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84F466891F08256F00225386 /* RSCore.framework */; };
84F466A21F0825A000225386 /* RSDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84F466951F08257700225386 /* RSDatabase.framework */; };
84F466A41F0825B400225386 /* FeedbinAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F466A31F0825B400225386 /* FeedbinAccount.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
84F466731F08255100225386 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 84F4665F1F08255100225386 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 84F466671F08255100225386;
remoteInfo = Feedbin;
};
84F466881F08256F00225386 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 84F466821F08256F00225386 /* RSCore.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 84CFF4F41AC3C69700CEA6C8;
remoteInfo = RSCore;
};
84F4668A1F08256F00225386 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 84F466821F08256F00225386 /* RSCore.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 84CFF4FF1AC3C69700CEA6C8;
remoteInfo = RSCoreTests;
};
84F4668C1F08256F00225386 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 84F466821F08256F00225386 /* RSCore.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 842DD7BC1E14993900E061EB;
remoteInfo = RSCoreiOS;
};
84F466941F08257700225386 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 84F4668E1F08257700225386 /* RSDatabase.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 84F22C551B52E0D9000060CE;
remoteInfo = RSDatabase;
};
84F466961F08257700225386 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 84F4668E1F08257700225386 /* RSDatabase.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 84F22C5F1B52E0D9000060CE;
remoteInfo = RSDatabaseTests;
};
84F466981F08257700225386 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 84F4668E1F08257700225386 /* RSDatabase.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 8400ABF71E0CFBD800AA7C57;
remoteInfo = RSDatabaseiOS;
};
84F4669E1F08258300225386 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 84F4669A1F08258300225386 /* DataModel.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 84C7AE921D68C558009FB883;
remoteInfo = DataModel;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
84F466681F08255100225386 /* Feedbin.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Feedbin.framework; sourceTree = BUILT_PRODUCTS_DIR; };
84F4666B1F08255100225386 /* Feedbin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Feedbin.h; sourceTree = "<group>"; };
84F4666C1F08255100225386 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
84F466711F08255100225386 /* FeedbinTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FeedbinTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
84F466761F08255100225386 /* FeedbinTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinTests.swift; sourceTree = "<group>"; };
84F466781F08255100225386 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
84F466821F08256F00225386 /* RSCore.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSCore.xcodeproj; path = ../RSCore/RSCore.xcodeproj; sourceTree = "<group>"; };
84F4668E1F08257700225386 /* RSDatabase.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSDatabase.xcodeproj; path = ../RSDatabase/RSDatabase.xcodeproj; sourceTree = "<group>"; };
84F4669A1F08258300225386 /* DataModel.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = DataModel.xcodeproj; path = ../DataModel/DataModel.xcodeproj; sourceTree = "<group>"; };
84F466A31F0825B400225386 /* FeedbinAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedbinAccount.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
84F466641F08255100225386 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
84F466A01F0825A000225386 /* DataModel.framework in Frameworks */,
84F466A11F0825A000225386 /* RSCore.framework in Frameworks */,
84F466A21F0825A000225386 /* RSDatabase.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
84F4666E1F08255100225386 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
84F466721F08255100225386 /* Feedbin.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
84F4665E1F08255100225386 = {
isa = PBXGroup;
children = (
84F4666A1F08255100225386 /* Feedbin */,
84F466751F08255100225386 /* FeedbinTests */,
84F466691F08255100225386 /* Products */,
84F466821F08256F00225386 /* RSCore.xcodeproj */,
84F4668E1F08257700225386 /* RSDatabase.xcodeproj */,
84F4669A1F08258300225386 /* DataModel.xcodeproj */,
);
sourceTree = "<group>";
};
84F466691F08255100225386 /* Products */ = {
isa = PBXGroup;
children = (
84F466681F08255100225386 /* Feedbin.framework */,
84F466711F08255100225386 /* FeedbinTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
84F4666A1F08255100225386 /* Feedbin */ = {
isa = PBXGroup;
children = (
84F4666B1F08255100225386 /* Feedbin.h */,
84F466A31F0825B400225386 /* FeedbinAccount.swift */,
84F4666C1F08255100225386 /* Info.plist */,
);
path = Feedbin;
sourceTree = "<group>";
};
84F466751F08255100225386 /* FeedbinTests */ = {
isa = PBXGroup;
children = (
84F466761F08255100225386 /* FeedbinTests.swift */,
84F466781F08255100225386 /* Info.plist */,
);
path = FeedbinTests;
sourceTree = "<group>";
};
84F466831F08256F00225386 /* Products */ = {
isa = PBXGroup;
children = (
84F466891F08256F00225386 /* RSCore.framework */,
84F4668B1F08256F00225386 /* RSCoreTests.xctest */,
84F4668D1F08256F00225386 /* RSCore.framework */,
);
name = Products;
sourceTree = "<group>";
};
84F4668F1F08257700225386 /* Products */ = {
isa = PBXGroup;
children = (
84F466951F08257700225386 /* RSDatabase.framework */,
84F466971F08257700225386 /* RSDatabaseTests.xctest */,
84F466991F08257700225386 /* RSDatabase.framework */,
);
name = Products;
sourceTree = "<group>";
};
84F4669B1F08258300225386 /* Products */ = {
isa = PBXGroup;
children = (
84F4669F1F08258300225386 /* DataModel.framework */,
);
name = Products;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
84F466651F08255100225386 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
84F466791F08255100225386 /* Feedbin.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
84F466671F08255100225386 /* Feedbin */ = {
isa = PBXNativeTarget;
buildConfigurationList = 84F4667C1F08255100225386 /* Build configuration list for PBXNativeTarget "Feedbin" */;
buildPhases = (
84F466631F08255100225386 /* Sources */,
84F466641F08255100225386 /* Frameworks */,
84F466651F08255100225386 /* Headers */,
84F466661F08255100225386 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = Feedbin;
productName = Feedbin;
productReference = 84F466681F08255100225386 /* Feedbin.framework */;
productType = "com.apple.product-type.framework";
};
84F466701F08255100225386 /* FeedbinTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 84F4667F1F08255100225386 /* Build configuration list for PBXNativeTarget "FeedbinTests" */;
buildPhases = (
84F4666D1F08255100225386 /* Sources */,
84F4666E1F08255100225386 /* Frameworks */,
84F4666F1F08255100225386 /* Resources */,
);
buildRules = (
);
dependencies = (
84F466741F08255100225386 /* PBXTargetDependency */,
);
name = FeedbinTests;
productName = FeedbinTests;
productReference = 84F466711F08255100225386 /* FeedbinTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
84F4665F1F08255100225386 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0830;
LastUpgradeCheck = 0830;
ORGANIZATIONNAME = "Ranchero Software, LLC";
TargetAttributes = {
84F466671F08255100225386 = {
CreatedOnToolsVersion = 8.3;
DevelopmentTeam = M8L2WTLA8W;
LastSwiftMigration = 0830;
ProvisioningStyle = Automatic;
};
84F466701F08255100225386 = {
CreatedOnToolsVersion = 8.3;
DevelopmentTeam = M8L2WTLA8W;
ProvisioningStyle = Automatic;
};
};
};
buildConfigurationList = 84F466621F08255100225386 /* Build configuration list for PBXProject "Feedbin" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = 84F4665E1F08255100225386;
productRefGroup = 84F466691F08255100225386 /* Products */;
projectDirPath = "";
projectReferences = (
{
ProductGroup = 84F4669B1F08258300225386 /* Products */;
ProjectRef = 84F4669A1F08258300225386 /* DataModel.xcodeproj */;
},
{
ProductGroup = 84F466831F08256F00225386 /* Products */;
ProjectRef = 84F466821F08256F00225386 /* RSCore.xcodeproj */;
},
{
ProductGroup = 84F4668F1F08257700225386 /* Products */;
ProjectRef = 84F4668E1F08257700225386 /* RSDatabase.xcodeproj */;
},
);
projectRoot = "";
targets = (
84F466671F08255100225386 /* Feedbin */,
84F466701F08255100225386 /* FeedbinTests */,
);
};
/* End PBXProject section */
/* Begin PBXReferenceProxy section */
84F466891F08256F00225386 /* RSCore.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = RSCore.framework;
remoteRef = 84F466881F08256F00225386 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
84F4668B1F08256F00225386 /* RSCoreTests.xctest */ = {
isa = PBXReferenceProxy;
fileType = wrapper.cfbundle;
path = RSCoreTests.xctest;
remoteRef = 84F4668A1F08256F00225386 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
84F4668D1F08256F00225386 /* RSCore.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = RSCore.framework;
remoteRef = 84F4668C1F08256F00225386 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
84F466951F08257700225386 /* RSDatabase.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = RSDatabase.framework;
remoteRef = 84F466941F08257700225386 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
84F466971F08257700225386 /* RSDatabaseTests.xctest */ = {
isa = PBXReferenceProxy;
fileType = wrapper.cfbundle;
path = RSDatabaseTests.xctest;
remoteRef = 84F466961F08257700225386 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
84F466991F08257700225386 /* RSDatabase.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = RSDatabase.framework;
remoteRef = 84F466981F08257700225386 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
84F4669F1F08258300225386 /* DataModel.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = DataModel.framework;
remoteRef = 84F4669E1F08258300225386 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
/* End PBXReferenceProxy section */
/* Begin PBXResourcesBuildPhase section */
84F466661F08255100225386 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
84F4666F1F08255100225386 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
84F466631F08255100225386 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
84F466A41F0825B400225386 /* FeedbinAccount.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
84F4666D1F08255100225386 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
84F466771F08255100225386 /* FeedbinTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
84F466741F08255100225386 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 84F466671F08255100225386 /* Feedbin */;
targetProxy = 84F466731F08255100225386 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
84F4667A1F08255100225386 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.12;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
84F4667B1F08255100225386 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.12;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
84F4667D1F08255100225386 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
COMBINE_HIDPI_IMAGES = YES;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = M8L2WTLA8W;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_VERSION = A;
INFOPLIST_FILE = Feedbin/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.ranchero.Feedbin;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 3.0;
};
name = Debug;
};
84F4667E1F08255100225386 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
COMBINE_HIDPI_IMAGES = YES;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = M8L2WTLA8W;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_VERSION = A;
INFOPLIST_FILE = Feedbin/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.ranchero.Feedbin;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 3.0;
};
name = Release;
};
84F466801F08255100225386 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = M8L2WTLA8W;
INFOPLIST_FILE = FeedbinTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.ranchero.FeedbinTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 3.0;
};
name = Debug;
};
84F466811F08255100225386 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = M8L2WTLA8W;
INFOPLIST_FILE = FeedbinTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.ranchero.FeedbinTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 3.0;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
84F466621F08255100225386 /* Build configuration list for PBXProject "Feedbin" */ = {
isa = XCConfigurationList;
buildConfigurations = (
84F4667A1F08255100225386 /* Debug */,
84F4667B1F08255100225386 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
84F4667C1F08255100225386 /* Build configuration list for PBXNativeTarget "Feedbin" */ = {
isa = XCConfigurationList;
buildConfigurations = (
84F4667D1F08255100225386 /* Debug */,
84F4667E1F08255100225386 /* Release */,
);
defaultConfigurationIsVisible = 0;
};
84F4667F1F08255100225386 /* Build configuration list for PBXNativeTarget "FeedbinTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
84F466801F08255100225386 /* Debug */,
84F466811F08255100225386 /* Release */,
);
defaultConfigurationIsVisible = 0;
};
/* End XCConfigurationList section */
};
rootObject = 84F4665F1F08255100225386 /* Project object */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:Feedbin.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,19 @@
//
// Feedbin.h
// Feedbin
//
// Created by Brent Simmons on 7/1/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved.
//
#import <Cocoa/Cocoa.h>
//! Project version number for Feedbin.
FOUNDATION_EXPORT double FeedbinVersionNumber;
//! Project version string for Feedbin.
FOUNDATION_EXPORT const unsigned char FeedbinVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <Feedbin/PublicHeader.h>

View File

@ -0,0 +1,71 @@
//
// FeedbinAccount.swift
// Feedbin
//
// Created by Brent Simmons on 7/1/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved.
//
import Foundation
import RSCore
import DataModel
public let feedbinAccountType = "Feedbin"
public final class FeedbinAccount: Account, PlistProvider {
public let identifier: String
public let type = feedbinAccountType
public var nameForDisplay = "Feedbin"
private let settingsFile: String
private let dataFolder: String
private let diskSaver: DiskSaver
fileprivate let database: FeedbinDatabase
fileprivate var feeds = Set<FeedbinFeed>()
public var account: Account? {
get {
return self
}
}
public var unreadCount = 0 {
didSet {
postUnreadCountDidChangeNotification()
}
}
required public init(settingsFile: String, dataFolder: String, identifier: String) {
self.settingsFile = settingsFile
self.dataFolder = dataFolder
self.identifier = identifier
let databaseFile = (dataFolder as NSString).appendingPathComponent("FeedbinArticles0.db")
self.localDatabase = FeedbinDatabase(databaseFile: databaseFile)
self.diskSaver = DiskSaver(path: settingsFile)
self.localDatabase.account = self
self.diskSaver.delegate = self
self.refresher.account = self
pullSettingsAndTopLevelItemsFromFile()
self.database.startup()
updateUnreadCountsForTopLevelFolders()
updateUnreadCount()
NotificationCenter.default.addObserver(self, selector: #selector(articleStatusesDidChange(_:)), name: .ArticleStatusesDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(refreshProgressDidChange(_:)), name: .DownloadProgressDidChange, object: nil)
DispatchQueue.main.async() { () -> Void in
self.updateUnreadCounts(feedIDs: self.flattenedFeedIDs)
}
}
}

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2017 Ranchero Software, LLC. All rights reserved.</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>

View File

@ -0,0 +1,36 @@
//
// FeedbinTests.swift
// FeedbinTests
//
// Created by Brent Simmons on 7/1/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved.
//
import XCTest
@testable import Feedbin
class FeedbinTests: XCTestCase {
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}
func testExample() {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
func testPerformanceExample() {
// This is an example of a performance test case.
self.measure {
// Put the code you want to measure the time of here.
}
}
}

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>

View File

@ -8,7 +8,7 @@
import Foundation
import RSCore
import RSXML
import RSParser
import RSWeb
import DataModel
@ -130,7 +130,7 @@ public final class LocalAccount: Account, PlistProvider {
public func importOPML(_ opmlDocument: Any) {
if let opmlItems = (opmlDocument as? RSOPMLDocument)?.children as? [RSOPMLItem] {
if let opmlItems = (opmlDocument as? RSOPMLDocument)?.children {
performDataModelBatchUpdates {
importOPMLItems(opmlItems)
}
@ -369,7 +369,7 @@ public final class LocalAccount: Account, PlistProvider {
// MARK: Updating
func update(_ feed: LocalFeed, parsedFeed: RSParsedFeed, completionHandler: @escaping RSVoidCompletionBlock) {
func update(_ feed: LocalFeed, parsedFeed: ParsedFeed, completionHandler: @escaping RSVoidCompletionBlock) {
if let titleFromFeed = parsedFeed.title {
if feed.name != titleFromFeed {
@ -377,7 +377,7 @@ public final class LocalAccount: Account, PlistProvider {
self.diskSaver.dirty = true
}
}
if let linkFromFeed = parsedFeed.link {
if let linkFromFeed = parsedFeed.homePageURL {
if feed.homePageURL != linkFromFeed {
feed.homePageURL = linkFromFeed
self.diskSaver.dirty = true
@ -562,7 +562,7 @@ private extension LocalAccount {
for oneItem in items {
if oneItem.isFolder, let childItems = oneItem.children as? [RSOPMLItem] {
if oneItem.isFolder, let childItems = oneItem.children {
importOPMLTopLevelFolder(oneItem, childItems)
}
}
@ -592,7 +592,7 @@ private extension LocalAccount {
for oneItem in items {
if oneItem.isFolder, let childItems = oneItem.children as? [RSOPMLItem] {
if oneItem.isFolder, let childItems = oneItem.children {
importOPMLItemsIntoFolder(childItems, folder)
continue
}

View File

@ -23,8 +23,9 @@
84D37AD81D68CFD500110870 /* DataModel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84D37AD71D68CFD500110870 /* DataModel.framework */; };
84D37ADA1D68CFEE00110870 /* RSCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84D37AD91D68CFEE00110870 /* RSCore.framework */; };
84D37ADC1D68CFFC00110870 /* RSDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84D37ADB1D68CFFC00110870 /* RSDatabase.framework */; };
84D37ADE1D68D00700110870 /* RSXML.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84D37ADD1D68D00700110870 /* RSXML.framework */; };
84D37AE21D68D07700110870 /* RSWeb.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84D37AE11D68D07700110870 /* RSWeb.framework */; };
84F466591F0352F000225386 /* RSParser.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84F466581F0352F000225386 /* RSParser.framework */; };
84F4665B1F07321400225386 /* LocalAuthor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F4665A1F07321400225386 /* LocalAuthor.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -59,6 +60,8 @@
84D37ADB1D68CFFC00110870 /* RSDatabase.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RSDatabase.framework; path = "../../../../../Library/Developer/Xcode/DerivedData/Rainier-cidsoqwawkdqqphkdtrqrojskege/Build/Products/Debug/RSDatabase.framework"; sourceTree = "<group>"; };
84D37ADD1D68D00700110870 /* RSXML.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RSXML.framework; path = "../../../../../Library/Developer/Xcode/DerivedData/Rainier-cidsoqwawkdqqphkdtrqrojskege/Build/Products/Debug/RSXML.framework"; sourceTree = "<group>"; };
84D37AE11D68D07700110870 /* RSWeb.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RSWeb.framework; path = "../../../../../Library/Developer/Xcode/DerivedData/Rainier-cidsoqwawkdqqphkdtrqrojskege/Build/Products/Debug/RSWeb.framework"; sourceTree = "<group>"; };
84F466581F0352F000225386 /* RSParser.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RSParser.framework; path = "../../../../Library/Developer/Xcode/DerivedData/Evergreen-edcdqyvewhytnmcfaiesnqiqeynn/Build/Products/Debug/RSParser.framework"; sourceTree = "<group>"; };
84F4665A1F07321400225386 /* LocalAuthor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalAuthor.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -66,8 +69,8 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
84F466591F0352F000225386 /* RSParser.framework in Frameworks */,
84D37AE21D68D07700110870 /* RSWeb.framework in Frameworks */,
84D37ADE1D68D00700110870 /* RSXML.framework in Frameworks */,
84D37ADC1D68CFFC00110870 /* RSDatabase.framework in Frameworks */,
84D37ADA1D68CFEE00110870 /* RSCore.framework in Frameworks */,
84D37AD81D68CFD500110870 /* DataModel.framework in Frameworks */,
@ -104,6 +107,7 @@
8471A2E01ED4CFF3008F099E /* LocalFolder.swift */,
8471A2E21ED4CFFB008F099E /* LocalFeed.swift */,
8471A2E41ED4D007008F099E /* LocalArticle.swift */,
84F4665A1F07321400225386 /* LocalAuthor.swift */,
8471A2E61ED4D012008F099E /* LocalArticleStatus.swift */,
8471A2E81ED4D01B008F099E /* DiskDictionaryConstants.swift */,
8442EB3F1D68C8B200D709AE /* Database */,
@ -135,6 +139,7 @@
84D37AD61D68CFD500110870 /* Frameworks */ = {
isa = PBXGroup;
children = (
84F466581F0352F000225386 /* RSParser.framework */,
84D37AE11D68D07700110870 /* RSWeb.framework */,
84D37ADD1D68D00700110870 /* RSXML.framework */,
84D37ADB1D68CFFC00110870 /* RSDatabase.framework */,
@ -265,6 +270,7 @@
8471A2FE1ED4D0AD008F099E /* LocalStatusesManager.swift in Sources */,
8471A2DD1ED4CFE4008F099E /* LocalAccount.swift in Sources */,
8471A2DF1ED4CFEB008F099E /* LocalAccountRefresher.swift in Sources */,
84F4665B1F07321400225386 /* LocalAuthor.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -8,7 +8,7 @@
import Foundation
import RSCore
import RSXML
import RSParser
import RSWeb
final class LocalAccountRefresher: DownloadSessionDelegate {
@ -67,8 +67,8 @@ final class LocalAccountRefresher: DownloadSessionDelegate {
return
}
let xmlData = RSXMLData(data: data, urlString: feed.url)
RSParseFeed(xmlData) { (parsedFeed, error) in
let parserData = ParserData(url: feed.url, data: data)
FeedParser.parse(parserData) { (parsedFeed, error) in
guard let account = self.account, let parsedFeed = parsedFeed, error == nil else {
return
@ -101,8 +101,8 @@ final class LocalAccountRefresher: DownloadSessionDelegate {
}
if data.count > 4096 {
let xmlData = RSXMLData(data: data, urlString: feed.url)
return RSCanParseFeed(xmlData)
let parserData = ParserData(url: feed.url, data: data)
return FeedParser.canParse(parserData)
}
return true

View File

@ -8,8 +8,9 @@
import Foundation
import DataModel
import RSParser
public final class LocalArticle: NSObject, Article {
public final class LocalArticle: Article, Hashable {
public let account: Account?
public let feedID: String
@ -46,18 +47,12 @@ public final class LocalArticle: NSObject, Article {
private let _hash: Int
public override var hashValue: Int {
get {
return _hash
}
}
public init(account: Account, feedID: String, articleID: String) {
self.account = account
self.feedID = feedID
self.articleID = articleID
self._hash = articleID.hashValue
self._hash = databaseID.hashValue
}
public override func isEqual(_ object: Any?) -> Bool {
@ -84,7 +79,7 @@ public final class LocalArticle: NSObject, Article {
import RSCore
import RSDatabase
import RSXML
import RSParser
// Database columns *and* value keys.
@ -103,18 +98,18 @@ private let mergeablePropertyNames = [articleGuidKey, articleTitleKey, articleBo
public extension LocalArticle {
convenience init(account: Account, feedID: String, parsedArticle: RSParsedArticle) {
convenience init(account: Account, feedID: String, parsedItem: ParsedItem) {
self.init(account: account, feedID: feedID, articleID: parsedArticle.articleID)
self.init(account: account, feedID: feedID, articleID: parsedItem.databaseID)
self.guid = parsedArticle.guid
self.title = parsedArticle.title
self.body = parsedArticle.body
self.datePublished = parsedArticle.datePublished
self.dateModified = parsedArticle.dateModified
self.link = parsedArticle.link
self.permalink = parsedArticle.permalink
self.author = parsedArticle.author
self.guid = parsedItem.uniqueID
self.title = parsedItem.title
self.body = (parsedItem.contentHTML ?? parsedItem.contentText) ?? ""
self.datePublished = parsedItem.datePublished
self.dateModified = parsedItem.dateModified
self.link = parsedItem.externalURL
self.permalink = parsedItem.url
self.authors = parsedItem.authors
}
private enum ColumnIndex: Int {
@ -145,7 +140,7 @@ public extension LocalArticle {
}
}
func updateWithParsedArticle(_ parsedArticle: RSParsedArticle) -> NSDictionary? {
func updateWithParsedArticle(_ parsedArticle: ParsedItem) -> NSDictionary? {
let d: NSDictionary = rs_mergeValues(withObjectReturningChanges: parsedArticle, propertyNames: mergeablePropertyNames) as NSDictionary
if d.count < 1 {

View File

@ -0,0 +1,16 @@
//
// LocalAuthor.swift
// LocalAccount
//
// Created by Brent Simmons on 6/30/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved.
//
import Foundation
import RSParser
import DataModel
extension ParsedAuthor: Author {
}
public typealias LocalAuthor = ParsedAuthor

View File

@ -8,7 +8,7 @@
import Foundation
import RSCore
import RSXML
import RSParser
import RSDatabase
import DataModel
@ -182,7 +182,7 @@ final class LocalDatabase {
unreadCounts[oneFeedID] = self.unreadCount(oneFeedID, database)
}
DispatchQueue.main.async() { () -> Void in
DispatchQueue.main.async() {
completion(unreadCounts)
}
}
@ -191,14 +191,14 @@ final class LocalDatabase {
// MARK: Updating Articles
func updateFeedWithParsedFeed(_ feed: LocalFeed, parsedFeed: RSParsedFeed, completionHandler: @escaping RSVoidCompletionBlock) {
func updateFeedWithParsedFeed(_ feed: LocalFeed, parsedFeed: ParsedFeed, completionHandler: @escaping RSVoidCompletionBlock) {
if parsedFeed.articles.isEmpty {
if parsedFeed.items.isEmpty {
completionHandler()
return
}
let parsedArticlesDictionary = self.articlesDictionary(parsedFeed.articles as NSSet) as! [String: RSParsedArticle]
let parsedArticlesDictionary = self.articlesDictionary(parsedFeed.articles as NSSet) as! [String: ParsedItem]
fetchArticlesForFeedAsync(feed) { (articles) -> Void in
@ -259,7 +259,7 @@ private extension LocalDatabase {
// MARK: Updating Articles
func updateArticles(_ articles: [String: LocalArticle], parsedArticles: [String: RSParsedArticle], feed: LocalFeed, completionHandler: @escaping RSVoidCompletionBlock) {
func updateArticles(_ articles: [String: LocalArticle], parsedArticles: [String: ParsedItem], feed: LocalFeed, completionHandler: @escaping RSVoidCompletionBlock) {
statusesManager.ensureStatusesForParsedArticles(Set(parsedArticles.values)) {
@ -282,7 +282,7 @@ private extension LocalDatabase {
return d
}
func updateExistingArticles(_ articles: [String: LocalArticle], _ parsedArticles: [String: RSParsedArticle]) -> Set<NSDictionary> {
func updateExistingArticles(_ articles: [String: LocalArticle], _ parsedArticles: [String: ParsedItem]) -> Set<NSDictionary> {
var articleChanges = Set<NSDictionary>()
@ -299,12 +299,12 @@ private extension LocalDatabase {
// MARK: Creating Articles
func createNewArticlesWithParsedArticles(_ parsedArticles: Set<RSParsedArticle>, feedID: String) -> Set<LocalArticle> {
func createNewArticlesWithParsedArticles(_ parsedArticles: Set<ParsedItem>, feedID: String) -> Set<LocalArticle> {
return Set(parsedArticles.map { LocalArticle(account: account, feedID: feedID, parsedArticle: $0) })
}
func articlesWithParsedArticles(_ parsedArticles: Set<RSParsedArticle>, feedID: String) -> Set<LocalArticle> {
func articlesWithParsedArticles(_ parsedArticles: Set<ParsedItem>, feedID: String) -> Set<LocalArticle> {
var localArticles = Set<LocalArticle>()
@ -316,7 +316,7 @@ private extension LocalDatabase {
return localArticles
}
func createNewArticles(_ existingArticles: [String: LocalArticle], parsedArticles: [String: RSParsedArticle], feedID: String) -> Set<LocalArticle> {
func createNewArticles(_ existingArticles: [String: LocalArticle], parsedArticles: [String: ParsedItem], feedID: String) -> Set<LocalArticle> {
let newParsedArticles = parsedArticlesMinusExistingArticles(parsedArticles, existingArticles: existingArticles)
let newArticles = createNewArticlesWithParsedArticles(newParsedArticles, feedID: feedID)
@ -326,13 +326,13 @@ private extension LocalDatabase {
return newArticles
}
func parsedArticlesMinusExistingArticles(_ parsedArticles: [String: RSParsedArticle], existingArticles: [String: LocalArticle]) -> Set<RSParsedArticle> {
func parsedArticlesMinusExistingArticles(_ parsedArticles: [String: ParsedItem], existingArticles: [String: LocalArticle]) -> Set<ParsedItem> {
var result = Set<RSParsedArticle>()
var result = Set<ParsedItem>()
for oneParsedArticle in parsedArticles.values {
if let _ = existingArticles[oneParsedArticle.articleID] {
if let _ = existingArticles[oneParsedArticle.databaseID] {
continue
}
result.insert(oneParsedArticle)

View File

@ -9,7 +9,7 @@
import Foundation
import RSCore
import RSDatabase
import RSXML
import RSParser
import DataModel
final class LocalStatusesManager {
@ -43,9 +43,9 @@ final class LocalStatusesManager {
}
}
func ensureStatusesForParsedArticles(_ parsedArticles: Set<RSParsedArticle>, _ callback: @escaping RSVoidCompletionBlock) {
func ensureStatusesForParsedArticles(_ parsedArticles: [ParsedItem], _ callback: @escaping RSVoidCompletionBlock) {
var articleIDs = Set(parsedArticles.map { $0.articleID })
var articleIDs = Set(parsedArticles.map { $0.databaseID })
articleIDs = articleIDsMissingStatuses(articleIDs)
if articleIDs.isEmpty {
callback()
@ -56,7 +56,7 @@ final class LocalStatusesManager {
let statuses = self.fetchStatusesForArticleIDs(articleIDs, database: database)
DispatchQueue.main.async { () -> Void in
DispatchQueue.main.async {
self.cacheStatuses(statuses)
@ -209,3 +209,11 @@ private extension LocalStatusesManager {
}
}
extension ParsedItem {
var databaseID: String {
get {
return "\(feedURL) \(uniqueID)"
}
}
}

View File

@ -16,11 +16,11 @@
8430C47C1D58033D00E02399 /* sixcolors.html in Resources */ = {isa = PBXBuildFile; fileRef = 8430C4791D58033D00E02399 /* sixcolors.html */; };
8444267C1D5138A00092EDD4 /* FeedFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8444267B1D5138A00092EDD4 /* FeedFinder.swift */; };
84B06FEF1ED3808F00F0B54B /* RSWeb.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84B06FEE1ED3808F00F0B54B /* RSWeb.framework */; };
84B06FF11ED380A700F0B54B /* RSXML.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84B06FF01ED380A700F0B54B /* RSXML.framework */; };
84B06FF31ED3812600F0B54B /* RSCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84B06FF21ED3812600F0B54B /* RSCore.framework */; };
84BAAE231C8E6B3B009F5239 /* RSFeedFinder.h in Headers */ = {isa = PBXBuildFile; fileRef = 84BAAE221C8E6B3B009F5239 /* RSFeedFinder.h */; settings = {ATTRIBUTES = (Public, ); }; };
84BAAE2A1C8E6B3B009F5239 /* RSFeedFinder.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84BAAE1F1C8E6B3B009F5239 /* RSFeedFinder.framework */; };
84BAAE2F1C8E6B3B009F5239 /* RSFeedFinderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 84BAAE2E1C8E6B3B009F5239 /* RSFeedFinderTests.m */; };
84F466571F03523100225386 /* RSParser.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84F466561F03523100225386 /* RSParser.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -54,6 +54,7 @@
84E697E51C8E6C10009C585A /* RSCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RSCore.framework; path = ../RSCore/build/Debug/RSCore.framework; sourceTree = "<group>"; };
84E697E71C8E6C16009C585A /* RSXML.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RSXML.framework; path = ../RSXML/build/Debug/RSXML.framework; sourceTree = "<group>"; };
84E697E91C8E6C20009C585A /* RSWeb.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RSWeb.framework; path = ../RSWeb/build/Debug/RSWeb.framework; sourceTree = "<group>"; };
84F466561F03523100225386 /* RSParser.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RSParser.framework; path = ../RSParser/build/Debug/RSParser.framework; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -61,8 +62,8 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
84F466571F03523100225386 /* RSParser.framework in Frameworks */,
84B06FF31ED3812600F0B54B /* RSCore.framework in Frameworks */,
84B06FF11ED380A700F0B54B /* RSXML.framework in Frameworks */,
84B06FEF1ED3808F00F0B54B /* RSWeb.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -81,6 +82,7 @@
84B06FED1ED3808E00F0B54B /* Frameworks */ = {
isa = PBXGroup;
children = (
84F466561F03523100225386 /* RSParser.framework */,
84B06FF21ED3812600F0B54B /* RSCore.framework */,
84B06FF01ED380A700F0B54B /* RSXML.framework */,
84B06FEE1ED3808F00F0B54B /* RSWeb.framework */,

View File

@ -7,7 +7,7 @@
//
import Foundation
import RSXML
import RSParser
import RSWeb
import RSCore
@ -89,8 +89,8 @@ private extension FeedFinder {
func possibleFeedsInHTMLPage(htmlData: Data, urlString: String) -> Set<FeedSpecifier> {
let xmlData = RSXMLData(data: htmlData, urlString: urlString)
var feedSpecifiers = HTMLFeedFinder(xmlData: xmlData).feedSpecifiers
let parserData = ParserData(url: urlString, data: htmlData)
var feedSpecifiers = HTMLFeedFinder(parserData: parserData).feedSpecifiers
if feedSpecifiers.isEmpty {
// Odds are decent its a WordPress site, and just adding /feed/ will work.
@ -201,8 +201,8 @@ private extension FeedFinder {
}
func isFeed(_ data: Data, _ urlString: String) -> Bool {
let xmlData = RSXMLData(data: data, urlString: urlString)
return RSCanParseFeed(xmlData)
let parserData = ParserData(url: urlString, data: data)
return FeedParser.canParse(parserData)
}
}

View File

@ -7,7 +7,7 @@
//
import Foundation
import RSXML
import RSParser
private let feedURLWordsToMatch = ["feed", "xml", "rss", "atom"]
@ -21,9 +21,9 @@ class HTMLFeedFinder {
fileprivate var feedSpecifiersDictionary = [String: FeedSpecifier]()
init(xmlData: RSXMLData) {
init(parserData: ParserData) {
let metadata = RSHTMLMetadataParser(xmlData: xmlData).metadata
let metadata = RSHTMLMetadataParser.htmlMetadata(with: parserData)
for oneFeedLink in metadata.feedLinks {
if let oneURLString = oneFeedLink.urlString {
@ -32,7 +32,7 @@ class HTMLFeedFinder {
}
}
if let bodyLinks = RSHTMLLinkParser.htmlLinks(with: xmlData) {
if let bodyLinks = RSHTMLLinkParser.htmlLinks(with: parserData) {
for oneBodyLink in bodyLinks {
if linkMightBeFeed(oneBodyLink) {

View File

@ -8,7 +8,7 @@
import XCTest
@testable import RSFeedFinder
import RSXML
import RSParser
class HTMLFeedFinderTests: XCTestCase {

View File

@ -11,8 +11,22 @@ import Foundation
// FeedParser handles RSS, Atom, JSON Feed, and RSS-in-JSON.
// You dont need to know the type of feed.
public typealias FeedParserCallback = (_ parsedFeed: ParsedFeed?, _ error: Error?) -> Void
public struct FeedParser {
public static func canParse(_ parserData: ParserData) -> Bool {
let type = feedType(parserData)
switch type {
case .jsonFeed, .rssInJSON, .rss, .atom:
return true
default:
return false
}
}
public static func parse(_ parserData: ParserData) throws -> ParsedFeed? {
// This is generally fast enough to call on the main thread 
@ -42,4 +56,21 @@ public struct FeedParser {
}
catch { throw error }
}
public static func parse(_ parserData: ParserData, _ callback: @escaping FeedParserCallback) {
DispatchQueue.global(qos: .background).async {
do {
let parsedFeed = try parse(parserData)
DispatchQueue.main.async {
callback(parsedFeed, nil)
}
}
catch {
DispatchQueue.main.async {
callback(nil, error)
}
}
}
}
}

View File

@ -39,7 +39,7 @@ public struct JSONFeedParser {
let expired = parsedObject["expired"] as? Bool ?? false
let hubs = parseHubs(parsedObject)
let items = parseItems(itemsArray)
let items = parseItems(itemsArray, parserData.url)
return ParsedFeed(type: .jsonFeed, title: title, homePageURL: homePageURL, feedURL: feedURL, feedDescription: feedDescription, nextURL: nextURL, iconURL: iconURL, faviconURL: faviconURL, authors: authors, expired: expired, hubs: hubs, items: items)
@ -81,14 +81,14 @@ private extension JSONFeedParser {
return hubs.isEmpty ? nil : hubs
}
static func parseItems(_ itemsArray: JSONArray) -> [ParsedItem] {
static func parseItems(_ itemsArray: JSONArray, _ feedURL: String) -> [ParsedItem] {
return itemsArray.flatMap { (oneItemDictionary) -> ParsedItem? in
return parseItem(oneItemDictionary)
return parseItem(oneItemDictionary, feedURL)
}
}
static func parseItem(_ itemDictionary: JSONDictionary) -> ParsedItem? {
static func parseItem(_ itemDictionary: JSONDictionary, _ feedURL: String) -> ParsedItem? {
guard let uniqueID = parseUniqueID(itemDictionary) else {
return nil
@ -114,7 +114,7 @@ private extension JSONFeedParser {
let tags = itemDictionary["tags"] as? [String]
let attachments = parseAttachments(itemDictionary)
return ParsedItem(uniqueID: uniqueID, url: url, externalURL: externalURL, title: title, contentHTML: contentHTML, contentText: contentText, summary: summary, imageURL: imageURL, bannerImageURL: bannerImageURL, datePublished: datePublished, dateModified: dateModified, authors: authors, tags: tags, attachments: attachments)
return ParsedItem(uniqueID: uniqueID, feedURL: feedURL, url: url, externalURL: externalURL, title: title, contentHTML: contentHTML, contentText: contentText, summary: summary, imageURL: imageURL, bannerImageURL: bannerImageURL, datePublished: datePublished, dateModified: dateModified, authors: authors, tags: tags, attachments: attachments)
}
static func parseUniqueID(_ itemDictionary: JSONDictionary) -> String? {

View File

@ -47,7 +47,7 @@ public struct RSSInJSONParser {
let feedURL = parserData.url
let feedDescription = channelObject["description"] as? String
let items = parseItems(itemsObject!)
let items = parseItems(itemsObject!, parserData.url)
return ParsedFeed(type: .rssInJSON, title: title, homePageURL: homePageURL, feedURL: feedURL, feedDescription: feedDescription, nextURL: nil, iconURL: nil, faviconURL: nil, authors: nil, expired: false, hubs: nil, items: items)
@ -58,15 +58,15 @@ public struct RSSInJSONParser {
private extension RSSInJSONParser {
static func parseItems(_ itemsObject: JSONArray) -> [ParsedItem] {
static func parseItems(_ itemsObject: JSONArray, _ feedURL: String) -> [ParsedItem] {
return itemsObject.flatMap{ (oneItemDictionary) -> ParsedItem? in
return parsedItemWithDictionary(oneItemDictionary)
return parsedItemWithDictionary(oneItemDictionary, feedURL)
}
}
static func parsedItemWithDictionary(_ itemDictionary: JSONDictionary) -> ParsedItem? {
static func parsedItemWithDictionary(_ itemDictionary: JSONDictionary, _ feedURL: String) -> ParsedItem? {
let externalURL = itemDictionary["link"] as? String
let title = itemDictionary["title"] as? String
@ -126,7 +126,10 @@ private extension RSSInJSONParser {
uniqueID = (s as NSString).rsparser_md5Hash()
}
return ParsedItem(uniqueID: uniqueID, url: nil, externalURL: externalURL, title: title, contentHTML: contentHTML, contentText: contentText, summary: nil, imageURL: nil, bannerImageURL: nil, datePublished: datePublished, dateModified: nil, authors: authors, tags: tags, attachments: attachments)
if let uniqueID = uniqueID {
return ParsedItem(uniqueID: uniqueID, feedURL: feedURL, url: nil, externalURL: externalURL, title: title, contentHTML: contentHTML, contentText: contentText, summary: nil, imageURL: nil, bannerImageURL: nil, datePublished: datePublished, dateModified: nil, authors: authors, tags: tags, attachments: attachments)
}
return nil
}
static func parseAuthors(_ itemDictionary: JSONDictionary) -> [ParsedAuthor]? {

View File

@ -10,7 +10,8 @@ import Foundation
public struct ParsedItem {
public let uniqueID: String?
public let uniqueID: String
public let feedURL: String
public let url: String?
public let externalURL: String?
public let title: String?
@ -25,9 +26,10 @@ public struct ParsedItem {
public let tags: [String]?
public let attachments: [ParsedAttachment]?
init(uniqueID: String?, url: String?, externalURL: String?, title: String?, contentHTML: String?, contentText: String?, summary: String?, imageURL: String?, bannerImageURL: String?, datePublished: Date?, dateModified: Date?, authors: [ParsedAuthor]?, tags: [String]?, attachments: [ParsedAttachment]?) {
init(uniqueID: String, feedURL: String, url: String?, externalURL: String?, title: String?, contentHTML: String?, contentText: String?, summary: String?, imageURL: String?, bannerImageURL: String?, datePublished: Date?, dateModified: Date?, authors: [ParsedAuthor]?, tags: [String]?, attachments: [ParsedAttachment]?) {
self.uniqueID = uniqueID
self.feedURL = feedURL
self.url = url
self.externalURL = externalURL
self.title = title

View File

@ -46,7 +46,7 @@ private extension RSParsedFeedTransformer {
let dateModified = parsedArticle.dateModified
let authors = parsedAuthors(parsedArticle.author)
return ParsedItem(uniqueID: uniqueID, url: url, externalURL: externalURL, title: title, contentHTML: contentHTML, contentText: nil, summary: nil, imageURL: nil, bannerImageURL: nil, datePublished: datePublished, dateModified: dateModified, authors: authors, tags: nil, attachments: nil)
return ParsedItem(uniqueID: uniqueID, feedURL: parsedArticle.feedURL, url: url, externalURL: externalURL, title: title, contentHTML: contentHTML, contentText: nil, summary: nil, imageURL: nil, bannerImageURL: nil, datePublished: datePublished, dateModified: dateModified, authors: authors, tags: nil, attachments: nil)
}
static func parsedAuthors(_ authorEmailAddress: String?) -> [ParsedAuthor]? {

View File

@ -1,6 +1,6 @@
//
// RSDateParserTests.m
// RSXML
// RSParser
//
// Created by Brent Simmons on 12/26/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.