Deletes cached apps after they’ve been uninstalled from device

This commit is contained in:
Riley Testut
2020-03-23 12:12:49 -07:00
parent 9e465f8eaa
commit 590ce5c928
3 changed files with 85 additions and 49 deletions

View File

@@ -48,19 +48,13 @@ extension AppManager
{ {
func update() func update()
{ {
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
#if targetEnvironment(simulator) #if targetEnvironment(simulator)
// Apps aren't ever actually installed to simulator, so just do nothing rather than delete them from database. // Apps aren't ever actually installed to simulator, so just do nothing rather than delete them from database.
return
#else #else
let context = DatabaseManager.shared.persistentContainer.newBackgroundSavingViewContext()
let fetchRequest = InstalledApp.fetchRequest() as NSFetchRequest<InstalledApp>
fetchRequest.returnsObjectsAsFaults = false
do do
{ {
let installedApps = try context.fetch(fetchRequest) let installedApps = InstalledApp.all(in: context)
if UserDefaults.standard.legacySideloadedApps == nil if UserDefaults.standard.legacySideloadedApps == nil
{ {
@@ -93,10 +87,40 @@ extension AppManager
} }
catch catch
{ {
print("Error while fetching installed apps") print("Error while fetching installed apps.", error)
} }
#endif #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
{
print("Failed to remove cached apps.", error)
}
}
} }
@discardableResult @discardableResult

View File

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

View File

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