2019-05-20 21:26:01 +02:00
//
// I n s t a l l e d A p p . s w i f t
// A l t S t o r e
//
// C r e a t e d b y R i l e y T e s t u t o n 5 / 2 0 / 1 9 .
// C o p y r i g h t © 2 0 1 9 R i l e y T e s t u t . A l l r i g h t s r e s e r v e d .
//
import Foundation
import CoreData
2019-07-28 15:08:13 -07:00
import AltSign
2023-01-19 07:52:47 -08:00
import SemanticVersion
2019-07-28 15:08:13 -07:00
2020-04-01 13:05:31 -07:00
// F r e e d e v e l o p e r a c c o u n t s a r e l i m i t e d t o o n l y 3 a c t i v e s i d e l o a d e d a p p s a t a t i m e a s o f i O S 1 3 . 3 . 1 .
2020-09-03 16:39:08 -07:00
public let ALTActiveAppsLimit = 3
2020-04-01 13:05:31 -07:00
2020-09-03 16:39:08 -07:00
public protocol InstalledAppProtocol : Fetchable
2020-01-21 16:53:34 -08:00
{
var name : String { get }
var bundleIdentifier : String { get }
var resignedBundleIdentifier : String { get }
var version : String { get }
var refreshedDate : Date { get }
var expirationDate : Date { get }
var installedDate : Date { get }
}
2019-05-20 21:26:01 +02:00
@objc ( InstalledApp )
2020-09-03 16:39:08 -07:00
public class InstalledApp : NSManagedObject , InstalledAppProtocol
2019-05-20 21:26:01 +02:00
{
/* P r o p e r t i e s */
2020-09-03 16:39:08 -07:00
@NSManaged public var name : String
@NSManaged public var bundleIdentifier : String
@NSManaged public var resignedBundleIdentifier : String
@NSManaged public var version : String
2019-05-20 21:26:01 +02:00
2020-09-03 16:39:08 -07:00
@NSManaged public var refreshedDate : Date
@NSManaged public var expirationDate : Date
@NSManaged public var installedDate : Date
2019-05-20 21:26:01 +02:00
2020-09-03 16:39:08 -07:00
@NSManaged public var isActive : Bool
2020-10-01 11:51:39 -07:00
@NSManaged public var needsResign : Bool
2020-10-01 14:09:45 -07:00
@NSManaged public var hasAlternateIcon : Bool
2020-03-11 14:43:19 -07:00
2020-09-03 16:39:08 -07:00
@NSManaged public var certificateSerialNumber : String ?
2020-03-06 17:08:35 -08:00
2020-09-08 13:12:40 -07:00
/* T r a n s i e n t */
@NSManaged public var isRefreshing : Bool
2019-05-20 21:26:01 +02:00
/* R e l a t i o n s h i p s */
2020-09-03 16:39:08 -07:00
@NSManaged public var storeApp : StoreApp ?
@NSManaged public var team : Team ?
@NSManaged public var appExtensions : Set < InstalledExtension >
2019-05-20 21:26:01 +02:00
2022-09-08 16:14:28 -05:00
@NSManaged public private ( set ) var loggedErrors : NSSet /* S e t < L o g g e d E r r o r > */ // U s e N S S e t t o a v o i d e a g e r l y f e t c h i n g v a l u e s .
2020-09-03 16:39:08 -07:00
public var isSideloaded : Bool {
2019-07-28 15:51:36 -07:00
return self . storeApp = = nil
}
2023-01-19 07:52:47 -08:00
@objc public var hasUpdate : Bool {
if self . storeApp = = nil { return false }
if self . storeApp ! . latestVersion = = nil { return false }
#if DEBUG
print ( " Comparing versions for app ` \( self . bundleIdentifier ) ` between currentVersion ` \( self . version ) ` and latestVersion ` \( self . storeApp ! . latestVersion ! . version ) ` " )
#endif
let currentVersion = SemanticVersion ( self . version )
let latestVersion = SemanticVersion ( self . storeApp ! . latestVersion ! . version )
if currentVersion = = nil || latestVersion = = nil {
#if DEBUG
print ( " One of the versions is not valid SemVer, using fallback method " )
#endif
// T h i s s h o u l d c o m p a r e e a c h c h a r a c t e r
return self . version < self . storeApp ! . latestVersion ! . version
}
#if DEBUG
print ( " Both versions are valid SemVer, using SemVer comparison " )
#endif
return currentVersion ! < latestVersion !
}
2020-09-03 16:39:08 -07:00
public var appIDCount : Int {
2020-03-20 16:32:31 -07:00
return 1 + self . appExtensions . count
}
2020-09-03 16:39:08 -07:00
public var requiredActiveSlots : Int {
2020-05-17 23:36:30 -07:00
let requiredActiveSlots = UserDefaults . standard . activeAppLimitIncludesExtensions ? self . appIDCount : 1
return requiredActiveSlots
}
2019-05-20 21:26:01 +02:00
private override init ( entity : NSEntityDescription , insertInto context : NSManagedObjectContext ? )
{
super . init ( entity : entity , insertInto : context )
}
2020-09-03 16:39:08 -07:00
public init ( resignedApp : ALTApplication , originalBundleIdentifier : String , certificateSerialNumber : String ? , context : NSManagedObjectContext )
2019-05-20 21:26:01 +02:00
{
super . init ( entity : InstalledApp . entity ( ) , insertInto : context )
2019-07-28 15:08:13 -07:00
self . bundleIdentifier = originalBundleIdentifier
2019-06-21 11:20:03 -07:00
2022-12-03 17:25:15 -05:00
print ( " InstalledApp `self.bundleIdentifier`: \( self . bundleIdentifier ) " )
2020-01-24 15:03:16 -08:00
self . refreshedDate = Date ( )
self . installedDate = Date ( )
self . expirationDate = self . refreshedDate . addingTimeInterval ( 60 * 60 * 24 * 7 ) // R o u g h e s t i m a t e u n t i l w e g e t r e a l v a l u e s f r o m p r o v i s i o n i n g p r o f i l e .
2020-03-06 17:08:35 -08:00
self . update ( resignedApp : resignedApp , certificateSerialNumber : certificateSerialNumber )
2020-01-24 15:03:16 -08:00
}
2020-09-03 16:39:08 -07:00
public func update ( resignedApp : ALTApplication , certificateSerialNumber : String ? )
2020-01-24 15:03:16 -08:00
{
self . name = resignedApp . name
self . resignedBundleIdentifier = resignedApp . bundleIdentifier
2019-07-28 15:08:13 -07:00
self . version = resignedApp . version
2020-03-06 17:08:35 -08:00
self . certificateSerialNumber = certificateSerialNumber
2019-07-28 15:08:13 -07:00
if let provisioningProfile = resignedApp . provisioningProfile
{
2020-03-06 17:08:35 -08:00
self . update ( provisioningProfile : provisioningProfile )
2019-07-28 15:08:13 -07:00
}
2019-05-20 21:26:01 +02:00
}
2020-03-06 17:08:35 -08:00
2020-09-03 16:39:08 -07:00
public func update ( provisioningProfile : ALTProvisioningProfile )
2020-03-06 17:08:35 -08:00
{
self . refreshedDate = provisioningProfile . creationDate
self . expirationDate = provisioningProfile . expirationDate
}
2020-10-01 14:09:45 -07:00
public func loadIcon ( completion : @ escaping ( Result < UIImage ? , Error > ) -> Void )
{
let hasAlternateIcon = self . hasAlternateIcon
let alternateIconURL = self . alternateIconURL
let fileURL = self . fileURL
DispatchQueue . global ( ) . async {
do
{
if hasAlternateIcon ,
case let data = try Data ( contentsOf : alternateIconURL ) ,
let icon = UIImage ( data : data )
{
return completion ( . success ( icon ) )
}
let application = ALTApplication ( fileURL : fileURL )
completion ( . success ( application ? . icon ) )
}
catch
{
completion ( . failure ( error ) )
}
}
}
2019-05-20 21:26:01 +02:00
}
2020-09-03 16:39:08 -07:00
public extension InstalledApp
2019-05-20 21:26:01 +02:00
{
@ nonobjc class func fetchRequest ( ) -> NSFetchRequest < InstalledApp >
{
return NSFetchRequest < InstalledApp > ( entityName : " InstalledApp " )
}
2019-06-21 11:20:03 -07:00
2019-07-24 13:52:58 -07:00
class func updatesFetchRequest ( ) -> NSFetchRequest < InstalledApp >
{
let fetchRequest = InstalledApp . fetchRequest ( ) as NSFetchRequest < InstalledApp >
2023-01-19 07:52:47 -08:00
fetchRequest . predicate = NSPredicate ( format : " %K == YES AND %K == YES " ,
# keyPath ( InstalledApp . isActive ) , # keyPath ( InstalledApp . hasUpdate ) )
2020-03-11 14:43:19 -07:00
return fetchRequest
}
class func activeAppsFetchRequest ( ) -> NSFetchRequest < InstalledApp >
{
let fetchRequest = InstalledApp . fetchRequest ( ) as NSFetchRequest < InstalledApp >
fetchRequest . predicate = NSPredicate ( format : " %K == YES " , # keyPath ( InstalledApp . isActive ) )
2022-12-03 17:25:15 -05:00
print ( " Active Apps Fetch Request: \( String ( describing : fetchRequest . predicate ) ) " )
2019-07-24 13:52:58 -07:00
return fetchRequest
}
2019-06-21 11:20:03 -07:00
class func fetchAltStore ( in context : NSManagedObjectContext ) -> InstalledApp ?
{
2019-07-31 14:07:00 -07:00
let predicate = NSPredicate ( format : " %K == %@ " , # keyPath ( InstalledApp . bundleIdentifier ) , StoreApp . altstoreAppID )
2022-12-03 17:25:15 -05:00
print ( " Fetch 'AltStore' Predicate: \( String ( describing : predicate ) ) " )
2019-06-21 11:20:03 -07:00
let altStore = InstalledApp . first ( satisfying : predicate , in : context )
return altStore
}
2020-03-11 14:43:19 -07:00
class func fetchActiveApps ( in context : NSManagedObjectContext ) -> [ InstalledApp ]
{
let activeApps = InstalledApp . fetch ( InstalledApp . activeAppsFetchRequest ( ) , in : context )
return activeApps
}
2019-06-21 11:20:03 -07:00
class func fetchAppsForRefreshingAll ( in context : NSManagedObjectContext ) -> [ InstalledApp ]
{
2020-03-11 14:43:19 -07:00
var predicate = NSPredicate ( format : " %K == YES AND %K != %@ " , # keyPath ( InstalledApp . isActive ) , # keyPath ( InstalledApp . bundleIdentifier ) , StoreApp . altstoreAppID )
2022-12-03 17:25:15 -05:00
print ( " Fetch Apps for Refreshing All 'AltStore' predicate: \( String ( describing : predicate ) ) " )
2019-08-28 11:13:22 -07:00
2022-12-11 15:41:43 -06:00
// i f l e t p a t r e o n A c c o u n t = D a t a b a s e M a n a g e r . s h a r e d . p a t r e o n A c c o u n t ( i n : c o n t e x t ) , p a t r e o n A c c o u n t . i s P a t r o n , P a t r e o n A P I . s h a r e d . i s A u t h e n t i c a t e d
// {
// / / N o a d d i t i o n a l p r e d i c a t e
// }
// e l s e
// {
// p r e d i c a t e = N S C o m p o u n d P r e d i c a t e ( a n d P r e d i c a t e W i t h S u b p r e d i c a t e s : [ p r e d i c a t e ,
// N S P r e d i c a t e ( f o r m a t : " % K = = n i l O R % K = = N O " , # k e y P a t h ( I n s t a l l e d A p p . s t o r e A p p ) , # k e y P a t h ( I n s t a l l e d A p p . s t o r e A p p . i s B e t a ) ) ] )
// }
2019-06-21 11:20:03 -07:00
var installedApps = InstalledApp . all ( satisfying : predicate ,
sortedBy : [ NSSortDescriptor ( keyPath : \ InstalledApp . expirationDate , ascending : true ) ] ,
in : context )
if let altStoreApp = InstalledApp . fetchAltStore ( in : context )
{
// R e f r e s h A l t S t o r e l a s t s i n c e i t c a u s e s a p p t o q u i t .
installedApps . append ( altStoreApp )
}
return installedApps
}
class func fetchAppsForBackgroundRefresh ( in context : NSManagedObjectContext ) -> [ InstalledApp ]
{
2019-08-28 11:07:49 -07:00
// D a t e 6 h o u r s b e f o r e n o w .
let date = Date ( ) . addingTimeInterval ( - 1 * 6 * 60 * 60 )
2019-06-21 11:20:03 -07:00
2020-03-11 14:43:19 -07:00
var predicate = NSPredicate ( format : " (%K == YES) AND (%K < %@) AND (%K != %@) " ,
# keyPath ( InstalledApp . isActive ) ,
2019-06-21 11:20:03 -07:00
# keyPath ( InstalledApp . refreshedDate ) , date as NSDate ,
2019-07-31 14:07:00 -07:00
# keyPath ( InstalledApp . bundleIdentifier ) , StoreApp . altstoreAppID )
2022-12-03 17:25:15 -05:00
print ( " Active Apps For Background Refresh 'AltStore' predicate: \( String ( describing : predicate ) ) " )
2019-06-21 11:20:03 -07:00
2022-12-11 15:41:43 -06:00
// i f l e t p a t r e o n A c c o u n t = D a t a b a s e M a n a g e r . s h a r e d . p a t r e o n A c c o u n t ( i n : c o n t e x t ) , p a t r e o n A c c o u n t . i s P a t r o n , P a t r e o n A P I . s h a r e d . i s A u t h e n t i c a t e d
// {
// / / N o a d d i t i o n a l p r e d i c a t e
// }
// e l s e
// {
// p r e d i c a t e = N S C o m p o u n d P r e d i c a t e ( a n d P r e d i c a t e W i t h S u b p r e d i c a t e s : [ p r e d i c a t e ,
// N S P r e d i c a t e ( f o r m a t : " % K = = n i l O R % K = = N O " , # k e y P a t h ( I n s t a l l e d A p p . s t o r e A p p ) , # k e y P a t h ( I n s t a l l e d A p p . s t o r e A p p . i s B e t a ) ) ] )
// }
2019-08-28 11:13:22 -07:00
2019-06-21 11:20:03 -07:00
var installedApps = InstalledApp . all ( satisfying : predicate ,
sortedBy : [ NSSortDescriptor ( keyPath : \ InstalledApp . expirationDate , ascending : true ) ] ,
in : context )
if let altStoreApp = InstalledApp . fetchAltStore ( in : context ) , altStoreApp . refreshedDate < date
{
2020-03-06 17:08:35 -08:00
// R e f r e s h A l t S t o r e l a s t s i n c e i t m a y c a u s e a p p t o q u i t .
2019-06-21 11:20:03 -07:00
installedApps . append ( altStoreApp )
}
return installedApps
}
2019-05-20 21:26:01 +02:00
}
2019-06-04 13:53:21 -07:00
2020-09-03 16:39:08 -07:00
public extension InstalledApp
2019-06-04 13:53:21 -07:00
{
var openAppURL : URL {
2019-07-28 15:08:13 -07:00
let openAppURL = URL ( string : " altstore- " + self . bundleIdentifier + " :// " ) !
return openAppURL
}
class func openAppURL ( for app : AppProtocol ) -> URL
{
let openAppURL = URL ( string : " altstore- " + app . bundleIdentifier + " :// " ) !
2019-06-04 13:53:21 -07:00
return openAppURL
}
}
2020-09-03 16:39:08 -07:00
public extension InstalledApp
2019-06-04 13:53:21 -07:00
{
class var appsDirectoryURL : URL {
2020-09-14 14:31:46 -07:00
let baseDirectory = FileManager . default . altstoreSharedDirectory ? ? FileManager . default . applicationSupportDirectory
let appsDirectoryURL = baseDirectory . appendingPathComponent ( " Apps " )
2019-06-04 13:53:21 -07:00
do { try FileManager . default . createDirectory ( at : appsDirectoryURL , withIntermediateDirectories : true , attributes : nil ) }
2022-12-03 17:25:15 -05:00
catch { print ( " Creating App Directory Error: \( error ) " ) }
print ( " `appsDirectoryURL` is set to: \( appsDirectoryURL . absoluteString ) " )
2019-06-04 13:53:21 -07:00
return appsDirectoryURL
}
2020-09-16 12:09:12 -07:00
class var legacyAppsDirectoryURL : URL {
let baseDirectory = FileManager . default . applicationSupportDirectory
let appsDirectoryURL = baseDirectory . appendingPathComponent ( " Apps " )
2022-12-03 17:25:15 -05:00
print ( " legacy `appsDirectoryURL` is set to: \( appsDirectoryURL . absoluteString ) " )
2020-09-16 12:09:12 -07:00
return appsDirectoryURL
}
2019-07-28 15:08:13 -07:00
class func fileURL ( for app : AppProtocol ) -> URL
2019-06-04 13:53:21 -07:00
{
2019-06-21 11:20:03 -07:00
let appURL = self . directoryURL ( for : app ) . appendingPathComponent ( " App.app " )
return appURL
2019-06-04 13:53:21 -07:00
}
2019-07-28 15:08:13 -07:00
class func refreshedIPAURL ( for app : AppProtocol ) -> URL
2019-06-10 15:03:47 -07:00
{
let ipaURL = self . directoryURL ( for : app ) . appendingPathComponent ( " Refreshed.ipa " )
2022-12-03 17:25:15 -05:00
print ( " `ipaURL`: \( ipaURL . absoluteString ) " )
2019-06-10 15:03:47 -07:00
return ipaURL
}
2019-07-28 15:08:13 -07:00
class func directoryURL ( for app : AppProtocol ) -> URL
2019-06-04 13:53:21 -07:00
{
2019-07-28 15:08:13 -07:00
let directoryURL = InstalledApp . appsDirectoryURL . appendingPathComponent ( app . bundleIdentifier )
2019-06-04 13:53:21 -07:00
do { try FileManager . default . createDirectory ( at : directoryURL , withIntermediateDirectories : true , attributes : nil ) }
catch { print ( error ) }
return directoryURL
}
2019-12-17 19:17:45 -08:00
class func installedAppUTI ( forBundleIdentifier bundleIdentifier : String ) -> String
{
let installedAppUTI = " io.altstore.Installed. " + bundleIdentifier
return installedAppUTI
}
2020-05-16 16:17:18 -07:00
class func installedBackupAppUTI ( forBundleIdentifier bundleIdentifier : String ) -> String
{
let installedBackupAppUTI = InstalledApp . installedAppUTI ( forBundleIdentifier : bundleIdentifier ) + " .backup "
return installedBackupAppUTI
}
2020-10-01 14:09:45 -07:00
class func alternateIconURL ( for app : AppProtocol ) -> URL
{
let installedBackupAppUTI = self . directoryURL ( for : app ) . appendingPathComponent ( " AltIcon.png " )
return installedBackupAppUTI
}
2019-06-04 13:53:21 -07:00
var directoryURL : URL {
2019-07-28 15:08:13 -07:00
return InstalledApp . directoryURL ( for : self )
2019-06-04 13:53:21 -07:00
}
2019-06-21 11:20:03 -07:00
var fileURL : URL {
2019-07-28 15:08:13 -07:00
return InstalledApp . fileURL ( for : self )
2019-06-04 13:53:21 -07:00
}
2019-06-10 15:03:47 -07:00
var refreshedIPAURL : URL {
2019-07-28 15:08:13 -07:00
return InstalledApp . refreshedIPAURL ( for : self )
2019-06-10 15:03:47 -07:00
}
2019-12-17 19:17:45 -08:00
var installedAppUTI : String {
return InstalledApp . installedAppUTI ( forBundleIdentifier : self . resignedBundleIdentifier )
}
2020-05-16 16:17:18 -07:00
var installedBackupAppUTI : String {
return InstalledApp . installedBackupAppUTI ( forBundleIdentifier : self . resignedBundleIdentifier )
}
2020-10-01 14:09:45 -07:00
var alternateIconURL : URL {
return InstalledApp . alternateIconURL ( for : self )
}
2019-06-04 13:53:21 -07:00
}