mirror of
https://github.com/Ranchero-Software/NetNewsWire.git
synced 2025-01-11 01:04:05 +01:00
Make SyncDatabase an actor and SyncStatusTable a struct. This matches the structure in ArticlesDatabase — and it makes sense, because the resource to be protected is the database, not the table.
This commit is contained in:
parent
123e72ba41
commit
1ddbe76653
@ -9,62 +9,105 @@
|
||||
import Foundation
|
||||
import RSCore
|
||||
import Database
|
||||
import FMDB
|
||||
|
||||
public struct SyncDatabase: Sendable {
|
||||
public actor SyncDatabase {
|
||||
|
||||
private let syncStatusTable: SyncStatusTable
|
||||
private var database: FMDatabase?
|
||||
private var databasePath: String
|
||||
private let syncStatusTable = SyncStatusTable()
|
||||
|
||||
public init(databaseFilePath: String) {
|
||||
public init(databasePath: String) {
|
||||
|
||||
self.syncStatusTable = SyncStatusTable(databasePath: databaseFilePath)
|
||||
let database = FMDatabase.openAndSetUpDatabase(path: databasePath)
|
||||
database.runCreateStatements(Self.creationStatements)
|
||||
database.vacuum()
|
||||
|
||||
self.database = database
|
||||
self.databasePath = databasePath
|
||||
}
|
||||
|
||||
// MARK: - API
|
||||
|
||||
public func insertStatuses(_ statuses: [SyncStatus]) async throws {
|
||||
try await syncStatusTable.insertStatuses(statuses)
|
||||
public func insertStatuses(_ statuses: [SyncStatus]) throws {
|
||||
|
||||
guard let database else {
|
||||
throw DatabaseError.suspended
|
||||
}
|
||||
syncStatusTable.insertStatuses(statuses, database: database)
|
||||
}
|
||||
|
||||
public func selectForProcessing(limit: Int? = nil) async throws -> Set<SyncStatus>? {
|
||||
try await syncStatusTable.selectForProcessing(limit: limit)
|
||||
public func selectForProcessing(limit: Int? = nil) throws -> Set<SyncStatus>? {
|
||||
|
||||
guard let database else {
|
||||
throw DatabaseError.suspended
|
||||
}
|
||||
return syncStatusTable.selectForProcessing(limit: limit, database: database)
|
||||
}
|
||||
|
||||
public func selectPendingCount() async throws -> Int? {
|
||||
try await syncStatusTable.selectPendingCount()
|
||||
public func selectPendingCount() throws -> Int? {
|
||||
|
||||
guard let database else {
|
||||
throw DatabaseError.suspended
|
||||
}
|
||||
return syncStatusTable.selectPendingCount(database: database)
|
||||
}
|
||||
|
||||
public func selectPendingReadStatusArticleIDs() async throws -> Set<String>? {
|
||||
try await syncStatusTable.selectPendingReadStatusArticleIDs()
|
||||
public func selectPendingReadStatusArticleIDs() throws -> Set<String>? {
|
||||
|
||||
guard let database else {
|
||||
throw DatabaseError.suspended
|
||||
}
|
||||
return syncStatusTable.selectPendingReadStatusArticleIDs(database: database)
|
||||
}
|
||||
|
||||
public func selectPendingStarredStatusArticleIDs() async throws -> Set<String>? {
|
||||
try await syncStatusTable.selectPendingStarredStatusArticleIDs()
|
||||
public func selectPendingStarredStatusArticleIDs() throws -> Set<String>? {
|
||||
|
||||
guard let database else {
|
||||
throw DatabaseError.suspended
|
||||
}
|
||||
return syncStatusTable.selectPendingStarredStatusArticleIDs(database: database)
|
||||
}
|
||||
|
||||
public func resetAllSelectedForProcessing() async throws {
|
||||
try await syncStatusTable.resetAllSelectedForProcessing()
|
||||
public func resetAllSelectedForProcessing() throws {
|
||||
|
||||
guard let database else {
|
||||
throw DatabaseError.suspended
|
||||
}
|
||||
syncStatusTable.resetAllSelectedForProcessing(database: database)
|
||||
}
|
||||
|
||||
public func resetSelectedForProcessing(_ articleIDs: [String]) async throws {
|
||||
try await syncStatusTable.resetSelectedForProcessing(articleIDs)
|
||||
public func resetSelectedForProcessing(_ articleIDs: [String]) throws {
|
||||
|
||||
guard let database else {
|
||||
throw DatabaseError.suspended
|
||||
}
|
||||
syncStatusTable.resetSelectedForProcessing(articleIDs, database: database)
|
||||
}
|
||||
|
||||
public func deleteSelectedForProcessing(_ articleIDs: [String]) async throws {
|
||||
try await syncStatusTable.deleteSelectedForProcessing(articleIDs)
|
||||
public func deleteSelectedForProcessing(_ articleIDs: [String]) throws {
|
||||
|
||||
guard let database else {
|
||||
throw DatabaseError.suspended
|
||||
}
|
||||
syncStatusTable.deleteSelectedForProcessing(articleIDs, database: database)
|
||||
}
|
||||
|
||||
// MARK: - Suspend and Resume (for iOS)
|
||||
|
||||
/// Close the database and stop running database calls.
|
||||
///
|
||||
/// On Macs, suspend() and resume() do nothing. They’re not needed.
|
||||
public func suspend() async {
|
||||
await syncStatusTable.suspend()
|
||||
public func suspend() {
|
||||
#if os(iOS)
|
||||
database?.close()
|
||||
database = nil
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Open the database and allow for running database calls again.
|
||||
public func resume() async {
|
||||
await syncStatusTable.resume()
|
||||
func resume() {
|
||||
#if os(iOS)
|
||||
if database == nil {
|
||||
self.database = FMDatabase.openAndSetUpDatabase(path: databasePath)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,9 +121,9 @@ public typealias SyncStatusesCompletionBlock = @Sendable (SyncStatusesResult) ->
|
||||
public typealias SyncStatusArticleIDsResult = Result<Set<String>, DatabaseError>
|
||||
public typealias SyncStatusArticleIDsCompletionBlock = @Sendable (SyncStatusArticleIDsResult) -> Void
|
||||
|
||||
extension SyncDatabase {
|
||||
public extension SyncDatabase {
|
||||
|
||||
public func insertStatuses(_ statuses: [SyncStatus], completion: @escaping DatabaseCompletionBlock) {
|
||||
nonisolated func insertStatuses(_ statuses: [SyncStatus], completion: @escaping DatabaseCompletionBlock) {
|
||||
|
||||
Task {
|
||||
do {
|
||||
@ -92,7 +135,7 @@ extension SyncDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
public func selectForProcessing(limit: Int? = nil, completion: @escaping SyncStatusesCompletionBlock) {
|
||||
nonisolated func selectForProcessing(limit: Int? = nil, completion: @escaping SyncStatusesCompletionBlock) {
|
||||
|
||||
Task {
|
||||
do {
|
||||
@ -107,7 +150,7 @@ extension SyncDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
public func selectPendingCount(completion: @escaping DatabaseIntCompletionBlock) {
|
||||
nonisolated func selectPendingCount(completion: @escaping DatabaseIntCompletionBlock) {
|
||||
|
||||
Task {
|
||||
do {
|
||||
@ -123,7 +166,7 @@ extension SyncDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
public func selectPendingReadStatusArticleIDs(completion: @escaping SyncStatusArticleIDsCompletionBlock) {
|
||||
nonisolated func selectPendingReadStatusArticleIDs(completion: @escaping SyncStatusArticleIDsCompletionBlock) {
|
||||
|
||||
Task {
|
||||
do {
|
||||
@ -138,7 +181,7 @@ extension SyncDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
public func selectPendingStarredStatusArticleIDs(completion: @escaping SyncStatusArticleIDsCompletionBlock) {
|
||||
nonisolated func selectPendingStarredStatusArticleIDs(completion: @escaping SyncStatusArticleIDsCompletionBlock) {
|
||||
|
||||
Task {
|
||||
do {
|
||||
@ -153,7 +196,7 @@ extension SyncDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
public func resetAllSelectedForProcessing(completion: DatabaseCompletionBlock? = nil) {
|
||||
nonisolated func resetAllSelectedForProcessing(completion: DatabaseCompletionBlock? = nil) {
|
||||
|
||||
Task {
|
||||
do {
|
||||
@ -165,7 +208,7 @@ extension SyncDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
public func resetSelectedForProcessing(_ articleIDs: [String], completion: DatabaseCompletionBlock? = nil) {
|
||||
nonisolated func resetSelectedForProcessing(_ articleIDs: [String], completion: DatabaseCompletionBlock? = nil) {
|
||||
|
||||
Task {
|
||||
do {
|
||||
@ -177,7 +220,7 @@ extension SyncDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
public func deleteSelectedForProcessing(_ articleIDs: [String], completion: DatabaseCompletionBlock? = nil) {
|
||||
nonisolated func deleteSelectedForProcessing(_ articleIDs: [String], completion: DatabaseCompletionBlock? = nil) {
|
||||
|
||||
Task {
|
||||
do {
|
||||
@ -188,23 +231,12 @@ extension SyncDatabase {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Suspend and Resume (for iOS)
|
||||
|
||||
/// Close the database and stop running database calls.
|
||||
/// Any pending calls will complete first.
|
||||
public func suspend() {
|
||||
|
||||
Task {
|
||||
await self.suspend()
|
||||
}
|
||||
}
|
||||
|
||||
/// Open the database and allow for running database calls again.
|
||||
public func resume() {
|
||||
|
||||
Task {
|
||||
await self.resume()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension SyncDatabase {
|
||||
|
||||
static let creationStatements = """
|
||||
CREATE TABLE if not EXISTS syncStatus (articleID TEXT NOT NULL, key TEXT NOT NULL, flag BOOL NOT NULL DEFAULT 0, selected BOOL NOT NULL DEFAULT 0, PRIMARY KEY (articleID, key));
|
||||
"""
|
||||
|
||||
}
|
||||
|
@ -12,48 +12,11 @@ import Articles
|
||||
import Database
|
||||
import FMDB
|
||||
|
||||
actor SyncStatusTable {
|
||||
struct SyncStatusTable {
|
||||
|
||||
static private let tableName = "syncStatus"
|
||||
static private let name = "syncStatus"
|
||||
|
||||
private var database: FMDatabase?
|
||||
private let databasePath: String
|
||||
|
||||
init(databasePath: String) {
|
||||
|
||||
let database = FMDatabase.openAndSetUpDatabase(path: databasePath)
|
||||
database.runCreateStatements(SyncStatusTable.creationStatements)
|
||||
database.vacuum()
|
||||
|
||||
self.database = database
|
||||
self.databasePath = databasePath
|
||||
}
|
||||
|
||||
func suspend() {
|
||||
#if os(iOS)
|
||||
database?.close()
|
||||
database = nil
|
||||
#endif
|
||||
}
|
||||
|
||||
func resume() {
|
||||
#if os(iOS)
|
||||
if database == nil {
|
||||
self.database = FMDatabase.openAndSetUpDatabase(path: databasePath)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
func close() {
|
||||
|
||||
database?.close()
|
||||
}
|
||||
|
||||
func selectForProcessing(limit: Int?) throws -> Set<SyncStatus>? {
|
||||
|
||||
guard let database else {
|
||||
throw DatabaseError.suspended
|
||||
}
|
||||
func selectForProcessing(limit: Int?, database: FMDatabase) -> Set<SyncStatus>? {
|
||||
|
||||
let updateSQL = "update syncStatus set selected = true"
|
||||
database.executeUpdateInTransaction(updateSQL, withArgumentsIn: nil)
|
||||
@ -73,11 +36,7 @@ actor SyncStatusTable {
|
||||
return statuses
|
||||
}
|
||||
|
||||
func selectPendingCount() throws -> Int? {
|
||||
|
||||
guard let database else {
|
||||
throw DatabaseError.suspended
|
||||
}
|
||||
func selectPendingCount(database: FMDatabase) -> Int? {
|
||||
|
||||
let sql = "select count(*) from syncStatus"
|
||||
guard let resultSet = database.executeQuery(sql, withArgumentsIn: nil) else {
|
||||
@ -88,32 +47,27 @@ actor SyncStatusTable {
|
||||
return count
|
||||
}
|
||||
|
||||
func selectPendingReadStatusArticleIDs() throws -> Set<String>? {
|
||||
try selectPendingArticleIDs(.read)
|
||||
func selectPendingReadStatusArticleIDs(database: FMDatabase) -> Set<String>? {
|
||||
|
||||
selectPendingArticleIDs(.read, database: database)
|
||||
}
|
||||
|
||||
func selectPendingStarredStatusArticleIDs() throws -> Set<String>? {
|
||||
try selectPendingArticleIDs(.starred)
|
||||
func selectPendingStarredStatusArticleIDs(database: FMDatabase) -> Set<String>? {
|
||||
|
||||
selectPendingArticleIDs(.starred, database: database)
|
||||
}
|
||||
|
||||
func resetAllSelectedForProcessing() throws {
|
||||
|
||||
guard let database else {
|
||||
throw DatabaseError.suspended
|
||||
}
|
||||
func resetAllSelectedForProcessing(database: FMDatabase) {
|
||||
|
||||
let updateSQL = "update syncStatus set selected = false"
|
||||
database.executeUpdateInTransaction(updateSQL, withArgumentsIn: nil)
|
||||
}
|
||||
|
||||
func resetSelectedForProcessing(_ articleIDs: [String]) throws {
|
||||
func resetSelectedForProcessing(_ articleIDs: [String], database: FMDatabase) {
|
||||
|
||||
guard !articleIDs.isEmpty else {
|
||||
return
|
||||
}
|
||||
guard let database else {
|
||||
throw DatabaseError.suspended
|
||||
}
|
||||
|
||||
let parameters = articleIDs.map { $0 as AnyObject }
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(articleIDs.count))!
|
||||
@ -122,14 +76,11 @@ actor SyncStatusTable {
|
||||
database.executeUpdateInTransaction(updateSQL, withArgumentsIn: parameters)
|
||||
}
|
||||
|
||||
func deleteSelectedForProcessing(_ articleIDs: [String]) throws {
|
||||
func deleteSelectedForProcessing(_ articleIDs: [String], database: FMDatabase) {
|
||||
|
||||
guard !articleIDs.isEmpty else {
|
||||
return
|
||||
}
|
||||
guard let database else {
|
||||
throw DatabaseError.suspended
|
||||
}
|
||||
|
||||
let parameters = articleIDs.map { $0 as AnyObject }
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(articleIDs.count))!
|
||||
@ -138,16 +89,12 @@ actor SyncStatusTable {
|
||||
database.executeUpdateInTransaction(deleteSQL, withArgumentsIn: parameters)
|
||||
}
|
||||
|
||||
func insertStatuses(_ statuses: [SyncStatus]) throws {
|
||||
|
||||
guard let database else {
|
||||
throw DatabaseError.suspended
|
||||
}
|
||||
func insertStatuses(_ statuses: [SyncStatus], database: FMDatabase) {
|
||||
|
||||
database.beginTransaction()
|
||||
|
||||
let statusArray = statuses.map { $0.databaseDictionary() }
|
||||
database.insertRows(statusArray, insertType: .orReplace, tableName: Self.tableName)
|
||||
database.insertRows(statusArray, insertType: .orReplace, tableName: Self.name)
|
||||
|
||||
database.commit()
|
||||
}
|
||||
@ -155,10 +102,6 @@ actor SyncStatusTable {
|
||||
|
||||
private extension SyncStatusTable {
|
||||
|
||||
static let creationStatements = """
|
||||
CREATE TABLE if not EXISTS syncStatus (articleID TEXT NOT NULL, key TEXT NOT NULL, flag BOOL NOT NULL DEFAULT 0, selected BOOL NOT NULL DEFAULT 0, PRIMARY KEY (articleID, key));
|
||||
"""
|
||||
|
||||
func statusWithRow(_ row: FMResultSet) -> SyncStatus? {
|
||||
|
||||
guard let articleID = row.string(forColumn: DatabaseKey.articleID),
|
||||
@ -173,11 +116,7 @@ private extension SyncStatusTable {
|
||||
return SyncStatus(articleID: articleID, key: key, flag: flag, selected: selected)
|
||||
}
|
||||
|
||||
func selectPendingArticleIDs(_ statusKey: ArticleStatus.Key) throws -> Set<String>? {
|
||||
|
||||
guard let database else {
|
||||
throw DatabaseError.suspended
|
||||
}
|
||||
func selectPendingArticleIDs(_ statusKey: ArticleStatus.Key, database: FMDatabase) -> Set<String>? {
|
||||
|
||||
let sql = "select articleID from syncStatus where selected == false and key = \"\(statusKey.rawValue)\";"
|
||||
guard let resultSet = database.executeQuery(sql, withArgumentsIn: nil) else {
|
||||
|
Loading…
Reference in New Issue
Block a user