mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-09 06:43:25 +01:00
Merge branch 'develop' into NoIdle
Signed-off-by: June Park <rjp2030@outlook.com>
This commit is contained in:
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@@ -1 +1 @@
|
|||||||
* @JoeMatt @lonkelle
|
* @JoeMatt @lonkelle @nythepegasus @Spidy123222 @SternXD
|
||||||
|
|||||||
1
.github/workflows/stable.yml
vendored
1
.github/workflows/stable.yml
vendored
@@ -3,6 +3,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- '[0-9]+.[0-9]+.[0-9]+' # example: 1.0.0
|
- '[0-9]+.[0-9]+.[0-9]+' # example: 1.0.0
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 54;
|
objectVersion = 52;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
@@ -332,6 +332,7 @@
|
|||||||
D57FE84428C7DB7100216002 /* ErrorLogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57FE84328C7DB7100216002 /* ErrorLogViewController.swift */; };
|
D57FE84428C7DB7100216002 /* ErrorLogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57FE84328C7DB7100216002 /* ErrorLogViewController.swift */; };
|
||||||
D58916FE28C7C55C00E39C8B /* LoggedError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58916FD28C7C55C00E39C8B /* LoggedError.swift */; };
|
D58916FE28C7C55C00E39C8B /* LoggedError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58916FD28C7C55C00E39C8B /* LoggedError.swift */; };
|
||||||
D593F1942717749A006E82DE /* PatchAppOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D593F1932717749A006E82DE /* PatchAppOperation.swift */; };
|
D593F1942717749A006E82DE /* PatchAppOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D593F1932717749A006E82DE /* PatchAppOperation.swift */; };
|
||||||
|
D5ACE84528E3B8450021CAB9 /* ClearAppCacheOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5ACE84428E3B8450021CAB9 /* ClearAppCacheOperation.swift */; };
|
||||||
D5CA0C4B280E141900469595 /* ManagedPatron.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5CA0C4A280E141900469595 /* ManagedPatron.swift */; };
|
D5CA0C4B280E141900469595 /* ManagedPatron.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5CA0C4A280E141900469595 /* ManagedPatron.swift */; };
|
||||||
D5CA0C4E280E249E00469595 /* AltStore9ToAltStore10.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = D5CA0C4D280E249E00469595 /* AltStore9ToAltStore10.xcmappingmodel */; };
|
D5CA0C4E280E249E00469595 /* AltStore9ToAltStore10.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = D5CA0C4D280E249E00469595 /* AltStore9ToAltStore10.xcmappingmodel */; };
|
||||||
D5DAE0942804B0B80034D8D4 /* ScreenshotProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5DAE0932804B0B80034D8D4 /* ScreenshotProcessor.swift */; };
|
D5DAE0942804B0B80034D8D4 /* ScreenshotProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5DAE0932804B0B80034D8D4 /* ScreenshotProcessor.swift */; };
|
||||||
@@ -839,6 +840,7 @@
|
|||||||
D57FE84328C7DB7100216002 /* ErrorLogViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorLogViewController.swift; sourceTree = "<group>"; };
|
D57FE84328C7DB7100216002 /* ErrorLogViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorLogViewController.swift; sourceTree = "<group>"; };
|
||||||
D58916FD28C7C55C00E39C8B /* LoggedError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggedError.swift; sourceTree = "<group>"; };
|
D58916FD28C7C55C00E39C8B /* LoggedError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggedError.swift; sourceTree = "<group>"; };
|
||||||
D593F1932717749A006E82DE /* PatchAppOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatchAppOperation.swift; sourceTree = "<group>"; };
|
D593F1932717749A006E82DE /* PatchAppOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatchAppOperation.swift; sourceTree = "<group>"; };
|
||||||
|
D5ACE84428E3B8450021CAB9 /* ClearAppCacheOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClearAppCacheOperation.swift; sourceTree = "<group>"; };
|
||||||
D5CA0C4A280E141900469595 /* ManagedPatron.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedPatron.swift; sourceTree = "<group>"; };
|
D5CA0C4A280E141900469595 /* ManagedPatron.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedPatron.swift; sourceTree = "<group>"; };
|
||||||
D5CA0C4C280E242500469595 /* AltStore 10.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "AltStore 10.xcdatamodel"; sourceTree = "<group>"; };
|
D5CA0C4C280E242500469595 /* AltStore 10.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "AltStore 10.xcdatamodel"; sourceTree = "<group>"; };
|
||||||
D5CA0C4D280E249E00469595 /* AltStore9ToAltStore10.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = AltStore9ToAltStore10.xcmappingmodel; sourceTree = "<group>"; };
|
D5CA0C4D280E249E00469595 /* AltStore9ToAltStore10.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = AltStore9ToAltStore10.xcmappingmodel; sourceTree = "<group>"; };
|
||||||
@@ -1697,6 +1699,7 @@
|
|||||||
D57F2C9026E0070200B9FA39 /* EnableJITOperation.swift */,
|
D57F2C9026E0070200B9FA39 /* EnableJITOperation.swift */,
|
||||||
D5DAE0952804DF430034D8D4 /* UpdatePatronsOperation.swift */,
|
D5DAE0952804DF430034D8D4 /* UpdatePatronsOperation.swift */,
|
||||||
D5E1E7C028077DE90016FC96 /* FetchTrustedSourcesOperation.swift */,
|
D5E1E7C028077DE90016FC96 /* FetchTrustedSourcesOperation.swift */,
|
||||||
|
D5ACE84428E3B8450021CAB9 /* ClearAppCacheOperation.swift */,
|
||||||
BF7B44062725A4B8005288A4 /* Patch App */,
|
BF7B44062725A4B8005288A4 /* Patch App */,
|
||||||
);
|
);
|
||||||
path = Operations;
|
path = Operations;
|
||||||
@@ -2504,6 +2507,7 @@
|
|||||||
BFD6B03322DFF20800B86064 /* MyAppsComponents.swift in Sources */,
|
BFD6B03322DFF20800B86064 /* MyAppsComponents.swift in Sources */,
|
||||||
BF41B808233433C100C593A3 /* LoadingState.swift in Sources */,
|
BF41B808233433C100C593A3 /* LoadingState.swift in Sources */,
|
||||||
BFF0B69A2322D7D0007A79E1 /* UIScreen+CompactHeight.swift in Sources */,
|
BFF0B69A2322D7D0007A79E1 /* UIScreen+CompactHeight.swift in Sources */,
|
||||||
|
D5ACE84528E3B8450021CAB9 /* ClearAppCacheOperation.swift in Sources */,
|
||||||
D5F2F6A92720B7C20081CCF5 /* PatchViewController.swift in Sources */,
|
D5F2F6A92720B7C20081CCF5 /* PatchViewController.swift in Sources */,
|
||||||
B39F16132918D7C5002E9404 /* Consts.swift in Sources */,
|
B39F16132918D7C5002E9404 /* Consts.swift in Sources */,
|
||||||
BF8F69C222E659F700049BA1 /* AppContentViewController.swift in Sources */,
|
BF8F69C222E659F700049BA1 /* AppContentViewController.swift in Sources */,
|
||||||
|
|||||||
@@ -91,13 +91,18 @@ private extension AppIDsViewController
|
|||||||
|
|
||||||
cell.bannerView.buttonLabel.isHidden = false
|
cell.bannerView.buttonLabel.isHidden = false
|
||||||
|
|
||||||
let currentDate = Date()
|
let formatter = DateComponentsFormatter()
|
||||||
|
formatter.unitsStyle = .full
|
||||||
|
formatter.includesApproximationPhrase = false
|
||||||
|
formatter.includesTimeRemainingPhrase = false
|
||||||
|
formatter.allowedUnits = [.minute, .hour, .day]
|
||||||
|
formatter.maximumUnitCount = 1
|
||||||
|
|
||||||
let numberOfDays = expirationDate.numberOfCalendarDays(since: currentDate)
|
cell.bannerView.button.setTitle(formatter.string(from: Date(), to: expirationDate)?.uppercased(), for: .normal)
|
||||||
let numberOfDaysText = (numberOfDays == 1) ? NSLocalizedString("1 day", comment: "") : String(format: NSLocalizedString("%@ days", comment: ""), NSNumber(value: numberOfDays))
|
|
||||||
cell.bannerView.button.setTitle(numberOfDaysText.uppercased(), for: .normal)
|
|
||||||
|
|
||||||
attributedAccessibilityLabel.mutableString.append(String(format: NSLocalizedString("Expires in %@.", comment: ""), numberOfDaysText) + " ")
|
formatter.includesTimeRemainingPhrase = true
|
||||||
|
|
||||||
|
attributedAccessibilityLabel.mutableString.append((formatter.string(from: Date(), to: expirationDate) ?? NSLocalizedString("Unknown", comment: "")) + " ")
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -206,7 +211,7 @@ extension AppIDsViewController: UICollectionViewDelegateFlowLayout
|
|||||||
**App IDs can't be deleted**, but they do expire after one week. SideStore will automatically renew App IDs for all active apps once they've expired.
|
**App IDs can't be deleted**, but they do expire after one week. SideStore will automatically renew App IDs for all active apps once they've expired.
|
||||||
""", comment: "")
|
""", comment: "")
|
||||||
|
|
||||||
let attributedText = NSAttributedString(markdownRepresentation: text, attributes: [.font: headerView.textLabel.font as Any])
|
let attributedText = NSAttributedString(markdownRepresentation: text)
|
||||||
headerView.textLabel.attributedText = attributedText
|
headerView.textLabel.attributedText = attributedText
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -307,6 +307,16 @@ extension AppManager
|
|||||||
presentingViewController.present(alertController, animated: true, completion: nil)
|
presentingViewController.present(alertController, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func clearAppCache(completion: @escaping (Result<Void, Error>) -> Void)
|
||||||
|
{
|
||||||
|
let clearAppCacheOperation = ClearAppCacheOperation()
|
||||||
|
clearAppCacheOperation.resultHandler = { result in
|
||||||
|
completion(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.run([clearAppCacheOperation], context: nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AppManager
|
extension AppManager
|
||||||
@@ -754,6 +764,12 @@ extension AppManager
|
|||||||
let progress = self.refreshProgress[app.bundleIdentifier]
|
let progress = self.refreshProgress[app.bundleIdentifier]
|
||||||
return progress
|
return progress
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isActivelyManagingApp(withBundleID bundleID: String) -> Bool
|
||||||
|
{
|
||||||
|
let isActivelyManaging = self.installationProgress.keys.contains(bundleID) || self.refreshProgress.keys.contains(bundleID)
|
||||||
|
return isActivelyManaging
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AppManager
|
extension AppManager
|
||||||
@@ -808,12 +824,6 @@ private extension AppManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func isActivelyManagingApp(withBundleID bundleID: String) -> Bool
|
|
||||||
{
|
|
||||||
let isActivelyManaging = self.installationProgress.keys.contains(bundleID) || self.refreshProgress.keys.contains(bundleID)
|
|
||||||
return isActivelyManaging
|
|
||||||
}
|
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
private func perform(_ operations: [AppOperation], presentingViewController: UIViewController?, group: RefreshGroup) -> RefreshGroup
|
private func perform(_ operations: [AppOperation], presentingViewController: UIViewController?, group: RefreshGroup) -> RefreshGroup
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -329,21 +329,25 @@ private extension MyAppsViewController
|
|||||||
let currentDate = Date()
|
let currentDate = Date()
|
||||||
|
|
||||||
let numberOfDays = installedApp.expirationDate.numberOfCalendarDays(since: currentDate)
|
let numberOfDays = installedApp.expirationDate.numberOfCalendarDays(since: currentDate)
|
||||||
let numberOfDaysText: String
|
|
||||||
|
|
||||||
if numberOfDays == 1
|
let formatter = DateComponentsFormatter()
|
||||||
{
|
formatter.unitsStyle = .full
|
||||||
numberOfDaysText = NSLocalizedString("1 day", comment: "")
|
formatter.includesApproximationPhrase = false
|
||||||
}
|
formatter.includesTimeRemainingPhrase = false
|
||||||
else
|
|
||||||
{
|
formatter.allowedUnits = [.day, .hour, .minute]
|
||||||
numberOfDaysText = String(format: NSLocalizedString("%@ days", comment: ""), NSNumber(value: numberOfDays))
|
|
||||||
}
|
formatter.maximumUnitCount = 1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
cell.bannerView.button.setTitle(formatter.string(from: currentDate, to: installedApp.expirationDate)?.uppercased(), for: .normal)
|
||||||
|
|
||||||
cell.bannerView.button.setTitle(numberOfDaysText.uppercased(), for: .normal)
|
|
||||||
cell.bannerView.button.accessibilityLabel = String(format: NSLocalizedString("Refresh %@", comment: ""), installedApp.name)
|
cell.bannerView.button.accessibilityLabel = String(format: NSLocalizedString("Refresh %@", comment: ""), installedApp.name)
|
||||||
|
|
||||||
cell.bannerView.accessibilityLabel? += ". " + String(format: NSLocalizedString("Expires in %@", comment: ""), numberOfDaysText)
|
formatter.includesTimeRemainingPhrase = true
|
||||||
|
|
||||||
|
cell.bannerView.accessibilityLabel? += ". " + (formatter.string(from: currentDate, to: installedApp.expirationDate) ?? NSLocalizedString("Unknown", comment: "")) + " "
|
||||||
|
|
||||||
// Make sure refresh button is correct size.
|
// Make sure refresh button is correct size.
|
||||||
cell.layoutIfNeeded()
|
cell.layoutIfNeeded()
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import Network
|
|||||||
|
|
||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
import AltSign
|
import AltSign
|
||||||
|
import minimuxer
|
||||||
|
|
||||||
enum AuthenticationError: LocalizedError
|
enum AuthenticationError: LocalizedError
|
||||||
{
|
{
|
||||||
@@ -593,7 +594,7 @@ private extension AuthenticationOperation
|
|||||||
|
|
||||||
func registerCurrentDevice(for team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTDevice, Error>) -> Void)
|
func registerCurrentDevice(for team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTDevice, Error>) -> Void)
|
||||||
{
|
{
|
||||||
guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else {
|
guard let udid = fetch_udid()?.toString() else {
|
||||||
return completionHandler(.failure(OperationError.unknownUDID))
|
return completionHandler(.failure(OperationError.unknownUDID))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
208
AltStore/Operations/ClearAppCacheOperation.swift
Normal file
208
AltStore/Operations/ClearAppCacheOperation.swift
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
//
|
||||||
|
// ClearAppCacheOperation.swift
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 9/27/22.
|
||||||
|
// Copyright © 2022 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import AltStoreCore
|
||||||
|
/*
|
||||||
|
struct BatchError: ALTLocalizedError
|
||||||
|
{
|
||||||
|
|
||||||
|
enum Code: Int, ALTErrorCode
|
||||||
|
{
|
||||||
|
typealias Error = BatchError
|
||||||
|
|
||||||
|
case batchError
|
||||||
|
}
|
||||||
|
|
||||||
|
var code: Code = .batchError
|
||||||
|
var underlyingErrors: [Error]
|
||||||
|
|
||||||
|
var errorTitle: String?
|
||||||
|
var errorFailure: String?
|
||||||
|
|
||||||
|
init(errors: [Error])
|
||||||
|
{
|
||||||
|
self.underlyingErrors = errors
|
||||||
|
}
|
||||||
|
|
||||||
|
var errorFailureReason: String {
|
||||||
|
guard !self.underlyingErrors.isEmpty else { return NSLocalizedString("An unknown error occured.", comment: "") }
|
||||||
|
|
||||||
|
let errorMessages = self.underlyingErrors.map { $0.localizedDescription }
|
||||||
|
|
||||||
|
let message = errorMessages.joined(separator: "\n\n")
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
@objc(ClearAppCacheOperation)
|
||||||
|
class ClearAppCacheOperation: ResultOperation<Void>
|
||||||
|
{
|
||||||
|
private let coordinator = NSFileCoordinator()
|
||||||
|
private let coordinatorQueue = OperationQueue()
|
||||||
|
|
||||||
|
override init()
|
||||||
|
{
|
||||||
|
self.coordinatorQueue.name = "AltStore - ClearAppCacheOperation Queue"
|
||||||
|
}
|
||||||
|
|
||||||
|
override func main()
|
||||||
|
{
|
||||||
|
super.main()
|
||||||
|
|
||||||
|
var allErrors = [Error]()
|
||||||
|
|
||||||
|
self.clearTemporaryDirectory { result in
|
||||||
|
switch result
|
||||||
|
{
|
||||||
|
//case .failure(let batchError as BatchError): allErrors.append(contentsOf: batchError.underlyingErrors)
|
||||||
|
case .failure(let error): allErrors.append(error)
|
||||||
|
case .success: break
|
||||||
|
}
|
||||||
|
|
||||||
|
self.removeUninstalledAppBackupDirectories { result in
|
||||||
|
switch result
|
||||||
|
{
|
||||||
|
//case .failure(let batchError as BatchError): allErrors.append(contentsOf: batchError.underlyingErrors)
|
||||||
|
case .failure(let error): allErrors.append(error)
|
||||||
|
case .success: break
|
||||||
|
}
|
||||||
|
|
||||||
|
if allErrors.isEmpty
|
||||||
|
{
|
||||||
|
self.finish(.success(()))
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.finish(.failure(OperationError.cacheClearError(errors: allErrors.map({ error in
|
||||||
|
return error.localizedDescription
|
||||||
|
}))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension ClearAppCacheOperation
|
||||||
|
{
|
||||||
|
func clearTemporaryDirectory(completion: @escaping (Result<Void, Error>) -> Void)
|
||||||
|
{
|
||||||
|
let intent = NSFileAccessIntent.writingIntent(with: FileManager.default.temporaryDirectory, options: [.forDeleting])
|
||||||
|
self.coordinator.coordinate(with: [intent], queue: self.coordinatorQueue) { (error) in
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if let error
|
||||||
|
{
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
let fileURLs = try FileManager.default.contentsOfDirectory(at: intent.url,
|
||||||
|
includingPropertiesForKeys: [],
|
||||||
|
options: [.skipsSubdirectoryDescendants, .skipsHiddenFiles])
|
||||||
|
var errors = [Error]()
|
||||||
|
|
||||||
|
for fileURL in fileURLs
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
print("[ALTLog] Removing item from temporary directory:", fileURL.lastPathComponent)
|
||||||
|
try FileManager.default.removeItem(at: fileURL)
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
print("[ALTLog] Failed to remove \(fileURL.lastPathComponent) from temporary directory.", error)
|
||||||
|
errors.append(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !errors.isEmpty
|
||||||
|
{
|
||||||
|
completion(.failure(OperationError.cacheClearError(errors: errors.map({ error in
|
||||||
|
return error.localizedDescription
|
||||||
|
}))))
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
completion(.success(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
completion(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeUninstalledAppBackupDirectories(completion: @escaping (Result<Void, Error>) -> Void)
|
||||||
|
{
|
||||||
|
guard let backupsDirectory = FileManager.default.appBackupsDirectory else { return completion(.failure(OperationError.missingAppGroup)) }
|
||||||
|
|
||||||
|
DatabaseManager.shared.persistentContainer.performBackgroundTask { context in
|
||||||
|
let installedAppBundleIDs = Set(InstalledApp.all(in: context).map { $0.bundleIdentifier })
|
||||||
|
|
||||||
|
let intent = NSFileAccessIntent.writingIntent(with: backupsDirectory, options: [.forDeleting])
|
||||||
|
self.coordinator.coordinate(with: [intent], queue: self.coordinatorQueue) { (error) in
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if let error
|
||||||
|
{
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
var isDirectory: ObjCBool = false
|
||||||
|
guard FileManager.default.fileExists(atPath: intent.url.path, isDirectory: &isDirectory), isDirectory.boolValue else {
|
||||||
|
completion(.success(()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let fileURLs = try FileManager.default.contentsOfDirectory(at: intent.url,
|
||||||
|
includingPropertiesForKeys: [.isDirectoryKey, .nameKey],
|
||||||
|
options: [.skipsSubdirectoryDescendants, .skipsHiddenFiles])
|
||||||
|
var errors = [Error]()
|
||||||
|
|
||||||
|
|
||||||
|
for backupDirectory in fileURLs
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
let resourceValues = try backupDirectory.resourceValues(forKeys: [.isDirectoryKey, .nameKey])
|
||||||
|
guard let isDirectory = resourceValues.isDirectory, let bundleID = resourceValues.name else { continue }
|
||||||
|
|
||||||
|
if isDirectory && !installedAppBundleIDs.contains(bundleID) && !AppManager.shared.isActivelyManagingApp(withBundleID: bundleID)
|
||||||
|
{
|
||||||
|
print("[ALTLog] Removing backup directory for uninstalled app:", bundleID)
|
||||||
|
try FileManager.default.removeItem(at: backupDirectory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
print("[ALTLog] Failed to remove app backup directory:", error)
|
||||||
|
errors.append(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !errors.isEmpty
|
||||||
|
{
|
||||||
|
completion(.failure(OperationError.cacheClearError(errors: errors.map({ error in
|
||||||
|
return error.localizedDescription
|
||||||
|
}))))
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
completion(.success(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
print("[ALTLog] Failed to remove app backup directory:", error)
|
||||||
|
completion(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,6 +40,8 @@ enum OperationError: LocalizedError
|
|||||||
case provisioningError(result: String, message: String?)
|
case provisioningError(result: String, message: String?)
|
||||||
case anisetteV3Error(message: String)
|
case anisetteV3Error(message: String)
|
||||||
|
|
||||||
|
case cacheClearError(errors: [String])
|
||||||
|
|
||||||
var failureReason: String? {
|
var failureReason: String? {
|
||||||
switch self {
|
switch self {
|
||||||
case .unknown: return NSLocalizedString("An unknown error occured.", comment: "")
|
case .unknown: return NSLocalizedString("An unknown error occured.", comment: "")
|
||||||
@@ -60,6 +62,7 @@ enum OperationError: LocalizedError
|
|||||||
case .anisetteV1Error(let message): return String(format: NSLocalizedString("An error occurred when getting anisette data from a V1 server: %@. Try using another anisette server.", comment: ""), message)
|
case .anisetteV1Error(let message): return String(format: NSLocalizedString("An error occurred when getting anisette data from a V1 server: %@. Try using another anisette server.", comment: ""), message)
|
||||||
case .provisioningError(let result, let message): return String(format: NSLocalizedString("An error occurred when provisioning: %@%@. Please try again. If the issue persists, report it on GitHub Issues!", comment: ""), result, message != nil ? (" (" + message! + ")") : "")
|
case .provisioningError(let result, let message): return String(format: NSLocalizedString("An error occurred when provisioning: %@%@. Please try again. If the issue persists, report it on GitHub Issues!", comment: ""), result, message != nil ? (" (" + message! + ")") : "")
|
||||||
case .anisetteV3Error(let message): return String(format: NSLocalizedString("An error occurred when getting anisette data from a V3 server: %@. Please try again. If the issue persists, report it on GitHub Issues!", comment: ""), message)
|
case .anisetteV3Error(let message): return String(format: NSLocalizedString("An error occurred when getting anisette data from a V3 server: %@. Please try again. If the issue persists, report it on GitHub Issues!", comment: ""), message)
|
||||||
|
case .cacheClearError(let errors): return String(format: NSLocalizedString("An error occurred while clearing cache: %@", comment: ""), errors.joined(separator: "\n"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import Roxas
|
|||||||
|
|
||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
import AltSign
|
import AltSign
|
||||||
|
import minimuxer
|
||||||
|
|
||||||
@objc(ResignAppOperation)
|
@objc(ResignAppOperation)
|
||||||
final class ResignAppOperation: ResultOperation<ALTApplication>
|
final class ResignAppOperation: ResultOperation<ALTApplication>
|
||||||
@@ -181,7 +182,7 @@ private extension ResignAppOperation
|
|||||||
|
|
||||||
if app.isAltStoreApp
|
if app.isAltStoreApp
|
||||||
{
|
{
|
||||||
guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else { throw OperationError.unknownUDID }
|
guard let udid = fetch_udid()?.toString() as? String else { throw OperationError.unknownUDID }
|
||||||
guard let pairingFileString = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.devicePairingString) as? String else { throw OperationError.unknownUDID }
|
guard let pairingFileString = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.devicePairingString) as? String else { throw OperationError.unknownUDID }
|
||||||
additionalValues[Bundle.Info.devicePairingString] = pairingFileString
|
additionalValues[Bundle.Info.devicePairingString] = pairingFileString
|
||||||
additionalValues[Bundle.Info.deviceID] = udid
|
additionalValues[Bundle.Info.deviceID] = udid
|
||||||
@@ -202,7 +203,7 @@ private extension ResignAppOperation
|
|||||||
// The embedded certificate + certificate identifier are already in app bundle, no need to update them.
|
// The embedded certificate + certificate identifier are already in app bundle, no need to update them.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if infoDictionary.keys.contains(Bundle.Info.deviceID), let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String
|
else if infoDictionary.keys.contains(Bundle.Info.deviceID), let udid = fetch_udid()?.toString() as? String
|
||||||
{
|
{
|
||||||
// There is an ALTDeviceID entry, so assume the app is using AltKit and replace it with the device's UDID.
|
// There is an ALTDeviceID entry, so assume the app is using AltKit and replace it with the device's UDID.
|
||||||
additionalValues[Bundle.Info.deviceID] = udid
|
additionalValues[Bundle.Info.deviceID] = udid
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<color key="separatorColor" white="1" alpha="0.25" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="separatorColor" white="1" alpha="0.25" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<label key="tableFooterView" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SideStore 1.0" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="bUR-rp-Nw2">
|
<label key="tableFooterView" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SideStore 1.0" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="bUR-rp-Nw2">
|
||||||
<rect key="frame" x="0.0" y="1177" width="375" height="25"/>
|
<rect key="frame" x="0.0" y="1245" width="375" height="25"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="0.69999999999999996" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="0.69999999999999996" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
@@ -618,9 +618,42 @@
|
|||||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||||
</userDefinedRuntimeAttributes>
|
</userDefinedRuntimeAttributes>
|
||||||
<connections>
|
<connections>
|
||||||
<segue destination="g8a-Rf-zWa" kind="show" identifier="showErrorLog" id="SSW-vL-86I"/>
|
<segue destination="g8a-Rf-zWa" kind="show" id="vFC-Id-Ww6"/>
|
||||||
</connections>
|
</connections>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="eZ3-BT-q4D" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
|
<rect key="frame" x="0.0" y="1023" width="375" height="51"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="eZ3-BT-q4D" id="17m-VV-hzf">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Clear Cache" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="IbH-V1-ce3">
|
||||||
|
<rect key="frame" x="30" y="15.5" width="98.5" height="20.5"/>
|
||||||
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="FZe-BJ-fOm">
|
||||||
|
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||||
|
</imageView>
|
||||||
|
</subviews>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="FZe-BJ-fOm" firstAttribute="centerY" secondItem="17m-VV-hzf" secondAttribute="centerY" id="bGv-Np-5aO"/>
|
||||||
|
<constraint firstAttribute="trailingMargin" secondItem="FZe-BJ-fOm" secondAttribute="trailing" id="ccb-JP-Eqi"/>
|
||||||
|
<constraint firstItem="IbH-V1-ce3" firstAttribute="centerY" secondItem="17m-VV-hzf" secondAttribute="centerY" id="iQJ-gN-sRF"/>
|
||||||
|
<constraint firstItem="IbH-V1-ce3" firstAttribute="leading" secondItem="17m-VV-hzf" secondAttribute="leadingMargin" id="m1g-Y6-aT5"/>
|
||||||
|
</constraints>
|
||||||
|
</tableViewCellContentView>
|
||||||
|
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
||||||
|
<userDefinedRuntimeAttributes>
|
||||||
|
<userDefinedRuntimeAttribute type="number" keyPath="style">
|
||||||
|
<integer key="value" value="2"/>
|
||||||
|
</userDefinedRuntimeAttribute>
|
||||||
|
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||||
|
</userDefinedRuntimeAttributes>
|
||||||
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="VNn-u4-cN8" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="VNn-u4-cN8" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="1074" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="1074" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
|||||||
@@ -54,9 +54,11 @@ extension SettingsViewController
|
|||||||
case sendFeedback
|
case sendFeedback
|
||||||
case refreshAttempts
|
case refreshAttempts
|
||||||
case errorLog
|
case errorLog
|
||||||
|
case clearCache
|
||||||
case resetPairingFile
|
case resetPairingFile
|
||||||
case resetAdiPb
|
case resetAdiPb
|
||||||
case advancedSettings
|
case advancedSettings
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,6 +300,39 @@ private extension SettingsViewController
|
|||||||
self.present(viewController, animated: true, completion: nil)
|
self.present(viewController, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func clearCache()
|
||||||
|
{
|
||||||
|
let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to clear SideStore's cache?", comment: ""),
|
||||||
|
message: NSLocalizedString("This will remove all temporary files as well as backups for uninstalled apps.", comment: ""),
|
||||||
|
preferredStyle: .actionSheet)
|
||||||
|
alertController.addAction(UIAlertAction(title: UIAlertAction.cancel.title, style: UIAlertAction.cancel.style) { [weak self] _ in
|
||||||
|
self?.tableView.indexPathForSelectedRow.map { self?.tableView.deselectRow(at: $0, animated: true) }
|
||||||
|
})
|
||||||
|
alertController.addAction(UIAlertAction(title: NSLocalizedString("Clear Cache", comment: ""), style: .destructive) { [weak self] _ in
|
||||||
|
AppManager.shared.clearAppCache { result in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self?.tableView.indexPathForSelectedRow.map { self?.tableView.deselectRow(at: $0, animated: true) }
|
||||||
|
|
||||||
|
switch result
|
||||||
|
{
|
||||||
|
case .success: break
|
||||||
|
case .failure(let error):
|
||||||
|
let alertController = UIAlertController(title: NSLocalizedString("Unable to Clear Cache", comment: ""), message: error.localizedDescription, preferredStyle: .alert)
|
||||||
|
alertController.addAction(.ok)
|
||||||
|
self?.present(alertController, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if let popoverController = alertController.popoverPresentationController {
|
||||||
|
popoverController.sourceView = self.view
|
||||||
|
popoverController.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.present(alertController, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
@IBAction func handleDebugModeGesture(_ gestureRecognizer: UISwipeGestureRecognizer)
|
@IBAction func handleDebugModeGesture(_ gestureRecognizer: UISwipeGestureRecognizer)
|
||||||
{
|
{
|
||||||
self.debugGestureCounter += 1
|
self.debugGestureCounter += 1
|
||||||
@@ -489,6 +524,7 @@ extension SettingsViewController
|
|||||||
self.addRefreshAppsShortcut()
|
self.addRefreshAppsShortcut()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
case .credits:
|
case .credits:
|
||||||
let row = CreditsRow.allCases[indexPath.row]
|
let row = CreditsRow.allCases[indexPath.row]
|
||||||
switch row
|
switch row
|
||||||
@@ -526,6 +562,9 @@ extension SettingsViewController
|
|||||||
let toastView = ToastView(text: NSLocalizedString("Cannot Send Mail", comment: ""), detailText: nil)
|
let toastView = ToastView(text: NSLocalizedString("Cannot Send Mail", comment: ""), detailText: nil)
|
||||||
toastView.show(in: self)
|
toastView.show(in: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case .clearCache: self.clearCache()
|
||||||
|
|
||||||
case .resetPairingFile:
|
case .resetPairingFile:
|
||||||
let filename = "ALTPairingFile.mobiledevicepairing"
|
let filename = "ALTPairingFile.mobiledevicepairing"
|
||||||
let fm = FileManager.default
|
let fm = FileManager.default
|
||||||
@@ -578,6 +617,7 @@ extension SettingsViewController
|
|||||||
ELOG("UIApplication.openSettingsURLString invalid")
|
ELOG("UIApplication.openSettingsURLString invalid")
|
||||||
}
|
}
|
||||||
case .refreshAttempts, .errorLog: break
|
case .refreshAttempts, .errorLog: break
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default: break
|
default: break
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
// Configuration settings file format documentation can be found at:
|
// Configuration settings file format documentation can be found at:
|
||||||
// https://help.apple.com/xcode/#/dev745c5c974
|
// https://help.apple.com/xcode/#/dev745c5c974
|
||||||
|
|
||||||
MARKETING_VERSION = 0.5.3
|
MARKETING_VERSION = 0.5.4
|
||||||
CURRENT_PROJECT_VERSION = 5030
|
CURRENT_PROJECT_VERSION = 5040
|
||||||
|
|
||||||
// Vars to be overwritten by `CodeSigning.xcconfig` if exists
|
// Vars to be overwritten by `CodeSigning.xcconfig` if exists
|
||||||
DEVELOPMENT_TEAM = S32Z3HMYVQ
|
DEVELOPMENT_TEAM = S32Z3HMYVQ
|
||||||
|
|||||||
Reference in New Issue
Block a user