mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-28 02:51:02 +00:00
Use app group container
This commit is contained in:
parent
567fc9eeda
commit
095abbeea9
11 changed files with 91 additions and 33 deletions
|
@ -17,21 +17,19 @@ public struct ContentDatabase {
|
||||||
useHomeTimelineLastReadId: Bool,
|
useHomeTimelineLastReadId: Bool,
|
||||||
useNotificationsLastReadId: Bool,
|
useNotificationsLastReadId: Bool,
|
||||||
inMemory: Bool,
|
inMemory: Bool,
|
||||||
|
appGroup: String,
|
||||||
keychain: Keychain.Type) throws {
|
keychain: Keychain.Type) throws {
|
||||||
if inMemory {
|
if inMemory {
|
||||||
databaseWriter = DatabaseQueue()
|
databaseWriter = DatabaseQueue()
|
||||||
|
try Self.migrator.migrate(databaseWriter)
|
||||||
} else {
|
} else {
|
||||||
let path = try Self.fileURL(id: id).path
|
databaseWriter = try DatabasePool.withFileCoordinator(
|
||||||
var configuration = Configuration()
|
url: Self.fileURL(id: id, appGroup: appGroup),
|
||||||
|
migrator: Self.migrator) {
|
||||||
configuration.prepareDatabase {
|
try Secrets.databaseKey(identityId: id, keychain: keychain)
|
||||||
try $0.usePassphrase(Secrets.databaseKey(identityId: id, keychain: keychain))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
databaseWriter = try DatabasePool(path: path, configuration: configuration)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try Self.migrator.migrate(databaseWriter)
|
|
||||||
try Self.clean(
|
try Self.clean(
|
||||||
databaseWriter,
|
databaseWriter,
|
||||||
useHomeTimelineLastReadId: useHomeTimelineLastReadId,
|
useHomeTimelineLastReadId: useHomeTimelineLastReadId,
|
||||||
|
@ -47,8 +45,8 @@ public struct ContentDatabase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension ContentDatabase {
|
public extension ContentDatabase {
|
||||||
static func delete(id: Identity.Id) throws {
|
static func delete(id: Identity.Id, appGroup: String) throws {
|
||||||
try FileManager.default.removeItem(at: fileURL(id: id))
|
try FileManager.default.removeItem(at: fileURL(id: id, appGroup: appGroup))
|
||||||
}
|
}
|
||||||
|
|
||||||
func insert(status: Status) -> AnyPublisher<Never, Error> {
|
func insert(status: Status) -> AnyPublisher<Never, Error> {
|
||||||
|
@ -411,8 +409,8 @@ public extension ContentDatabase {
|
||||||
|
|
||||||
private extension ContentDatabase {
|
private extension ContentDatabase {
|
||||||
static let cleanAfterLastReadIdCount = 40
|
static let cleanAfterLastReadIdCount = 40
|
||||||
static func fileURL(id: Identity.Id) throws -> URL {
|
static func fileURL(id: Identity.Id, appGroup: String) throws -> URL {
|
||||||
try FileManager.default.databaseDirectoryURL(name: id.uuidString)
|
try FileManager.default.databaseDirectoryURL(name: id.uuidString, appGroup: appGroup)
|
||||||
}
|
}
|
||||||
|
|
||||||
// swiftlint:disable:next function_body_length
|
// swiftlint:disable:next function_body_length
|
||||||
|
|
51
DB/Sources/DB/Extensions/DatabasePool+Extensions.swift
Normal file
51
DB/Sources/DB/Extensions/DatabasePool+Extensions.swift
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import GRDB
|
||||||
|
|
||||||
|
// https://github.com/groue/GRDB.swift/blob/master/Documentation/SharingADatabase.md
|
||||||
|
|
||||||
|
extension DatabasePool {
|
||||||
|
class func withFileCoordinator(url: URL,
|
||||||
|
migrator: DatabaseMigrator,
|
||||||
|
passphrase: @escaping (() throws -> String)) throws -> Self {
|
||||||
|
let coordinator = NSFileCoordinator(filePresenter: nil)
|
||||||
|
var coordinatorError: NSError?
|
||||||
|
var dbPool: Self?
|
||||||
|
var dbError: Error?
|
||||||
|
|
||||||
|
coordinator.coordinate(writingItemAt: url, options: .forMerging, error: &coordinatorError) { coordinatedURL in
|
||||||
|
do {
|
||||||
|
var configuration = Configuration()
|
||||||
|
|
||||||
|
configuration.prepareDatabase { db in
|
||||||
|
try db.usePassphrase(passphrase())
|
||||||
|
try db.execute(sql: "PRAGMA cipher_plaintext_header_size = 32")
|
||||||
|
|
||||||
|
if !db.configuration.readonly {
|
||||||
|
var flag: CInt = 1
|
||||||
|
let code = withUnsafeMutablePointer(to: &flag) {
|
||||||
|
sqlite3_file_control(db.sqliteConnection, nil, SQLITE_FCNTL_PERSIST_WAL, $0)
|
||||||
|
}
|
||||||
|
|
||||||
|
guard code == SQLITE_OK else {
|
||||||
|
throw DatabaseError(resultCode: ResultCode(rawValue: code))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dbPool = try Self(path: coordinatedURL.path, configuration: configuration)
|
||||||
|
|
||||||
|
try migrator.migrate(dbPool!)
|
||||||
|
} catch {
|
||||||
|
dbError = error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let error = dbError ?? coordinatorError {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
return dbPool!
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,12 +3,17 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
extension FileManager {
|
extension FileManager {
|
||||||
func databaseDirectoryURL(name: String) throws -> URL {
|
enum DatabaseDirectoryError: Error {
|
||||||
let databaseDirectoryURL = try url(for: .applicationSupportDirectory,
|
case containerURLNotFound
|
||||||
in: .userDomainMask,
|
case unexpectedFileExistsWithDBDirectoryName
|
||||||
appropriateFor: nil,
|
}
|
||||||
create: true)
|
|
||||||
.appendingPathComponent("DB")
|
func databaseDirectoryURL(name: String, appGroup: String) throws -> URL {
|
||||||
|
guard let containerURL = containerURL(forSecurityApplicationGroupIdentifier: appGroup) else {
|
||||||
|
throw DatabaseDirectoryError.containerURLNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
let databaseDirectoryURL = containerURL.appendingPathComponent("DB")
|
||||||
var isDirectory: ObjCBool = false
|
var isDirectory: ObjCBool = false
|
||||||
|
|
||||||
if !fileExists(atPath: databaseDirectoryURL.path, isDirectory: &isDirectory) {
|
if !fileExists(atPath: databaseDirectoryURL.path, isDirectory: &isDirectory) {
|
||||||
|
@ -16,7 +21,7 @@ extension FileManager {
|
||||||
withIntermediateDirectories: false,
|
withIntermediateDirectories: false,
|
||||||
attributes: [.protectionKey: FileProtectionType.complete])
|
attributes: [.protectionKey: FileProtectionType.complete])
|
||||||
} else if !isDirectory.boolValue {
|
} else if !isDirectory.boolValue {
|
||||||
throw NSError(domain: NSCocoaErrorDomain, code: NSFileWriteFileExistsError, userInfo: nil)
|
throw DatabaseDirectoryError.unexpectedFileExistsWithDBDirectoryName
|
||||||
}
|
}
|
||||||
|
|
||||||
return databaseDirectoryURL.appendingPathComponent(name)
|
return databaseDirectoryURL.appendingPathComponent(name)
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import GRDB
|
import GRDB
|
||||||
|
|
||||||
extension IdentityDatabase {
|
extension IdentityDatabase {
|
||||||
var migrator: DatabaseMigrator {
|
static var migrator: DatabaseMigrator {
|
||||||
var migrator = DatabaseMigrator()
|
var migrator = DatabaseMigrator()
|
||||||
|
|
||||||
migrator.registerMigration("0.1.0") { db in
|
migrator.registerMigration("0.1.0") { db in
|
||||||
|
|
|
@ -14,21 +14,17 @@ public enum IdentityDatabaseError: Error {
|
||||||
public struct IdentityDatabase {
|
public struct IdentityDatabase {
|
||||||
private let databaseWriter: DatabaseWriter
|
private let databaseWriter: DatabaseWriter
|
||||||
|
|
||||||
public init(inMemory: Bool, keychain: Keychain.Type) throws {
|
public init(inMemory: Bool, appGroup: String, keychain: Keychain.Type) throws {
|
||||||
if inMemory {
|
if inMemory {
|
||||||
databaseWriter = DatabaseQueue()
|
databaseWriter = DatabaseQueue()
|
||||||
|
try Self.migrator.migrate(databaseWriter)
|
||||||
} else {
|
} else {
|
||||||
let path = try FileManager.default.databaseDirectoryURL(name: Self.name).path
|
let url = try FileManager.default.databaseDirectoryURL(name: Self.name, appGroup: appGroup)
|
||||||
var configuration = Configuration()
|
|
||||||
|
|
||||||
configuration.prepareDatabase {
|
databaseWriter = try DatabasePool.withFileCoordinator(url: url, migrator: Self.migrator) {
|
||||||
try $0.usePassphrase(Secrets.databaseKey(identityId: nil, keychain: keychain))
|
try Secrets.databaseKey(identityId: nil, keychain: keychain)
|
||||||
}
|
}
|
||||||
|
|
||||||
databaseWriter = try DatabasePool(path: path, configuration: configuration)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try migrator.migrate(databaseWriter)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -172,7 +172,7 @@ public extension Secrets {
|
||||||
|
|
||||||
private extension Secrets {
|
private extension Secrets {
|
||||||
static let keychainServiceName = "com.metabolist.metatext"
|
static let keychainServiceName = "com.metabolist.metatext"
|
||||||
static let databaseKeyLength = 32
|
static let databaseKeyLength = 48
|
||||||
|
|
||||||
private static func set(_ data: SecretsStorable, forAccount account: String, keychain: Keychain.Type) throws {
|
private static func set(_ data: SecretsStorable, forAccount account: String, keychain: Keychain.Type) throws {
|
||||||
try keychain.setGenericPassword(
|
try keychain.setGenericPassword(
|
||||||
|
|
|
@ -40,12 +40,14 @@ public struct AppEnvironment {
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension AppEnvironment {
|
public extension AppEnvironment {
|
||||||
|
static let appGroup = "group.metabolist.metatext"
|
||||||
|
|
||||||
static func live(userNotificationCenter: UNUserNotificationCenter, reduceMotion: @escaping () -> Bool) -> Self {
|
static func live(userNotificationCenter: UNUserNotificationCenter, reduceMotion: @escaping () -> Bool) -> Self {
|
||||||
Self(
|
Self(
|
||||||
session: URLSession.shared,
|
session: URLSession.shared,
|
||||||
webAuthSessionType: LiveWebAuthSession.self,
|
webAuthSessionType: LiveWebAuthSession.self,
|
||||||
keychain: LiveKeychain.self,
|
keychain: LiveKeychain.self,
|
||||||
userDefaults: .standard,
|
userDefaults: UserDefaults(suiteName: appGroup)!,
|
||||||
userNotificationClient: .live(userNotificationCenter),
|
userNotificationClient: .live(userNotificationCenter),
|
||||||
reduceMotion: reduceMotion,
|
reduceMotion: reduceMotion,
|
||||||
uuid: UUID.init,
|
uuid: UUID.init,
|
||||||
|
|
|
@ -18,6 +18,7 @@ public struct AllIdentitiesService {
|
||||||
self.environment = environment
|
self.environment = environment
|
||||||
self.database = try environment.fixtureDatabase ?? IdentityDatabase(
|
self.database = try environment.fixtureDatabase ?? IdentityDatabase(
|
||||||
inMemory: environment.inMemoryContent,
|
inMemory: environment.inMemoryContent,
|
||||||
|
appGroup: AppEnvironment.appGroup,
|
||||||
keychain: environment.keychain)
|
keychain: environment.keychain)
|
||||||
identitiesCreated = identitiesCreatedSubject.eraseToAnyPublisher()
|
identitiesCreated = identitiesCreatedSubject.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
@ -88,7 +89,7 @@ public extension AllIdentitiesService {
|
||||||
database.deleteIdentity(id: id)
|
database.deleteIdentity(id: id)
|
||||||
.collect()
|
.collect()
|
||||||
.tryMap { _ -> AnyPublisher<Never, Error> in
|
.tryMap { _ -> AnyPublisher<Never, Error> in
|
||||||
try ContentDatabase.delete(id: id)
|
try ContentDatabase.delete(id: id, appGroup: AppEnvironment.appGroup)
|
||||||
|
|
||||||
let secrets = Secrets(identityId: id, keychain: environment.keychain)
|
let secrets = Secrets(identityId: id, keychain: environment.keychain)
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ public struct IdentityService {
|
||||||
useHomeTimelineLastReadId: appPreferences.homeTimelineBehavior == .rememberPosition,
|
useHomeTimelineLastReadId: appPreferences.homeTimelineBehavior == .rememberPosition,
|
||||||
useNotificationsLastReadId: appPreferences.notificationsTabBehavior == .rememberPosition,
|
useNotificationsLastReadId: appPreferences.notificationsTabBehavior == .rememberPosition,
|
||||||
inMemory: environment.inMemoryContent,
|
inMemory: environment.inMemoryContent,
|
||||||
|
appGroup: AppEnvironment.appGroup,
|
||||||
keychain: environment.keychain)
|
keychain: environment.keychain)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,10 @@
|
||||||
</array>
|
</array>
|
||||||
<key>com.apple.security.app-sandbox</key>
|
<key>com.apple.security.app-sandbox</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>com.apple.security.application-groups</key>
|
||||||
|
<array>
|
||||||
|
<string>group.metabolist.metatext</string>
|
||||||
|
</array>
|
||||||
<key>com.apple.security.network.client</key>
|
<key>com.apple.security.network.client</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>keychain-access-groups</key>
|
<key>keychain-access-groups</key>
|
||||||
|
|
|
@ -15,7 +15,7 @@ import ViewModels
|
||||||
|
|
||||||
let db: IdentityDatabase = {
|
let db: IdentityDatabase = {
|
||||||
let id = Identity.Id()
|
let id = Identity.Id()
|
||||||
let db = try! IdentityDatabase(inMemory: true, keychain: MockKeychain.self)
|
let db = try! IdentityDatabase(inMemory: true, appGroup: "", keychain: MockKeychain.self)
|
||||||
let secrets = Secrets(identityId: id, keychain: MockKeychain.self)
|
let secrets = Secrets(identityId: id, keychain: MockKeychain.self)
|
||||||
|
|
||||||
try! secrets.setInstanceURL(.previewInstanceURL)
|
try! secrets.setInstanceURL(.previewInstanceURL)
|
||||||
|
|
Loading…
Reference in a new issue