mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-09 06:43:25 +01:00
Supports refreshing apps with Siri on iOS 14
This commit is contained in:
@@ -14,13 +14,13 @@
|
||||
BF0241AA22F29CCD00129732 /* UserDefaults+AltServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0241A922F29CCD00129732 /* UserDefaults+AltServer.swift */; };
|
||||
BF08858322DE795100DE9F1E /* MyAppsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF08858222DE795100DE9F1E /* MyAppsViewController.swift */; };
|
||||
BF08858522DE7EC800DE9F1E /* UpdateCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF08858422DE7EC800DE9F1E /* UpdateCollectionViewCell.swift */; };
|
||||
BF088D0F25019ABA008082D9 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; productRef = BF088D0E25019ABA008082D9 /* SwiftPackageProductDependency */; };
|
||||
BF088D2D2501A18E008082D9 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; productRef = BF088D2C2501A18E008082D9 /* SwiftPackageProductDependency */; };
|
||||
BF088D2E2501A18E008082D9 /* BuildFile in Embed Frameworks */ = {isa = PBXBuildFile; productRef = BF088D2C2501A18E008082D9 /* SwiftPackageProductDependency */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
BF088D0F25019ABA008082D9 /* AltSign-Static in Frameworks */ = {isa = PBXBuildFile; productRef = BF088D0E25019ABA008082D9 /* AltSign-Static */; };
|
||||
BF088D2D2501A18E008082D9 /* AltSign-Dynamic in Frameworks */ = {isa = PBXBuildFile; productRef = BF088D2C2501A18E008082D9 /* AltSign-Dynamic */; };
|
||||
BF088D2E2501A18E008082D9 /* AltSign-Dynamic in Embed Frameworks */ = {isa = PBXBuildFile; productRef = BF088D2C2501A18E008082D9 /* AltSign-Dynamic */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
BF088D332501A4FF008082D9 /* OpenSSL.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF088D322501A4FF008082D9 /* OpenSSL.xcframework */; };
|
||||
BF088D342501A4FF008082D9 /* OpenSSL.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF088D322501A4FF008082D9 /* OpenSSL.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
BF088D362501A821008082D9 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; productRef = BF088D352501A821008082D9 /* SwiftPackageProductDependency */; };
|
||||
BF088D372501A821008082D9 /* BuildFile in Embed Frameworks */ = {isa = PBXBuildFile; productRef = BF088D352501A821008082D9 /* SwiftPackageProductDependency */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
BF088D362501A821008082D9 /* AltSign-Dynamic in Frameworks */ = {isa = PBXBuildFile; productRef = BF088D352501A821008082D9 /* AltSign-Dynamic */; };
|
||||
BF088D372501A821008082D9 /* AltSign-Dynamic in Embed Frameworks */ = {isa = PBXBuildFile; productRef = BF088D352501A821008082D9 /* AltSign-Dynamic */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
BF088D382501A833008082D9 /* OpenSSL.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF088D322501A4FF008082D9 /* OpenSSL.xcframework */; };
|
||||
BF088D392501A833008082D9 /* OpenSSL.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF088D322501A4FF008082D9 /* OpenSSL.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
BF0C4EBD22A1BD8B009A2DD7 /* AppManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0C4EBC22A1BD8B009A2DD7 /* AppManager.swift */; };
|
||||
@@ -168,7 +168,7 @@
|
||||
BF66EEE92501AED0007EE018 /* JSONDecoder+Properties.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEE52501AED0007EE018 /* JSONDecoder+Properties.swift */; };
|
||||
BF66EEEA2501AED0007EE018 /* UIColor+Hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEE62501AED0007EE018 /* UIColor+Hex.swift */; };
|
||||
BF66EEEB2501AED0007EE018 /* UIApplication+AppExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEE72501AED0007EE018 /* UIApplication+AppExtension.swift */; };
|
||||
BF66EEF12501AF9D007EE018 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; productRef = BF66EEF02501AF9D007EE018 /* SwiftPackageProductDependency */; };
|
||||
BF66EEF12501AF9D007EE018 /* AltSign-Dynamic in Frameworks */ = {isa = PBXBuildFile; productRef = BF66EEF02501AF9D007EE018 /* AltSign-Dynamic */; };
|
||||
BF6A5320246DC1B0004F59C8 /* FileManager+SharedDirectories.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6A531F246DC1B0004F59C8 /* FileManager+SharedDirectories.swift */; };
|
||||
BF6C336224197D700034FD24 /* NSError+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6C336124197D700034FD24 /* NSError+AltStore.swift */; };
|
||||
BF6C8FAC242935ED00125131 /* NSAttributedString+Markdown.m in Sources */ = {isa = PBXBuildFile; fileRef = BF6C8FAA242935ED00125131 /* NSAttributedString+Markdown.m */; };
|
||||
@@ -273,6 +273,7 @@
|
||||
BFDB6A0D22AAFC1A007EA6D6 /* OperationError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB6A0C22AAFC19007EA6D6 /* OperationError.swift */; };
|
||||
BFDB6A0F22AB2776007EA6D6 /* SendAppOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB6A0E22AB2776007EA6D6 /* SendAppOperation.swift */; };
|
||||
BFDBBD80246CB84F004ED2F3 /* RemoveAppBackupOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDBBD7F246CB84F004ED2F3 /* RemoveAppBackupOperation.swift */; };
|
||||
BFE00A202503097F00EB4D0C /* INInteraction+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE00A1F2503097F00EB4D0C /* INInteraction+AltStore.swift */; };
|
||||
BFE338DF22F0EADB002E24B9 /* FetchSourceOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE338DE22F0EADB002E24B9 /* FetchSourceOperation.swift */; };
|
||||
BFE338E822F10E56002E24B9 /* LaunchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE338E722F10E56002E24B9 /* LaunchViewController.swift */; };
|
||||
BFE48975238007CE003239E0 /* AnisetteDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE48974238007CE003239E0 /* AnisetteDataManager.swift */; };
|
||||
@@ -305,6 +306,9 @@
|
||||
BFECAC9424FD98BA0077C41F /* NSError+ALTServerError.m in Sources */ = {isa = PBXBuildFile; fileRef = BF1E314922A060F400370A3C /* NSError+ALTServerError.m */; };
|
||||
BFECAC9524FD98BB0077C41F /* CFNotificationName+AltStore.m in Sources */ = {isa = PBXBuildFile; fileRef = BF718BC823C919E300A89F2D /* CFNotificationName+AltStore.m */; };
|
||||
BFECAC9624FD98BB0077C41F /* NSError+ALTServerError.m in Sources */ = {isa = PBXBuildFile; fileRef = BF1E314922A060F400370A3C /* NSError+ALTServerError.m */; };
|
||||
BFF00D302501BD7D00746320 /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = BFF00D2F2501BD7D00746320 /* Intents.intentdefinition */; };
|
||||
BFF00D322501BDA100746320 /* BackgroundRefreshAppsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF00D312501BDA100746320 /* BackgroundRefreshAppsOperation.swift */; };
|
||||
BFF00D342501BDCF00746320 /* IntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF00D332501BDCF00746320 /* IntentHandler.swift */; };
|
||||
BFF0B68E23219520007A79E1 /* PatreonViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF0B68D23219520007A79E1 /* PatreonViewController.swift */; };
|
||||
BFF0B69023219C6D007A79E1 /* PatreonComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF0B68F23219C6D007A79E1 /* PatreonComponents.swift */; };
|
||||
BFF0B6922321A305007A79E1 /* AboutPatreonHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BFF0B6912321A305007A79E1 /* AboutPatreonHeaderView.xib */; };
|
||||
@@ -350,7 +354,7 @@
|
||||
files = (
|
||||
BF088D392501A833008082D9 /* OpenSSL.xcframework in Embed Frameworks */,
|
||||
BF44CC6D232AEB90004DA9C3 /* LaunchAtLogin.framework in Embed Frameworks */,
|
||||
BF088D372501A821008082D9 /* BuildFile in Embed Frameworks */,
|
||||
BF088D372501A821008082D9 /* AltSign-Dynamic in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -362,7 +366,7 @@
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
BF088D342501A4FF008082D9 /* OpenSSL.xcframework in Embed Frameworks */,
|
||||
BF088D2E2501A18E008082D9 /* BuildFile in Embed Frameworks */,
|
||||
BF088D2E2501A18E008082D9 /* AltSign-Dynamic in Embed Frameworks */,
|
||||
BF66EE862501AE50007EE018 /* AltStoreCore.framework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
@@ -675,6 +679,7 @@
|
||||
BFDB6A0C22AAFC19007EA6D6 /* OperationError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationError.swift; sourceTree = "<group>"; };
|
||||
BFDB6A0E22AB2776007EA6D6 /* SendAppOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendAppOperation.swift; sourceTree = "<group>"; };
|
||||
BFDBBD7F246CB84F004ED2F3 /* RemoveAppBackupOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveAppBackupOperation.swift; sourceTree = "<group>"; };
|
||||
BFE00A1F2503097F00EB4D0C /* INInteraction+AltStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "INInteraction+AltStore.swift"; sourceTree = "<group>"; };
|
||||
BFE338DE22F0EADB002E24B9 /* FetchSourceOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchSourceOperation.swift; sourceTree = "<group>"; };
|
||||
BFE338E722F10E56002E24B9 /* LaunchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchViewController.swift; sourceTree = "<group>"; };
|
||||
BFE48974238007CE003239E0 /* AnisetteDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnisetteDataManager.swift; sourceTree = "<group>"; };
|
||||
@@ -685,6 +690,9 @@
|
||||
BFE60741231B07E6002B0E8E /* SettingsHeaderFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsHeaderFooterView.swift; sourceTree = "<group>"; };
|
||||
BFE6325922A83BEB00F30809 /* Authentication.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Authentication.storyboard; sourceTree = "<group>"; };
|
||||
BFE6326B22A86FF300F30809 /* AuthenticationOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationOperation.swift; sourceTree = "<group>"; };
|
||||
BFF00D2F2501BD7D00746320 /* Intents.intentdefinition */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.intentdefinition; path = Intents.intentdefinition; sourceTree = "<group>"; };
|
||||
BFF00D312501BDA100746320 /* BackgroundRefreshAppsOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackgroundRefreshAppsOperation.swift; sourceTree = "<group>"; };
|
||||
BFF00D332501BDCF00746320 /* IntentHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IntentHandler.swift; sourceTree = "<group>"; };
|
||||
BFF0B68D23219520007A79E1 /* PatreonViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatreonViewController.swift; sourceTree = "<group>"; };
|
||||
BFF0B68F23219C6D007A79E1 /* PatreonComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatreonComponents.swift; sourceTree = "<group>"; };
|
||||
BFF0B6912321A305007A79E1 /* AboutPatreonHeaderView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AboutPatreonHeaderView.xib; sourceTree = "<group>"; };
|
||||
@@ -715,7 +723,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
EFB988A976C401E5710498B7 /* libPods-AltDaemon.a in Frameworks */,
|
||||
BF088D0F25019ABA008082D9 /* BuildFile in Frameworks */,
|
||||
BF088D0F25019ABA008082D9 /* AltSign-Static in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -727,7 +735,7 @@
|
||||
BF4588882298DD3F00BD7491 /* libxml2.tbd in Frameworks */,
|
||||
BF44CC6C232AEB90004DA9C3 /* LaunchAtLogin.framework in Frameworks */,
|
||||
BF4588472298D4B000BD7491 /* libimobiledevice.a in Frameworks */,
|
||||
BF088D362501A821008082D9 /* BuildFile in Frameworks */,
|
||||
BF088D362501A821008082D9 /* AltSign-Dynamic in Frameworks */,
|
||||
A8BCEBEAC0620CF80A2FD26D /* Pods_AltServer.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -751,7 +759,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
BF66EEF12501AF9D007EE018 /* BuildFile in Frameworks */,
|
||||
BF66EEF12501AF9D007EE018 /* AltSign-Dynamic in Frameworks */,
|
||||
0E33F94B8D78AB969FD309A3 /* Pods_AltStoreCore.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -762,7 +770,7 @@
|
||||
files = (
|
||||
BF088D332501A4FF008082D9 /* OpenSSL.xcframework in Frameworks */,
|
||||
BF66EE852501AE50007EE018 /* AltStoreCore.framework in Frameworks */,
|
||||
BF088D2D2501A18E008082D9 /* BuildFile in Frameworks */,
|
||||
BF088D2D2501A18E008082D9 /* AltSign-Dynamic in Frameworks */,
|
||||
2A77E3D272F3D92436FAC272 /* Pods_AltStore.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -1315,6 +1323,7 @@
|
||||
BFC84A4B2421A13000853474 /* Sources */,
|
||||
BFC51D7922972F1F00388324 /* Server */,
|
||||
BF0DCA642433BDE200E3A595 /* Analytics */,
|
||||
BFF00D2E2501BD4B00746320 /* Intents */,
|
||||
BFDB6A0922AAEDA1007EA6D6 /* Operations */,
|
||||
BFD2478D2284C4C700981D42 /* Components */,
|
||||
BF3D648922E79A7700E9056B /* Types */,
|
||||
@@ -1407,6 +1416,7 @@
|
||||
BF663C4E2433ED8200DAA738 /* FileManager+DirectorySize.swift */,
|
||||
BF6A531F246DC1B0004F59C8 /* FileManager+SharedDirectories.swift */,
|
||||
BF8CAE4D248AEABA004D6CCE /* UIDevice+Jailbreak.swift */,
|
||||
BFE00A1F2503097F00EB4D0C /* INInteraction+AltStore.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
@@ -1467,6 +1477,7 @@
|
||||
BF44EEFB246B4550002A52F2 /* RemoveAppOperation.swift */,
|
||||
BF3432FA246B894F0052F4A1 /* BackupAppOperation.swift */,
|
||||
BFDBBD7F246CB84F004ED2F3 /* RemoveAppBackupOperation.swift */,
|
||||
BFF00D312501BDA100746320 /* BackgroundRefreshAppsOperation.swift */,
|
||||
);
|
||||
path = Operations;
|
||||
sourceTree = "<group>";
|
||||
@@ -1482,6 +1493,15 @@
|
||||
path = Authentication;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BFF00D2E2501BD4B00746320 /* Intents */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BFF00D2F2501BD7D00746320 /* Intents.intentdefinition */,
|
||||
BFF00D332501BDCF00746320 /* IntentHandler.swift */,
|
||||
);
|
||||
path = Intents;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BFF767C32489A6800097E58C /* Extensions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -1595,7 +1615,7 @@
|
||||
);
|
||||
name = AltDaemon;
|
||||
packageProductDependencies = (
|
||||
BF088D0E25019ABA008082D9 /* SwiftPackageProductDependency */,
|
||||
BF088D0E25019ABA008082D9 /* AltSign-Static */,
|
||||
);
|
||||
productName = AltDaemon;
|
||||
productReference = BF18BFE724857D7900DD5981 /* AltDaemon */;
|
||||
@@ -1622,7 +1642,7 @@
|
||||
);
|
||||
name = AltServer;
|
||||
packageProductDependencies = (
|
||||
BF088D352501A821008082D9 /* SwiftPackageProductDependency */,
|
||||
BF088D352501A821008082D9 /* AltSign-Dynamic */,
|
||||
);
|
||||
productName = AltServer;
|
||||
productReference = BF45868D229872EA00BD7491 /* AltServer.app */;
|
||||
@@ -1696,7 +1716,7 @@
|
||||
);
|
||||
name = AltStoreCore;
|
||||
packageProductDependencies = (
|
||||
BF66EEF02501AF9D007EE018 /* SwiftPackageProductDependency */,
|
||||
BF66EEF02501AF9D007EE018 /* AltSign-Dynamic */,
|
||||
);
|
||||
productName = AltStoreCore;
|
||||
productReference = BF66EE7E2501AE50007EE018 /* AltStoreCore.framework */;
|
||||
@@ -1720,7 +1740,7 @@
|
||||
);
|
||||
name = AltStore;
|
||||
packageProductDependencies = (
|
||||
BF088D2C2501A18E008082D9 /* SwiftPackageProductDependency */,
|
||||
BF088D2C2501A18E008082D9 /* AltSign-Dynamic */,
|
||||
);
|
||||
productName = AltStore;
|
||||
productReference = BFD2476A2284B9A500981D42 /* AltStore.app */;
|
||||
@@ -2254,9 +2274,11 @@
|
||||
BFD2478C2284C4C300981D42 /* AppIconImageView.swift in Sources */,
|
||||
BF8F69C422E662D300049BA1 /* AppViewController.swift in Sources */,
|
||||
BFF0B68E23219520007A79E1 /* PatreonViewController.swift in Sources */,
|
||||
BFF00D302501BD7D00746320 /* Intents.intentdefinition in Sources */,
|
||||
BF6C336224197D700034FD24 /* NSError+AltStore.swift in Sources */,
|
||||
BFD2476E2284B9A500981D42 /* AppDelegate.swift in Sources */,
|
||||
BF41B806233423AE00C593A3 /* TabBarController.swift in Sources */,
|
||||
BFE00A202503097F00EB4D0C /* INInteraction+AltStore.swift in Sources */,
|
||||
BFDB6A0B22AAEDB7007EA6D6 /* Operation.swift in Sources */,
|
||||
BF770E6722BD57C4002A40FE /* BackgroundTaskManager.swift in Sources */,
|
||||
BF44EEFC246B4550002A52F2 /* RemoveAppOperation.swift in Sources */,
|
||||
@@ -2286,8 +2308,10 @@
|
||||
BF770E5122BB1CF6002A40FE /* InstallAppOperation.swift in Sources */,
|
||||
BF9ABA4B22DD1380008935CF /* NavigationBar.swift in Sources */,
|
||||
BF6C8FAC242935ED00125131 /* NSAttributedString+Markdown.m in Sources */,
|
||||
BFF00D322501BDA100746320 /* BackgroundRefreshAppsOperation.swift in Sources */,
|
||||
BF0C4EBD22A1BD8B009A2DD7 /* AppManager.swift in Sources */,
|
||||
BF2901312318F7A800D88A45 /* AppBannerView.swift in Sources */,
|
||||
BFF00D342501BDCF00746320 /* IntentHandler.swift in Sources */,
|
||||
BFDBBD80246CB84F004ED2F3 /* RemoveAppBackupOperation.swift in Sources */,
|
||||
BFF0B6942321CB85007A79E1 /* AuthenticationViewController.swift in Sources */,
|
||||
BF3432FB246B894F0052F4A1 /* BackupAppOperation.swift in Sources */,
|
||||
@@ -3058,19 +3082,19 @@
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
BF088D0E25019ABA008082D9 /* SwiftPackageProductDependency */ = {
|
||||
BF088D0E25019ABA008082D9 /* AltSign-Static */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = "AltSign-Static";
|
||||
};
|
||||
BF088D2C2501A18E008082D9 /* SwiftPackageProductDependency */ = {
|
||||
BF088D2C2501A18E008082D9 /* AltSign-Dynamic */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = "AltSign-Dynamic";
|
||||
};
|
||||
BF088D352501A821008082D9 /* SwiftPackageProductDependency */ = {
|
||||
BF088D352501A821008082D9 /* AltSign-Dynamic */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = "AltSign-Dynamic";
|
||||
};
|
||||
BF66EEF02501AF9D007EE018 /* SwiftPackageProductDependency */ = {
|
||||
BF66EEF02501AF9D007EE018 /* AltSign-Dynamic */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = "AltSign-Dynamic";
|
||||
};
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
<dict>
|
||||
<key>aps-environment</key>
|
||||
<string>development</string>
|
||||
<key>com.apple.developer.siri</key>
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.com.rileytestut.AltStore</string>
|
||||
|
||||
@@ -9,47 +9,12 @@
|
||||
import UIKit
|
||||
import UserNotifications
|
||||
import AVFoundation
|
||||
import Intents
|
||||
|
||||
import AltStoreCore
|
||||
import AltSign
|
||||
import Roxas
|
||||
|
||||
private enum RefreshError: LocalizedError
|
||||
{
|
||||
case noInstalledApps
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self
|
||||
{
|
||||
case .noInstalledApps: return NSLocalizedString("No active apps require refreshing.", comment: "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension CFNotificationName
|
||||
{
|
||||
static let requestAppState = CFNotificationName("com.altstore.RequestAppState" as CFString)
|
||||
static let appIsRunning = CFNotificationName("com.altstore.AppState.Running" as CFString)
|
||||
|
||||
static func requestAppState(for appID: String) -> CFNotificationName
|
||||
{
|
||||
let name = String(CFNotificationName.requestAppState.rawValue) + "." + appID
|
||||
return CFNotificationName(name as CFString)
|
||||
}
|
||||
|
||||
static func appIsRunning(for appID: String) -> CFNotificationName
|
||||
{
|
||||
let name = String(CFNotificationName.appIsRunning.rawValue) + "." + appID
|
||||
return CFNotificationName(name as CFString)
|
||||
}
|
||||
}
|
||||
|
||||
private let ReceivedApplicationState: @convention(c) (CFNotificationCenter?, UnsafeMutableRawPointer?, CFNotificationName?, UnsafeRawPointer?, CFDictionary?) -> Void =
|
||||
{ (center, observer, name, object, userInfo) in
|
||||
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate, let name = name else { return }
|
||||
appDelegate.receivedApplicationState(notification: name)
|
||||
}
|
||||
|
||||
extension AppDelegate
|
||||
{
|
||||
static let openPatreonSettingsDeepLinkNotification = Notification.Name("com.rileytestut.AltStore.OpenPatreonSettingsDeepLinkNotification")
|
||||
@@ -68,8 +33,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
private var runningApplications: Set<String>?
|
||||
private var backgroundRefreshContext: NSManagedObjectContext? // Keep context alive until finished refreshing.
|
||||
private lazy var intentHandler = IntentHandler()
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
|
||||
{
|
||||
@@ -128,6 +92,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
{
|
||||
return self.open(url)
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, handlerFor intent: INIntent) -> Any?
|
||||
{
|
||||
guard intent is RefreshAllIntent else { return nil }
|
||||
return self.intentHandler
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 13, *)
|
||||
@@ -263,93 +233,92 @@ extension AppDelegate
|
||||
|
||||
func application(_ application: UIApplication, performFetchWithCompletionHandler backgroundFetchCompletionHandler: @escaping (UIBackgroundFetchResult) -> Void)
|
||||
{
|
||||
if UserDefaults.standard.isBackgroundRefreshEnabled
|
||||
if UserDefaults.standard.isBackgroundRefreshEnabled && !UserDefaults.standard.presentedLaunchReminderNotification
|
||||
{
|
||||
ServerManager.shared.startDiscovering()
|
||||
let threeHours: TimeInterval = 3 * 60 * 60
|
||||
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: threeHours, repeats: false)
|
||||
|
||||
if !UserDefaults.standard.presentedLaunchReminderNotification
|
||||
{
|
||||
let threeHours: TimeInterval = 3 * 60 * 60
|
||||
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: threeHours, repeats: false)
|
||||
|
||||
let content = UNMutableNotificationContent()
|
||||
content.title = NSLocalizedString("App Refresh Tip", comment: "")
|
||||
content.body = NSLocalizedString("The more you open AltStore, the more chances it's given to refresh apps in the background.", comment: "")
|
||||
|
||||
let request = UNNotificationRequest(identifier: "background-refresh-reminder5", content: content, trigger: trigger)
|
||||
UNUserNotificationCenter.current().add(request)
|
||||
|
||||
UserDefaults.standard.presentedLaunchReminderNotification = true
|
||||
}
|
||||
let content = UNMutableNotificationContent()
|
||||
content.title = NSLocalizedString("App Refresh Tip", comment: "")
|
||||
content.body = NSLocalizedString("The more you open AltStore, the more chances it's given to refresh apps in the background.", comment: "")
|
||||
|
||||
let request = UNNotificationRequest(identifier: "background-refresh-reminder5", content: content, trigger: trigger)
|
||||
UNUserNotificationCenter.current().add(request)
|
||||
|
||||
UserDefaults.standard.presentedLaunchReminderNotification = true
|
||||
}
|
||||
|
||||
let refreshIdentifier = UUID().uuidString
|
||||
|
||||
BackgroundTaskManager.shared.performExtendedBackgroundTask { (taskResult, taskCompletionHandler) in
|
||||
|
||||
func finish(_ result: Result<[String: Result<InstalledApp, Error>], Error>)
|
||||
{
|
||||
// If finish is actually called, that means an error occured during installation.
|
||||
|
||||
if UserDefaults.standard.isBackgroundRefreshEnabled
|
||||
{
|
||||
ServerManager.shared.stopDiscovering()
|
||||
self.scheduleFinishedRefreshingNotification(for: result, identifier: refreshIdentifier, delay: 0)
|
||||
}
|
||||
|
||||
taskCompletionHandler()
|
||||
|
||||
self.backgroundRefreshContext = nil
|
||||
}
|
||||
|
||||
if let error = taskResult.error
|
||||
{
|
||||
print("Error starting extended background task. Aborting.", error)
|
||||
backgroundFetchCompletionHandler(.failed)
|
||||
finish(.failure(error))
|
||||
taskCompletionHandler()
|
||||
return
|
||||
}
|
||||
|
||||
if !DatabaseManager.shared.isStarted
|
||||
{
|
||||
DatabaseManager.shared.start() { (error) in
|
||||
if let error = error
|
||||
if error != nil
|
||||
{
|
||||
backgroundFetchCompletionHandler(.failed)
|
||||
finish(.failure(error))
|
||||
taskCompletionHandler()
|
||||
}
|
||||
else
|
||||
{
|
||||
self.refreshApps(identifier: refreshIdentifier, backgroundFetchCompletionHandler: backgroundFetchCompletionHandler, completionHandler: finish(_:))
|
||||
self.performBackgroundFetch { (backgroundFetchResult) in
|
||||
backgroundFetchCompletionHandler(backgroundFetchResult)
|
||||
} refreshAppsCompletionHandler: { (refreshAppsResult) in
|
||||
taskCompletionHandler()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
self.refreshApps(identifier: refreshIdentifier, backgroundFetchCompletionHandler: backgroundFetchCompletionHandler, completionHandler: finish(_:))
|
||||
self.performBackgroundFetch { (backgroundFetchResult) in
|
||||
backgroundFetchCompletionHandler(backgroundFetchResult)
|
||||
} refreshAppsCompletionHandler: { (refreshAppsResult) in
|
||||
taskCompletionHandler()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func performBackgroundFetch(backgroundFetchCompletionHandler: @escaping (UIBackgroundFetchResult) -> Void,
|
||||
refreshAppsCompletionHandler: @escaping (Result<[String: Result<InstalledApp, Error>], Error>) -> Void)
|
||||
{
|
||||
self.fetchSources { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure: backgroundFetchCompletionHandler(.failed)
|
||||
case .success: backgroundFetchCompletionHandler(.newData)
|
||||
}
|
||||
|
||||
if !UserDefaults.standard.isBackgroundRefreshEnabled
|
||||
{
|
||||
refreshAppsCompletionHandler(.success([:]))
|
||||
}
|
||||
}
|
||||
|
||||
guard UserDefaults.standard.isBackgroundRefreshEnabled else { return }
|
||||
|
||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||
let installedApps = InstalledApp.fetchAppsForBackgroundRefresh(in: context)
|
||||
AppManager.shared.backgroundRefresh(installedApps, completionHandler: refreshAppsCompletionHandler)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension AppDelegate
|
||||
{
|
||||
func refreshApps(identifier: String,
|
||||
backgroundFetchCompletionHandler: @escaping (UIBackgroundFetchResult) -> Void,
|
||||
completionHandler: @escaping (Result<[String: Result<InstalledApp, Error>], Error>) -> Void)
|
||||
func fetchSources(completionHandler: @escaping (Result<Set<Source>, Error>) -> Void)
|
||||
{
|
||||
var fetchSourcesResult: Result<Set<Source>, Error>?
|
||||
var serversResult: Result<Void, Error>?
|
||||
|
||||
let dispatchGroup = DispatchGroup()
|
||||
dispatchGroup.enter()
|
||||
|
||||
AppManager.shared.fetchSources() { (result) in
|
||||
fetchSourcesResult = result.map { $0.0 }.mapError { $0 as Error }
|
||||
|
||||
do
|
||||
{
|
||||
let (_, context) = try result.get()
|
||||
let (sources, context) = try result.get()
|
||||
|
||||
let previousUpdatesFetchRequest = InstalledApp.updatesFetchRequest() as! NSFetchRequest<NSFetchRequestResult>
|
||||
previousUpdatesFetchRequest.includesPendingChanges = false
|
||||
@@ -412,223 +381,14 @@ private extension AppDelegate
|
||||
DispatchQueue.main.async {
|
||||
UIApplication.shared.applicationIconBadgeNumber = updates.count
|
||||
}
|
||||
|
||||
completionHandler(.success(sources))
|
||||
}
|
||||
catch
|
||||
{
|
||||
print("Error fetching apps:", error)
|
||||
|
||||
fetchSourcesResult = .failure(error)
|
||||
}
|
||||
|
||||
dispatchGroup.leave()
|
||||
}
|
||||
|
||||
if UserDefaults.standard.isBackgroundRefreshEnabled
|
||||
{
|
||||
dispatchGroup.enter()
|
||||
|
||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||
let installedApps = InstalledApp.fetchAppsForBackgroundRefresh(in: context)
|
||||
guard !installedApps.isEmpty else {
|
||||
serversResult = .success(())
|
||||
dispatchGroup.leave()
|
||||
|
||||
completionHandler(.failure(RefreshError.noInstalledApps))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
self.runningApplications = []
|
||||
self.backgroundRefreshContext = context
|
||||
|
||||
let identifiers = installedApps.compactMap { $0.bundleIdentifier }
|
||||
print("Apps to refresh:", identifiers)
|
||||
|
||||
DispatchQueue.global().async {
|
||||
let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
|
||||
|
||||
for identifier in identifiers
|
||||
{
|
||||
let appIsRunningNotification = CFNotificationName.appIsRunning(for: identifier)
|
||||
CFNotificationCenterAddObserver(notificationCenter, nil, ReceivedApplicationState, appIsRunningNotification.rawValue, nil, .deliverImmediately)
|
||||
|
||||
let requestAppStateNotification = CFNotificationName.requestAppState(for: identifier)
|
||||
CFNotificationCenterPostNotification(notificationCenter, requestAppStateNotification, nil, nil, true)
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for three seconds to:
|
||||
// a) give us time to discover AltServers
|
||||
// b) give other processes a chance to respond to requestAppState notification
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
|
||||
context.perform {
|
||||
if ServerManager.shared.discoveredServers.isEmpty
|
||||
{
|
||||
serversResult = .failure(ConnectionError.serverNotFound)
|
||||
}
|
||||
else
|
||||
{
|
||||
serversResult = .success(())
|
||||
}
|
||||
|
||||
dispatchGroup.leave()
|
||||
|
||||
let filteredApps = installedApps.filter { !(self.runningApplications?.contains($0.bundleIdentifier) ?? false) }
|
||||
print("Filtered Apps to Refresh:", filteredApps.map { $0.bundleIdentifier })
|
||||
|
||||
let group = AppManager.shared.refresh(filteredApps, presentingViewController: nil)
|
||||
group.beginInstallationHandler = { (installedApp) in
|
||||
guard installedApp.bundleIdentifier == StoreApp.altstoreAppID else { return }
|
||||
|
||||
// We're starting to install AltStore, which means the app is about to quit.
|
||||
// So, we schedule a "refresh successful" local notification to be displayed after a delay,
|
||||
// but if the app is still running, we cancel the notification.
|
||||
// Then, we schedule another notification and repeat the process.
|
||||
|
||||
// Also since AltServer has already received the app, it can finish installing even if we're no longer running in background.
|
||||
|
||||
if let error = group.context.error
|
||||
{
|
||||
self.scheduleFinishedRefreshingNotification(for: .failure(error), identifier: identifier)
|
||||
}
|
||||
else
|
||||
{
|
||||
var results = group.results
|
||||
results[installedApp.bundleIdentifier] = .success(installedApp)
|
||||
|
||||
self.scheduleFinishedRefreshingNotification(for: .success(results), identifier: identifier)
|
||||
}
|
||||
}
|
||||
group.completionHandler = { (results) in
|
||||
completionHandler(.success(results))
|
||||
}
|
||||
}
|
||||
}
|
||||
completionHandler(.failure(error))
|
||||
}
|
||||
}
|
||||
|
||||
dispatchGroup.notify(queue: .main) {
|
||||
if !UserDefaults.standard.isBackgroundRefreshEnabled
|
||||
{
|
||||
guard let fetchSourcesResult = fetchSourcesResult else {
|
||||
backgroundFetchCompletionHandler(.failed)
|
||||
return
|
||||
}
|
||||
|
||||
switch fetchSourcesResult
|
||||
{
|
||||
case .failure: backgroundFetchCompletionHandler(.failed)
|
||||
case .success: backgroundFetchCompletionHandler(.newData)
|
||||
}
|
||||
|
||||
completionHandler(.success([:]))
|
||||
}
|
||||
else
|
||||
{
|
||||
guard let fetchSourcesResult = fetchSourcesResult, let serversResult = serversResult else {
|
||||
backgroundFetchCompletionHandler(.failed)
|
||||
return
|
||||
}
|
||||
|
||||
// Call completionHandler early to improve chances of refreshing in the background again.
|
||||
switch (fetchSourcesResult, serversResult)
|
||||
{
|
||||
case (.success, .success): backgroundFetchCompletionHandler(.newData)
|
||||
case (.success, .failure(ConnectionError.serverNotFound)): backgroundFetchCompletionHandler(.newData)
|
||||
case (.failure, _), (_, .failure): backgroundFetchCompletionHandler(.failed)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func receivedApplicationState(notification: CFNotificationName)
|
||||
{
|
||||
let baseName = String(CFNotificationName.appIsRunning.rawValue)
|
||||
|
||||
let appID = String(notification.rawValue).replacingOccurrences(of: baseName + ".", with: "")
|
||||
self.runningApplications?.insert(appID)
|
||||
}
|
||||
|
||||
func scheduleFinishedRefreshingNotification(for result: Result<[String: Result<InstalledApp, Error>], Error>, identifier: String, delay: TimeInterval = 5)
|
||||
{
|
||||
func scheduleFinishedRefreshingNotification()
|
||||
{
|
||||
self.cancelFinishedRefreshingNotification(identifier: identifier)
|
||||
|
||||
let content = UNMutableNotificationContent()
|
||||
|
||||
var shouldPresentAlert = true
|
||||
|
||||
do
|
||||
{
|
||||
let results = try result.get()
|
||||
shouldPresentAlert = !results.isEmpty
|
||||
|
||||
for (_, result) in results
|
||||
{
|
||||
guard case let .failure(error) = result else { continue }
|
||||
throw error
|
||||
}
|
||||
|
||||
content.title = NSLocalizedString("Refreshed Apps", comment: "")
|
||||
content.body = NSLocalizedString("All apps have been refreshed.", comment: "")
|
||||
}
|
||||
catch ConnectionError.serverNotFound
|
||||
{
|
||||
shouldPresentAlert = false
|
||||
}
|
||||
catch RefreshError.noInstalledApps
|
||||
{
|
||||
shouldPresentAlert = false
|
||||
}
|
||||
catch
|
||||
{
|
||||
print("Failed to refresh apps in background.", error)
|
||||
|
||||
content.title = NSLocalizedString("Failed to Refresh Apps", comment: "")
|
||||
content.body = error.localizedDescription
|
||||
|
||||
shouldPresentAlert = true
|
||||
}
|
||||
|
||||
if shouldPresentAlert
|
||||
{
|
||||
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: delay + 1, repeats: false)
|
||||
|
||||
let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger)
|
||||
UNUserNotificationCenter.current().add(request)
|
||||
|
||||
if delay > 0
|
||||
{
|
||||
DispatchQueue.global().asyncAfter(deadline: .now() + delay) {
|
||||
UNUserNotificationCenter.current().getPendingNotificationRequests() { (requests) in
|
||||
// If app is still running at this point, we schedule another notification with same identifier.
|
||||
// This prevents the currently scheduled notification from displaying, and starts another countdown timer.
|
||||
// First though, make sure there _is_ still a pending request, otherwise it's been cancelled
|
||||
// and we should stop polling.
|
||||
guard requests.contains(where: { $0.identifier == identifier }) else { return }
|
||||
|
||||
scheduleFinishedRefreshingNotification()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scheduleFinishedRefreshingNotification()
|
||||
|
||||
// Perform synchronously to ensure app doesn't quit before we've finishing saving to disk.
|
||||
let context = DatabaseManager.shared.persistentContainer.newBackgroundContext()
|
||||
context.performAndWait {
|
||||
_ = RefreshAttempt(identifier: identifier, result: result, context: context)
|
||||
|
||||
do { try context.save() }
|
||||
catch { print("Failed to save refresh attempt.", error) }
|
||||
}
|
||||
}
|
||||
|
||||
func cancelFinishedRefreshingNotification(identifier: String)
|
||||
{
|
||||
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [identifier])
|
||||
}
|
||||
}
|
||||
|
||||
23
AltStore/Extensions/INInteraction+AltStore.swift
Normal file
23
AltStore/Extensions/INInteraction+AltStore.swift
Normal file
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// INInteraction+AltStore.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 9/4/20.
|
||||
// Copyright © 2020 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import Intents
|
||||
|
||||
// Requires iOS 14 in-app intent handling.
|
||||
@available(iOS 14, *)
|
||||
extension INInteraction
|
||||
{
|
||||
static func refreshAllApps() -> INInteraction
|
||||
{
|
||||
let refreshAllIntent = RefreshAllIntent()
|
||||
refreshAllIntent.suggestedInvocationPhrase = NSString.deferredLocalizedIntentsString(with: "Refresh my apps") as String
|
||||
|
||||
let interaction = INInteraction(intent: refreshAllIntent, response: nil)
|
||||
return interaction
|
||||
}
|
||||
}
|
||||
@@ -66,6 +66,10 @@
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>INIntentsSupported</key>
|
||||
<array>
|
||||
<string>RefreshAllIntent</string>
|
||||
</array>
|
||||
<key>LSApplicationQueriesSchemes</key>
|
||||
<array>
|
||||
<string>altstore-com.rileytestut.AltStore</string>
|
||||
@@ -85,6 +89,10 @@
|
||||
</array>
|
||||
<key>NSLocalNetworkUsageDescription</key>
|
||||
<string>AltStore uses the local network to find and communicate with AltServer.</string>
|
||||
<key>NSUserActivityTypes</key>
|
||||
<array>
|
||||
<string>RefreshAllIntent</string>
|
||||
</array>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
|
||||
118
AltStore/Intents/IntentHandler.swift
Normal file
118
AltStore/Intents/IntentHandler.swift
Normal file
@@ -0,0 +1,118 @@
|
||||
//
|
||||
// IntentHandler.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 7/6/20.
|
||||
// Copyright © 2020 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
import AltStoreCore
|
||||
|
||||
class IntentHandler: NSObject, RefreshAllIntentHandling
|
||||
{
|
||||
private let queue = DispatchQueue(label: "io.altstore.IntentHandler")
|
||||
|
||||
private var completionHandlers = [RefreshAllIntent: (RefreshAllIntentResponse) -> Void]()
|
||||
private var queuedResponses = [RefreshAllIntent: RefreshAllIntentResponse]()
|
||||
|
||||
func confirm(intent: RefreshAllIntent, completion: @escaping (RefreshAllIntentResponse) -> Void)
|
||||
{
|
||||
// Refreshing apps usually, but not always, completes within alotted time.
|
||||
// As a workaround, we'll start refreshing apps in confirm() so we can
|
||||
// take advantage of some extra time before starting handle() timeout timer.
|
||||
|
||||
self.completionHandlers[intent] = { (response) in
|
||||
if response.code != .ready
|
||||
{
|
||||
// Operation finished before confirmation "timeout".
|
||||
// Cache response to return it when handle() is called.
|
||||
self.queuedResponses[intent] = response
|
||||
}
|
||||
|
||||
completion(RefreshAllIntentResponse(code: .ready, userActivity: nil))
|
||||
}
|
||||
|
||||
// Give ourselves 5 extra seconds before starting timeout timer.
|
||||
self.queue.asyncAfter(deadline: .now() + 5.0) {
|
||||
self.finish(intent, response: RefreshAllIntentResponse(code: .ready, userActivity: nil))
|
||||
}
|
||||
|
||||
if !DatabaseManager.shared.isStarted
|
||||
{
|
||||
DatabaseManager.shared.start() { (error) in
|
||||
if let error = error
|
||||
{
|
||||
self.finish(intent, response: RefreshAllIntentResponse.failure(localizedDescription: error.localizedDescription))
|
||||
}
|
||||
else
|
||||
{
|
||||
self.refreshApps(intent: intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
self.refreshApps(intent: intent)
|
||||
}
|
||||
}
|
||||
|
||||
func handle(intent: RefreshAllIntent, completion: @escaping (RefreshAllIntentResponse) -> Void)
|
||||
{
|
||||
self.completionHandlers[intent] = { (response) in
|
||||
// Ignore .ready response from confirm() timeout.
|
||||
guard response.code != .ready else { return }
|
||||
completion(response)
|
||||
}
|
||||
|
||||
if let response = self.queuedResponses[intent]
|
||||
{
|
||||
self.queuedResponses[intent] = nil
|
||||
self.finish(intent, response: response)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension IntentHandler
|
||||
{
|
||||
func finish(_ intent: RefreshAllIntent, response: RefreshAllIntentResponse)
|
||||
{
|
||||
self.queue.async {
|
||||
guard let completionHandler = self.completionHandlers[intent] else { return }
|
||||
self.completionHandlers[intent] = nil
|
||||
|
||||
completionHandler(response)
|
||||
}
|
||||
}
|
||||
|
||||
func refreshApps(intent: RefreshAllIntent)
|
||||
{
|
||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||
let installedApps = InstalledApp.fetchActiveApps(in: context)
|
||||
AppManager.shared.backgroundRefresh(installedApps, presentsNotifications: false) { (result) in
|
||||
do
|
||||
{
|
||||
let results = try result.get()
|
||||
|
||||
for (_, result) in results
|
||||
{
|
||||
guard case let .failure(error) = result else { continue }
|
||||
throw error
|
||||
}
|
||||
|
||||
self.finish(intent, response: RefreshAllIntentResponse(code: .success, userActivity: nil))
|
||||
}
|
||||
catch RefreshError.noInstalledApps
|
||||
{
|
||||
self.finish(intent, response: RefreshAllIntentResponse(code: .success, userActivity: nil))
|
||||
}
|
||||
catch let error as NSError
|
||||
{
|
||||
print("Failed to refresh apps in background.", error)
|
||||
self.finish(intent, response: RefreshAllIntentResponse.failure(localizedDescription: error.localizedFailureReason ?? error.localizedDescription))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
120
AltStore/Intents/Intents.intentdefinition
Normal file
120
AltStore/Intents/Intents.intentdefinition
Normal file
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>INEnums</key>
|
||||
<array/>
|
||||
<key>INIntentDefinitionModelVersion</key>
|
||||
<string>1.2</string>
|
||||
<key>INIntentDefinitionNamespace</key>
|
||||
<string>KyhEWE</string>
|
||||
<key>INIntentDefinitionSystemVersion</key>
|
||||
<string>20A5354i</string>
|
||||
<key>INIntentDefinitionToolsBuildVersion</key>
|
||||
<string>12A8189n</string>
|
||||
<key>INIntentDefinitionToolsVersion</key>
|
||||
<string>12.0</string>
|
||||
<key>INIntents</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>INIntentCategory</key>
|
||||
<string>generic</string>
|
||||
<key>INIntentConfigurable</key>
|
||||
<true/>
|
||||
<key>INIntentDescriptionID</key>
|
||||
<string>62S1rm</string>
|
||||
<key>INIntentLastParameterTag</key>
|
||||
<integer>3</integer>
|
||||
<key>INIntentManagedParameterCombinations</key>
|
||||
<dict>
|
||||
<key></key>
|
||||
<dict>
|
||||
<key>INIntentParameterCombinationSupportsBackgroundExecution</key>
|
||||
<true/>
|
||||
<key>INIntentParameterCombinationTitle</key>
|
||||
<string>Refresh All Apps</string>
|
||||
<key>INIntentParameterCombinationTitleID</key>
|
||||
<string>cJxa2I</string>
|
||||
<key>INIntentParameterCombinationUpdatesLinked</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>INIntentName</key>
|
||||
<string>RefreshAll</string>
|
||||
<key>INIntentParameterCombinations</key>
|
||||
<dict>
|
||||
<key></key>
|
||||
<dict>
|
||||
<key>INIntentParameterCombinationSupportsBackgroundExecution</key>
|
||||
<true/>
|
||||
<key>INIntentParameterCombinationTitle</key>
|
||||
<string>Refresh All Apps</string>
|
||||
<key>INIntentParameterCombinationTitleID</key>
|
||||
<string>DKTGdO</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>INIntentResponse</key>
|
||||
<dict>
|
||||
<key>INIntentResponseCodes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>INIntentResponseCodeConciseFormatString</key>
|
||||
<string>All apps have been refreshed.</string>
|
||||
<key>INIntentResponseCodeConciseFormatStringID</key>
|
||||
<string>3WMWsJ</string>
|
||||
<key>INIntentResponseCodeFormatString</key>
|
||||
<string>All apps have been refreshed.</string>
|
||||
<key>INIntentResponseCodeFormatStringID</key>
|
||||
<string>BjInD3</string>
|
||||
<key>INIntentResponseCodeName</key>
|
||||
<string>success</string>
|
||||
<key>INIntentResponseCodeSuccess</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>INIntentResponseCodeConciseFormatString</key>
|
||||
<string>${localizedDescription}</string>
|
||||
<key>INIntentResponseCodeConciseFormatStringID</key>
|
||||
<string>GJdShK</string>
|
||||
<key>INIntentResponseCodeFormatString</key>
|
||||
<string>${localizedDescription}</string>
|
||||
<key>INIntentResponseCodeFormatStringID</key>
|
||||
<string>oXAiOU</string>
|
||||
<key>INIntentResponseCodeName</key>
|
||||
<string>failure</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>INIntentResponseLastParameterTag</key>
|
||||
<integer>3</integer>
|
||||
<key>INIntentResponseParameters</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>INIntentResponseParameterDisplayName</key>
|
||||
<string>Localized Description</string>
|
||||
<key>INIntentResponseParameterDisplayNameID</key>
|
||||
<string>wdy22v</string>
|
||||
<key>INIntentResponseParameterDisplayPriority</key>
|
||||
<integer>1</integer>
|
||||
<key>INIntentResponseParameterName</key>
|
||||
<string>localizedDescription</string>
|
||||
<key>INIntentResponseParameterTag</key>
|
||||
<integer>3</integer>
|
||||
<key>INIntentResponseParameterType</key>
|
||||
<string>String</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<key>INIntentTitle</key>
|
||||
<string>Refresh All Apps</string>
|
||||
<key>INIntentTitleID</key>
|
||||
<string>2b6Xto</string>
|
||||
<key>INIntentType</key>
|
||||
<string>Custom</string>
|
||||
<key>INIntentVerb</key>
|
||||
<string>Do</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>INTypes</key>
|
||||
<array/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -499,6 +499,17 @@ extension AppManager
|
||||
}
|
||||
}
|
||||
|
||||
extension AppManager
|
||||
{
|
||||
func backgroundRefresh(_ installedApps: [InstalledApp], presentsNotifications: Bool = true, completionHandler: @escaping (Result<[String: Result<InstalledApp, Error>], Error>) -> Void)
|
||||
{
|
||||
let backgroundRefreshAppsOperation = BackgroundRefreshAppsOperation(installedApps: installedApps)
|
||||
backgroundRefreshAppsOperation.resultHandler = completionHandler
|
||||
backgroundRefreshAppsOperation.presentsFinishedNotification = presentsNotifications
|
||||
self.run([backgroundRefreshAppsOperation], context: nil)
|
||||
}
|
||||
}
|
||||
|
||||
private extension AppManager
|
||||
{
|
||||
enum AppOperation
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
import MobileCoreServices
|
||||
import Intents
|
||||
|
||||
import AltStoreCore
|
||||
import AltSign
|
||||
@@ -653,6 +654,15 @@ private extension MyAppsViewController
|
||||
self.collectionView.reloadSections([Section.activeApps.rawValue, Section.inactiveApps.rawValue])
|
||||
}
|
||||
}
|
||||
|
||||
if #available(iOS 14, *)
|
||||
{
|
||||
let interaction = INInteraction.refreshAllApps()
|
||||
interaction.donate { (error) in
|
||||
guard let error = error else { return }
|
||||
print("Failed to donate intent \(interaction.intent).", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func updateApp(_ sender: UIButton)
|
||||
|
||||
272
AltStore/Operations/BackgroundRefreshAppsOperation.swift
Normal file
272
AltStore/Operations/BackgroundRefreshAppsOperation.swift
Normal file
@@ -0,0 +1,272 @@
|
||||
//
|
||||
// BackgroundRefreshAppsOperation.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 7/6/20.
|
||||
// Copyright © 2020 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import CoreData
|
||||
|
||||
import AltStoreCore
|
||||
|
||||
enum RefreshError: LocalizedError
|
||||
{
|
||||
case noInstalledApps
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self
|
||||
{
|
||||
case .noInstalledApps: return NSLocalizedString("No active apps require refreshing.", comment: "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension CFNotificationName
|
||||
{
|
||||
static let requestAppState = CFNotificationName("com.altstore.RequestAppState" as CFString)
|
||||
static let appIsRunning = CFNotificationName("com.altstore.AppState.Running" as CFString)
|
||||
|
||||
static func requestAppState(for appID: String) -> CFNotificationName
|
||||
{
|
||||
let name = String(CFNotificationName.requestAppState.rawValue) + "." + appID
|
||||
return CFNotificationName(name as CFString)
|
||||
}
|
||||
|
||||
static func appIsRunning(for appID: String) -> CFNotificationName
|
||||
{
|
||||
let name = String(CFNotificationName.appIsRunning.rawValue) + "." + appID
|
||||
return CFNotificationName(name as CFString)
|
||||
}
|
||||
}
|
||||
|
||||
private let ReceivedApplicationState: @convention(c) (CFNotificationCenter?, UnsafeMutableRawPointer?, CFNotificationName?, UnsafeRawPointer?, CFDictionary?) -> Void =
|
||||
{ (center, observer, name, object, userInfo) in
|
||||
guard let name = name, let observer = observer else { return }
|
||||
|
||||
let operation = unsafeBitCast(observer, to: BackgroundRefreshAppsOperation.self)
|
||||
operation.receivedApplicationState(notification: name)
|
||||
}
|
||||
|
||||
@objc(BackgroundRefreshAppsOperation)
|
||||
class BackgroundRefreshAppsOperation: ResultOperation<[String: Result<InstalledApp, Error>]>
|
||||
{
|
||||
let installedApps: [InstalledApp]
|
||||
private let managedObjectContext: NSManagedObjectContext
|
||||
|
||||
var presentsFinishedNotification: Bool = true
|
||||
|
||||
private let refreshIdentifier: String = UUID().uuidString
|
||||
private var runningApplications: Set<String> = []
|
||||
|
||||
init(installedApps: [InstalledApp])
|
||||
{
|
||||
self.installedApps = installedApps
|
||||
self.managedObjectContext = installedApps.compactMap({ $0.managedObjectContext }).first ?? DatabaseManager.shared.persistentContainer.newBackgroundContext()
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
||||
override func finish(_ result: Result<[String: Result<InstalledApp, Error>], Error>)
|
||||
{
|
||||
super.finish(result)
|
||||
|
||||
self.scheduleFinishedRefreshingNotification(for: result, delay: 0)
|
||||
|
||||
self.managedObjectContext.perform {
|
||||
self.stopListeningForRunningApps()
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
if UIApplication.shared.applicationState == .background
|
||||
{
|
||||
ServerManager.shared.stopDiscovering()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func main()
|
||||
{
|
||||
super.main()
|
||||
|
||||
guard !self.installedApps.isEmpty else {
|
||||
self.finish(.failure(RefreshError.noInstalledApps))
|
||||
return
|
||||
}
|
||||
|
||||
if !ServerManager.shared.isDiscovering
|
||||
{
|
||||
ServerManager.shared.startDiscovering()
|
||||
}
|
||||
|
||||
self.managedObjectContext.perform {
|
||||
print("Apps to refresh:", self.installedApps.map(\.bundleIdentifier))
|
||||
|
||||
self.startListeningForRunningApps()
|
||||
|
||||
// Wait for three seconds to:
|
||||
// a) give us time to discover AltServers
|
||||
// b) give other processes a chance to respond to requestAppState notification
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
|
||||
self.managedObjectContext.perform {
|
||||
guard !ServerManager.shared.discoveredServers.isEmpty else { return self.finish(.failure(ConnectionError.serverNotFound)) }
|
||||
|
||||
let filteredApps = self.installedApps.filter { !self.runningApplications.contains($0.bundleIdentifier) }
|
||||
print("Filtered Apps to Refresh:", filteredApps.map { $0.bundleIdentifier })
|
||||
|
||||
let group = AppManager.shared.refresh(filteredApps, presentingViewController: nil)
|
||||
group.beginInstallationHandler = { (installedApp) in
|
||||
guard installedApp.bundleIdentifier == StoreApp.altstoreAppID else { return }
|
||||
|
||||
// We're starting to install AltStore, which means the app is about to quit.
|
||||
// So, we schedule a "refresh successful" local notification to be displayed after a delay,
|
||||
// but if the app is still running, we cancel the notification.
|
||||
// Then, we schedule another notification and repeat the process.
|
||||
|
||||
// Also since AltServer has already received the app, it can finish installing even if we're no longer running in background.
|
||||
|
||||
if let error = group.context.error
|
||||
{
|
||||
self.scheduleFinishedRefreshingNotification(for: .failure(error))
|
||||
}
|
||||
else
|
||||
{
|
||||
var results = group.results
|
||||
results[installedApp.bundleIdentifier] = .success(installedApp)
|
||||
|
||||
self.scheduleFinishedRefreshingNotification(for: .success(results))
|
||||
}
|
||||
}
|
||||
group.completionHandler = { (results) in
|
||||
self.finish(.success(results))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension BackgroundRefreshAppsOperation
|
||||
{
|
||||
func startListeningForRunningApps()
|
||||
{
|
||||
let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
|
||||
let observer = Unmanaged.passUnretained(self).toOpaque()
|
||||
|
||||
for installedApp in self.installedApps
|
||||
{
|
||||
let appIsRunningNotification = CFNotificationName.appIsRunning(for: installedApp.bundleIdentifier)
|
||||
CFNotificationCenterAddObserver(notificationCenter, observer, ReceivedApplicationState, appIsRunningNotification.rawValue, nil, .deliverImmediately)
|
||||
|
||||
let requestAppStateNotification = CFNotificationName.requestAppState(for: installedApp.bundleIdentifier)
|
||||
CFNotificationCenterPostNotification(notificationCenter, requestAppStateNotification, nil, nil, true)
|
||||
}
|
||||
}
|
||||
|
||||
func stopListeningForRunningApps()
|
||||
{
|
||||
let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
|
||||
let observer = Unmanaged.passUnretained(self).toOpaque()
|
||||
|
||||
for installedApp in self.installedApps
|
||||
{
|
||||
let appIsRunningNotification = CFNotificationName.appIsRunning(for: installedApp.bundleIdentifier)
|
||||
CFNotificationCenterRemoveObserver(notificationCenter, observer, appIsRunningNotification, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func receivedApplicationState(notification: CFNotificationName)
|
||||
{
|
||||
let baseName = String(CFNotificationName.appIsRunning.rawValue)
|
||||
|
||||
let appID = String(notification.rawValue).replacingOccurrences(of: baseName + ".", with: "")
|
||||
self.runningApplications.insert(appID)
|
||||
}
|
||||
|
||||
func scheduleFinishedRefreshingNotification(for result: Result<[String: Result<InstalledApp, Error>], Error>, delay: TimeInterval = 5)
|
||||
{
|
||||
func scheduleFinishedRefreshingNotification()
|
||||
{
|
||||
self.cancelFinishedRefreshingNotification()
|
||||
|
||||
let content = UNMutableNotificationContent()
|
||||
|
||||
var shouldPresentAlert = true
|
||||
|
||||
do
|
||||
{
|
||||
let results = try result.get()
|
||||
shouldPresentAlert = !results.isEmpty
|
||||
|
||||
for (_, result) in results
|
||||
{
|
||||
guard case let .failure(error) = result else { continue }
|
||||
throw error
|
||||
}
|
||||
|
||||
content.title = NSLocalizedString("Refreshed Apps", comment: "")
|
||||
content.body = NSLocalizedString("All apps have been refreshed.", comment: "")
|
||||
}
|
||||
catch ConnectionError.serverNotFound
|
||||
{
|
||||
shouldPresentAlert = false
|
||||
}
|
||||
catch RefreshError.noInstalledApps
|
||||
{
|
||||
shouldPresentAlert = false
|
||||
}
|
||||
catch
|
||||
{
|
||||
print("Failed to refresh apps in background.", error)
|
||||
|
||||
content.title = NSLocalizedString("Failed to Refresh Apps", comment: "")
|
||||
content.body = error.localizedDescription
|
||||
|
||||
shouldPresentAlert = true
|
||||
}
|
||||
|
||||
if shouldPresentAlert
|
||||
{
|
||||
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: delay + 1, repeats: false)
|
||||
|
||||
let request = UNNotificationRequest(identifier: self.refreshIdentifier, content: content, trigger: trigger)
|
||||
UNUserNotificationCenter.current().add(request)
|
||||
|
||||
if delay > 0
|
||||
{
|
||||
DispatchQueue.global().asyncAfter(deadline: .now() + delay) {
|
||||
UNUserNotificationCenter.current().getPendingNotificationRequests() { (requests) in
|
||||
// If app is still running at this point, we schedule another notification with same identifier.
|
||||
// This prevents the currently scheduled notification from displaying, and starts another countdown timer.
|
||||
// First though, make sure there _is_ still a pending request, otherwise it's been cancelled
|
||||
// and we should stop polling.
|
||||
guard requests.contains(where: { $0.identifier == self.refreshIdentifier }) else { return }
|
||||
|
||||
scheduleFinishedRefreshingNotification()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.presentsFinishedNotification
|
||||
{
|
||||
scheduleFinishedRefreshingNotification()
|
||||
}
|
||||
|
||||
// Perform synchronously to ensure app doesn't quit before we've finishing saving to disk.
|
||||
let context = DatabaseManager.shared.persistentContainer.newBackgroundContext()
|
||||
context.performAndWait {
|
||||
_ = RefreshAttempt(identifier: self.refreshIdentifier, result: result, context: context)
|
||||
|
||||
do { try context.save() }
|
||||
catch { print("Failed to save refresh attempt.", error) }
|
||||
}
|
||||
}
|
||||
|
||||
func cancelFinishedRefreshingNotification()
|
||||
{
|
||||
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [self.refreshIdentifier])
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user