2019-05-09 15:29:54 -07:00
//
2019-07-31 14:07:00 -07:00
// S t o r e A p p . s w i f t
2019-05-09 15:29:54 -07:00
// A l t S t o r e
//
2019-05-20 21:24:53 +02:00
// 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 .
2019-05-09 15:29:54 -07:00
// 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
2019-05-20 21:24:53 +02:00
import CoreData
2019-05-09 15:29:54 -07:00
2019-05-30 17:10:50 -07:00
import Roxas
2019-07-28 15:08:13 -07:00
import AltSign
2019-05-30 17:10:50 -07:00
2020-09-03 16:39:08 -07:00
public extension StoreApp
2019-06-17 16:31:10 -07:00
{
2020-04-01 11:51:00 -07:00
#if ALPHA
2022-12-30 16:51:36 -05:00
static let altstoreAppID = Bundle . Info . appbundleIdentifier
2020-04-01 11:51:00 -07:00
#elseif BETA
2022-12-30 16:51:36 -05:00
static let altstoreAppID = Bundle . Info . appbundleIdentifier
2019-09-12 13:04:15 -07:00
#else
2022-12-30 16:51:36 -05:00
static let altstoreAppID = Bundle . Info . appbundleIdentifier
2019-09-12 13:04:15 -07:00
#endif
2020-05-08 11:45:23 -07:00
static let dolphinAppID = " me.oatmealdome.dolphinios-njb "
2019-06-17 16:31:10 -07:00
}
2021-09-15 20:55:41 -04:00
@objc
2023-01-04 09:31:28 -05:00
public enum Platform : UInt , Codable {
2021-09-15 20:55:41 -04:00
case ios
case tvos
case macos
}
@objc
public final class PlatformURL : NSManagedObject , Decodable {
/* P r o p e r t i e s */
@NSManaged public private ( set ) var platform : Platform
@NSManaged public private ( set ) var downloadURL : URL
private enum CodingKeys : String , CodingKey
{
case platform
case downloadURL
}
public init ( from decoder : Decoder ) throws
{
guard let context = decoder . managedObjectContext else { preconditionFailure ( " Decoder must have non-nil NSManagedObjectContext. " ) }
// M u s t i n i t i a l i z e w i t h c o n t e x t i n o r d e r f o r c h i l d c o n t e x t s a v e s t o w o r k c o r r e c t l y .
super . init ( entity : PlatformURL . entity ( ) , insertInto : context )
do
{
let container = try decoder . container ( keyedBy : CodingKeys . self )
self . platform = try container . decode ( Platform . self , forKey : . platform )
self . downloadURL = try container . decode ( URL . self , forKey : . downloadURL )
}
catch
{
if let context = self . managedObjectContext
{
context . delete ( self )
}
throw error
}
}
}
extension PlatformURL : Comparable {
public static func < ( lhs : PlatformURL , rhs : PlatformURL ) -> Bool {
return lhs . platform . rawValue < rhs . platform . rawValue
}
public static func > ( lhs : PlatformURL , rhs : PlatformURL ) -> Bool {
return lhs . platform . rawValue > rhs . platform . rawValue
}
public static func <= ( lhs : PlatformURL , rhs : PlatformURL ) -> Bool {
return lhs . platform . rawValue <= rhs . platform . rawValue
}
public static func >= ( lhs : PlatformURL , rhs : PlatformURL ) -> Bool {
return lhs . platform . rawValue >= rhs . platform . rawValue
}
}
public typealias PlatformURLs = [ PlatformURL ]
2019-07-31 14:07:00 -07:00
@objc ( StoreApp )
2020-09-03 16:39:08 -07:00
public class StoreApp : NSManagedObject , Decodable , Fetchable
2019-05-09 15:29:54 -07:00
{
2019-05-20 21:24:53 +02:00
/* P r o p e r t i e s */
2020-09-03 16:39:08 -07:00
@NSManaged public private ( set ) var name : String
@NSManaged public private ( set ) var bundleIdentifier : String
@NSManaged public private ( set ) var subtitle : String ?
2019-05-09 17:21:55 -07:00
2020-09-03 16:39:08 -07:00
@NSManaged public private ( set ) var developerName : String
@NSManaged public private ( set ) var localizedDescription : String
2022-09-12 17:44:21 -07:00
@NSManaged @objc ( size ) internal var _size : Int32
2019-05-09 15:29:54 -07:00
2020-09-03 16:39:08 -07:00
@NSManaged public private ( set ) var iconURL : URL
@NSManaged public private ( set ) var screenshotURLs : [ URL ]
2019-05-20 21:24:53 +02:00
2022-09-12 17:44:21 -07:00
@NSManaged @objc ( version ) internal var _version : String
@NSManaged @objc ( versionDate ) internal var _versionDate : Date
@NSManaged @objc ( versionDescription ) internal var _versionDescription : String ?
2019-05-20 22:24:16 +02:00
2022-09-12 17:44:21 -07:00
@NSManaged @objc ( downloadURL ) internal var _downloadURL : URL
2021-09-15 20:55:41 -04:00
@NSManaged public private ( set ) var platformURLs : PlatformURLs ?
2020-09-03 16:39:08 -07:00
@NSManaged public private ( set ) var tintColor : UIColor ?
@NSManaged public private ( set ) var isBeta : Bool
2019-05-31 18:24:08 -07:00
2022-09-12 17:05:55 -07:00
@objc public internal ( set ) var sourceIdentifier : String ? {
get {
self . willAccessValue ( forKey : # keyPath ( sourceIdentifier ) )
defer { self . didAccessValue ( forKey : # keyPath ( sourceIdentifier ) ) }
let sourceIdentifier = self . primitiveSourceIdentifier
return sourceIdentifier
}
set {
self . willChangeValue ( forKey : # keyPath ( sourceIdentifier ) )
self . primitiveSourceIdentifier = newValue
self . didChangeValue ( forKey : # keyPath ( sourceIdentifier ) )
for version in self . versions
{
version . sourceID = newValue
}
}
}
@NSManaged private var primitiveSourceIdentifier : String ?
2020-03-24 13:27:44 -07:00
2020-09-03 16:39:08 -07:00
@NSManaged public var sortIndex : Int32
2019-07-30 17:00:04 -07:00
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 installedApp : InstalledApp ?
@NSManaged public var newsItems : Set < NewsItem >
2020-03-24 13:27:44 -07:00
2020-09-03 16:39:08 -07:00
@NSManaged @objc ( source ) public var _source : Source ?
@NSManaged @objc ( permissions ) public var _permissions : NSOrderedSet
2020-03-24 13:27:44 -07:00
2022-09-12 15:42:33 -07:00
@NSManaged public private ( set ) var latestVersion : AppVersion ?
@NSManaged @objc ( versions ) public private ( set ) var _versions : NSOrderedSet
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
@ nonobjc public var source : Source ? {
2020-03-24 13:27:44 -07:00
set {
self . _source = newValue
self . sourceIdentifier = newValue ? . identifier
}
get {
return self . _source
}
}
2019-07-24 12:23:54 -07:00
2020-09-03 16:39:08 -07:00
@ nonobjc public var permissions : [ AppPermission ] {
2019-07-24 12:23:54 -07:00
return self . _permissions . array as ! [ AppPermission ]
}
2019-05-20 21:26:01 +02:00
2022-09-12 15:42:33 -07:00
@ nonobjc public var versions : [ AppVersion ] {
return self . _versions . array as ! [ AppVersion ]
}
2022-09-12 17:05:55 -07:00
@ nonobjc public var size : Int64 ? {
guard let version = self . latestVersion else { return nil }
return version . size
}
@ nonobjc public var version : String ? {
guard let version = self . latestVersion else { return nil }
return version . version
}
@ nonobjc public var versionDescription : String ? {
guard let version = self . latestVersion else { return nil }
return version . localizedDescription
}
@ nonobjc public var versionDate : Date ? {
guard let version = self . latestVersion else { return nil }
return version . date
}
@ nonobjc public var downloadURL : URL ? {
guard let version = self . latestVersion else { return nil }
return version . downloadURL
}
2019-05-20 21:24:53 +02:00
private override init ( entity : NSEntityDescription , insertInto context : NSManagedObjectContext ? )
{
super . init ( entity : entity , insertInto : context )
}
private enum CodingKeys : String , CodingKey
{
case name
2019-07-28 15:08:13 -07:00
case bundleIdentifier
2019-05-20 21:24:53 +02:00
case developerName
case localizedDescription
2019-05-20 22:24:16 +02:00
case version
case versionDescription
case versionDate
2019-08-20 19:06:03 -05:00
case iconURL
case screenshotURLs
2019-05-31 18:24:08 -07:00
case downloadURL
2021-09-15 20:55:41 -04:00
case platformURLs
2019-07-16 14:25:09 -07:00
case tintColor
case subtitle
2019-07-24 12:23:54 -07:00
case permissions
2019-07-29 16:02:15 -07:00
case size
2019-08-28 11:13:22 -07:00
case isBeta = " beta "
2022-09-12 17:05:55 -07:00
case versions
2019-05-20 21:24:53 +02:00
}
2020-09-03 16:39:08 -07:00
public required init ( from decoder : Decoder ) throws
2019-05-20 21:24:53 +02:00
{
guard let context = decoder . managedObjectContext else { preconditionFailure ( " Decoder must have non-nil NSManagedObjectContext. " ) }
2020-08-27 16:23:50 -07:00
// M u s t i n i t i a l i z e w i t h c o n t e x t i n o r d e r f o r c h i l d c o n t e x t s a v e s t o w o r k c o r r e c t l y .
super . init ( entity : StoreApp . entity ( ) , insertInto : context )
2019-05-20 21:24:53 +02:00
2020-08-27 16:23:50 -07:00
do
2019-07-16 14:25:09 -07:00
{
2020-08-27 16:23:50 -07:00
let container = try decoder . container ( keyedBy : CodingKeys . self )
self . name = try container . decode ( String . self , forKey : . name )
self . bundleIdentifier = try container . decode ( String . self , forKey : . bundleIdentifier )
self . developerName = try container . decode ( String . self , forKey : . developerName )
self . localizedDescription = try container . decode ( String . self , forKey : . localizedDescription )
self . subtitle = try container . decodeIfPresent ( String . self , forKey : . subtitle )
self . iconURL = try container . decode ( URL . self , forKey : . iconURL )
self . screenshotURLs = try container . decodeIfPresent ( [ URL ] . self , forKey : . screenshotURLs ) ? ? [ ]
2021-09-15 20:55:41 -04:00
let downloadURL = try container . decodeIfPresent ( URL . self , forKey : . downloadURL )
let platformURLs = try container . decodeIfPresent ( PlatformURLs . self . self , forKey : . platformURLs )
if let platformURLs = platformURLs {
self . platformURLs = platformURLs
// B a c k w a r d s c o m p a t i b i l i t y , u s e t h e f i i r s t ( i O S w i l l b e f i r s t s i n c e s o r t e d t h a t w a y )
if let first = platformURLs . sorted ( ) . first {
2022-09-12 17:05:55 -07:00
self . _downloadURL = first . downloadURL
2021-09-15 20:55:41 -04:00
} else {
throw DecodingError . dataCorruptedError ( forKey : . platformURLs , in : container , debugDescription : " platformURLs has no entries " )
}
} else if let downloadURL = downloadURL {
2022-09-12 17:05:55 -07:00
self . _downloadURL = downloadURL
2021-09-15 20:55:41 -04:00
} else {
throw DecodingError . dataCorruptedError ( forKey : . downloadURL , in : container , debugDescription : " E downloadURL:String or downloadURLs:[[Platform:URL]] key required. " )
}
2020-08-27 16:23:50 -07:00
if let tintColorHex = try container . decodeIfPresent ( String . self , forKey : . tintColor )
{
guard let tintColor = UIColor ( hexString : tintColorHex ) else {
throw DecodingError . dataCorruptedError ( forKey : . tintColor , in : container , debugDescription : " Hex code is invalid. " )
}
self . tintColor = tintColor
2019-07-16 14:25:09 -07:00
}
2020-08-27 16:23:50 -07:00
self . isBeta = try container . decodeIfPresent ( Bool . self , forKey : . isBeta ) ? ? false
let permissions = try container . decodeIfPresent ( [ AppPermission ] . self , forKey : . permissions ) ? ? [ ]
self . _permissions = NSOrderedSet ( array : permissions )
2022-09-12 17:05:55 -07:00
if let versions = try container . decodeIfPresent ( [ AppVersion ] . self , forKey : . versions )
{
// TODO: T h r o w e r r o r i f t h e r e i s n ' t a t l e a s t o n e v e r s i o n .
for version in versions
{
version . appBundleID = self . bundleIdentifier
}
self . setVersions ( versions )
}
else
{
let version = try container . decode ( String . self , forKey : . version )
let versionDate = try container . decode ( Date . self , forKey : . versionDate )
let versionDescription = try container . decodeIfPresent ( String . self , forKey : . versionDescription )
let downloadURL = try container . decode ( URL . self , forKey : . downloadURL )
let size = try container . decode ( Int32 . self , forKey : . size )
let appVersion = AppVersion . makeAppVersion ( version : version ,
date : versionDate ,
localizedDescription : versionDescription ,
downloadURL : downloadURL ,
size : Int64 ( size ) ,
appBundleID : self . bundleIdentifier ,
in : context )
self . setVersions ( [ appVersion ] )
}
2020-08-27 16:23:50 -07:00
}
catch
{
if let context = self . managedObjectContext
{
context . delete ( self )
}
throw error
2019-07-16 14:25:09 -07:00
}
2019-05-20 21:24:53 +02:00
}
}
2022-09-12 17:05:55 -07:00
private extension StoreApp
{
func setVersions ( _ versions : [ AppVersion ] )
{
guard let latestVersion = versions . first else { preconditionFailure ( " StoreApp must have at least one AppVersion. " ) }
self . latestVersion = latestVersion
self . _versions = NSOrderedSet ( array : versions )
// P r e s e r v e b a c k w a r d s c o m p a t i b i l i t y b y a s s i g n i n g l e g a c y p r o p e r t y v a l u e s .
self . _version = latestVersion . version
self . _versionDate = latestVersion . date
self . _versionDescription = latestVersion . localizedDescription
self . _downloadURL = latestVersion . downloadURL
self . _size = Int32 ( latestVersion . size )
}
}
2020-09-03 16:39:08 -07:00
public extension StoreApp
2019-05-20 21:24:53 +02:00
{
2019-07-31 14:07:00 -07:00
@ nonobjc class func fetchRequest ( ) -> NSFetchRequest < StoreApp >
2019-05-20 21:24:53 +02:00
{
2019-07-31 14:07:00 -07:00
return NSFetchRequest < StoreApp > ( entityName : " StoreApp " )
2019-05-20 21:24:53 +02:00
}
2019-06-17 16:31:10 -07:00
2019-07-31 14:07:00 -07:00
class func makeAltStoreApp ( in context : NSManagedObjectContext ) -> StoreApp
2019-06-17 16:31:10 -07:00
{
2019-07-31 14:07:00 -07:00
let app = StoreApp ( context : context )
2022-11-05 23:50:07 -07:00
app . name = " SideStore "
2023-02-02 07:31:46 -08:00
let currentAppVersion = Bundle . main . infoDictionary ? [ " CFBundleShortVersionString " ] as ? String
if currentAppVersion != nil {
if currentAppVersion ! . contains ( " beta " ) {
app . name += " (Beta) "
}
if currentAppVersion ! . contains ( " nightly " ) {
app . name += " (Nightly) "
}
}
2019-07-31 14:07:00 -07:00
app . bundleIdentifier = StoreApp . altstoreAppID
2023-02-02 07:31:46 -08:00
app . developerName = " SideStore Team "
app . localizedDescription = " SideStore is an alternative app store for non-jailbroken devices. \n \n SideStore allows you to sideload other .ipa files and apps from the Files app or via the SideStore Library. "
app . iconURL = URL ( string : " https://sidestore.io/assets/icon.png " ) !
2019-08-20 19:06:03 -05:00
app . screenshotURLs = [ ]
2022-09-12 17:05:55 -07:00
app . sourceIdentifier = Source . altStoreIdentifier
2023-02-02 07:31:46 -08:00
let appVersion = AppVersion . makeAppVersion ( version : " 0.0.0 " , // t h i s i s s e t t o t h e c u r r e n t a p p v e r s i o n l a t e r
2022-09-12 17:05:55 -07:00
date : Date ( ) ,
2023-02-02 07:31:46 -08:00
downloadURL : URL ( string : " https://sidestore.io " ) ! ,
2022-09-12 17:05:55 -07:00
size : 0 ,
appBundleID : app . bundleIdentifier ,
sourceID : Source . altStoreIdentifier ,
in : context )
app . setVersions ( [ appVersion ] )
2019-06-17 16:31:10 -07:00
2022-12-03 17:25:15 -05:00
print ( " makeAltStoreApp StoreApp: \( String ( describing : app ) ) " )
2019-06-17 16:31:10 -07:00
return app
}
2019-05-09 15:29:54 -07:00
}