mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-18 11:13:28 +01:00
Migrates database + cached apps from app sandbox to app group
This commit is contained in:
@@ -41,6 +41,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||||||
|
|
||||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
|
||||||
{
|
{
|
||||||
|
// Register default settings before doing anything else.
|
||||||
|
UserDefaults.registerDefaults()
|
||||||
|
|
||||||
DatabaseManager.shared.start { (error) in
|
DatabaseManager.shared.start { (error) in
|
||||||
if let error = error
|
if let error = error
|
||||||
{
|
{
|
||||||
@@ -58,9 +61,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||||||
|
|
||||||
ServerManager.shared.startDiscovering()
|
ServerManager.shared.startDiscovering()
|
||||||
|
|
||||||
SecureValueTransformer.register()
|
SecureValueTransformer.register()
|
||||||
|
|
||||||
UserDefaults.standard.registerDefaults()
|
|
||||||
|
|
||||||
if UserDefaults.standard.firstLaunch == nil
|
if UserDefaults.standard.firstLaunch == nil
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,7 +12,15 @@ import Roxas
|
|||||||
|
|
||||||
public extension UserDefaults
|
public extension UserDefaults
|
||||||
{
|
{
|
||||||
|
static let shared: UserDefaults = {
|
||||||
|
guard let appGroup = Bundle.main.appGroups.first else { return .standard }
|
||||||
|
|
||||||
|
let sharedUserDefaults = UserDefaults(suiteName: appGroup)!
|
||||||
|
return sharedUserDefaults
|
||||||
|
}()
|
||||||
|
|
||||||
@NSManaged var firstLaunch: Date?
|
@NSManaged var firstLaunch: Date?
|
||||||
|
@NSManaged var requiresAppGroupMigration: Bool
|
||||||
|
|
||||||
@NSManaged var preferredServerID: String?
|
@NSManaged var preferredServerID: String?
|
||||||
|
|
||||||
@@ -42,16 +50,20 @@ public extension UserDefaults
|
|||||||
}
|
}
|
||||||
@NSManaged @objc(activeAppsLimit) private var _activeAppsLimit: NSNumber?
|
@NSManaged @objc(activeAppsLimit) private var _activeAppsLimit: NSNumber?
|
||||||
|
|
||||||
func registerDefaults()
|
class func registerDefaults()
|
||||||
{
|
{
|
||||||
let ios13_5 = OperatingSystemVersion(majorVersion: 13, minorVersion: 5, patchVersion: 0)
|
let ios13_5 = OperatingSystemVersion(majorVersion: 13, minorVersion: 5, patchVersion: 0)
|
||||||
let isLegacyDeactivationSupported = !ProcessInfo.processInfo.isOperatingSystemAtLeast(ios13_5)
|
let isLegacyDeactivationSupported = !ProcessInfo.processInfo.isOperatingSystemAtLeast(ios13_5)
|
||||||
let activeAppLimitIncludesExtensions = !ProcessInfo.processInfo.isOperatingSystemAtLeast(ios13_5)
|
let activeAppLimitIncludesExtensions = !ProcessInfo.processInfo.isOperatingSystemAtLeast(ios13_5)
|
||||||
|
|
||||||
self.register(defaults: [
|
let defaults = [
|
||||||
#keyPath(UserDefaults.isBackgroundRefreshEnabled): true,
|
#keyPath(UserDefaults.isBackgroundRefreshEnabled): true,
|
||||||
#keyPath(UserDefaults.isLegacyDeactivationSupported): isLegacyDeactivationSupported,
|
#keyPath(UserDefaults.isLegacyDeactivationSupported): isLegacyDeactivationSupported,
|
||||||
#keyPath(UserDefaults.activeAppLimitIncludesExtensions): activeAppLimitIncludesExtensions
|
#keyPath(UserDefaults.activeAppLimitIncludesExtensions): activeAppLimitIncludesExtensions,
|
||||||
])
|
#keyPath(UserDefaults.requiresAppGroupMigration): true
|
||||||
|
]
|
||||||
|
|
||||||
|
UserDefaults.standard.register(defaults: defaults)
|
||||||
|
UserDefaults.shared.register(defaults: defaults)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import CoreData
|
|||||||
import AltSign
|
import AltSign
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
private class PersistentContainer: RSTPersistentContainer
|
fileprivate class PersistentContainer: RSTPersistentContainer
|
||||||
{
|
{
|
||||||
override class func defaultDirectoryURL() -> URL
|
override class func defaultDirectoryURL() -> URL
|
||||||
{
|
{
|
||||||
@@ -22,6 +22,11 @@ private class PersistentContainer: RSTPersistentContainer
|
|||||||
|
|
||||||
return databaseDirectoryURL
|
return databaseDirectoryURL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class func legacyDirectoryURL() -> URL
|
||||||
|
{
|
||||||
|
return super.defaultDirectoryURL()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DatabaseManager
|
public class DatabaseManager
|
||||||
@@ -35,6 +40,9 @@ public class DatabaseManager
|
|||||||
private var startCompletionHandlers = [(Error?) -> Void]()
|
private var startCompletionHandlers = [(Error?) -> Void]()
|
||||||
private let dispatchQueue = DispatchQueue(label: "io.altstore.DatabaseManager")
|
private let dispatchQueue = DispatchQueue(label: "io.altstore.DatabaseManager")
|
||||||
|
|
||||||
|
private let coordinator = NSFileCoordinator()
|
||||||
|
private let coordinatorQueue = OperationQueue()
|
||||||
|
|
||||||
private init()
|
private init()
|
||||||
{
|
{
|
||||||
self.persistentContainer = PersistentContainer(name: "AltStore", bundle: Bundle(for: DatabaseManager.self))
|
self.persistentContainer = PersistentContainer(name: "AltStore", bundle: Bundle(for: DatabaseManager.self))
|
||||||
@@ -65,14 +73,21 @@ public extension DatabaseManager
|
|||||||
|
|
||||||
guard !self.isStarted else { return finish(nil) }
|
guard !self.isStarted else { return finish(nil) }
|
||||||
|
|
||||||
self.persistentContainer.loadPersistentStores { (description, error) in
|
self.migrateDatabaseToAppGroupIfNeeded { (result) in
|
||||||
guard error == nil else { return finish(error!) }
|
switch result
|
||||||
|
{
|
||||||
self.prepareDatabase() { (result) in
|
case .failure(let error): finish(error)
|
||||||
switch result
|
case .success:
|
||||||
{
|
self.persistentContainer.loadPersistentStores { (description, error) in
|
||||||
case .failure(let error): finish(error)
|
guard error == nil else { return finish(error!) }
|
||||||
case .success: finish(nil)
|
|
||||||
|
self.prepareDatabase() { (result) in
|
||||||
|
switch result
|
||||||
|
{
|
||||||
|
case .failure(let error): finish(error)
|
||||||
|
case .success: finish(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -290,4 +305,75 @@ private extension DatabaseManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func migrateDatabaseToAppGroupIfNeeded(completion: @escaping (Result<Void, Error>) -> Void)
|
||||||
|
{
|
||||||
|
guard UserDefaults.shared.requiresAppGroupMigration else { return completion(.success(())) }
|
||||||
|
|
||||||
|
func finish(_ result: Result<Void, Error>)
|
||||||
|
{
|
||||||
|
switch result
|
||||||
|
{
|
||||||
|
case .failure(let error): completion(.failure(error))
|
||||||
|
case .success:
|
||||||
|
UserDefaults.shared.requiresAppGroupMigration = false
|
||||||
|
completion(.success(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let previousDatabaseURL = PersistentContainer.legacyDirectoryURL().appendingPathComponent("AltStore.sqlite")
|
||||||
|
let databaseURL = PersistentContainer.defaultDirectoryURL().appendingPathComponent("AltStore.sqlite")
|
||||||
|
|
||||||
|
let previousAppsDirectoryURL = InstalledApp.legacyAppsDirectoryURL
|
||||||
|
let appsDirectoryURL = InstalledApp.appsDirectoryURL
|
||||||
|
|
||||||
|
let databaseIntent = NSFileAccessIntent.writingIntent(with: databaseURL, options: [.forReplacing])
|
||||||
|
let appsIntent = NSFileAccessIntent.writingIntent(with: appsDirectoryURL, options: [.forReplacing])
|
||||||
|
|
||||||
|
self.coordinator.coordinate(with: [databaseIntent, appsIntent], queue: self.coordinatorQueue) { (error) in
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if let error = error
|
||||||
|
{
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
let description = NSPersistentStoreDescription(url: previousDatabaseURL)
|
||||||
|
|
||||||
|
// Disable WAL to remove extra files automatically during migration.
|
||||||
|
description.setOption(["journal_mode": "DELETE"] as NSDictionary, forKey: NSSQLitePragmasOption)
|
||||||
|
|
||||||
|
let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: self.persistentContainer.managedObjectModel)
|
||||||
|
|
||||||
|
// Migrate database
|
||||||
|
if FileManager.default.fileExists(atPath: previousDatabaseURL.path)
|
||||||
|
{
|
||||||
|
if FileManager.default.fileExists(atPath: databaseURL.path, isDirectory: nil)
|
||||||
|
{
|
||||||
|
try FileManager.default.removeItem(at: databaseURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
let previousDatabase = try persistentStoreCoordinator.addPersistentStore(ofType: description.type, configurationName: description.configuration, at: description.url, options: description.options)
|
||||||
|
|
||||||
|
// Pass nil options to prevent later error due to self.persistentContainer using WAL.
|
||||||
|
try persistentStoreCoordinator.migratePersistentStore(previousDatabase, to: databaseURL, options: nil, withType: NSSQLiteStoreType)
|
||||||
|
|
||||||
|
try FileManager.default.removeItem(at: previousDatabaseURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migrate apps
|
||||||
|
if FileManager.default.fileExists(atPath: previousAppsDirectoryURL.path, isDirectory: nil)
|
||||||
|
{
|
||||||
|
_ = try FileManager.default.replaceItemAt(appsDirectoryURL, withItemAt: previousAppsDirectoryURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
finish(.success(()))
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
print("Failed to migrate database to app group:", error)
|
||||||
|
finish(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -228,6 +228,12 @@ public extension InstalledApp
|
|||||||
return appsDirectoryURL
|
return appsDirectoryURL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class var legacyAppsDirectoryURL: URL {
|
||||||
|
let baseDirectory = FileManager.default.applicationSupportDirectory
|
||||||
|
let appsDirectoryURL = baseDirectory.appendingPathComponent("Apps")
|
||||||
|
return appsDirectoryURL
|
||||||
|
}
|
||||||
|
|
||||||
class func fileURL(for app: AppProtocol) -> URL
|
class func fileURL(for app: AppProtocol) -> URL
|
||||||
{
|
{
|
||||||
let appURL = self.directoryURL(for: app).appendingPathComponent("App.app")
|
let appURL = self.directoryURL(for: app).appendingPathComponent("App.app")
|
||||||
|
|||||||
Reference in New Issue
Block a user