Use app group container

This commit is contained in:
Justin Mazzocchi 2020-11-08 19:07:23 -08:00
parent 567fc9eeda
commit 095abbeea9
No known key found for this signature in database
GPG key ID: E223E6937AAFB01C
11 changed files with 91 additions and 33 deletions

View file

@ -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

View 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!
}
}

View file

@ -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)

View file

@ -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

View file

@ -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)
}
}

View file

@ -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(

View file

@ -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,

View file

@ -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)

View file

@ -34,6 +34,7 @@ public struct IdentityService {
useHomeTimelineLastReadId: appPreferences.homeTimelineBehavior == .rememberPosition,
useNotificationsLastReadId: appPreferences.notificationsTabBehavior == .rememberPosition,
inMemory: environment.inMemoryContent,
appGroup: AppEnvironment.appGroup,
keychain: environment.keychain)
}
}

View file

@ -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>

View file

@ -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)