mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-09 06:43:25 +01:00
Supports new “versions” key in source JSON
Allows sources to list multiple versions of an app. Preserves backwards compatibility by assigning legacy version values when assigning AppVersions.
This commit is contained in:
committed by
Joseph Mattello
parent
5765cb8330
commit
fa160124d2
@@ -47,7 +47,6 @@ public class AppVersion: NSManagedObject, Decodable, Fetchable
|
||||
super.init(entity: entity, insertInto: context)
|
||||
}
|
||||
|
||||
|
||||
private enum CodingKeys: String, CodingKey
|
||||
{
|
||||
case version
|
||||
@@ -55,31 +54,34 @@ public class AppVersion: NSManagedObject, Decodable, Fetchable
|
||||
case localizedDescription
|
||||
case downloadURL
|
||||
case size
|
||||
case _minOSVersion
|
||||
case _maxOSVersion
|
||||
case appBundleID
|
||||
case sourceID
|
||||
}
|
||||
|
||||
public required init(from decoder: Decoder) throws
|
||||
{
|
||||
guard let context = decoder.managedObjectContext else { preconditionFailure("Decoder must have non-nil NSManagedObjectContext.") }
|
||||
|
||||
super.init(entity: NewsItem.entity(), insertInto: context)
|
||||
super.init(entity: AppVersion.entity(), insertInto: context)
|
||||
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.version = try container.decode(String.self, forKey: .version)
|
||||
self.date = try container.decode(Date.self, forKey: .date)
|
||||
|
||||
self.localizedDescription = try container.decodeIfPresent(String.self, forKey: .localizedDescription)
|
||||
self.downloadURL = try container.decode(URL.self, forKey: .downloadURL)
|
||||
self.size = try container.decode(Int64.self, forKey: .size)
|
||||
|
||||
self._minOSVersion = try container.decodeIfPresent(String.self, forKey: ._minOSVersion)
|
||||
self._maxOSVersion = try container.decodeIfPresent(String.self, forKey: ._maxOSVersion)
|
||||
|
||||
self.appBundleID = try container.decode(String.self, forKey: .appBundleID)
|
||||
self.sourceID = try container.decodeIfPresent(String.self, forKey: .sourceID)
|
||||
do
|
||||
{
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
self.version = try container.decode(String.self, forKey: .version)
|
||||
self.date = try container.decode(Date.self, forKey: .date)
|
||||
self.localizedDescription = try container.decodeIfPresent(String.self, forKey: .localizedDescription)
|
||||
|
||||
self.downloadURL = try container.decode(URL.self, forKey: .downloadURL)
|
||||
self.size = try container.decode(Int64.self, forKey: .size)
|
||||
}
|
||||
catch
|
||||
{
|
||||
if let context = self.managedObjectContext
|
||||
{
|
||||
context.delete(self)
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,4 +91,26 @@ public extension AppVersion
|
||||
{
|
||||
return NSFetchRequest<AppVersion>(entityName: "AppVersion")
|
||||
}
|
||||
|
||||
class func makeAppVersion(
|
||||
version: String,
|
||||
date: Date,
|
||||
localizedDescription: String? = nil,
|
||||
downloadURL: URL,
|
||||
size: Int64,
|
||||
appBundleID: String,
|
||||
sourceID: String? = nil,
|
||||
in context: NSManagedObjectContext) -> AppVersion
|
||||
{
|
||||
let appVersion = AppVersion(context: context)
|
||||
appVersion.version = version
|
||||
appVersion.date = date
|
||||
appVersion.localizedDescription = localizedDescription
|
||||
appVersion.downloadURL = downloadURL
|
||||
appVersion.size = size
|
||||
appVersion.appBundleID = appBundleID
|
||||
appVersion.sourceID = sourceID
|
||||
|
||||
return appVersion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,7 +211,7 @@ private extension DatabaseManager
|
||||
else
|
||||
{
|
||||
storeApp = StoreApp.makeAltStoreApp(in: context)
|
||||
storeApp.version = localApp.version
|
||||
storeApp.latestVersion?.version = localApp.version
|
||||
storeApp.source = altStoreSource
|
||||
}
|
||||
|
||||
|
||||
@@ -146,7 +146,7 @@ public extension InstalledApp
|
||||
{
|
||||
let fetchRequest = InstalledApp.fetchRequest() as NSFetchRequest<InstalledApp>
|
||||
fetchRequest.predicate = NSPredicate(format: "%K == YES AND %K != nil AND %K != %K",
|
||||
#keyPath(InstalledApp.isActive), #keyPath(InstalledApp.storeApp), #keyPath(InstalledApp.version), #keyPath(InstalledApp.storeApp.version))
|
||||
#keyPath(InstalledApp.isActive), #keyPath(InstalledApp.storeApp), #keyPath(InstalledApp.version), #keyPath(InstalledApp.storeApp.latestVersion.version))
|
||||
return fetchRequest
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,12 @@ open class MergePolicy: RSTRelationshipPreservingMergePolicy
|
||||
{
|
||||
permission.managedObjectContext?.delete(permission)
|
||||
}
|
||||
|
||||
// Delete previous versions (different than below).
|
||||
for case let appVersion as AppVersion in previousApp._versions where appVersion.app == nil
|
||||
{
|
||||
appVersion.managedObjectContext?.delete(appVersion)
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
@@ -55,6 +61,16 @@ open class MergePolicy: RSTRelationshipPreservingMergePolicy
|
||||
permission.managedObjectContext?.delete(permission)
|
||||
}
|
||||
|
||||
if let contextApp = conflict.conflictingObjects.first as? StoreApp
|
||||
{
|
||||
let contextVersions = Set(contextApp._versions.lazy.compactMap { $0 as? AppVersion }.map { $0.version })
|
||||
for case let appVersion as AppVersion in databaseObject._versions where !contextVersions.contains(appVersion.version)
|
||||
{
|
||||
print("[ALTLog] Deleting cached app version: \(appVersion.appBundleID + "_" + appVersion.version), not in:", contextApp.versions.map { $0.appBundleID + "_" + $0.version })
|
||||
appVersion.managedObjectContext?.delete(appVersion)
|
||||
}
|
||||
}
|
||||
|
||||
case let databaseObject as Source:
|
||||
guard let conflictedObject = conflict.conflictingObjects.first as? Source else { break }
|
||||
|
||||
|
||||
@@ -103,22 +103,41 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable
|
||||
|
||||
@NSManaged public private(set) var developerName: String
|
||||
@NSManaged public private(set) var localizedDescription: String
|
||||
@NSManaged public private(set) var size: Int32
|
||||
@NSManaged @objc(size) private var _size: Int32
|
||||
|
||||
@NSManaged public private(set) var iconURL: URL
|
||||
@NSManaged public private(set) var screenshotURLs: [URL]
|
||||
|
||||
@NSManaged public var version: String
|
||||
@NSManaged public private(set) var versionDate: Date
|
||||
@NSManaged public private(set) var versionDescription: String?
|
||||
@NSManaged @objc(version) private var _version: String
|
||||
@NSManaged @objc(versionDate) private var _versionDate: Date
|
||||
@NSManaged @objc(versionDescription) private var _versionDescription: String?
|
||||
|
||||
@NSManaged public private(set) var downloadURL: URL
|
||||
@NSManaged @objc(downloadURL) private var _downloadURL: URL
|
||||
@NSManaged public private(set) var platformURLs: PlatformURLs?
|
||||
|
||||
@NSManaged public private(set) var tintColor: UIColor?
|
||||
@NSManaged public private(set) var isBeta: Bool
|
||||
|
||||
@NSManaged public var sourceIdentifier: String?
|
||||
@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?
|
||||
|
||||
@NSManaged public var sortIndex: Int32
|
||||
|
||||
@@ -152,6 +171,31 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable
|
||||
return self._versions.array as! [AppVersion]
|
||||
}
|
||||
|
||||
@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
|
||||
}
|
||||
|
||||
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
|
||||
{
|
||||
super.init(entity: entity, insertInto: context)
|
||||
@@ -175,6 +219,7 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable
|
||||
case permissions
|
||||
case size
|
||||
case isBeta = "beta"
|
||||
case versions
|
||||
}
|
||||
|
||||
public required init(from decoder: Decoder) throws
|
||||
@@ -194,10 +239,6 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable
|
||||
|
||||
self.subtitle = try container.decodeIfPresent(String.self, forKey: .subtitle)
|
||||
|
||||
self.version = try container.decode(String.self, forKey: .version)
|
||||
self.versionDate = try container.decode(Date.self, forKey: .versionDate)
|
||||
self.versionDescription = try container.decodeIfPresent(String.self, forKey: .versionDescription)
|
||||
|
||||
self.iconURL = try container.decode(URL.self, forKey: .iconURL)
|
||||
self.screenshotURLs = try container.decodeIfPresent([URL].self, forKey: .screenshotURLs) ?? []
|
||||
|
||||
@@ -207,14 +248,14 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable
|
||||
self.platformURLs = platformURLs
|
||||
// Backwards compatibility, use the fiirst (iOS will be first since sorted that way)
|
||||
if let first = platformURLs.sorted().first {
|
||||
self.downloadURL = first.downloadURL
|
||||
self._downloadURL = first.downloadURL
|
||||
} else {
|
||||
throw DecodingError.dataCorruptedError(forKey: .platformURLs, in: container, debugDescription: "platformURLs has no entries")
|
||||
|
||||
}
|
||||
|
||||
} else if let downloadURL = downloadURL {
|
||||
self.downloadURL = downloadURL
|
||||
self._downloadURL = downloadURL
|
||||
} else {
|
||||
throw DecodingError.dataCorruptedError(forKey: .downloadURL, in: container, debugDescription: "E downloadURL:String or downloadURLs:[[Platform:URL]] key required.")
|
||||
}
|
||||
@@ -228,11 +269,40 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable
|
||||
self.tintColor = tintColor
|
||||
}
|
||||
|
||||
self.size = try container.decode(Int32.self, forKey: .size)
|
||||
self.isBeta = try container.decodeIfPresent(Bool.self, forKey: .isBeta) ?? false
|
||||
|
||||
let permissions = try container.decodeIfPresent([AppPermission].self, forKey: .permissions) ?? []
|
||||
self._permissions = NSOrderedSet(array: permissions)
|
||||
|
||||
if let versions = try container.decodeIfPresent([AppVersion].self, forKey: .versions)
|
||||
{
|
||||
//TODO: Throw error if there isn't at least one version.
|
||||
|
||||
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])
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -246,6 +316,24 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
// Preserve backwards compatibility by assigning legacy property values.
|
||||
self._version = latestVersion.version
|
||||
self._versionDate = latestVersion.date
|
||||
self._versionDescription = latestVersion.localizedDescription
|
||||
self._downloadURL = latestVersion.downloadURL
|
||||
self._size = Int32(latestVersion.size)
|
||||
}
|
||||
}
|
||||
|
||||
public extension StoreApp
|
||||
{
|
||||
@nonobjc class func fetchRequest() -> NSFetchRequest<StoreApp>
|
||||
@@ -262,9 +350,16 @@ public extension StoreApp
|
||||
app.localizedDescription = "SideStore is an alternative App Store."
|
||||
app.iconURL = URL(string: "https://user-images.githubusercontent.com/705880/63392210-540c5980-c37b-11e9-968c-8742fc68ab2e.png")!
|
||||
app.screenshotURLs = []
|
||||
app.version = "1.0"
|
||||
app.versionDate = Date()
|
||||
app.downloadURL = URL(string: "http://rileytestut.com")!
|
||||
app.sourceIdentifier = Source.altStoreIdentifier
|
||||
|
||||
let appVersion = AppVersion.makeAppVersion(version: "1.0",
|
||||
date: Date(),
|
||||
downloadURL: URL(string: "http://rileytestut.com")!,
|
||||
size: 0,
|
||||
appBundleID: app.bundleIdentifier,
|
||||
sourceID: Source.altStoreIdentifier,
|
||||
in: context)
|
||||
app.setVersions([appVersion])
|
||||
|
||||
#if BETA
|
||||
app.isBeta = true
|
||||
|
||||
Reference in New Issue
Block a user