mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-19 11:43:24 +01:00
Deletes cached apps after they’ve been uninstalled from device
This commit is contained in:
@@ -48,55 +48,79 @@ extension AppManager
|
|||||||
{
|
{
|
||||||
func update()
|
func update()
|
||||||
{
|
{
|
||||||
#if targetEnvironment(simulator)
|
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||||
// Apps aren't ever actually installed to simulator, so just do nothing rather than delete them from database.
|
#if targetEnvironment(simulator)
|
||||||
return
|
// Apps aren't ever actually installed to simulator, so just do nothing rather than delete them from database.
|
||||||
#else
|
#else
|
||||||
|
do
|
||||||
let context = DatabaseManager.shared.persistentContainer.newBackgroundSavingViewContext()
|
|
||||||
|
|
||||||
let fetchRequest = InstalledApp.fetchRequest() as NSFetchRequest<InstalledApp>
|
|
||||||
fetchRequest.returnsObjectsAsFaults = false
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
let installedApps = try context.fetch(fetchRequest)
|
|
||||||
|
|
||||||
if UserDefaults.standard.legacySideloadedApps == nil
|
|
||||||
{
|
{
|
||||||
// First time updating apps since updating AltStore to use custom UTIs,
|
let installedApps = InstalledApp.all(in: context)
|
||||||
// so cache all existing apps temporarily to prevent us from accidentally
|
|
||||||
// deleting them due to their custom UTI not existing (yet).
|
|
||||||
let apps = installedApps.map { $0.bundleIdentifier }
|
|
||||||
UserDefaults.standard.legacySideloadedApps = apps
|
|
||||||
}
|
|
||||||
|
|
||||||
let legacySideloadedApps = Set(UserDefaults.standard.legacySideloadedApps ?? [])
|
|
||||||
|
|
||||||
for app in installedApps
|
|
||||||
{
|
|
||||||
guard app.bundleIdentifier != StoreApp.altstoreAppID else {
|
|
||||||
self.scheduleExpirationWarningLocalNotification(for: app)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
let uti = UTTypeCopyDeclaration(app.installedAppUTI as CFString)?.takeRetainedValue() as NSDictionary?
|
if UserDefaults.standard.legacySideloadedApps == nil
|
||||||
if uti == nil && !legacySideloadedApps.contains(app.bundleIdentifier)
|
|
||||||
{
|
{
|
||||||
// This UTI is not declared by any apps, which means this app has been deleted by the user.
|
// First time updating apps since updating AltStore to use custom UTIs,
|
||||||
// This app is also not a legacy sideloaded app, so we can assume it's fine to delete it.
|
// so cache all existing apps temporarily to prevent us from accidentally
|
||||||
context.delete(app)
|
// deleting them due to their custom UTI not existing (yet).
|
||||||
|
let apps = installedApps.map { $0.bundleIdentifier }
|
||||||
|
UserDefaults.standard.legacySideloadedApps = apps
|
||||||
|
}
|
||||||
|
|
||||||
|
let legacySideloadedApps = Set(UserDefaults.standard.legacySideloadedApps ?? [])
|
||||||
|
|
||||||
|
for app in installedApps
|
||||||
|
{
|
||||||
|
guard app.bundleIdentifier != StoreApp.altstoreAppID else {
|
||||||
|
self.scheduleExpirationWarningLocalNotification(for: app)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
let uti = UTTypeCopyDeclaration(app.installedAppUTI as CFString)?.takeRetainedValue() as NSDictionary?
|
||||||
|
if uti == nil && !legacySideloadedApps.contains(app.bundleIdentifier)
|
||||||
|
{
|
||||||
|
// This UTI is not declared by any apps, which means this app has been deleted by the user.
|
||||||
|
// This app is also not a legacy sideloaded app, so we can assume it's fine to delete it.
|
||||||
|
context.delete(app)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try context.save()
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
print("Error while fetching installed apps.", error)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
let installedAppBundleIDs = InstalledApp.all(in: context).map { $0.bundleIdentifier }
|
||||||
|
|
||||||
|
let cachedAppDirectories = try FileManager.default.contentsOfDirectory(at: InstalledApp.appsDirectoryURL,
|
||||||
|
includingPropertiesForKeys: [.isDirectoryKey, .nameKey],
|
||||||
|
options: [.skipsSubdirectoryDescendants, .skipsHiddenFiles])
|
||||||
|
for appDirectory in cachedAppDirectories
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
let resourceValues = try appDirectory.resourceValues(forKeys: [.isDirectoryKey, .nameKey])
|
||||||
|
guard let isDirectory = resourceValues.isDirectory, let bundleID = resourceValues.name else { continue }
|
||||||
|
|
||||||
|
if isDirectory && !installedAppBundleIDs.contains(bundleID) && !self.installationProgress.keys.contains(bundleID)
|
||||||
|
{
|
||||||
|
try FileManager.default.removeItem(at: appDirectory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
print("Failed to remove cached app directory.", error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch
|
||||||
try context.save()
|
{
|
||||||
|
print("Failed to remove cached apps.", error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch
|
|
||||||
{
|
|
||||||
print("Error while fetching installed apps")
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ extension PatreonAPI
|
|||||||
func signOut(completion: @escaping (Result<Void, Swift.Error>) -> Void)
|
func signOut(completion: @escaping (Result<Void, Swift.Error>) -> Void)
|
||||||
{
|
{
|
||||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||||
let accounts = PatreonAccount.all(in: context)
|
let accounts = PatreonAccount.all(in: context, requestProperties: [\FetchRequest.returnsObjectsAsFaults: true])
|
||||||
accounts.forEach(context.delete(_:))
|
accounts.forEach(context.delete(_:))
|
||||||
|
|
||||||
do
|
do
|
||||||
|
|||||||
@@ -8,21 +8,25 @@
|
|||||||
|
|
||||||
import CoreData
|
import CoreData
|
||||||
|
|
||||||
|
typealias FetchRequest = NSFetchRequest<NSFetchRequestResult>
|
||||||
|
|
||||||
protocol Fetchable: NSManagedObject
|
protocol Fetchable: NSManagedObject
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Fetchable
|
extension Fetchable
|
||||||
{
|
{
|
||||||
static func first(satisfying predicate: NSPredicate? = nil, sortedBy sortDescriptors: [NSSortDescriptor]? = nil, in context: NSManagedObjectContext) -> Self?
|
static func first(satisfying predicate: NSPredicate? = nil, sortedBy sortDescriptors: [NSSortDescriptor]? = nil, in context: NSManagedObjectContext,
|
||||||
|
requestProperties: [PartialKeyPath<FetchRequest>: Any?] = [:]) -> Self?
|
||||||
{
|
{
|
||||||
let managedObjects = Self.all(satisfying: predicate, sortedBy: sortDescriptors, in: context, returnFirstResult: true)
|
let managedObjects = Self.all(satisfying: predicate, sortedBy: sortDescriptors, in: context, requestProperties: requestProperties, returnFirstResult: true)
|
||||||
return managedObjects.first
|
return managedObjects.first
|
||||||
}
|
}
|
||||||
|
|
||||||
static func all(satisfying predicate: NSPredicate? = nil, sortedBy sortDescriptors: [NSSortDescriptor]? = nil, in context: NSManagedObjectContext) -> [Self]
|
static func all(satisfying predicate: NSPredicate? = nil, sortedBy sortDescriptors: [NSSortDescriptor]? = nil, in context: NSManagedObjectContext,
|
||||||
|
requestProperties: [PartialKeyPath<FetchRequest>: Any?] = [:]) -> [Self]
|
||||||
{
|
{
|
||||||
let managedObjects = Self.all(satisfying: predicate, sortedBy: sortDescriptors, in: context, returnFirstResult: false)
|
let managedObjects = Self.all(satisfying: predicate, sortedBy: sortDescriptors, in: context, requestProperties: requestProperties, returnFirstResult: false)
|
||||||
return managedObjects
|
return managedObjects
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +44,7 @@ extension Fetchable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func all(satisfying predicate: NSPredicate? = nil, sortedBy sortDescriptors: [NSSortDescriptor]? = nil, in context: NSManagedObjectContext, returnFirstResult: Bool) -> [Self]
|
private static func all(satisfying predicate: NSPredicate? = nil, sortedBy sortDescriptors: [NSSortDescriptor]? = nil, in context: NSManagedObjectContext, requestProperties: [PartialKeyPath<FetchRequest>: Any?], returnFirstResult: Bool) -> [Self]
|
||||||
{
|
{
|
||||||
let registeredObjects = context.registeredObjects.lazy.compactMap({ $0 as? Self }).filter({ predicate?.evaluate(with: $0) != false })
|
let registeredObjects = context.registeredObjects.lazy.compactMap({ $0 as? Self }).filter({ predicate?.evaluate(with: $0) != false })
|
||||||
|
|
||||||
@@ -52,6 +56,14 @@ extension Fetchable
|
|||||||
let fetchRequest = self.fetchRequest() as! NSFetchRequest<Self>
|
let fetchRequest = self.fetchRequest() as! NSFetchRequest<Self>
|
||||||
fetchRequest.predicate = predicate
|
fetchRequest.predicate = predicate
|
||||||
fetchRequest.sortDescriptors = sortDescriptors
|
fetchRequest.sortDescriptors = sortDescriptors
|
||||||
|
fetchRequest.returnsObjectsAsFaults = false
|
||||||
|
|
||||||
|
for (keyPath, value) in requestProperties
|
||||||
|
{
|
||||||
|
// Still no easy way to cast PartialKeyPath back to usable WritableKeyPath :(
|
||||||
|
guard let objcKeyString = keyPath._kvcKeyPathString else { continue }
|
||||||
|
fetchRequest.setValue(value, forKey: objcKeyString)
|
||||||
|
}
|
||||||
|
|
||||||
let fetchedObjects = self.fetch(fetchRequest, in: context)
|
let fetchedObjects = self.fetch(fetchRequest, in: context)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user