Move common code to swift package
This commit is contained in:
parent
8547950d80
commit
2b0ab45c12
|
@ -8,20 +8,14 @@
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
D6451241276981A40046CCD2 /* WindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6451240276981A40046CCD2 /* WindowController.swift */; };
|
D6451241276981A40046CCD2 /* WindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6451240276981A40046CCD2 /* WindowController.swift */; };
|
||||||
D6451243276A408F0046CCD2 /* LocalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6451242276A408F0046CCD2 /* LocalData.swift */; };
|
D6559A5228721BAF000EEB4D /* MastoSearchCore in Frameworks */ = {isa = PBXBuildFile; productRef = D6559A5128721BAF000EEB4D /* MastoSearchCore */; };
|
||||||
D669039E2769236F00819C4D /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D669039C2769236F00819C4D /* ViewController.swift */; };
|
D669039E2769236F00819C4D /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D669039C2769236F00819C4D /* ViewController.swift */; };
|
||||||
D66903BE2769250B00819C4D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D66903BD2769250B00819C4D /* Main.storyboard */; };
|
D66903BE2769250B00819C4D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D66903BD2769250B00819C4D /* Main.storyboard */; };
|
||||||
D66903C127692EAB00819C4D /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = D66903C027692EAB00819C4D /* SwiftSoup */; };
|
D66903C127692EAB00819C4D /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = D66903C027692EAB00819C4D /* SwiftSoup */; };
|
||||||
D6A4B8A827C1BC5A0016F458 /* APIController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A4B8A727C1BC5A0016F458 /* APIController.swift */; };
|
|
||||||
D6A4B8B027C2B1770016F458 /* MastoSearchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A4B8AF27C2B1770016F458 /* MastoSearchTests.swift */; };
|
D6A4B8B027C2B1770016F458 /* MastoSearchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A4B8AF27C2B1770016F458 /* MastoSearchTests.swift */; };
|
||||||
D6A4B8B727C2B18C0016F458 /* ImportControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A4B8B627C2B18C0016F458 /* ImportControllerTests.swift */; };
|
D6A4B8B727C2B18C0016F458 /* ImportControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A4B8B627C2B18C0016F458 /* ImportControllerTests.swift */; };
|
||||||
D6A4B8BA27C2BE330016F458 /* UInt128.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A4B8B927C2BE330016F458 /* UInt128.swift */; };
|
|
||||||
D6B24DE927640CE100BA23B8 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B24DE827640CE100BA23B8 /* AppDelegate.swift */; };
|
D6B24DE927640CE100BA23B8 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B24DE827640CE100BA23B8 /* AppDelegate.swift */; };
|
||||||
D6B24DEB27640CE200BA23B8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D6B24DEA27640CE200BA23B8 /* Assets.xcassets */; };
|
D6B24DEB27640CE200BA23B8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D6B24DEA27640CE200BA23B8 /* Assets.xcassets */; };
|
||||||
D6B24DF727640D2600BA23B8 /* FMDB in Frameworks */ = {isa = PBXBuildFile; productRef = D6B24DF627640D2600BA23B8 /* FMDB */; };
|
|
||||||
D6B24DF927640DD700BA23B8 /* DatabaseController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B24DF827640DD700BA23B8 /* DatabaseController.swift */; };
|
|
||||||
D6D9CFE82764196E006FE2E7 /* ImportController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D9CFE72764196E006FE2E7 /* ImportController.swift */; };
|
|
||||||
D6D9CFEA27641D4A006FE2E7 /* Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D9CFE927641D4A006FE2E7 /* Status.swift */; };
|
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
@ -36,21 +30,16 @@
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
D6451240276981A40046CCD2 /* WindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowController.swift; sourceTree = "<group>"; };
|
D6451240276981A40046CCD2 /* WindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowController.swift; sourceTree = "<group>"; };
|
||||||
D6451242276A408F0046CCD2 /* LocalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalData.swift; sourceTree = "<group>"; };
|
|
||||||
D669039C2769236F00819C4D /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
|
D669039C2769236F00819C4D /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
|
||||||
D66903BD2769250B00819C4D /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = "<group>"; };
|
D66903BD2769250B00819C4D /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = "<group>"; };
|
||||||
D6A4B8A727C1BC5A0016F458 /* APIController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIController.swift; sourceTree = "<group>"; };
|
|
||||||
D6A4B8AD27C2B1770016F458 /* MastoSearchTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MastoSearchTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
D6A4B8AD27C2B1770016F458 /* MastoSearchTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MastoSearchTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
D6A4B8AF27C2B1770016F458 /* MastoSearchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastoSearchTests.swift; sourceTree = "<group>"; };
|
D6A4B8AF27C2B1770016F458 /* MastoSearchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastoSearchTests.swift; sourceTree = "<group>"; };
|
||||||
D6A4B8B627C2B18C0016F458 /* ImportControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportControllerTests.swift; sourceTree = "<group>"; };
|
D6A4B8B627C2B18C0016F458 /* ImportControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportControllerTests.swift; sourceTree = "<group>"; };
|
||||||
D6A4B8B927C2BE330016F458 /* UInt128.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UInt128.swift; sourceTree = "<group>"; };
|
|
||||||
D6B24DE527640CE100BA23B8 /* MastoSearch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MastoSearch.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
D6B24DE527640CE100BA23B8 /* MastoSearch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MastoSearch.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
D6B24DE827640CE100BA23B8 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
D6B24DE827640CE100BA23B8 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
D6B24DEA27640CE200BA23B8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
D6B24DEA27640CE200BA23B8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
D6B24DEF27640CE200BA23B8 /* MastoSearch.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MastoSearch.entitlements; sourceTree = "<group>"; };
|
D6B24DEF27640CE200BA23B8 /* MastoSearch.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MastoSearch.entitlements; sourceTree = "<group>"; };
|
||||||
D6B24DF827640DD700BA23B8 /* DatabaseController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseController.swift; sourceTree = "<group>"; };
|
D6E77D1428721A7600D8B732 /* MastoSearchCore */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = MastoSearchCore; sourceTree = "<group>"; };
|
||||||
D6D9CFE72764196E006FE2E7 /* ImportController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportController.swift; sourceTree = "<group>"; };
|
|
||||||
D6D9CFE927641D4A006FE2E7 /* Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Status.swift; sourceTree = "<group>"; };
|
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
@ -65,14 +54,21 @@
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
D6B24DF727640D2600BA23B8 /* FMDB in Frameworks */,
|
|
||||||
D66903C127692EAB00819C4D /* SwiftSoup in Frameworks */,
|
D66903C127692EAB00819C4D /* SwiftSoup in Frameworks */,
|
||||||
|
D6559A5228721BAF000EEB4D /* MastoSearchCore in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
|
D6559A5028721BAF000EEB4D /* Frameworks */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
);
|
||||||
|
name = Frameworks;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
D6A4B8AE27C2B1770016F458 /* MastoSearchTests */ = {
|
D6A4B8AE27C2B1770016F458 /* MastoSearchTests */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -82,20 +78,14 @@
|
||||||
path = MastoSearchTests;
|
path = MastoSearchTests;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
D6A4B8B827C2BE250016F458 /* Vendor */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
D6A4B8B927C2BE330016F458 /* UInt128.swift */,
|
|
||||||
);
|
|
||||||
path = Vendor;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
D6B24DDC27640CE100BA23B8 = {
|
D6B24DDC27640CE100BA23B8 = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
D6E77D1428721A7600D8B732 /* MastoSearchCore */,
|
||||||
D6B24DE727640CE100BA23B8 /* MastoSearch */,
|
D6B24DE727640CE100BA23B8 /* MastoSearch */,
|
||||||
D6A4B8AE27C2B1770016F458 /* MastoSearchTests */,
|
D6A4B8AE27C2B1770016F458 /* MastoSearchTests */,
|
||||||
D6B24DE627640CE100BA23B8 /* Products */,
|
D6B24DE627640CE100BA23B8 /* Products */,
|
||||||
|
D6559A5028721BAF000EEB4D /* Frameworks */,
|
||||||
);
|
);
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
@ -112,14 +102,8 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
D6B24DE827640CE100BA23B8 /* AppDelegate.swift */,
|
D6B24DE827640CE100BA23B8 /* AppDelegate.swift */,
|
||||||
D6451242276A408F0046CCD2 /* LocalData.swift */,
|
|
||||||
D6B24DF827640DD700BA23B8 /* DatabaseController.swift */,
|
|
||||||
D6D9CFE72764196E006FE2E7 /* ImportController.swift */,
|
|
||||||
D6A4B8A727C1BC5A0016F458 /* APIController.swift */,
|
|
||||||
D6D9CFE927641D4A006FE2E7 /* Status.swift */,
|
|
||||||
D6451240276981A40046CCD2 /* WindowController.swift */,
|
D6451240276981A40046CCD2 /* WindowController.swift */,
|
||||||
D669039C2769236F00819C4D /* ViewController.swift */,
|
D669039C2769236F00819C4D /* ViewController.swift */,
|
||||||
D6A4B8B827C2BE250016F458 /* Vendor */,
|
|
||||||
D6B24DEA27640CE200BA23B8 /* Assets.xcassets */,
|
D6B24DEA27640CE200BA23B8 /* Assets.xcassets */,
|
||||||
D66903BD2769250B00819C4D /* Main.storyboard */,
|
D66903BD2769250B00819C4D /* Main.storyboard */,
|
||||||
D6B24DEF27640CE200BA23B8 /* MastoSearch.entitlements */,
|
D6B24DEF27640CE200BA23B8 /* MastoSearch.entitlements */,
|
||||||
|
@ -162,8 +146,8 @@
|
||||||
);
|
);
|
||||||
name = MastoSearch;
|
name = MastoSearch;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
D6B24DF627640D2600BA23B8 /* FMDB */,
|
|
||||||
D66903C027692EAB00819C4D /* SwiftSoup */,
|
D66903C027692EAB00819C4D /* SwiftSoup */,
|
||||||
|
D6559A5128721BAF000EEB4D /* MastoSearchCore */,
|
||||||
);
|
);
|
||||||
productName = MastoSearch;
|
productName = MastoSearch;
|
||||||
productReference = D6B24DE527640CE100BA23B8 /* MastoSearch.app */;
|
productReference = D6B24DE527640CE100BA23B8 /* MastoSearch.app */;
|
||||||
|
@ -246,13 +230,7 @@
|
||||||
files = (
|
files = (
|
||||||
D669039E2769236F00819C4D /* ViewController.swift in Sources */,
|
D669039E2769236F00819C4D /* ViewController.swift in Sources */,
|
||||||
D6451241276981A40046CCD2 /* WindowController.swift in Sources */,
|
D6451241276981A40046CCD2 /* WindowController.swift in Sources */,
|
||||||
D6B24DF927640DD700BA23B8 /* DatabaseController.swift in Sources */,
|
|
||||||
D6B24DE927640CE100BA23B8 /* AppDelegate.swift in Sources */,
|
D6B24DE927640CE100BA23B8 /* AppDelegate.swift in Sources */,
|
||||||
D6A4B8A827C1BC5A0016F458 /* APIController.swift in Sources */,
|
|
||||||
D6D9CFE82764196E006FE2E7 /* ImportController.swift in Sources */,
|
|
||||||
D6A4B8BA27C2BE330016F458 /* UInt128.swift in Sources */,
|
|
||||||
D6D9CFEA27641D4A006FE2E7 /* Status.swift in Sources */,
|
|
||||||
D6451243276A408F0046CCD2 /* LocalData.swift in Sources */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -524,16 +502,15 @@
|
||||||
/* End XCRemoteSwiftPackageReference section */
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
|
|
||||||
/* Begin XCSwiftPackageProductDependency section */
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
|
D6559A5128721BAF000EEB4D /* MastoSearchCore */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
productName = MastoSearchCore;
|
||||||
|
};
|
||||||
D66903C027692EAB00819C4D /* SwiftSoup */ = {
|
D66903C027692EAB00819C4D /* SwiftSoup */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = D66903BF27692EAB00819C4D /* XCRemoteSwiftPackageReference "SwiftSoup" */;
|
package = D66903BF27692EAB00819C4D /* XCRemoteSwiftPackageReference "SwiftSoup" */;
|
||||||
productName = SwiftSoup;
|
productName = SwiftSoup;
|
||||||
};
|
};
|
||||||
D6B24DF627640D2600BA23B8 /* FMDB */ = {
|
|
||||||
isa = XCSwiftPackageProductDependency;
|
|
||||||
package = D6B24DF527640D2600BA23B8 /* XCRemoteSwiftPackageReference "fmdb" */;
|
|
||||||
productName = FMDB;
|
|
||||||
};
|
|
||||||
/* End XCSwiftPackageProductDependency section */
|
/* End XCSwiftPackageProductDependency section */
|
||||||
};
|
};
|
||||||
rootObject = D6B24DDD27640CE100BA23B8 /* Project object */;
|
rootObject = D6B24DDD27640CE100BA23B8 /* Project object */;
|
||||||
|
|
|
@ -10,18 +10,13 @@ import UniformTypeIdentifiers
|
||||||
import AuthenticationServices
|
import AuthenticationServices
|
||||||
import Combine
|
import Combine
|
||||||
import OSLog
|
import OSLog
|
||||||
|
import MastoSearchCore
|
||||||
|
|
||||||
@main
|
@main
|
||||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
|
|
||||||
@IBOutlet weak var accountMenu: NSMenu!
|
@IBOutlet weak var accountMenu: NSMenu!
|
||||||
|
|
||||||
let onSync = PassthroughSubject<Void, Never>()
|
|
||||||
|
|
||||||
private let syncLogger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "Sync")
|
|
||||||
private var authSession: ASWebAuthenticationSession?
|
|
||||||
private var syncTotal = 0
|
|
||||||
|
|
||||||
func applicationWillFinishLaunching(_ notification: Notification) {
|
func applicationWillFinishLaunching(_ notification: Notification) {
|
||||||
DatabaseController.shared.initialize()
|
DatabaseController.shared.initialize()
|
||||||
|
|
||||||
|
@ -29,7 +24,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
||||||
syncStatuses()
|
SyncController.shared.syncStatuses(errorHandler: self.handleSyncError(_:))
|
||||||
}
|
}
|
||||||
|
|
||||||
func applicationWillTerminate(_ aNotification: Notification) {
|
func applicationWillTerminate(_ aNotification: Notification) {
|
||||||
|
@ -51,55 +46,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func syncStatuses() {
|
|
||||||
DatabaseController.shared.getNewestStatus { status in
|
|
||||||
guard let status = status else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
self.syncLogger.log("Starting sync...")
|
|
||||||
self.syncTotal = 0
|
|
||||||
self.syncStatuses(range: .after(status.id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func syncStatuses(range: APIController.RequestRange) {
|
|
||||||
APIController.shared.getStatuses(range: range) { response in
|
|
||||||
switch response {
|
|
||||||
case .failure(let error):
|
|
||||||
self.syncLogger.error("Erorr syncing statuses: \(String(describing: error), privacy: .public)")
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
let alert = NSAlert()
|
|
||||||
alert.alertStyle = .warning
|
|
||||||
alert.messageText = "Error syncing statuses"
|
|
||||||
alert.informativeText = error.localizedDescription
|
|
||||||
alert.runModal()
|
|
||||||
}
|
|
||||||
|
|
||||||
case .success(let statuses):
|
|
||||||
guard statuses.count > 0 else {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.syncLogger.log("Finished sync of \(self.syncTotal, privacy: .public) statuses")
|
|
||||||
self.onSync.send()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
DatabaseController.shared.addStatuses(statuses.compactMap {
|
|
||||||
if $0.hasReblog {
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
return Status(id: $0.id, url: $0.url, summary: $0.spoiler_text, content: $0.content, published: $0.created_at)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
self.syncTotal += statuses.count
|
|
||||||
|
|
||||||
self.syncStatuses(range: .after(statuses.first!.id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@IBAction func importFile(_ sender: Any) {
|
@IBAction func importFile(_ sender: Any) {
|
||||||
let panel = NSOpenPanel()
|
let panel = NSOpenPanel()
|
||||||
panel.canChooseFiles = true
|
panel.canChooseFiles = true
|
||||||
|
@ -111,8 +57,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ImportController.shared.importCSV(url: panel.url!)
|
ImportController.shared.importCSV(url: panel.url!)
|
||||||
self.onSync.send()
|
SyncController.shared.onSync.send()
|
||||||
self.syncStatuses()
|
SyncController.shared.syncStatuses(errorHandler: self.handleSyncError(_:))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,60 +77,25 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalData.account = LocalData.AccountInfo(instanceURL: url, clientID: nil, clientSecret: nil, accessToken: nil)
|
LoginController.shared.logIn(with: url, presentationContextProvider: self) {
|
||||||
|
|
||||||
APIController.shared.register { response in
|
|
||||||
guard case .success(let registration) = response else {
|
|
||||||
fatalError()
|
|
||||||
}
|
|
||||||
|
|
||||||
LocalData.account!.clientID = registration.client_id
|
|
||||||
LocalData.account!.clientSecret = registration.client_secret
|
|
||||||
|
|
||||||
var authorizeComponents = URLComponents(url: url, resolvingAgainstBaseURL: false)!
|
|
||||||
authorizeComponents.path = "/oauth/authorize"
|
|
||||||
authorizeComponents.queryItems = [
|
|
||||||
URLQueryItem(name: "client_id", value: LocalData.account!.clientID),
|
|
||||||
URLQueryItem(name: "response_type", value: "code"),
|
|
||||||
URLQueryItem(name: "scope", value: APIController.shared.scopes),
|
|
||||||
URLQueryItem(name: "redirect_uri", value: APIController.shared.redirectURI),
|
|
||||||
]
|
|
||||||
|
|
||||||
self.authSession = ASWebAuthenticationSession(url: authorizeComponents.url!, callbackURLScheme: "mastosearch", completionHandler: { url, error in
|
|
||||||
guard error == nil,
|
|
||||||
let url = url,
|
|
||||||
let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
|
|
||||||
let item = components.queryItems?.first(where: { $0.name == "code" }),
|
|
||||||
let authCode = item.value else {
|
|
||||||
fatalError()
|
|
||||||
}
|
|
||||||
|
|
||||||
APIController.shared.getAccessToken(authCode: authCode) { response in
|
|
||||||
guard case .success(let settings) = response else {
|
|
||||||
fatalError()
|
|
||||||
}
|
|
||||||
|
|
||||||
LocalData.account!.accessToken = settings.access_token
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.updateAccountMenu()
|
self.updateAccountMenu()
|
||||||
self.syncStatuses()
|
SyncController.shared.syncStatuses(errorHandler: self.handleSyncError(_:))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.authSession!.presentationContextProvider = self
|
|
||||||
self.authSession!.start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func logOut() {
|
@objc func logOut() {
|
||||||
LocalData.account = nil
|
LocalData.account = nil
|
||||||
updateAccountMenu()
|
updateAccountMenu()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func handleSyncError(_ error: APIController.Error) {
|
||||||
|
let alert = NSAlert()
|
||||||
|
alert.alertStyle = .warning
|
||||||
|
alert.messageText = "Error syncing statuses"
|
||||||
|
alert.informativeText = error.localizedDescription
|
||||||
|
alert.runModal()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AppDelegate: ASWebAuthenticationPresentationContextProviding {
|
extension AppDelegate: ASWebAuthenticationPresentationContextProviding {
|
||||||
|
|
|
@ -734,7 +734,7 @@
|
||||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||||
<prototypeCellViews>
|
<prototypeCellViews>
|
||||||
<tableCellView identifier="date" id="yUJ-7Q-rvg">
|
<tableCellView identifier="date" id="yUJ-7Q-rvg">
|
||||||
<rect key="frame" x="8" y="0.0" width="146" height="24"/>
|
<rect key="frame" x="18" y="0.0" width="146" height="24"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<textField identifier="dateCell" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kPp-F2-rtb">
|
<textField identifier="dateCell" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kPp-F2-rtb">
|
||||||
|
@ -767,7 +767,7 @@
|
||||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||||
<prototypeCellViews>
|
<prototypeCellViews>
|
||||||
<tableCellView identifier="contentWarning" id="m4I-Cd-y2k">
|
<tableCellView identifier="contentWarning" id="m4I-Cd-y2k">
|
||||||
<rect key="frame" x="171" y="0.0" width="173" height="24"/>
|
<rect key="frame" x="181" y="0.0" width="173" height="24"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<textField identifier="contentWarningCell" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Zz1-AR-4EW">
|
<textField identifier="contentWarningCell" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Zz1-AR-4EW">
|
||||||
|
@ -800,7 +800,7 @@
|
||||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||||
<prototypeCellViews>
|
<prototypeCellViews>
|
||||||
<tableCellView identifier="content" id="BHM-ku-S45">
|
<tableCellView identifier="content" id="BHM-ku-S45">
|
||||||
<rect key="frame" x="361" y="0.0" width="303" height="17"/>
|
<rect key="frame" x="371" y="0.0" width="303" height="17"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Hwo-JH-icI">
|
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Hwo-JH-icI">
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
//
|
|
||||||
// Status.swift
|
|
||||||
// MastoSearch
|
|
||||||
//
|
|
||||||
// Created by Shadowfacts on 12/10/21.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
struct Status: Identifiable {
|
|
||||||
let id: String
|
|
||||||
let url: String
|
|
||||||
let summary: String?
|
|
||||||
let content: String
|
|
||||||
let published: Date
|
|
||||||
}
|
|
|
@ -8,6 +8,7 @@
|
||||||
import Cocoa
|
import Cocoa
|
||||||
import Combine
|
import Combine
|
||||||
import SwiftSoup
|
import SwiftSoup
|
||||||
|
import MastoSearchCore
|
||||||
|
|
||||||
class ViewController: NSViewController {
|
class ViewController: NSViewController {
|
||||||
|
|
||||||
|
@ -65,7 +66,7 @@ class ViewController: NSViewController {
|
||||||
.sink { [unowned self] in self.loadAll() }
|
.sink { [unowned self] in self.loadAll() }
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
(NSApp.delegate as! AppDelegate).onSync
|
SyncController.shared.onSync
|
||||||
.sink { [unowned self] in
|
.sink { [unowned self] in
|
||||||
self.allStatusesSnapshot = nil
|
self.allStatusesSnapshot = nil
|
||||||
self.loadAll()
|
self.loadAll()
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
import Cocoa
|
import Cocoa
|
||||||
import Combine
|
import Combine
|
||||||
|
import MastoSearchCore
|
||||||
|
|
||||||
class WindowController: NSWindowController {
|
class WindowController: NSWindowController {
|
||||||
|
|
||||||
|
@ -29,7 +30,7 @@ class WindowController: NSWindowController {
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
(NSApp.delegate as! AppDelegate).onSync
|
SyncController.shared.onSync
|
||||||
.sink { [unowned self] in self.updateSubtitle() }
|
.sink { [unowned self] in self.updateSubtitle() }
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
.DS_Store
|
||||||
|
/.build
|
||||||
|
/Packages
|
||||||
|
/*.xcodeproj
|
||||||
|
xcuserdata/
|
||||||
|
DerivedData/
|
||||||
|
.swiftpm/config/registries.json
|
||||||
|
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||||
|
.netrc
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"pins" : [
|
||||||
|
{
|
||||||
|
"identity" : "fmdb",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/ccgus/fmdb.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "61e51fde7f7aab6554f30ab061cc588b28a97d04",
|
||||||
|
"version" : "2.7.7"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version" : 2
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
// swift-tools-version: 5.7
|
||||||
|
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||||
|
|
||||||
|
import PackageDescription
|
||||||
|
|
||||||
|
let package = Package(
|
||||||
|
name: "MastoSearchCore",
|
||||||
|
platforms: [
|
||||||
|
.macOS(.v12),
|
||||||
|
],
|
||||||
|
products: [
|
||||||
|
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
||||||
|
.library(
|
||||||
|
name: "MastoSearchCore",
|
||||||
|
targets: ["MastoSearchCore"]),
|
||||||
|
],
|
||||||
|
dependencies: [
|
||||||
|
// Dependencies declare other packages that this package depends on.
|
||||||
|
// .package(url: /* package url */, from: "1.0.0"),
|
||||||
|
.package(url: "https://github.com/ccgus/fmdb.git", .upToNextMinor(from: "2.7.7")),
|
||||||
|
],
|
||||||
|
targets: [
|
||||||
|
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
||||||
|
// Targets can depend on other targets in this package, and on products in packages this package depends on.
|
||||||
|
.target(
|
||||||
|
name: "MastoSearchCore",
|
||||||
|
dependencies: [
|
||||||
|
.product(name: "FMDB", package: "fmdb"),
|
||||||
|
]),
|
||||||
|
.testTarget(
|
||||||
|
name: "MastoSearchCoreTests",
|
||||||
|
dependencies: ["MastoSearchCore"]),
|
||||||
|
]
|
||||||
|
)
|
|
@ -0,0 +1,3 @@
|
||||||
|
# MastoSearchCore
|
||||||
|
|
||||||
|
A description of this package.
|
|
@ -5,15 +5,15 @@
|
||||||
// Created by Shadowfacts on 2/19/22.
|
// Created by Shadowfacts on 2/19/22.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Cocoa
|
import Foundation
|
||||||
|
|
||||||
struct APIController {
|
public struct APIController {
|
||||||
|
|
||||||
static let shared = APIController()
|
public static let shared = APIController()
|
||||||
|
|
||||||
let scopes = "read"
|
public let scopes = "read"
|
||||||
let redirectScheme = "mastosearch"
|
public let redirectScheme = "mastosearch"
|
||||||
let redirectURI = "mastosearch://oauth"
|
public let redirectURI = "mastosearch://oauth"
|
||||||
|
|
||||||
private let decoder: JSONDecoder = {
|
private let decoder: JSONDecoder = {
|
||||||
let decoder = JSONDecoder()
|
let decoder = JSONDecoder()
|
||||||
|
@ -71,7 +71,7 @@ struct APIController {
|
||||||
task.resume()
|
task.resume()
|
||||||
}
|
}
|
||||||
|
|
||||||
func register(completion: @escaping (Result<ClientRegistration, Error>) -> Void) {
|
public func register(completion: @escaping (Result<ClientRegistration, Error>) -> Void) {
|
||||||
guard let account = LocalData.account else {
|
guard let account = LocalData.account else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -88,7 +88,7 @@ struct APIController {
|
||||||
run(request: req, completion: completion)
|
run(request: req, completion: completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAccessToken(authCode: String, completion: @escaping (Result<LoginSettings, Error>) -> Void) {
|
public func getAccessToken(authCode: String, completion: @escaping (Result<LoginSettings, Error>) -> Void) {
|
||||||
guard let account = LocalData.account else {
|
guard let account = LocalData.account else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -107,7 +107,7 @@ struct APIController {
|
||||||
run(request: req, completion: completion)
|
run(request: req, completion: completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getStatuses(range: RequestRange, completion: @escaping (Result<[Status], Error>) -> Void) {
|
public func getStatuses(range: RequestRange, completion: @escaping (Result<[Status], Error>) -> Void) {
|
||||||
guard let account = LocalData.account else {
|
guard let account = LocalData.account else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -115,6 +115,7 @@ struct APIController {
|
||||||
components.path = "/api/v1/accounts/1/statuses"
|
components.path = "/api/v1/accounts/1/statuses"
|
||||||
components.queryItems = range.queryParameters + [
|
components.queryItems = range.queryParameters + [
|
||||||
URLQueryItem(name: "exclude_replies", value: "false"),
|
URLQueryItem(name: "exclude_replies", value: "false"),
|
||||||
|
URLQueryItem(name: "limit", value: "50"),
|
||||||
]
|
]
|
||||||
run(request: URLRequest(url: components.url!), completion: completion)
|
run(request: URLRequest(url: components.url!), completion: completion)
|
||||||
}
|
}
|
||||||
|
@ -122,7 +123,7 @@ struct APIController {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension APIController {
|
extension APIController {
|
||||||
enum RequestRange {
|
public enum RequestRange {
|
||||||
case `default`
|
case `default`
|
||||||
case after(String)
|
case after(String)
|
||||||
|
|
||||||
|
@ -136,24 +137,24 @@ extension APIController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ClientRegistration: Decodable {
|
public struct ClientRegistration: Decodable {
|
||||||
let client_id: String
|
public let client_id: String
|
||||||
let client_secret: String
|
public let client_secret: String
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LoginSettings: Decodable {
|
public struct LoginSettings: Decodable {
|
||||||
let access_token: String
|
public let access_token: String
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Status: Decodable {
|
public struct Status: Decodable {
|
||||||
let id: String
|
public let id: String
|
||||||
let url: String
|
public let url: String
|
||||||
let spoiler_text: String
|
public let spoiler_text: String
|
||||||
let content: String
|
public let content: String
|
||||||
let created_at: Date
|
public let created_at: Date
|
||||||
let hasReblog: Bool
|
public let hasReblog: Bool
|
||||||
|
|
||||||
init(from decoder: Decoder) throws {
|
public init(from decoder: Decoder) throws {
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
self.id = try container.decode(String.self, forKey: .id)
|
self.id = try container.decode(String.self, forKey: .id)
|
||||||
self.url = try container.decode(String.self, forKey: .url)
|
self.url = try container.decode(String.self, forKey: .url)
|
||||||
|
@ -174,13 +175,13 @@ extension APIController {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension APIController {
|
extension APIController {
|
||||||
enum Error: Swift.Error {
|
public enum Error: Swift.Error {
|
||||||
case unexpectedStatusCode(Int)
|
case unexpectedStatusCode(Int)
|
||||||
case error(Swift.Error)
|
case error(Swift.Error)
|
||||||
case noData
|
case noData
|
||||||
case decoding(Swift.Error)
|
case decoding(Swift.Error)
|
||||||
|
|
||||||
var localizedDescription: String {
|
public var localizedDescription: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .unexpectedStatusCode(let code):
|
case .unexpectedStatusCode(let code):
|
||||||
return "Unexpected status code \(code)"
|
return "Unexpected status code \(code)"
|
|
@ -10,9 +10,9 @@ import FMDB
|
||||||
import OSLog
|
import OSLog
|
||||||
import Combine
|
import Combine
|
||||||
|
|
||||||
class DatabaseController {
|
public class DatabaseController {
|
||||||
|
|
||||||
static let shared = DatabaseController()
|
public static let shared = DatabaseController()
|
||||||
|
|
||||||
private let log = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "DatabaseController")
|
private let log = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "DatabaseController")
|
||||||
static let dateFormat = Date.ISO8601FormatStyle(includingFractionalSeconds: true)
|
static let dateFormat = Date.ISO8601FormatStyle(includingFractionalSeconds: true)
|
||||||
|
@ -22,8 +22,8 @@ class DatabaseController {
|
||||||
|
|
||||||
private var queue: FMDatabaseQueue!
|
private var queue: FMDatabaseQueue!
|
||||||
|
|
||||||
private(set) var isInitialized = false
|
public private(set) var isInitialized = false
|
||||||
let onInitialize = PassthroughSubject<(), Never>()
|
public let onInitialize = PassthroughSubject<(), Never>()
|
||||||
|
|
||||||
private init() {
|
private init() {
|
||||||
// this dir will be inside the application sandbox container
|
// this dir will be inside the application sandbox container
|
||||||
|
@ -31,7 +31,7 @@ class DatabaseController {
|
||||||
databaseURL = applicationSupport.appendingPathComponent("statuses").appendingPathExtension("sqlite")
|
databaseURL = applicationSupport.appendingPathComponent("statuses").appendingPathExtension("sqlite")
|
||||||
}
|
}
|
||||||
|
|
||||||
func initialize() {
|
public func initialize() {
|
||||||
if !FileManager.default.fileExists(atPath: databaseURL.absoluteString) {
|
if !FileManager.default.fileExists(atPath: databaseURL.absoluteString) {
|
||||||
FileManager.default.createFile(atPath: databaseURL.absoluteString, contents: nil, attributes: nil)
|
FileManager.default.createFile(atPath: databaseURL.absoluteString, contents: nil, attributes: nil)
|
||||||
}
|
}
|
||||||
|
@ -80,12 +80,12 @@ class DatabaseController {
|
||||||
onInitialize.send()
|
onInitialize.send()
|
||||||
}
|
}
|
||||||
|
|
||||||
func close() {
|
public func close() {
|
||||||
// db.close()
|
// db.close()
|
||||||
// log.info("Closed database")
|
// log.info("Closed database")
|
||||||
}
|
}
|
||||||
|
|
||||||
func addStatuses<S: Sequence>(_ statuses: S) where S.Element == Status {
|
public func addStatuses<S: Sequence>(_ statuses: S) where S.Element == Status {
|
||||||
queue.inTransaction { db, rollback in
|
queue.inTransaction { db, rollback in
|
||||||
var i = 0
|
var i = 0
|
||||||
for status in statuses {
|
for status in statuses {
|
||||||
|
@ -117,7 +117,7 @@ class DatabaseController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getStatuses(sortDescriptor: NSSortDescriptor?, completion: @escaping (StatusSequence) -> Void) {
|
public func getStatuses(sortDescriptor: NSSortDescriptor?, completion: @escaping (StatusSequence) -> Void) {
|
||||||
queue.inDatabase { db in
|
queue.inDatabase { db in
|
||||||
let sortKey = sortDescriptor?.key ?? "published"
|
let sortKey = sortDescriptor?.key ?? "published"
|
||||||
let asc = sortDescriptor?.ascending == true ? "ASC" : "DESC"
|
let asc = sortDescriptor?.ascending == true ? "ASC" : "DESC"
|
||||||
|
@ -127,7 +127,7 @@ class DatabaseController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getStatuses(query: String, sortDescriptor: NSSortDescriptor?, completion: @escaping (StatusSequence) -> Void) {
|
public func getStatuses(query: String, sortDescriptor: NSSortDescriptor?, completion: @escaping (StatusSequence) -> Void) {
|
||||||
queue.inDatabase { db in
|
queue.inDatabase { db in
|
||||||
let sortKey = sortDescriptor?.key ?? "rank"
|
let sortKey = sortDescriptor?.key ?? "rank"
|
||||||
let asc = sortDescriptor?.ascending == false ? "DESC" : "ASC"
|
let asc = sortDescriptor?.ascending == false ? "DESC" : "ASC"
|
||||||
|
@ -137,14 +137,14 @@ class DatabaseController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getNewestStatus(completion: @escaping (Status?) -> Void) {
|
public func getNewestStatus(completion: @escaping (Status?) -> Void) {
|
||||||
queue.inDatabase { db in
|
queue.inDatabase { db in
|
||||||
let results = try! db.executeQuery("SELECT * FROM statuses ORDER BY published DESC LIMIT 1", values: nil)
|
let results = try! db.executeQuery("SELECT * FROM statuses ORDER BY published DESC LIMIT 1", values: nil)
|
||||||
completion(StatusSequence(results: results).makeIterator().next())
|
completion(StatusSequence(results: results).makeIterator().next())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func countStatuses() -> Int {
|
public func countStatuses() -> Int {
|
||||||
var res: Int!
|
var res: Int!
|
||||||
queue.inDatabase { db in
|
queue.inDatabase { db in
|
||||||
let results = try! db.executeQuery("SELECT COUNT(*) AS count FROM statuses", values: nil)
|
let results = try! db.executeQuery("SELECT COUNT(*) AS count FROM statuses", values: nil)
|
||||||
|
@ -156,17 +156,17 @@ class DatabaseController {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct StatusSequence: Sequence {
|
public struct StatusSequence: Sequence {
|
||||||
typealias Element = Status
|
public typealias Element = Status
|
||||||
|
|
||||||
let results: FMResultSet
|
let results: FMResultSet
|
||||||
|
|
||||||
func makeIterator() -> Iterator {
|
public func makeIterator() -> Iterator {
|
||||||
return Iterator(results: results)
|
return Iterator(results: results)
|
||||||
}
|
}
|
||||||
|
|
||||||
class Iterator: IteratorProtocol {
|
public class Iterator: IteratorProtocol {
|
||||||
typealias Element = Status
|
public typealias Element = Status
|
||||||
|
|
||||||
let results: FMResultSet
|
let results: FMResultSet
|
||||||
|
|
||||||
|
@ -178,7 +178,7 @@ struct StatusSequence: Sequence {
|
||||||
results.close()
|
results.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func next() -> Status? {
|
public func next() -> Status? {
|
||||||
if results.next() {
|
if results.next() {
|
||||||
return Status(
|
return Status(
|
||||||
id: results.string(forColumn: "api_id")!,
|
id: results.string(forColumn: "api_id")!,
|
|
@ -18,8 +18,8 @@ import OSLog
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class ImportController {
|
public class ImportController {
|
||||||
static let shared = ImportController()
|
public static let shared = ImportController()
|
||||||
|
|
||||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "ImportController")
|
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "ImportController")
|
||||||
private let dateFormatter: DateFormatter = {
|
private let dateFormatter: DateFormatter = {
|
||||||
|
@ -32,7 +32,7 @@ class ImportController {
|
||||||
|
|
||||||
private init() {}
|
private init() {}
|
||||||
|
|
||||||
func importCSV(url: URL) {
|
public func importCSV(url: URL) {
|
||||||
var opts = CSVReadingOptions()
|
var opts = CSVReadingOptions()
|
||||||
opts.usesQuoting = true
|
opts.usesQuoting = true
|
||||||
opts.addDateParseStrategy(Date.ISO8601FormatStyle(includingFractionalSeconds: true))
|
opts.addDateParseStrategy(Date.ISO8601FormatStyle(includingFractionalSeconds: true))
|
|
@ -7,13 +7,13 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class LocalData {
|
public class LocalData {
|
||||||
private init() {}
|
private init() {}
|
||||||
|
|
||||||
private static let encoder = PropertyListEncoder()
|
private static let encoder = PropertyListEncoder()
|
||||||
private static let decoder = PropertyListDecoder()
|
private static let decoder = PropertyListDecoder()
|
||||||
|
|
||||||
static var account: AccountInfo? {
|
public static var account: AccountInfo? {
|
||||||
get {
|
get {
|
||||||
guard let data = UserDefaults.standard.data(forKey: "account") else {
|
guard let data = UserDefaults.standard.data(forKey: "account") else {
|
||||||
return nil
|
return nil
|
||||||
|
@ -30,11 +30,18 @@ class LocalData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AccountInfo: Codable {
|
public struct AccountInfo: Codable {
|
||||||
let instanceURL: URL
|
public let instanceURL: URL
|
||||||
var clientID: String!
|
public var clientID: String!
|
||||||
var clientSecret: String!
|
public var clientSecret: String!
|
||||||
var accessToken: String!
|
public var accessToken: String!
|
||||||
|
|
||||||
|
public init(instanceURL: URL, clientID: String? = nil, clientSecret: String? = nil, accessToken: String? = nil) {
|
||||||
|
self.instanceURL = instanceURL
|
||||||
|
self.clientID = clientID
|
||||||
|
self.clientSecret = clientSecret
|
||||||
|
self.accessToken = accessToken
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
//
|
||||||
|
// LoginController.swift
|
||||||
|
// MastoSearchCore
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 7/3/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import AuthenticationServices
|
||||||
|
|
||||||
|
public class LoginController {
|
||||||
|
private init() {}
|
||||||
|
|
||||||
|
public static let shared = LoginController()
|
||||||
|
|
||||||
|
private var authSession: ASWebAuthenticationSession?
|
||||||
|
|
||||||
|
public func logIn(with instanceURL: URL, presentationContextProvider: ASWebAuthenticationPresentationContextProviding, completion: @escaping () -> Void) {
|
||||||
|
LocalData.account = LocalData.AccountInfo(instanceURL: instanceURL, clientID: nil, clientSecret: nil, accessToken: nil)
|
||||||
|
|
||||||
|
APIController.shared.register { response in
|
||||||
|
guard case .success(let registration) = response else {
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalData.account!.clientID = registration.client_id
|
||||||
|
LocalData.account!.clientSecret = registration.client_secret
|
||||||
|
|
||||||
|
var authorizeComponents = URLComponents(url: instanceURL, resolvingAgainstBaseURL: false)!
|
||||||
|
authorizeComponents.path = "/oauth/authorize"
|
||||||
|
authorizeComponents.queryItems = [
|
||||||
|
URLQueryItem(name: "client_id", value: LocalData.account!.clientID),
|
||||||
|
URLQueryItem(name: "response_type", value: "code"),
|
||||||
|
URLQueryItem(name: "scope", value: APIController.shared.scopes),
|
||||||
|
URLQueryItem(name: "redirect_uri", value: APIController.shared.redirectURI),
|
||||||
|
]
|
||||||
|
|
||||||
|
self.authSession = ASWebAuthenticationSession(url: authorizeComponents.url!, callbackURLScheme: "mastosearch", completionHandler: { url, error in
|
||||||
|
guard error == nil,
|
||||||
|
let url = url,
|
||||||
|
let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
|
||||||
|
let item = components.queryItems?.first(where: { $0.name == "code" }),
|
||||||
|
let authCode = item.value else {
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
|
||||||
|
APIController.shared.getAccessToken(authCode: authCode) { response in
|
||||||
|
guard case .success(let settings) = response else {
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalData.account!.accessToken = settings.access_token
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
completion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.authSession!.presentationContextProvider = presentationContextProvider
|
||||||
|
self.authSession!.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
//
|
||||||
|
// Status.swift
|
||||||
|
// MastoSearch
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 12/10/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct Status: Identifiable {
|
||||||
|
public let id: String
|
||||||
|
public let url: String
|
||||||
|
public let summary: String?
|
||||||
|
public let content: String
|
||||||
|
public let published: Date
|
||||||
|
|
||||||
|
public init(id: String, url: String, summary: String?, content: String, published: Date) {
|
||||||
|
self.id = id
|
||||||
|
self.url = url
|
||||||
|
self.summary = summary
|
||||||
|
self.content = content
|
||||||
|
self.published = published
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
//
|
||||||
|
// SyncController.swift
|
||||||
|
// MastoSearchCore
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 7/3/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Combine
|
||||||
|
import OSLog
|
||||||
|
|
||||||
|
public class SyncController {
|
||||||
|
private init() {}
|
||||||
|
|
||||||
|
public static let shared = SyncController()
|
||||||
|
|
||||||
|
public let onSync = PassthroughSubject<Void, Never>()
|
||||||
|
|
||||||
|
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "Sync")
|
||||||
|
private var syncTotal = 0
|
||||||
|
|
||||||
|
public func syncStatuses(errorHandler: @escaping (APIController.Error) -> Void) {
|
||||||
|
DatabaseController.shared.getNewestStatus { status in
|
||||||
|
guard let status else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.logger.log("Starting sync...")
|
||||||
|
self.syncTotal = 0
|
||||||
|
self.syncStatuses(range: .after(status.id), errorHandler: errorHandler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func syncStatuses(range: APIController.RequestRange, errorHandler: @escaping (APIController.Error) -> Void) {
|
||||||
|
APIController.shared.getStatuses(range: range) { response in
|
||||||
|
switch response {
|
||||||
|
case .failure(let error):
|
||||||
|
self.logger.error("Erorr syncing statuses: \(String(describing: error), privacy: .public)")
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
errorHandler(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
case .success(let statuses):
|
||||||
|
guard statuses.count > 0 else {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.logger.log("Finished sync of \(self.syncTotal, privacy: .public) statuses")
|
||||||
|
self.onSync.send()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
DatabaseController.shared.addStatuses(statuses.compactMap {
|
||||||
|
if $0.hasReblog {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return Status(id: $0.id, url: $0.url, summary: $0.spoiler_text, content: $0.content, published: $0.created_at)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.onSync.send()
|
||||||
|
}
|
||||||
|
|
||||||
|
self.syncTotal += statuses.count
|
||||||
|
|
||||||
|
self.syncStatuses(range: .after(statuses.first!.id), errorHandler: errorHandler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue