mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-20 04:03:26 +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 */; };
|
BF0241AA22F29CCD00129732 /* UserDefaults+AltServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0241A922F29CCD00129732 /* UserDefaults+AltServer.swift */; };
|
||||||
BF08858322DE795100DE9F1E /* MyAppsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF08858222DE795100DE9F1E /* MyAppsViewController.swift */; };
|
BF08858322DE795100DE9F1E /* MyAppsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF08858222DE795100DE9F1E /* MyAppsViewController.swift */; };
|
||||||
BF08858522DE7EC800DE9F1E /* UpdateCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF08858422DE7EC800DE9F1E /* UpdateCollectionViewCell.swift */; };
|
BF08858522DE7EC800DE9F1E /* UpdateCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF08858422DE7EC800DE9F1E /* UpdateCollectionViewCell.swift */; };
|
||||||
BF088D0F25019ABA008082D9 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; productRef = BF088D0E25019ABA008082D9 /* SwiftPackageProductDependency */; };
|
BF088D0F25019ABA008082D9 /* AltSign-Static in Frameworks */ = {isa = PBXBuildFile; productRef = BF088D0E25019ABA008082D9 /* AltSign-Static */; };
|
||||||
BF088D2D2501A18E008082D9 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; productRef = BF088D2C2501A18E008082D9 /* SwiftPackageProductDependency */; };
|
BF088D2D2501A18E008082D9 /* AltSign-Dynamic in Frameworks */ = {isa = PBXBuildFile; productRef = BF088D2C2501A18E008082D9 /* AltSign-Dynamic */; };
|
||||||
BF088D2E2501A18E008082D9 /* BuildFile in Embed Frameworks */ = {isa = PBXBuildFile; productRef = BF088D2C2501A18E008082D9 /* SwiftPackageProductDependency */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
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 */; };
|
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, ); }; };
|
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 */; };
|
BF088D362501A821008082D9 /* AltSign-Dynamic in Frameworks */ = {isa = PBXBuildFile; productRef = BF088D352501A821008082D9 /* AltSign-Dynamic */; };
|
||||||
BF088D372501A821008082D9 /* BuildFile in Embed Frameworks */ = {isa = PBXBuildFile; productRef = BF088D352501A821008082D9 /* SwiftPackageProductDependency */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
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 */; };
|
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, ); }; };
|
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 */; };
|
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 */; };
|
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 */; };
|
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 */; };
|
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 */; };
|
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 */; };
|
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 */; };
|
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 */; };
|
BFDB6A0D22AAFC1A007EA6D6 /* OperationError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB6A0C22AAFC19007EA6D6 /* OperationError.swift */; };
|
||||||
BFDB6A0F22AB2776007EA6D6 /* SendAppOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB6A0E22AB2776007EA6D6 /* SendAppOperation.swift */; };
|
BFDB6A0F22AB2776007EA6D6 /* SendAppOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB6A0E22AB2776007EA6D6 /* SendAppOperation.swift */; };
|
||||||
BFDBBD80246CB84F004ED2F3 /* RemoveAppBackupOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDBBD7F246CB84F004ED2F3 /* RemoveAppBackupOperation.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 */; };
|
BFE338DF22F0EADB002E24B9 /* FetchSourceOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE338DE22F0EADB002E24B9 /* FetchSourceOperation.swift */; };
|
||||||
BFE338E822F10E56002E24B9 /* LaunchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE338E722F10E56002E24B9 /* LaunchViewController.swift */; };
|
BFE338E822F10E56002E24B9 /* LaunchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE338E722F10E56002E24B9 /* LaunchViewController.swift */; };
|
||||||
BFE48975238007CE003239E0 /* AnisetteDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE48974238007CE003239E0 /* AnisetteDataManager.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 */; };
|
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 */; };
|
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 */; };
|
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 */; };
|
BFF0B68E23219520007A79E1 /* PatreonViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF0B68D23219520007A79E1 /* PatreonViewController.swift */; };
|
||||||
BFF0B69023219C6D007A79E1 /* PatreonComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF0B68F23219C6D007A79E1 /* PatreonComponents.swift */; };
|
BFF0B69023219C6D007A79E1 /* PatreonComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF0B68F23219C6D007A79E1 /* PatreonComponents.swift */; };
|
||||||
BFF0B6922321A305007A79E1 /* AboutPatreonHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BFF0B6912321A305007A79E1 /* AboutPatreonHeaderView.xib */; };
|
BFF0B6922321A305007A79E1 /* AboutPatreonHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BFF0B6912321A305007A79E1 /* AboutPatreonHeaderView.xib */; };
|
||||||
@@ -350,7 +354,7 @@
|
|||||||
files = (
|
files = (
|
||||||
BF088D392501A833008082D9 /* OpenSSL.xcframework in Embed Frameworks */,
|
BF088D392501A833008082D9 /* OpenSSL.xcframework in Embed Frameworks */,
|
||||||
BF44CC6D232AEB90004DA9C3 /* LaunchAtLogin.framework in Embed Frameworks */,
|
BF44CC6D232AEB90004DA9C3 /* LaunchAtLogin.framework in Embed Frameworks */,
|
||||||
BF088D372501A821008082D9 /* BuildFile in Embed Frameworks */,
|
BF088D372501A821008082D9 /* AltSign-Dynamic in Embed Frameworks */,
|
||||||
);
|
);
|
||||||
name = "Embed Frameworks";
|
name = "Embed Frameworks";
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@@ -362,7 +366,7 @@
|
|||||||
dstSubfolderSpec = 10;
|
dstSubfolderSpec = 10;
|
||||||
files = (
|
files = (
|
||||||
BF088D342501A4FF008082D9 /* OpenSSL.xcframework in Embed Frameworks */,
|
BF088D342501A4FF008082D9 /* OpenSSL.xcframework in Embed Frameworks */,
|
||||||
BF088D2E2501A18E008082D9 /* BuildFile in Embed Frameworks */,
|
BF088D2E2501A18E008082D9 /* AltSign-Dynamic in Embed Frameworks */,
|
||||||
BF66EE862501AE50007EE018 /* AltStoreCore.framework in Embed Frameworks */,
|
BF66EE862501AE50007EE018 /* AltStoreCore.framework in Embed Frameworks */,
|
||||||
);
|
);
|
||||||
name = "Embed Frameworks";
|
name = "Embed Frameworks";
|
||||||
@@ -675,6 +679,7 @@
|
|||||||
BFDB6A0C22AAFC19007EA6D6 /* OperationError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationError.swift; sourceTree = "<group>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
BFF0B6912321A305007A79E1 /* AboutPatreonHeaderView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AboutPatreonHeaderView.xib; sourceTree = "<group>"; };
|
||||||
@@ -715,7 +723,7 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
EFB988A976C401E5710498B7 /* libPods-AltDaemon.a in Frameworks */,
|
EFB988A976C401E5710498B7 /* libPods-AltDaemon.a in Frameworks */,
|
||||||
BF088D0F25019ABA008082D9 /* BuildFile in Frameworks */,
|
BF088D0F25019ABA008082D9 /* AltSign-Static in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -727,7 +735,7 @@
|
|||||||
BF4588882298DD3F00BD7491 /* libxml2.tbd in Frameworks */,
|
BF4588882298DD3F00BD7491 /* libxml2.tbd in Frameworks */,
|
||||||
BF44CC6C232AEB90004DA9C3 /* LaunchAtLogin.framework in Frameworks */,
|
BF44CC6C232AEB90004DA9C3 /* LaunchAtLogin.framework in Frameworks */,
|
||||||
BF4588472298D4B000BD7491 /* libimobiledevice.a in Frameworks */,
|
BF4588472298D4B000BD7491 /* libimobiledevice.a in Frameworks */,
|
||||||
BF088D362501A821008082D9 /* BuildFile in Frameworks */,
|
BF088D362501A821008082D9 /* AltSign-Dynamic in Frameworks */,
|
||||||
A8BCEBEAC0620CF80A2FD26D /* Pods_AltServer.framework in Frameworks */,
|
A8BCEBEAC0620CF80A2FD26D /* Pods_AltServer.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@@ -751,7 +759,7 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
BF66EEF12501AF9D007EE018 /* BuildFile in Frameworks */,
|
BF66EEF12501AF9D007EE018 /* AltSign-Dynamic in Frameworks */,
|
||||||
0E33F94B8D78AB969FD309A3 /* Pods_AltStoreCore.framework in Frameworks */,
|
0E33F94B8D78AB969FD309A3 /* Pods_AltStoreCore.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@@ -762,7 +770,7 @@
|
|||||||
files = (
|
files = (
|
||||||
BF088D332501A4FF008082D9 /* OpenSSL.xcframework in Frameworks */,
|
BF088D332501A4FF008082D9 /* OpenSSL.xcframework in Frameworks */,
|
||||||
BF66EE852501AE50007EE018 /* AltStoreCore.framework in Frameworks */,
|
BF66EE852501AE50007EE018 /* AltStoreCore.framework in Frameworks */,
|
||||||
BF088D2D2501A18E008082D9 /* BuildFile in Frameworks */,
|
BF088D2D2501A18E008082D9 /* AltSign-Dynamic in Frameworks */,
|
||||||
2A77E3D272F3D92436FAC272 /* Pods_AltStore.framework in Frameworks */,
|
2A77E3D272F3D92436FAC272 /* Pods_AltStore.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@@ -1315,6 +1323,7 @@
|
|||||||
BFC84A4B2421A13000853474 /* Sources */,
|
BFC84A4B2421A13000853474 /* Sources */,
|
||||||
BFC51D7922972F1F00388324 /* Server */,
|
BFC51D7922972F1F00388324 /* Server */,
|
||||||
BF0DCA642433BDE200E3A595 /* Analytics */,
|
BF0DCA642433BDE200E3A595 /* Analytics */,
|
||||||
|
BFF00D2E2501BD4B00746320 /* Intents */,
|
||||||
BFDB6A0922AAEDA1007EA6D6 /* Operations */,
|
BFDB6A0922AAEDA1007EA6D6 /* Operations */,
|
||||||
BFD2478D2284C4C700981D42 /* Components */,
|
BFD2478D2284C4C700981D42 /* Components */,
|
||||||
BF3D648922E79A7700E9056B /* Types */,
|
BF3D648922E79A7700E9056B /* Types */,
|
||||||
@@ -1407,6 +1416,7 @@
|
|||||||
BF663C4E2433ED8200DAA738 /* FileManager+DirectorySize.swift */,
|
BF663C4E2433ED8200DAA738 /* FileManager+DirectorySize.swift */,
|
||||||
BF6A531F246DC1B0004F59C8 /* FileManager+SharedDirectories.swift */,
|
BF6A531F246DC1B0004F59C8 /* FileManager+SharedDirectories.swift */,
|
||||||
BF8CAE4D248AEABA004D6CCE /* UIDevice+Jailbreak.swift */,
|
BF8CAE4D248AEABA004D6CCE /* UIDevice+Jailbreak.swift */,
|
||||||
|
BFE00A1F2503097F00EB4D0C /* INInteraction+AltStore.swift */,
|
||||||
);
|
);
|
||||||
path = Extensions;
|
path = Extensions;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1467,6 +1477,7 @@
|
|||||||
BF44EEFB246B4550002A52F2 /* RemoveAppOperation.swift */,
|
BF44EEFB246B4550002A52F2 /* RemoveAppOperation.swift */,
|
||||||
BF3432FA246B894F0052F4A1 /* BackupAppOperation.swift */,
|
BF3432FA246B894F0052F4A1 /* BackupAppOperation.swift */,
|
||||||
BFDBBD7F246CB84F004ED2F3 /* RemoveAppBackupOperation.swift */,
|
BFDBBD7F246CB84F004ED2F3 /* RemoveAppBackupOperation.swift */,
|
||||||
|
BFF00D312501BDA100746320 /* BackgroundRefreshAppsOperation.swift */,
|
||||||
);
|
);
|
||||||
path = Operations;
|
path = Operations;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1482,6 +1493,15 @@
|
|||||||
path = Authentication;
|
path = Authentication;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
BFF00D2E2501BD4B00746320 /* Intents */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
BFF00D2F2501BD7D00746320 /* Intents.intentdefinition */,
|
||||||
|
BFF00D332501BDCF00746320 /* IntentHandler.swift */,
|
||||||
|
);
|
||||||
|
path = Intents;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
BFF767C32489A6800097E58C /* Extensions */ = {
|
BFF767C32489A6800097E58C /* Extensions */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -1595,7 +1615,7 @@
|
|||||||
);
|
);
|
||||||
name = AltDaemon;
|
name = AltDaemon;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
BF088D0E25019ABA008082D9 /* SwiftPackageProductDependency */,
|
BF088D0E25019ABA008082D9 /* AltSign-Static */,
|
||||||
);
|
);
|
||||||
productName = AltDaemon;
|
productName = AltDaemon;
|
||||||
productReference = BF18BFE724857D7900DD5981 /* AltDaemon */;
|
productReference = BF18BFE724857D7900DD5981 /* AltDaemon */;
|
||||||
@@ -1622,7 +1642,7 @@
|
|||||||
);
|
);
|
||||||
name = AltServer;
|
name = AltServer;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
BF088D352501A821008082D9 /* SwiftPackageProductDependency */,
|
BF088D352501A821008082D9 /* AltSign-Dynamic */,
|
||||||
);
|
);
|
||||||
productName = AltServer;
|
productName = AltServer;
|
||||||
productReference = BF45868D229872EA00BD7491 /* AltServer.app */;
|
productReference = BF45868D229872EA00BD7491 /* AltServer.app */;
|
||||||
@@ -1696,7 +1716,7 @@
|
|||||||
);
|
);
|
||||||
name = AltStoreCore;
|
name = AltStoreCore;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
BF66EEF02501AF9D007EE018 /* SwiftPackageProductDependency */,
|
BF66EEF02501AF9D007EE018 /* AltSign-Dynamic */,
|
||||||
);
|
);
|
||||||
productName = AltStoreCore;
|
productName = AltStoreCore;
|
||||||
productReference = BF66EE7E2501AE50007EE018 /* AltStoreCore.framework */;
|
productReference = BF66EE7E2501AE50007EE018 /* AltStoreCore.framework */;
|
||||||
@@ -1720,7 +1740,7 @@
|
|||||||
);
|
);
|
||||||
name = AltStore;
|
name = AltStore;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
BF088D2C2501A18E008082D9 /* SwiftPackageProductDependency */,
|
BF088D2C2501A18E008082D9 /* AltSign-Dynamic */,
|
||||||
);
|
);
|
||||||
productName = AltStore;
|
productName = AltStore;
|
||||||
productReference = BFD2476A2284B9A500981D42 /* AltStore.app */;
|
productReference = BFD2476A2284B9A500981D42 /* AltStore.app */;
|
||||||
@@ -2254,9 +2274,11 @@
|
|||||||
BFD2478C2284C4C300981D42 /* AppIconImageView.swift in Sources */,
|
BFD2478C2284C4C300981D42 /* AppIconImageView.swift in Sources */,
|
||||||
BF8F69C422E662D300049BA1 /* AppViewController.swift in Sources */,
|
BF8F69C422E662D300049BA1 /* AppViewController.swift in Sources */,
|
||||||
BFF0B68E23219520007A79E1 /* PatreonViewController.swift in Sources */,
|
BFF0B68E23219520007A79E1 /* PatreonViewController.swift in Sources */,
|
||||||
|
BFF00D302501BD7D00746320 /* Intents.intentdefinition in Sources */,
|
||||||
BF6C336224197D700034FD24 /* NSError+AltStore.swift in Sources */,
|
BF6C336224197D700034FD24 /* NSError+AltStore.swift in Sources */,
|
||||||
BFD2476E2284B9A500981D42 /* AppDelegate.swift in Sources */,
|
BFD2476E2284B9A500981D42 /* AppDelegate.swift in Sources */,
|
||||||
BF41B806233423AE00C593A3 /* TabBarController.swift in Sources */,
|
BF41B806233423AE00C593A3 /* TabBarController.swift in Sources */,
|
||||||
|
BFE00A202503097F00EB4D0C /* INInteraction+AltStore.swift in Sources */,
|
||||||
BFDB6A0B22AAEDB7007EA6D6 /* Operation.swift in Sources */,
|
BFDB6A0B22AAEDB7007EA6D6 /* Operation.swift in Sources */,
|
||||||
BF770E6722BD57C4002A40FE /* BackgroundTaskManager.swift in Sources */,
|
BF770E6722BD57C4002A40FE /* BackgroundTaskManager.swift in Sources */,
|
||||||
BF44EEFC246B4550002A52F2 /* RemoveAppOperation.swift in Sources */,
|
BF44EEFC246B4550002A52F2 /* RemoveAppOperation.swift in Sources */,
|
||||||
@@ -2286,8 +2308,10 @@
|
|||||||
BF770E5122BB1CF6002A40FE /* InstallAppOperation.swift in Sources */,
|
BF770E5122BB1CF6002A40FE /* InstallAppOperation.swift in Sources */,
|
||||||
BF9ABA4B22DD1380008935CF /* NavigationBar.swift in Sources */,
|
BF9ABA4B22DD1380008935CF /* NavigationBar.swift in Sources */,
|
||||||
BF6C8FAC242935ED00125131 /* NSAttributedString+Markdown.m in Sources */,
|
BF6C8FAC242935ED00125131 /* NSAttributedString+Markdown.m in Sources */,
|
||||||
|
BFF00D322501BDA100746320 /* BackgroundRefreshAppsOperation.swift in Sources */,
|
||||||
BF0C4EBD22A1BD8B009A2DD7 /* AppManager.swift in Sources */,
|
BF0C4EBD22A1BD8B009A2DD7 /* AppManager.swift in Sources */,
|
||||||
BF2901312318F7A800D88A45 /* AppBannerView.swift in Sources */,
|
BF2901312318F7A800D88A45 /* AppBannerView.swift in Sources */,
|
||||||
|
BFF00D342501BDCF00746320 /* IntentHandler.swift in Sources */,
|
||||||
BFDBBD80246CB84F004ED2F3 /* RemoveAppBackupOperation.swift in Sources */,
|
BFDBBD80246CB84F004ED2F3 /* RemoveAppBackupOperation.swift in Sources */,
|
||||||
BFF0B6942321CB85007A79E1 /* AuthenticationViewController.swift in Sources */,
|
BFF0B6942321CB85007A79E1 /* AuthenticationViewController.swift in Sources */,
|
||||||
BF3432FB246B894F0052F4A1 /* BackupAppOperation.swift in Sources */,
|
BF3432FB246B894F0052F4A1 /* BackupAppOperation.swift in Sources */,
|
||||||
@@ -3058,19 +3082,19 @@
|
|||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
/* Begin XCSwiftPackageProductDependency section */
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
BF088D0E25019ABA008082D9 /* SwiftPackageProductDependency */ = {
|
BF088D0E25019ABA008082D9 /* AltSign-Static */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
productName = "AltSign-Static";
|
productName = "AltSign-Static";
|
||||||
};
|
};
|
||||||
BF088D2C2501A18E008082D9 /* SwiftPackageProductDependency */ = {
|
BF088D2C2501A18E008082D9 /* AltSign-Dynamic */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
productName = "AltSign-Dynamic";
|
productName = "AltSign-Dynamic";
|
||||||
};
|
};
|
||||||
BF088D352501A821008082D9 /* SwiftPackageProductDependency */ = {
|
BF088D352501A821008082D9 /* AltSign-Dynamic */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
productName = "AltSign-Dynamic";
|
productName = "AltSign-Dynamic";
|
||||||
};
|
};
|
||||||
BF66EEF02501AF9D007EE018 /* SwiftPackageProductDependency */ = {
|
BF66EEF02501AF9D007EE018 /* AltSign-Dynamic */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
productName = "AltSign-Dynamic";
|
productName = "AltSign-Dynamic";
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
<dict>
|
<dict>
|
||||||
<key>aps-environment</key>
|
<key>aps-environment</key>
|
||||||
<string>development</string>
|
<string>development</string>
|
||||||
|
<key>com.apple.developer.siri</key>
|
||||||
|
<true/>
|
||||||
<key>com.apple.security.application-groups</key>
|
<key>com.apple.security.application-groups</key>
|
||||||
<array>
|
<array>
|
||||||
<string>group.com.rileytestut.AltStore</string>
|
<string>group.com.rileytestut.AltStore</string>
|
||||||
|
|||||||
@@ -9,47 +9,12 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
import UserNotifications
|
import UserNotifications
|
||||||
import AVFoundation
|
import AVFoundation
|
||||||
|
import Intents
|
||||||
|
|
||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
import AltSign
|
import AltSign
|
||||||
import Roxas
|
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
|
extension AppDelegate
|
||||||
{
|
{
|
||||||
static let openPatreonSettingsDeepLinkNotification = Notification.Name("com.rileytestut.AltStore.OpenPatreonSettingsDeepLinkNotification")
|
static let openPatreonSettingsDeepLinkNotification = Notification.Name("com.rileytestut.AltStore.OpenPatreonSettingsDeepLinkNotification")
|
||||||
@@ -68,8 +33,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||||||
|
|
||||||
var window: UIWindow?
|
var window: UIWindow?
|
||||||
|
|
||||||
private var runningApplications: Set<String>?
|
private lazy var intentHandler = IntentHandler()
|
||||||
private var backgroundRefreshContext: NSManagedObjectContext? // Keep context alive until finished refreshing.
|
|
||||||
|
|
||||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
|
||||||
{
|
{
|
||||||
@@ -128,6 +92,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||||||
{
|
{
|
||||||
return self.open(url)
|
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, *)
|
@available(iOS 13, *)
|
||||||
@@ -263,11 +233,7 @@ extension AppDelegate
|
|||||||
|
|
||||||
func application(_ application: UIApplication, performFetchWithCompletionHandler backgroundFetchCompletionHandler: @escaping (UIBackgroundFetchResult) -> Void)
|
func application(_ application: UIApplication, performFetchWithCompletionHandler backgroundFetchCompletionHandler: @escaping (UIBackgroundFetchResult) -> Void)
|
||||||
{
|
{
|
||||||
if UserDefaults.standard.isBackgroundRefreshEnabled
|
if UserDefaults.standard.isBackgroundRefreshEnabled && !UserDefaults.standard.presentedLaunchReminderNotification
|
||||||
{
|
|
||||||
ServerManager.shared.startDiscovering()
|
|
||||||
|
|
||||||
if !UserDefaults.standard.presentedLaunchReminderNotification
|
|
||||||
{
|
{
|
||||||
let threeHours: TimeInterval = 3 * 60 * 60
|
let threeHours: TimeInterval = 3 * 60 * 60
|
||||||
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: threeHours, repeats: false)
|
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: threeHours, repeats: false)
|
||||||
@@ -281,75 +247,78 @@ extension AppDelegate
|
|||||||
|
|
||||||
UserDefaults.standard.presentedLaunchReminderNotification = true
|
UserDefaults.standard.presentedLaunchReminderNotification = true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let refreshIdentifier = UUID().uuidString
|
|
||||||
|
|
||||||
BackgroundTaskManager.shared.performExtendedBackgroundTask { (taskResult, taskCompletionHandler) in
|
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
|
if let error = taskResult.error
|
||||||
{
|
{
|
||||||
print("Error starting extended background task. Aborting.", error)
|
print("Error starting extended background task. Aborting.", error)
|
||||||
backgroundFetchCompletionHandler(.failed)
|
backgroundFetchCompletionHandler(.failed)
|
||||||
finish(.failure(error))
|
taskCompletionHandler()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !DatabaseManager.shared.isStarted
|
if !DatabaseManager.shared.isStarted
|
||||||
{
|
{
|
||||||
DatabaseManager.shared.start() { (error) in
|
DatabaseManager.shared.start() { (error) in
|
||||||
if let error = error
|
if error != nil
|
||||||
{
|
{
|
||||||
backgroundFetchCompletionHandler(.failed)
|
backgroundFetchCompletionHandler(.failed)
|
||||||
finish(.failure(error))
|
taskCompletionHandler()
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
self.refreshApps(identifier: refreshIdentifier, backgroundFetchCompletionHandler: backgroundFetchCompletionHandler, completionHandler: finish(_:))
|
self.performBackgroundFetch { (backgroundFetchResult) in
|
||||||
|
backgroundFetchCompletionHandler(backgroundFetchResult)
|
||||||
|
} refreshAppsCompletionHandler: { (refreshAppsResult) in
|
||||||
|
taskCompletionHandler()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
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
|
private extension AppDelegate
|
||||||
{
|
{
|
||||||
func refreshApps(identifier: String,
|
func fetchSources(completionHandler: @escaping (Result<Set<Source>, Error>) -> Void)
|
||||||
backgroundFetchCompletionHandler: @escaping (UIBackgroundFetchResult) -> Void,
|
|
||||||
completionHandler: @escaping (Result<[String: Result<InstalledApp, Error>], Error>) -> Void)
|
|
||||||
{
|
{
|
||||||
var fetchSourcesResult: Result<Set<Source>, Error>?
|
|
||||||
var serversResult: Result<Void, Error>?
|
|
||||||
|
|
||||||
let dispatchGroup = DispatchGroup()
|
|
||||||
dispatchGroup.enter()
|
|
||||||
|
|
||||||
AppManager.shared.fetchSources() { (result) in
|
AppManager.shared.fetchSources() { (result) in
|
||||||
fetchSourcesResult = result.map { $0.0 }.mapError { $0 as Error }
|
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
let (_, context) = try result.get()
|
let (sources, context) = try result.get()
|
||||||
|
|
||||||
let previousUpdatesFetchRequest = InstalledApp.updatesFetchRequest() as! NSFetchRequest<NSFetchRequestResult>
|
let previousUpdatesFetchRequest = InstalledApp.updatesFetchRequest() as! NSFetchRequest<NSFetchRequestResult>
|
||||||
previousUpdatesFetchRequest.includesPendingChanges = false
|
previousUpdatesFetchRequest.includesPendingChanges = false
|
||||||
@@ -412,223 +381,14 @@ private extension AppDelegate
|
|||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
UIApplication.shared.applicationIconBadgeNumber = updates.count
|
UIApplication.shared.applicationIconBadgeNumber = updates.count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
completionHandler(.success(sources))
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
print("Error fetching apps:", error)
|
print("Error fetching apps:", error)
|
||||||
|
completionHandler(.failure(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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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>
|
</array>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
|
<key>INIntentsSupported</key>
|
||||||
|
<array>
|
||||||
|
<string>RefreshAllIntent</string>
|
||||||
|
</array>
|
||||||
<key>LSApplicationQueriesSchemes</key>
|
<key>LSApplicationQueriesSchemes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>altstore-com.rileytestut.AltStore</string>
|
<string>altstore-com.rileytestut.AltStore</string>
|
||||||
@@ -85,6 +89,10 @@
|
|||||||
</array>
|
</array>
|
||||||
<key>NSLocalNetworkUsageDescription</key>
|
<key>NSLocalNetworkUsageDescription</key>
|
||||||
<string>AltStore uses the local network to find and communicate with AltServer.</string>
|
<string>AltStore uses the local network to find and communicate with AltServer.</string>
|
||||||
|
<key>NSUserActivityTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>RefreshAllIntent</string>
|
||||||
|
</array>
|
||||||
<key>UIApplicationSceneManifest</key>
|
<key>UIApplicationSceneManifest</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>UIApplicationSupportsMultipleScenes</key>
|
<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
|
private extension AppManager
|
||||||
{
|
{
|
||||||
enum AppOperation
|
enum AppOperation
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import MobileCoreServices
|
import MobileCoreServices
|
||||||
|
import Intents
|
||||||
|
|
||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
import AltSign
|
import AltSign
|
||||||
@@ -653,6 +654,15 @@ private extension MyAppsViewController
|
|||||||
self.collectionView.reloadSections([Section.activeApps.rawValue, Section.inactiveApps.rawValue])
|
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)
|
@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