Add DataModel framework.

This commit is contained in:
Brent Simmons 2017-05-23 13:14:30 -07:00
parent dec10ad0a1
commit 91d81831e9
13 changed files with 939 additions and 0 deletions

View File

@ -7,6 +7,8 @@
objects = {
/* Begin PBXBuildFile section */
8471A2C41ED4CEBF008F099E /* DataModel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8471A2B71ED4CEAD008F099E /* DataModel.framework */; };
8471A2C51ED4CEBF008F099E /* DataModel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8471A2B71ED4CEAD008F099E /* DataModel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
849C64641ED37A5D003D8FC0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849C64631ED37A5D003D8FC0 /* AppDelegate.swift */; };
849C64661ED37A5D003D8FC0 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849C64651ED37A5D003D8FC0 /* ViewController.swift */; };
849C64681ED37A5D003D8FC0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 849C64671ED37A5D003D8FC0 /* Assets.xcassets */; };
@ -31,6 +33,20 @@
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
8471A2B61ED4CEAD008F099E /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 8471A2B21ED4CEAD008F099E /* DataModel.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 84C7AE921D68C558009FB883;
remoteInfo = DataModel;
};
8471A2C61ED4CEBF008F099E /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 8471A2B21ED4CEAD008F099E /* DataModel.xcodeproj */;
proxyType = 1;
remoteGlobalIDString = 84C7AE911D68C558009FB883;
remoteInfo = DataModel;
};
849C64721ED37A5D003D8FC0 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 849C64581ED37A5D003D8FC0 /* Project object */;
@ -249,6 +265,7 @@
84B06FFE1ED3818D00F0B54B /* RSTree.framework in Embed Frameworks */,
84B06FAF1ED37DBD00F0B54B /* RSCore.framework in Embed Frameworks */,
84B06FD01ED37F7D00F0B54B /* DB5.framework in Embed Frameworks */,
8471A2C51ED4CEBF008F099E /* DataModel.framework in Embed Frameworks */,
84B06FC31ED37E9600F0B54B /* RSWeb.framework in Embed Frameworks */,
84B06F831ED37BDD00F0B54B /* RSXML.framework in Embed Frameworks */,
);
@ -258,6 +275,7 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
8471A2B21ED4CEAD008F099E /* DataModel.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = DataModel.xcodeproj; path = Frameworks/DataModel/DataModel.xcodeproj; sourceTree = "<group>"; };
849C64601ED37A5D003D8FC0 /* Evergreen.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Evergreen.app; sourceTree = BUILT_PRODUCTS_DIR; };
849C64631ED37A5D003D8FC0 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = Evergreen/AppDelegate.swift; sourceTree = "<group>"; };
849C64651ED37A5D003D8FC0 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ViewController.swift; path = Evergreen/ViewController.swift; sourceTree = "<group>"; };
@ -288,6 +306,7 @@
84B06FFD1ED3818D00F0B54B /* RSTree.framework in Frameworks */,
84B06FAE1ED37DBD00F0B54B /* RSCore.framework in Frameworks */,
84B06FCF1ED37F7D00F0B54B /* DB5.framework in Frameworks */,
8471A2C41ED4CEBF008F099E /* DataModel.framework in Frameworks */,
84B06FC21ED37E9600F0B54B /* RSWeb.framework in Frameworks */,
84B06F821ED37BDD00F0B54B /* RSXML.framework in Frameworks */,
);
@ -303,6 +322,14 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
8471A2B31ED4CEAD008F099E /* Products */ = {
isa = PBXGroup;
children = (
8471A2B71ED4CEAD008F099E /* DataModel.framework */,
);
name = Products;
sourceTree = "<group>";
};
849C64571ED37A5D003D8FC0 = {
isa = PBXGroup;
children = (
@ -313,6 +340,7 @@
849C646C1ED37A5D003D8FC0 /* Info.plist */,
849C64741ED37A5D003D8FC0 /* EvergreenTests */,
849C64611ED37A5D003D8FC0 /* Products */,
8471A2B21ED4CEAD008F099E /* DataModel.xcodeproj */,
84B06FC61ED37F7200F0B54B /* DB5.xcodeproj */,
84B06FA21ED37DAC00F0B54B /* RSCore.xcodeproj */,
84B06F961ED37DA000F0B54B /* RSDatabase.xcodeproj */,
@ -441,6 +469,7 @@
84B06FEC1ED3803A00F0B54B /* PBXTargetDependency */,
84B070001ED3818D00F0B54B /* PBXTargetDependency */,
84B0700D1ED3822600F0B54B /* PBXTargetDependency */,
8471A2C71ED4CEBF008F099E /* PBXTargetDependency */,
);
name = Evergreen;
productName = Evergreen;
@ -500,6 +529,10 @@
productRefGroup = 849C64611ED37A5D003D8FC0 /* Products */;
projectDirPath = "";
projectReferences = (
{
ProductGroup = 8471A2B31ED4CEAD008F099E /* Products */;
ProjectRef = 8471A2B21ED4CEAD008F099E /* DataModel.xcodeproj */;
},
{
ProductGroup = 84B06FC71ED37F7200F0B54B /* Products */;
ProjectRef = 84B06FC61ED37F7200F0B54B /* DB5.xcodeproj */;
@ -542,6 +575,13 @@
/* End PBXProject section */
/* Begin PBXReferenceProxy section */
8471A2B71ED4CEAD008F099E /* DataModel.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = DataModel.framework;
remoteRef = 8471A2B61ED4CEAD008F099E /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
84B06F7D1ED37BCA00F0B54B /* RSXML.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
@ -724,6 +764,11 @@
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
8471A2C71ED4CEBF008F099E /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
name = DataModel;
targetProxy = 8471A2C61ED4CEBF008F099E /* PBXContainerItemProxy */;
};
849C64731ED37A5D003D8FC0 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 849C645F1ED37A5D003D8FC0 /* Evergreen */;

View File

@ -0,0 +1,44 @@
//
// AccountProtocol.swift
// Rainier
//
// Created by Brent Simmons on 4/17/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
//
import Foundation
public protocol Account: class, Folder {
var identifier: String {get}
var type: String {get}
var refreshInProgress: Bool {get}
init(settingsFile: String, dataFolder: String, identifier: String)
func refreshAll()
func markArticles(_ articles: NSSet, statusKey: ArticleStatusKey, flag: Bool)
func hasFeedWithURLString(_: String) -> Bool
func importOPML(_: Any)
func fetchArticles(for: [AnyObject]) -> [Article]
}
public extension Account {
func hasFeedWithURLString(_ urlString: String) -> Bool {
if let _ = existingFeedWithURL(urlString) {
return true
}
return false
}
public func postArticleStatusesDidChangeNotification(_ articles: NSSet) {
NotificationCenter.default.post(name: .ArticleStatusesDidChange, object: self, userInfo: [articlesKey: articles])
}
}

View File

@ -0,0 +1,68 @@
//
// ArticleProtocol.swift
// Rainier
//
// 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,43 @@
//
// ArticleStatusProtocol.swift
// Rainier
//
// Created by Brent Simmons on 4/23/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
//
import Foundation
public enum ArticleStatusKey: String {
case read = "read"
case starred = "starred"
case userDeleted = "userDeleted"
}
public protocol ArticleStatus {
var read: Bool {get set}
var starred: Bool {get set}
var userDeleted: Bool {get set}
var dateArrived: Date {get}
func boolStatusForKey(_ articleStatusKey: ArticleStatusKey) -> Bool
func setBoolStatusForKey(_ status: Bool, articleStatusKey: ArticleStatusKey)
}
public extension ArticleStatus {
func boolStatusForKey(_ articleStatusKey: ArticleStatusKey) -> Bool {
switch articleStatusKey {
case .read:
return read
case .starred:
return starred
case .userDeleted:
return userDeleted
}
}
}

View File

@ -0,0 +1,76 @@
//
// BatchUpdates.swift
// DataModel
//
// Created by Brent Simmons on 9/12/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
//
import Foundation
private final class BatchUpdatesTracker {
private var batchUpdatesCount = 0
var isPerformingBatchUpdates: Bool {
get {
return batchUpdatesCount > 0
}
}
func incrementBatchUpdatesCount() {
batchUpdatesCount = batchUpdatesCount + 1
}
func decrementBatchUpdatesCount() {
batchUpdatesCount = batchUpdatesCount - 1
if batchUpdatesCount < 1 {
if batchUpdatesCount < 0 {
assertionFailure("Batch updates count should never be below 0.")
batchUpdatesCount = 0
}
batchUpdatesCount = 0
postDataModelDidPerformBatchUpdates()
}
}
func postDataModelDidPerformBatchUpdates() {
NotificationCenter.default.post(name: .DataModelDidPerformBatchUpdates, object: nil)
}
}
fileprivate let batchUpdatesTracker = BatchUpdatesTracker()
public func dataModelIsPerformingBatchUpdates() -> Bool {
return batchUpdatesTracker.isPerformingBatchUpdates
}
public typealias BatchUpdatesBlock = () -> Void
public func performDataModelBatchUpdates(_ batchUpdatesBlock: BatchUpdatesBlock) {
startDataModelBatchUpdates()
batchUpdatesBlock()
endDataModelBatchUpdates()
}
private func startDataModelBatchUpdates() {
batchUpdatesTracker.incrementBatchUpdatesCount()
}
private func endDataModelBatchUpdates() {
batchUpdatesTracker.decrementBatchUpdatesCount()
}

View File

@ -0,0 +1,340 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* 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 */; };
8471A2D81ED4CEFA008F099E /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8471A2D31ED4CEFA008F099E /* Notifications.swift */; };
8471A2D91ED4CEFA008F099E /* UnreadCountProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8471A2D41ED4CEFA008F099E /* UnreadCountProviderProtocol.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>"; };
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; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
84C7AE8E1D68C558009FB883 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
8439773E1DA07F2400F0FCBD /* RSCore.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
8439773C1DA07F2400F0FCBD /* Frameworks */ = {
isa = PBXGroup;
children = (
8439773D1DA07F2400F0FCBD /* RSCore.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
84C7AE881D68C558009FB883 = {
isa = PBXGroup;
children = (
8471A2C81ED4CEEE008F099E /* AccountProtocol.swift */,
8471A2C91ED4CEEE008F099E /* ArticleProtocol.swift */,
8471A2CA1ED4CEEE008F099E /* ArticleStatusProtocol.swift */,
8471A2CB1ED4CEEE008F099E /* BatchUpdates.swift */,
8471A2D01ED4CEFA008F099E /* DisplayNameProviderProtocol.swift */,
8471A2D11ED4CEFA008F099E /* FeedProtocol.swift */,
8471A2D21ED4CEFA008F099E /* FolderProtocol.swift */,
8471A2D31ED4CEFA008F099E /* Notifications.swift */,
8471A2D41ED4CEFA008F099E /* UnreadCountProviderProtocol.swift */,
8471A2DA1ED4CF01008F099E /* Info.plist */,
84C7AE931D68C558009FB883 /* Products */,
8439773C1DA07F2400F0FCBD /* Frameworks */,
);
sourceTree = "<group>";
};
84C7AE931D68C558009FB883 /* Products */ = {
isa = PBXGroup;
children = (
84C7AE921D68C558009FB883 /* DataModel.framework */,
);
name = Products;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
84C7AE8F1D68C558009FB883 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
84C7AE911D68C558009FB883 /* DataModel */ = {
isa = PBXNativeTarget;
buildConfigurationList = 84C7AE9A1D68C558009FB883 /* Build configuration list for PBXNativeTarget "DataModel" */;
buildPhases = (
84C7AE8D1D68C558009FB883 /* Sources */,
84C7AE8E1D68C558009FB883 /* Frameworks */,
84C7AE8F1D68C558009FB883 /* Headers */,
84C7AE901D68C558009FB883 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = DataModel;
productName = DataModel;
productReference = 84C7AE921D68C558009FB883 /* DataModel.framework */;
productType = "com.apple.product-type.framework";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
84C7AE891D68C558009FB883 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0800;
ORGANIZATIONNAME = "Ranchero Software, LLC";
TargetAttributes = {
84C7AE911D68C558009FB883 = {
CreatedOnToolsVersion = 8.0;
LastSwiftMigration = 0800;
ProvisioningStyle = Automatic;
};
};
};
buildConfigurationList = 84C7AE8C1D68C558009FB883 /* Build configuration list for PBXProject "DataModel" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = 84C7AE881D68C558009FB883;
productRefGroup = 84C7AE931D68C558009FB883 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
84C7AE911D68C558009FB883 /* DataModel */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
84C7AE901D68C558009FB883 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
84C7AE8D1D68C558009FB883 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8471A2CC1ED4CEEE008F099E /* AccountProtocol.swift in Sources */,
8471A2D61ED4CEFA008F099E /* FeedProtocol.swift in Sources */,
8471A2CF1ED4CEEE008F099E /* BatchUpdates.swift in Sources */,
8471A2CE1ED4CEEE008F099E /* ArticleStatusProtocol.swift in Sources */,
8471A2D91ED4CEFA008F099E /* UnreadCountProviderProtocol.swift in Sources */,
8471A2CD1ED4CEEE008F099E /* ArticleProtocol.swift in Sources */,
8471A2D51ED4CEFA008F099E /* DisplayNameProviderProtocol.swift in Sources */,
8471A2D81ED4CEFA008F099E /* Notifications.swift in Sources */,
8471A2D71ED4CEFA008F099E /* FolderProtocol.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
84C7AE981D68C558009FB883 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
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_MOVES = 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.11;
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;
};
84C7AE991D68C558009FB883 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
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_MOVES = 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.11;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
84C7AE9B1D68C558009FB883 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
COMBINE_HIDPI_IMAGES = YES;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_VERSION = A;
INFOPLIST_FILE = Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.ranchero.DataModel;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 3.0;
};
name = Debug;
};
84C7AE9C1D68C558009FB883 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
COMBINE_HIDPI_IMAGES = YES;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_VERSION = A;
INFOPLIST_FILE = Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.ranchero.DataModel;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 3.0;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
84C7AE8C1D68C558009FB883 /* Build configuration list for PBXProject "DataModel" */ = {
isa = XCConfigurationList;
buildConfigurations = (
84C7AE981D68C558009FB883 /* Debug */,
84C7AE991D68C558009FB883 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
84C7AE9A1D68C558009FB883 /* Build configuration list for PBXNativeTarget "DataModel" */ = {
isa = XCConfigurationList;
buildConfigurations = (
84C7AE9B1D68C558009FB883 /* Debug */,
84C7AE9C1D68C558009FB883 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 84C7AE891D68C558009FB883 /* Project object */;
}

View File

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

View File

@ -0,0 +1,15 @@
//
// DisplayNameProviderProtocol.swift
// DataModel
//
// Created by Brent Simmons on 7/28/16.
// Copyright © 2016 Ranchero Software. All rights reserved.
//
import Foundation
public protocol DisplayNameProvider {
var nameForDisplay: String {get}
}

View File

@ -0,0 +1,45 @@
//
// FeedProtocol.swift
// Rainier
//
// 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,171 @@
//
// FolderProtocol.swift
// Rainier
//
// Created by Brent Simmons on 4/17/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
//
import Foundation
import RSCore
public typealias FolderVisitBlock = (_ obj: AnyObject) -> Bool
public let FolderChildrenDidChangeNotification = "FolderChildNodesDidChangeNotification"
public func FolderPostChildrenDidChangeNotification(_ folder: Folder) {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: FolderChildrenDidChangeNotification), object: folder)
}
public protocol Folder: class, UnreadCountProvider, DisplayNameProvider {
// 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
var flattenedFeeds: NSSet {get}
func objectIsChild(_ obj: AnyObject) -> Bool
func objectIsDescendant(_ obj: AnyObject) -> Bool
func fetchArticles() -> [Article]
// visitBlock should return true to stop visiting.
// visitObjects returns true if a visitBlock returned true.
func visitObjects(_ recurse: Bool, visitBlock: FolderVisitBlock) -> Bool
func visitChildren(visitBlock: FolderVisitBlock) -> Bool // Above with recurse = false
func findObject(_ recurse: Bool, visitBlock: FolderVisitBlock) -> AnyObject?
func canAddItem(_ item: AnyObject) -> Bool
func addItem(_ item: AnyObject) -> Bool // Return true even if item already exists.
func addItems(_ items: [AnyObject]) -> Bool // Return true even if some items already exist.
func canAddFolderWithName(_ folderName: String) -> Bool // Special case: folder with name exists. Return true in that case.
func ensureFolderWithName(_ folderName: String) -> Folder? // Return folder even if item already exists.
// Recurses
func existingFeedWithID(_ feedID: String) -> Feed?
func existingFeedWithURL(_ urlString: String) -> Feed?
// Does not recurse.
func existingFolderWithName(_ name: String) -> Folder?
// Doesn't add feed. Just creates instance.
func createFeedWithName(_ name: String?, editedName: String?, urlString: String) -> Feed?
func deleteItems(_ items: [AnyObject])
// Exporting OPML.
func opmlString(indentLevel: Int) -> String
}
public extension Folder {
var hasAtLeastOneFeed: Bool {
get {
return visitObjects(true) { (oneObject) in
return oneObject is Feed
}
}
}
func visitChildren(visitBlock: FolderVisitBlock) -> Bool {
return visitObjects(false, visitBlock: visitBlock)
}
func findObject(_ recurse: Bool, visitBlock: FolderVisitBlock) -> AnyObject? {
var foundObject: AnyObject?
let _ = visitObjects(recurse) { (oneObject) in
if let _ = foundObject {
return true
}
if visitBlock(oneObject) {
foundObject = oneObject
return true
}
return false
}
return foundObject
}
func objectIsChild(_ obj: AnyObject) -> Bool {
return visitObjects(false) { (oneObject) in
return obj === oneObject
}
}
func objectIsDescendant(_ obj: AnyObject) -> Bool {
return visitObjects(true) { (oneObject) in
return obj === oneObject
}
}
func existingFolderWithName(_ name: String) -> Folder? {
let foundObject = findObject(false) { (oneObject) in
if let oneFolder = oneObject as? Folder, oneFolder.nameForDisplay == name {
return true
}
return false
}
return foundObject as! Folder?
}
func addItems(_ items: [AnyObject]) -> Bool {
var atLeastOneItemAdded = false
items.forEach { (oneItem) in
if addItem(oneItem) {
atLeastOneItemAdded = true
}
}
return atLeastOneItemAdded
}
func opmlString(indentLevel: Int) -> String {
let escapedTitle = nameForDisplay.rs_stringByEscapingSpecialXMLCharacters()
var s = "<outline text=\"\(escapedTitle)\" title=\"\(escapedTitle)\">\n"
s = s.rs_string(byPrependingNumberOfTabs: indentLevel)
var hasAtLeastOneChild = false
let _ = visitChildren { (oneChild) -> Bool in
hasAtLeastOneChild = true
if let oneFolder = oneChild as? Folder {
s = s + oneFolder.opmlString(indentLevel: indentLevel + 1)
}
else if let oneFeed = oneChild as? Feed {
s = s + oneFeed.opmlString(indentLevel: indentLevel + 1)
}
return false
}
if !hasAtLeastOneChild {
s = "<outline text=\"\(escapedTitle)\" title=\"\(escapedTitle)\"/>\n"
s = s.rs_string(byPrependingNumberOfTabs: indentLevel)
return s
}
s = s + NSString.rs_string(withNumberOfTabs: indentLevel) + "</outline>\n"
return s
}
}

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 © 2016 Ranchero Software, LLC. All rights reserved.</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>

View File

@ -0,0 +1,21 @@
//
// Notifications.swift
// DataModel
//
// Created by Brent Simmons on 9/10/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
//
import Foundation
public extension Notification.Name {
public static let ArticleStatusesDidChange = Notification.Name(rawValue: "ArticleStatusesDidChange")
public static let UnreadCountDidChange = Notification.Name(rawValue: "UnreadCountDidChangeNotification")
public static let DataModelDidPerformBatchUpdates = Notification.Name(rawValue: "DataModelDidPerformBatchUpdatesDidPerformBatchUpdatesNotification")
public static let AccountRefreshProgressDidChange = Notification.Name(rawValue: "AccountRefreshProgressDidChangeNotification")
}
public let articlesKey = "articles"
public let unreadCountKey = "unreadCount"
public let progressKey = "progress" //RSProgress

View File

@ -0,0 +1,38 @@
//
// UnreadCountProtocol.swift
// Rainier
//
// Created by Brent Simmons on 4/8/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
//
import Foundation
public protocol UnreadCountProvider {
var unreadCount: Int {get}
func updateUnreadCount()
}
public func calculateUnreadCount<T: Collection>(_ children: T) -> Int {
var updatedUnreadCount = 0
children.forEach { (oneChild) in
if let oneUnreadCountProvider = oneChild as? UnreadCountProvider {
updatedUnreadCount += oneUnreadCountProvider.unreadCount
}
}
return updatedUnreadCount
}
public extension UnreadCountProvider {
public func postUnreadCountDidChangeNotification() {
NotificationCenter.default.post(name: .UnreadCountDidChange, object: self, userInfo: [unreadCountKey: unreadCount])
}
}