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,
|
||||
useNotificationsLastReadId: Bool,
|
||||
inMemory: Bool,
|
||||
appGroup: String,
|
||||
keychain: Keychain.Type) throws {
|
||||
if inMemory {
|
||||
databaseWriter = DatabaseQueue()
|
||||
} else {
|
||||
let path = try Self.fileURL(id: id).path
|
||||
var configuration = Configuration()
|
||||
|
||||
configuration.prepareDatabase {
|
||||
try $0.usePassphrase(Secrets.databaseKey(identityId: id, keychain: keychain))
|
||||
}
|
||||
|
||||
databaseWriter = try DatabasePool(path: path, configuration: configuration)
|
||||
}
|
||||
|
||||
try Self.migrator.migrate(databaseWriter)
|
||||
} else {
|
||||
databaseWriter = try DatabasePool.withFileCoordinator(
|
||||
url: Self.fileURL(id: id, appGroup: appGroup),
|
||||
migrator: Self.migrator) {
|
||||
try Secrets.databaseKey(identityId: id, keychain: keychain)
|
||||
}
|
||||
}
|
||||
|
||||
try Self.clean(
|
||||
databaseWriter,
|
||||
useHomeTimelineLastReadId: useHomeTimelineLastReadId,
|
||||
|
@ -47,8 +45,8 @@ public struct ContentDatabase {
|
|||
}
|
||||
|
||||
public extension ContentDatabase {
|
||||
static func delete(id: Identity.Id) throws {
|
||||
try FileManager.default.removeItem(at: fileURL(id: id))
|
||||
static func delete(id: Identity.Id, appGroup: String) throws {
|
||||
try FileManager.default.removeItem(at: fileURL(id: id, appGroup: appGroup))
|
||||
}
|
||||
|
||||
func insert(status: Status) -> AnyPublisher<Never, Error> {
|
||||
|
@ -411,8 +409,8 @@ public extension ContentDatabase {
|
|||
|
||||
private extension ContentDatabase {
|
||||
static let cleanAfterLastReadIdCount = 40
|
||||
static func fileURL(id: Identity.Id) throws -> URL {
|
||||
try FileManager.default.databaseDirectoryURL(name: id.uuidString)
|
||||
static func fileURL(id: Identity.Id, appGroup: String) throws -> URL {
|
||||
try FileManager.default.databaseDirectoryURL(name: id.uuidString, appGroup: appGroup)
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
extension FileManager {
|
||||
func databaseDirectoryURL(name: String) throws -> URL {
|
||||
let databaseDirectoryURL = try url(for: .applicationSupportDirectory,
|
||||
in: .userDomainMask,
|
||||
appropriateFor: nil,
|
||||
create: true)
|
||||
.appendingPathComponent("DB")
|
||||
enum DatabaseDirectoryError: Error {
|
||||
case containerURLNotFound
|
||||
case unexpectedFileExistsWithDBDirectoryName
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
if !fileExists(atPath: databaseDirectoryURL.path, isDirectory: &isDirectory) {
|
||||
|
@ -16,7 +21,7 @@ extension FileManager {
|
|||
withIntermediateDirectories: false,
|
||||
attributes: [.protectionKey: FileProtectionType.complete])
|
||||
} else if !isDirectory.boolValue {
|
||||
throw NSError(domain: NSCocoaErrorDomain, code: NSFileWriteFileExistsError, userInfo: nil)
|
||||
throw DatabaseDirectoryError.unexpectedFileExistsWithDBDirectoryName
|
||||
}
|
||||
|
||||
return databaseDirectoryURL.appendingPathComponent(name)
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import GRDB
|
||||
|
||||
extension IdentityDatabase {
|
||||
var migrator: DatabaseMigrator {
|
||||
static var migrator: DatabaseMigrator {
|
||||
var migrator = DatabaseMigrator()
|
||||
|
||||
migrator.registerMigration("0.1.0") { db in
|
||||
|
|
|
@ -14,21 +14,17 @@ public enum IdentityDatabaseError: Error {
|
|||
public struct IdentityDatabase {
|
||||
private let databaseWriter: DatabaseWriter
|
||||
|
||||
public init(inMemory: Bool, keychain: Keychain.Type) throws {
|
||||
public init(inMemory: Bool, appGroup: String, keychain: Keychain.Type) throws {
|
||||
if inMemory {
|
||||
databaseWriter = DatabaseQueue()
|
||||
try Self.migrator.migrate(databaseWriter)
|
||||
} else {
|
||||
let path = try FileManager.default.databaseDirectoryURL(name: Self.name).path
|
||||
var configuration = Configuration()
|
||||
let url = try FileManager.default.databaseDirectoryURL(name: Self.name, appGroup: appGroup)
|
||||
|
||||
configuration.prepareDatabase {
|
||||
try $0.usePassphrase(Secrets.databaseKey(identityId: nil, keychain: keychain))
|
||||
databaseWriter = try DatabasePool.withFileCoordinator(url: url, migrator: Self.migrator) {
|
||||
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 {
|
||||
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 {
|
||||
try keychain.setGenericPassword(
|
||||
|
|
|
@ -40,12 +40,14 @@ public struct AppEnvironment {
|
|||
}
|
||||
|
||||
public extension AppEnvironment {
|
||||
static let appGroup = "group.metabolist.metatext"
|
||||
|
||||
static func live(userNotificationCenter: UNUserNotificationCenter, reduceMotion: @escaping () -> Bool) -> Self {
|
||||
Self(
|
||||
session: URLSession.shared,
|
||||
webAuthSessionType: LiveWebAuthSession.self,
|
||||
keychain: LiveKeychain.self,
|
||||
userDefaults: .standard,
|
||||
userDefaults: UserDefaults(suiteName: appGroup)!,
|
||||
userNotificationClient: .live(userNotificationCenter),
|
||||
reduceMotion: reduceMotion,
|
||||
uuid: UUID.init,
|
||||
|
|
|
@ -18,6 +18,7 @@ public struct AllIdentitiesService {
|
|||
self.environment = environment
|
||||
self.database = try environment.fixtureDatabase ?? IdentityDatabase(
|
||||
inMemory: environment.inMemoryContent,
|
||||
appGroup: AppEnvironment.appGroup,
|
||||
keychain: environment.keychain)
|
||||
identitiesCreated = identitiesCreatedSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
@ -88,7 +89,7 @@ public extension AllIdentitiesService {
|
|||
database.deleteIdentity(id: id)
|
||||
.collect()
|
||||
.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)
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ public struct IdentityService {
|
|||
useHomeTimelineLastReadId: appPreferences.homeTimelineBehavior == .rememberPosition,
|
||||
useNotificationsLastReadId: appPreferences.notificationsTabBehavior == .rememberPosition,
|
||||
inMemory: environment.inMemoryContent,
|
||||
appGroup: AppEnvironment.appGroup,
|
||||
keychain: environment.keychain)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,10 @@
|
|||
</array>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.metabolist.metatext</string>
|
||||
</array>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>keychain-access-groups</key>
|
||||
|
|
|
@ -15,7 +15,7 @@ import ViewModels
|
|||
|
||||
let db: IdentityDatabase = {
|
||||
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)
|
||||
|
||||
try! secrets.setInstanceURL(.previewInstanceURL)
|
||||
|
|
Loading…
Reference in a new issue