mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-10 07:13:28 +01:00
Merge branch 'sources_tab'
# Conflicts: # AltStore.xcodeproj/project.pbxproj
This commit is contained in:
@@ -10,6 +10,13 @@ import Foundation
|
||||
|
||||
public extension Date
|
||||
{
|
||||
private static let mediumDateFormatter: DateFormatter = {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateStyle = .medium
|
||||
dateFormatter.timeStyle = .none
|
||||
return dateFormatter
|
||||
}()
|
||||
|
||||
func numberOfCalendarDays(since date: Date) -> Int
|
||||
{
|
||||
let today = Calendar.current.startOfDay(for: self)
|
||||
@@ -19,15 +26,15 @@ public extension Date
|
||||
return components.day!
|
||||
}
|
||||
|
||||
func relativeDateString(since date: Date, dateFormatter: DateFormatter) -> String
|
||||
func relativeDateString(since date: Date, dateFormatter: DateFormatter? = nil) -> String
|
||||
{
|
||||
let dateFormatter = dateFormatter ?? Date.mediumDateFormatter
|
||||
let numberOfDays = self.numberOfCalendarDays(since: date)
|
||||
|
||||
switch numberOfDays
|
||||
{
|
||||
case 0: return NSLocalizedString("Today", comment: "")
|
||||
case 1: return NSLocalizedString("Yesterday", comment: "")
|
||||
case 2...7: return String(format: NSLocalizedString("%@ days ago", comment: ""), NSNumber(value: numberOfDays))
|
||||
default: return dateFormatter.string(from: date)
|
||||
}
|
||||
}
|
||||
|
||||
16
AltStoreCore/Extensions/ProcessInfo+Previews.swift
Normal file
16
AltStoreCore/Extensions/ProcessInfo+Previews.swift
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// ProcessInfo+Previews.swift
|
||||
// AltStoreCore
|
||||
//
|
||||
// Created by Riley Testut on 10/11/23.
|
||||
// Copyright © 2023 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public extension ProcessInfo
|
||||
{
|
||||
var isPreview: Bool {
|
||||
ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1"
|
||||
}
|
||||
}
|
||||
@@ -87,6 +87,22 @@ public extension DatabaseManager
|
||||
|
||||
guard !self.isStarted else { return finish(nil) }
|
||||
|
||||
#if DEBUG
|
||||
// Wrap in #if DEBUG to *ensure* we never accidentally delete production databases.
|
||||
if ProcessInfo.processInfo.isPreview
|
||||
{
|
||||
do
|
||||
{
|
||||
print("!!! Purging database for preview...")
|
||||
try FileManager.default.removeItem(at: PersistentContainer.defaultDirectoryURL())
|
||||
}
|
||||
catch
|
||||
{
|
||||
print("Failed to remove database directory for preview.", error)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if self.persistentContainer.isMigrationRequired
|
||||
{
|
||||
// Quit any other running AltStore processes to prevent concurrent database access during and after migration.
|
||||
@@ -166,6 +182,22 @@ public extension DatabaseManager
|
||||
}
|
||||
}
|
||||
|
||||
public extension DatabaseManager
|
||||
{
|
||||
func startForPreview()
|
||||
{
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
|
||||
self.dispatchQueue.async {
|
||||
self.startCompletionHandlers.append { error in
|
||||
semaphore.signal()
|
||||
}
|
||||
}
|
||||
|
||||
_ = semaphore.wait(timeout: .now() + 2.0)
|
||||
}
|
||||
}
|
||||
|
||||
public extension DatabaseManager
|
||||
{
|
||||
var viewContext: NSManagedObjectContext {
|
||||
|
||||
@@ -236,6 +236,23 @@ public extension Source
|
||||
return isAdded
|
||||
}
|
||||
}
|
||||
|
||||
var isRecommended: Bool {
|
||||
guard let recommendedSources = UserDefaults.shared.recommendedSources else { return false }
|
||||
|
||||
// TODO: Support alternate URLs
|
||||
let isRecommended = recommendedSources.contains { source in
|
||||
return source.identifier == self.identifier || source.sourceURL?.absoluteString.lowercased() == self.sourceURL.absoluteString
|
||||
}
|
||||
return isRecommended
|
||||
}
|
||||
|
||||
var lastUpdatedDate: Date? {
|
||||
let allDates = self.apps.compactMap { $0.latestAvailableVersion?.date } + self.newsItems.map { $0.date }
|
||||
|
||||
let lastUpdatedDate = allDates.sorted().last
|
||||
return lastUpdatedDate
|
||||
}
|
||||
}
|
||||
|
||||
internal extension Source
|
||||
@@ -283,4 +300,14 @@ public extension Source
|
||||
let source = Source.first(satisfying: NSPredicate(format: "%K == %@", #keyPath(Source.identifier), Source.altStoreIdentifier), in: context)
|
||||
return source
|
||||
}
|
||||
|
||||
class func make(name: String, identifier: String, sourceURL: URL, context: NSManagedObjectContext) -> Source
|
||||
{
|
||||
let source = Source(context: context)
|
||||
source.name = name
|
||||
source.identifier = identifier
|
||||
source.sourceURL = sourceURL
|
||||
|
||||
return source
|
||||
}
|
||||
}
|
||||
|
||||
69
AltStoreCore/Types/KnownSource.swift
Normal file
69
AltStoreCore/Types/KnownSource.swift
Normal file
@@ -0,0 +1,69 @@
|
||||
//
|
||||
// KnownSource.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 5/16/23.
|
||||
// Copyright © 2023 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct KnownSource: Decodable
|
||||
{
|
||||
public var identifier: String
|
||||
public var sourceURL: URL?
|
||||
public var bundleIDs: [String]?
|
||||
}
|
||||
|
||||
private extension KnownSource
|
||||
{
|
||||
var dictionaryRepresentation: [String: Any] {
|
||||
let dictionary: [String: Any?] = [
|
||||
KnownSource.CodingKeys.identifier.stringValue: identifier,
|
||||
KnownSource.CodingKeys.sourceURL.stringValue: self.sourceURL?.absoluteString,
|
||||
KnownSource.CodingKeys.bundleIDs.stringValue: self.bundleIDs
|
||||
]
|
||||
|
||||
return dictionary.compactMapValues { $0 }
|
||||
}
|
||||
|
||||
init?(dictionary: [String: Any])
|
||||
{
|
||||
guard let identifier = dictionary[CodingKeys.identifier.stringValue] as? String else { return nil }
|
||||
self.identifier = identifier
|
||||
|
||||
if let sourceURLString = dictionary[CodingKeys.sourceURL.stringValue] as? String
|
||||
{
|
||||
self.sourceURL = URL(string: sourceURLString)
|
||||
}
|
||||
|
||||
let bundleIDs = dictionary[CodingKeys.bundleIDs.stringValue] as? [String]
|
||||
self.bundleIDs = bundleIDs
|
||||
}
|
||||
}
|
||||
|
||||
public extension UserDefaults
|
||||
{
|
||||
// Cache recommended sources just in case we need to check whether source is recommended or not.
|
||||
@nonobjc var recommendedSources: [KnownSource]? {
|
||||
get {
|
||||
guard let sources = _recommendedSources?.compactMap({ KnownSource(dictionary: $0) }) else { return nil }
|
||||
return sources
|
||||
}
|
||||
set {
|
||||
_recommendedSources = newValue?.map { $0.dictionaryRepresentation }
|
||||
}
|
||||
}
|
||||
@NSManaged @objc(recommendedSources) private var _recommendedSources: [[String: Any]]?
|
||||
|
||||
@nonobjc var blockedSources: [KnownSource]? {
|
||||
get {
|
||||
guard let sources = _blockedSources?.compactMap({ KnownSource(dictionary: $0) }) else { return nil }
|
||||
return sources
|
||||
}
|
||||
set {
|
||||
_blockedSources = newValue?.map { $0.dictionaryRepresentation }
|
||||
}
|
||||
}
|
||||
@NSManaged @objc(blockedSources) private var _blockedSources: [[String: Any]]?
|
||||
}
|
||||
Reference in New Issue
Block a user