From 2e247f177349b1742cea50b15eb1af15c5aee865 Mon Sep 17 00:00:00 2001 From: Magesh K <47920326+mahee96@users.noreply.github.com> Date: Sat, 28 Dec 2024 14:54:38 +0530 Subject: [PATCH 01/59] [README]: updated iOS requirement --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index db8f16fb..acbc94e6 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ SideStore's goal is to provide an untethered sideloading experience. It's a comm - iOS 15+ - Rustup (`brew install rustup`) -Why iOS 14? Targeting such a recent version of iOS allows us to accelerate development, especially since not many developers have older devices to test on. This is corrobated by the fact that SwiftUI support is much better, allowing us to transistion to a more modern UI codebase. +Why iOS 15? Targeting such a recent version of iOS allows us to accelerate development, especially since not many developers have older devices to test on. This is corrobated by the fact that SwiftUI support is much better, allowing us to transistion to a more modern UI codebase. ## Project Overview ### SideStore From 2e01116f1fecd750e3ef42cf34805c5f0b8a1bd4 Mon Sep 17 00:00:00 2001 From: Magesh K <47920326+mahee96@users.noreply.github.com> Date: Sun, 29 Dec 2024 03:12:59 +0530 Subject: [PATCH 02/59] [Console-Log]: Added raw console logging in ErrorLog section (ladybug icon) --- AltStore.xcodeproj/project.pbxproj | 48 +++++ AltStore/AppDelegate.swift | 11 ++ .../Settings/Error Log/ConsoleLogView.swift | 123 +++++++++++++ .../Error Log/ErrorLogViewController.swift | 156 ++++++++++++++-- .../Utils/common/AbstractClassError.swift | 12 ++ SideStore/Utils/common/FileOutputStream.swift | 29 +++ SideStore/Utils/common/OutputStream.swift | 15 ++ SideStore/Utils/iostreams/ConsoleLog.swift | 73 ++++++++ SideStore/Utils/iostreams/ConsoleLogger.swift | 166 ++++++++++++++++++ 9 files changed, 616 insertions(+), 17 deletions(-) create mode 100644 AltStore/Settings/Error Log/ConsoleLogView.swift create mode 100644 SideStore/Utils/common/AbstractClassError.swift create mode 100644 SideStore/Utils/common/FileOutputStream.swift create mode 100644 SideStore/Utils/common/OutputStream.swift create mode 100644 SideStore/Utils/iostreams/ConsoleLog.swift create mode 100644 SideStore/Utils/iostreams/ConsoleLogger.swift diff --git a/AltStore.xcodeproj/project.pbxproj b/AltStore.xcodeproj/project.pbxproj index 87e53fa4..fe7ce1b3 100644 --- a/AltStore.xcodeproj/project.pbxproj +++ b/AltStore.xcodeproj/project.pbxproj @@ -61,6 +61,12 @@ A8945AA62D059B6100D86CBE /* Roxas.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A8945AA52D059B6100D86CBE /* Roxas.framework */; }; A8A543302D04F14400D72399 /* libfragmentzip.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A8A5432F2D04F0C100D72399 /* libfragmentzip.a */; }; A8BB34E52D04EC8E000A8B4D /* minimuxer-helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = A809F6A52D04DA1900F0F0F3 /* minimuxer-helpers.swift */; }; + A8C38C242D206A3A00E83DBD /* ConsoleLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C38C1D2D206A3A00E83DBD /* ConsoleLogger.swift */; }; + A8C38C262D206A3A00E83DBD /* ConsoleLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C38C1E2D206A3A00E83DBD /* ConsoleLog.swift */; }; + A8C38C2A2D206AC100E83DBD /* OutputStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C38C282D206AC100E83DBD /* OutputStream.swift */; }; + A8C38C2C2D206AD900E83DBD /* AbstractClassError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C38C2B2D206AD900E83DBD /* AbstractClassError.swift */; }; + A8C38C322D206B2500E83DBD /* FileOutputStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C38C312D206B2500E83DBD /* FileOutputStream.swift */; }; + A8C38C382D2084D000E83DBD /* ConsoleLogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C38C372D2084D000E83DBD /* ConsoleLogView.swift */; }; A8C6D50C2D1EE87600DF01F1 /* AltSign-Static in Frameworks */ = {isa = PBXBuildFile; productRef = A8C6D50B2D1EE87600DF01F1 /* AltSign-Static */; }; A8C6D5122D1EE8AF00DF01F1 /* AltSign-Static in Frameworks */ = {isa = PBXBuildFile; productRef = A8C6D5112D1EE8AF00DF01F1 /* AltSign-Static */; }; A8C6D5132D1EE8D700DF01F1 /* OpenSSL.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = A859ED5B2D1EE80D003DCC58 /* OpenSSL.xcframework */; }; @@ -624,6 +630,12 @@ A86202322D1F35640091187B /* AltStore.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AltStore.xcconfig; sourceTree = ""; }; A86202332D1F35640091187B /* AltStoreCore.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AltStoreCore.xcconfig; sourceTree = ""; }; A8945AA52D059B6100D86CBE /* Roxas.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Roxas.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A8C38C1D2D206A3A00E83DBD /* ConsoleLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsoleLogger.swift; sourceTree = ""; }; + A8C38C1E2D206A3A00E83DBD /* ConsoleLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsoleLog.swift; sourceTree = ""; }; + A8C38C282D206AC100E83DBD /* OutputStream.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutputStream.swift; sourceTree = ""; }; + A8C38C2B2D206AD900E83DBD /* AbstractClassError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AbstractClassError.swift; sourceTree = ""; }; + A8C38C312D206B2500E83DBD /* FileOutputStream.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileOutputStream.swift; sourceTree = ""; }; + A8C38C372D2084D000E83DBD /* ConsoleLogView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsoleLogView.swift; sourceTree = ""; }; A8D484D72D0CD306002C691D /* AltBackup.ipa */ = {isa = PBXFileReference; lastKnownFileType = file; path = AltBackup.ipa; sourceTree = ""; }; A8F66C3C2D04D433009689E6 /* em_proxy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = em_proxy.h; sourceTree = ""; }; A8F66C602D04D464009689E6 /* minimuxer.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = minimuxer.xcodeproj; sourceTree = ""; }; @@ -1161,6 +1173,34 @@ name = Products; sourceTree = ""; }; + A8C38C1C2D2068D100E83DBD /* Utils */ = { + isa = PBXGroup; + children = ( + A8C38C272D206AA500E83DBD /* common */, + A8C38C202D206A3A00E83DBD /* iostreams */, + ); + path = Utils; + sourceTree = ""; + }; + A8C38C202D206A3A00E83DBD /* iostreams */ = { + isa = PBXGroup; + children = ( + A8C38C1D2D206A3A00E83DBD /* ConsoleLogger.swift */, + A8C38C1E2D206A3A00E83DBD /* ConsoleLog.swift */, + ); + path = iostreams; + sourceTree = ""; + }; + A8C38C272D206AA500E83DBD /* common */ = { + isa = PBXGroup; + children = ( + A8C38C282D206AC100E83DBD /* OutputStream.swift */, + A8C38C312D206B2500E83DBD /* FileOutputStream.swift */, + A8C38C2B2D206AD900E83DBD /* AbstractClassError.swift */, + ); + path = common; + sourceTree = ""; + }; A8F66C072D04C025009689E6 /* SideStore */ = { isa = PBXGroup; children = ( @@ -1170,6 +1210,7 @@ B343F84D295F6323002B1159 /* em_proxy.xcodeproj */, 19104DB32909C06D00C49C7B /* EmotionalDamage */, B343F886295F7F9B002B1159 /* libfragmentzip.xcodeproj */, + A8C38C1C2D2068D100E83DBD /* Utils */, ); path = SideStore; sourceTree = ""; @@ -2095,6 +2136,7 @@ D589170128C7D93500E39C8B /* Error Log */ = { isa = PBXGroup; children = ( + A8C38C372D2084D000E83DBD /* ConsoleLogView.swift */, D57FE84328C7DB7100216002 /* ErrorLogViewController.swift */, D54DED1328CBC44B008B27A0 /* ErrorLogTableViewCell.swift */, 0EE7FDCC2BE9124400D1E390 /* ErrorDetailsViewController.swift */, @@ -2838,6 +2880,7 @@ files = ( BFDB6A0F22AB2776007EA6D6 /* SendAppOperation.swift in Sources */, BFDB6A0D22AAFC1A007EA6D6 /* OperationError.swift in Sources */, + A8C38C2C2D206AD900E83DBD /* AbstractClassError.swift in Sources */, BF74989B23621C0700CED65F /* ForwardingNavigationController.swift in Sources */, D57F2C9426E01BC700B9FA39 /* UIDevice+Vibration.swift in Sources */, BFD2478F2284C8F900981D42 /* Button.swift in Sources */, @@ -2865,11 +2908,14 @@ BFCCB51A245E3401001853EA /* VerifyAppOperation.swift in Sources */, BFF0B6982322CAB8007A79E1 /* InstructionsViewController.swift in Sources */, BF9ABA4522DCFF43008935CF /* BrowseViewController.swift in Sources */, + A8C38C242D206A3A00E83DBD /* ConsoleLogger.swift in Sources */, + A8C38C262D206A3A00E83DBD /* ConsoleLog.swift in Sources */, D5935AED29C39DE300C157EF /* SourceComponents.swift in Sources */, D5935AED29C39DE300C157EF /* SourceComponents.swift in Sources */, A8FD917C2D0478D200322782 /* VerificationError.swift in Sources */, D5A0537329B91DB400997551 /* SourceDetailContentViewController.swift in Sources */, BF770E5422BC044E002A40FE /* OperationContexts.swift in Sources */, + A8C38C322D206B2500E83DBD /* FileOutputStream.swift in Sources */, BFD2478C2284C4C300981D42 /* AppIconImageView.swift in Sources */, BF8F69C422E662D300049BA1 /* AppViewController.swift in Sources */, BFF0B68E23219520007A79E1 /* PatreonViewController.swift in Sources */, @@ -2929,12 +2975,14 @@ BF9ABA4B22DD1380008935CF /* NavigationBar.swift in Sources */, A8FD917B2D0472DD00322782 /* DeprecatedAPIs.swift in Sources */, BF6C8FAC242935ED00125131 /* NSAttributedString+Markdown.m in Sources */, + A8C38C382D2084D000E83DBD /* ConsoleLogView.swift in Sources */, BFF00D322501BDA100746320 /* BackgroundRefreshAppsOperation.swift in Sources */, BF0C4EBD22A1BD8B009A2DD7 /* AppManager.swift in Sources */, BF2901312318F7A800D88A45 /* AppBannerView.swift in Sources */, 0EE7FDC42BE8BC7900D1E390 /* ALTLocalizedError.swift in Sources */, BFF00D342501BDCF00746320 /* IntentHandler.swift in Sources */, BFDBBD80246CB84F004ED2F3 /* RemoveAppBackupOperation.swift in Sources */, + A8C38C2A2D206AC100E83DBD /* OutputStream.swift in Sources */, BFF0B6942321CB85007A79E1 /* AuthenticationViewController.swift in Sources */, 0EE7FDCD2BE9124400D1E390 /* ErrorDetailsViewController.swift in Sources */, D561AF822B21669400BF59C6 /* VerifyAppPledgeOperation.swift in Sources */, diff --git a/AltStore/AppDelegate.swift b/AltStore/AppDelegate.swift index 49c1bda1..ec270382 100644 --- a/AltStore/AppDelegate.swift +++ b/AltStore/AppDelegate.swift @@ -41,8 +41,14 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { private let intentHandler = IntentHandler() private let viewAppIntentHandler = ViewAppIntentHandler() + public let consoleLog = ConsoleLog() + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + + // start logging to console immediately on startup + consoleLog.startCapturing() + // Override point for customization after application launch. // UserDefaults.standard.setValue(true, forKey: "com.apple.CoreData.MigrationDebug") // UserDefaults.standard.setValue(true, forKey: "com.apple.CoreData.SQLDebug") @@ -130,6 +136,11 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { default: return nil } } + + func applicationWillTerminate(_ application: UIApplication) { + // Stop console logging and clean up resources + consoleLog.stopCapturing() + } } extension AppDelegate diff --git a/AltStore/Settings/Error Log/ConsoleLogView.swift b/AltStore/Settings/Error Log/ConsoleLogView.swift new file mode 100644 index 00000000..36568db3 --- /dev/null +++ b/AltStore/Settings/Error Log/ConsoleLogView.swift @@ -0,0 +1,123 @@ +// +// ConsoleLogView.swift +// AltStore +// +// Created by Magesh K on 29/12/24. +// Copyright © 2024 SideStore. All rights reserved. +// +import SwiftUI + +class ConsoleLogViewModel: ObservableObject { + @Published var logLines: [String] = [] + + private var fileWatcher: DispatchSourceFileSystemObject? + + private let backgroundQueue = DispatchQueue(label: "com.myapp.backgroundQueue", qos: .background) + private var logURL: URL + + init(logURL: URL) { + self.logURL = logURL + startFileWatcher() // Start monitoring the log file for changes + reloadLogData() // Load initial log data + } + + private func startFileWatcher() { + let fileDescriptor = open(logURL.path, O_RDONLY) + guard fileDescriptor != -1 else { + print("Unable to open file for reading.") + return + } + + fileWatcher = DispatchSource.makeFileSystemObjectSource(fileDescriptor: fileDescriptor, eventMask: .write, queue: backgroundQueue) + fileWatcher?.setEventHandler { + self.reloadLogData() + } + fileWatcher?.resume() + } + + private func reloadLogData() { + if let fileContents = try? String(contentsOf: logURL) { + let lines = fileContents.split(whereSeparator: \.isNewline).map { String($0) } + DispatchQueue.main.async { + self.logLines = lines + } + } + } + + deinit { + fileWatcher?.cancel() + } +} + + +public struct ConsoleLogView: View { + + @ObservedObject var viewModel: ConsoleLogViewModel + @State private var isAtBottom: Bool = true +// private let linesToShow: Int = 100 // Number of lines to show at once + @State private var scrollToBottom: Bool = false // State variable to trigger scroll + + init(logURL: URL) { + self.viewModel = ConsoleLogViewModel(logURL: logURL) + } + + public var body: some View { + VStack { + // Custom Header Bar (similar to QuickLook's preview screen) + HStack { + Text("Console Log") + .font(.system(size: 22, weight: .semibold)) + .foregroundColor(.white) + Spacer() + SwiftUI.Button(action: { + scrollToBottom.toggle() + }) { + Image(systemName: "ellipsis") + .foregroundColor(.white) + .imageScale(.large) + } + } + .padding(15) + .padding(.top, 5) + .padding(.bottom, 2.5) + .background(Color.black.opacity(0.9)) + .overlay( + Rectangle() + .frame(height: 1) + .foregroundColor(Color.gray.opacity(0.2)), alignment: .bottom + ) + + // Main Log Display (scrollable area) + ScrollView(.vertical) { + ScrollViewReader { scrollViewProxy in + LazyVStack(alignment: .leading, spacing: 4) { + ForEach(viewModel.logLines.indices, id: \.self) { index in + Text(viewModel.logLines[index]) + .font(.system(size: 12, design: .monospaced)) + .foregroundColor(.white) + } + } + .onChange(of: scrollToBottom) { _ in + scrollToBottomIfNeeded(scrollViewProxy: scrollViewProxy) + } + } + } + } + .background(Color.black) // Set background color to mimic QL's dark theme + .edgesIgnoringSafeArea(.all) + } + + // Scroll to the last index (bottom) only if logLines is not empty + private func scrollToBottomIfNeeded(scrollViewProxy: ScrollViewProxy) { + // Ensure we have log data before attempting to scroll + guard !viewModel.logLines.isEmpty else { + return + } + + let last = viewModel.logLines.count - 1 + let lastIdx = viewModel.logLines.indices.last + assert(last == lastIdx) +// scrollViewProxy.scrollTo(lastIdx, anchor: .bottom) + scrollViewProxy.scrollTo(last, anchor: .bottom) + } +} diff --git a/AltStore/Settings/Error Log/ErrorLogViewController.swift b/AltStore/Settings/Error Log/ErrorLogViewController.swift index 498609c7..28578194 100644 --- a/AltStore/Settings/Error Log/ErrorLogViewController.swift +++ b/AltStore/Settings/Error Log/ErrorLogViewController.swift @@ -16,6 +16,7 @@ import Roxas import Nuke import QuickLook +import SwiftUI final class ErrorLogViewController: UITableViewController, QLPreviewControllerDelegate { @@ -216,15 +217,124 @@ private extension ErrorLogViewController } } - @IBAction func showMinimuxerLogs(_ sender: UIBarButtonItem) - { - // Show minimuxer.log - let previewController = QLPreviewController() - previewController.dataSource = self - let navigationController = UINavigationController(rootViewController: previewController) - present(navigationController, animated: true, completion: nil) + + enum LogView: String { + case consoleLog = "console-log" + case minimuxerLog = "minimuxer-log" + +// // This class will manage the QLPreviewController and the timer. +// private class LogViewManager { +// var previewController: QLPreviewController +// var refreshTimer: Timer? +// +// init(previewController: QLPreviewController) { +// self.previewController = previewController +// } +// +// // Start refreshing the preview controller every second +// func startRefreshing() { +// refreshTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(refreshPreview), userInfo: nil, repeats: true) +// } +// +// @objc private func refreshPreview() { +// previewController.reloadData() +// } +// +// // Stop the timer to prevent leaks +// func stopRefreshing() { +// refreshTimer?.invalidate() +// refreshTimer = nil +// } +// } +// +// // Method to get the QLPreviewController for this log type +// func getViewController(_ dataSource: QLPreviewControllerDataSource) -> QLPreviewController { +// let previewController = QLPreviewController() +// previewController.restorationIdentifier = self.rawValue +// previewController.dataSource = dataSource +// +// // Create LogViewManager and start refreshing +// let manager = LogViewManager(previewController: previewController) +// manager.startRefreshing() +// +// return previewController +// } + + // This class will manage the QLPreviewController and the timer. + private class LogViewManager { + var previewController: QLPreviewController + var refreshTimer: Timer? + var logView: LogView + + init(previewController: QLPreviewController, logView: LogView) { + self.previewController = previewController + self.logView = logView + } + + // Start refreshing the preview controller every second + func startRefreshing() { + refreshTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(refreshPreview), userInfo: nil, repeats: true) + } + + @objc private func refreshPreview() { + previewController.reloadData() + } + + // Stop the timer to prevent leaks + func stopRefreshing() { + refreshTimer?.invalidate() + refreshTimer = nil + } + + func updateLogPath() { + // Force the QLPreviewController to reload by changing the file path + previewController.reloadData() + } + } + + // Method to get the QLPreviewController for this log type + func getViewController(_ dataSource: QLPreviewControllerDataSource) -> QLPreviewController { + let previewController = QLPreviewController() + previewController.restorationIdentifier = self.rawValue + previewController.dataSource = dataSource + + // Create LogViewManager and start refreshing + let manager = LogViewManager(previewController: previewController, logView: self) + manager.startRefreshing() + + return previewController + } + + func getLogPath() -> URL { + switch self { + case .consoleLog: + let appDelegate = UIApplication.shared.delegate as! AppDelegate + return appDelegate.consoleLog.logFileURL + case .minimuxerLog: + return FileManager.default.documentsDirectory.appendingPathComponent("minimuxer.log") + } + } } + @IBAction func showMinimuxerLogs(_ sender: UIBarButtonItem) { + // Create the SwiftUI ConsoleLogView with the URL + let consoleLogView = ConsoleLogView(logURL: (UIApplication.shared.delegate as! AppDelegate).consoleLog.logFileURL) + + // Create the UIHostingController + let consoleLogController = UIHostingController(rootView: consoleLogView) + + // Configure the bottom sheet presentation + consoleLogController.modalPresentationStyle = .pageSheet + if let sheet = consoleLogController.sheetPresentationController { + sheet.detents = [.medium(), .large()] // You can adjust the size of the sheet (medium/large) + sheet.prefersGrabberVisible = true // Optional: Shows a grabber at the top of the sheet + sheet.selectedDetentIdentifier = .large // Default size when presented + } + + // Present the bottom sheet + present(consoleLogController, animated: true, completion: nil) + } + @IBAction func clearLoggedErrors(_ sender: UIBarButtonItem) { let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to clear the error log?", comment: ""), message: nil, preferredStyle: .actionSheet) @@ -297,13 +407,21 @@ private extension ErrorLogViewController // All logs since the app launched. let position = store.position(timeIntervalSinceLatestBoot: 0) - let predicate = NSPredicate(format: "subsystem == %@", Logger.altstoreSubsystem) - - let entries = try store.getEntries(at: position, matching: predicate) - .compactMap { $0 as? OSLogEntryLog } - .map { "[\($0.date.formatted())] [\($0.category)] [\($0.level.localizedName)] \($0.composedMessage)" } - - let outputText = entries.joined(separator: "\n") +// let predicate = NSPredicate(format: "subsystem == %@", Logger.altstoreSubsystem) +// +// let entries = try store.getEntries(at: position, matching: predicate) +// .compactMap { $0 as? OSLogEntryLog } +// .map { "[\($0.date.formatted())] [\($0.category)] [\($0.level.localizedName)] \($0.composedMessage)" } +// + // Remove the predicate to get all log entries +// let entries = try store.getEntries(at: position) +// .compactMap { $0 as? OSLogEntryLog } +// .map { "[\($0.date.formatted())] [\($0.category)] [\($0.level.localizedName)] \($0.composedMessage)" } + + let entries = try store.getEntries(at: position) + +// let outputText = entries.joined(separator: "\n") + let outputText = entries.map { $0.description }.joined(separator: "\n") let outputDirectory = FileManager.default.uniqueTemporaryURL() try FileManager.default.createDirectory(at: outputDirectory, withIntermediateDirectories: true) @@ -412,9 +530,13 @@ extension ErrorLogViewController: QLPreviewControllerDataSource { return 1 } - func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem { - let fileURL = FileManager.default.documentsDirectory.appendingPathComponent("minimuxer.log") - return fileURL as QLPreviewItem + func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem + { + guard let identifier = controller.restorationIdentifier, + let logView = LogView(rawValue: identifier) else { + fatalError("Invalid restorationIdentifier") + } + return logView.getLogPath() as QLPreviewItem } } diff --git a/SideStore/Utils/common/AbstractClassError.swift b/SideStore/Utils/common/AbstractClassError.swift new file mode 100644 index 00000000..15d3004e --- /dev/null +++ b/SideStore/Utils/common/AbstractClassError.swift @@ -0,0 +1,12 @@ +// +// OutputStream.swift +// AltStore +// +// Created by Magesh K on 28/12/24. +// Copyright © 2024 SideStore. All rights reserved. +// + +public enum AbstractClassError: Error { + case abstractInitializerInvoked + case abstractMethodInvoked +} diff --git a/SideStore/Utils/common/FileOutputStream.swift b/SideStore/Utils/common/FileOutputStream.swift new file mode 100644 index 00000000..fdee0a2f --- /dev/null +++ b/SideStore/Utils/common/FileOutputStream.swift @@ -0,0 +1,29 @@ +// +// FileOutputStream.swift +// AltStore +// +// Created by Magesh K on 28/12/24. +// Copyright © 2024 SideStore. All rights reserved. +// + +import Foundation + +public class FileOutputStream: OutputStream { + private let fileHandle: FileHandle + + init(_ fileHandle: FileHandle) { + self.fileHandle = fileHandle + } + + public func write(_ data: Data) { + fileHandle.write(data) + } + + public func flush() { + fileHandle.synchronizeFile() + } + + public func close() { + fileHandle.closeFile() + } +} diff --git a/SideStore/Utils/common/OutputStream.swift b/SideStore/Utils/common/OutputStream.swift new file mode 100644 index 00000000..ddc421ca --- /dev/null +++ b/SideStore/Utils/common/OutputStream.swift @@ -0,0 +1,15 @@ +// +// OutputStream.swift +// AltStore +// +// Created by Magesh K on 28/12/24. +// Copyright © 2024 SideStore. All rights reserved. +// + +import Foundation + +public protocol OutputStream { + func write(_ data: Data) + func flush() + func close() +} diff --git a/SideStore/Utils/iostreams/ConsoleLog.swift b/SideStore/Utils/iostreams/ConsoleLog.swift new file mode 100644 index 00000000..3b1177a5 --- /dev/null +++ b/SideStore/Utils/iostreams/ConsoleLog.swift @@ -0,0 +1,73 @@ +// +// ConsoleLog.swift +// AltStore +// +// Created by Magesh K on 25/11/24. +// Copyright © 2024 SideStore. All rights reserved. +// +// + +import Foundation + +class ConsoleLog { + private static let CONSOLE_LOGS_DIRECTORY = "ConsoleLogs" + private static let CONSOLE_LOG_NAME_PREFIX = "console" + private static let CONSOLE_LOG_EXTN = ".log" + + private lazy var consoleLogger: ConsoleLogger = { + let logFileHandle = createLogFileHandle() + let fileOutputStream = FileOutputStream(logFileHandle) + + return UnBufferedConsoleLogger(stream: fileOutputStream) + }() + + private lazy var consoleLogsDir: URL = { + // create a directory for console logs + let docsDir = FileManager.default.documentsDirectory + let consoleLogsDir = docsDir.appendingPathComponent(ConsoleLog.CONSOLE_LOGS_DIRECTORY) + if !FileManager.default.fileExists(atPath: consoleLogsDir.path) { + try! FileManager.default.createDirectory(at: consoleLogsDir, withIntermediateDirectories: true, attributes: nil) + } + return consoleLogsDir + }() + + public lazy var logName: String = { + logFileURL.lastPathComponent + }() + + public lazy var logFileURL: URL = { + // get current timestamp + let currentTime = Date() + let dateTimeStamp = ConsoleLog.getDateInTimeStamp(date: currentTime) + + // create a log file with the current timestamp + let logName = "\(ConsoleLog.CONSOLE_LOG_NAME_PREFIX)-\(dateTimeStamp)\(ConsoleLog.CONSOLE_LOG_EXTN)" + let logFileURL = consoleLogsDir.appendingPathComponent(logName) + return logFileURL + }() + + + private func createLogFileHandle() -> FileHandle { + if !FileManager.default.fileExists(atPath: logFileURL.path) { + FileManager.default.createFile(atPath: logFileURL.path, contents: nil, attributes: nil) + } + + // return the file handle + return try! FileHandle(forWritingTo: logFileURL) + } + + private static func getDateInTimeStamp(date: Date) -> String { + let formatter = DateFormatter() + formatter.dateFormat = "yyyyMMdd_HHmmss" // Format: 20241228_142345 + return formatter.string(from: date) + } + + func startCapturing() { + consoleLogger.startCapturing() + } + + func stopCapturing() { + consoleLogger.stopCapturing() + } +} + diff --git a/SideStore/Utils/iostreams/ConsoleLogger.swift b/SideStore/Utils/iostreams/ConsoleLogger.swift new file mode 100644 index 00000000..479cc5c8 --- /dev/null +++ b/SideStore/Utils/iostreams/ConsoleLogger.swift @@ -0,0 +1,166 @@ +// +// ConsoleCapture.swift +// AltStore +// +// Created by Magesh K on 25/11/24. +// Copyright © 2024 SideStore. All rights reserved. +// + +import Foundation + +protocol ConsoleLogger{ + func startCapturing() + func stopCapturing() +} + +public class AbstractConsoleLogger: ConsoleLogger{ + var outPipe: Pipe? + var errPipe: Pipe? + + var outputHandle: FileHandle? + var errorHandle: FileHandle? + + var originalStdout: Int32? + var originalStderr: Int32? + + let ostream: T + + let writeQueue = DispatchQueue(label: "async-write-queue") + + public init(stream: T) throws { + // Since swift doesn't support compile time abstract classes Instantiation checking, + // we are using runtime check to prevent direct instantiation :( + if Self.self === AbstractConsoleLogger.self { + throw AbstractClassError.abstractInitializerInvoked + } + + self.ostream = stream + } + + deinit { + stopCapturing() + } + + public func startCapturing() { // made it public coz, let client ask for capturing + + // if already initialized within current instance, bail out + guard outPipe == nil, errPipe == nil else { + return + } + + // Create new pipes for stdout and stderr + self.outPipe = Pipe() + self.errPipe = Pipe() + + outputHandle = self.outPipe?.fileHandleForReading + errorHandle = self.errPipe?.fileHandleForReading + + // Store original file descriptors + originalStdout = dup(STDOUT_FILENO) + originalStderr = dup(STDERR_FILENO) + + // Redirect stdout and stderr to our pipes + dup2(self.outPipe?.fileHandleForWriting.fileDescriptor ?? -1, STDOUT_FILENO) + dup2(self.errPipe?.fileHandleForWriting.fileDescriptor ?? -1, STDERR_FILENO) + + // Setup readability handlers for raw data + setupReadabilityHandler(for: outputHandle, isError: false) + setupReadabilityHandler(for: errorHandle, isError: true) + } + + private func setupReadabilityHandler(for handle: FileHandle?, isError: Bool) { + handle?.readabilityHandler = { [weak self] handle in + let data = handle.availableData + if !data.isEmpty { + self?.writeQueue.async { + try? self?.writeData(data) + } + + // Forward to original std stream + if let originalFD = isError ? self?.originalStderr : self?.originalStdout { + data.withUnsafeBytes { (bufferPointer) -> Void in + if let baseAddress = bufferPointer.baseAddress, bufferPointer.count > 0 { + write(originalFD, baseAddress, bufferPointer.count) + } + } + } + } + } + } + + func writeData(_ data: Data) throws { + throw AbstractClassError.abstractMethodInvoked + } + + func stopCapturing() { + ostream.close() + + // Restore original stdout and stderr + if let stdout = originalStdout { + dup2(stdout, STDOUT_FILENO) + close(stdout) + } + if let stderr = originalStderr { + dup2(stderr, STDERR_FILENO) + close(stderr) + } + + // Clean up + outPipe?.fileHandleForReading.readabilityHandler = nil + errPipe?.fileHandleForReading.readabilityHandler = nil + outPipe = nil + errPipe = nil + outputHandle = nil + errorHandle = nil + originalStdout = nil + originalStderr = nil + } +} + + +public class UnBufferedConsoleLogger: AbstractConsoleLogger { + + required override init(stream: T) { + // cannot throw abstractInitializerInvoked, so need to override else client needs to handle it unnecessarily + try! super.init(stream: stream) + } + + override func writeData(_ data: Data) throws { + // directly write data to the stream without buffering + ostream.write(data) + } +} + +public class BufferedConsoleLogger: AbstractConsoleLogger { + + // Buffer size (bytes) and storage + private let maxBufferSize: Int + private var bufferedData = Data() + + required init(stream: T, bufferSize: Int = 1024) { + self.maxBufferSize = bufferSize + try! super.init(stream: stream) + } + + override func writeData(_ data: Data) throws { + // Append data to buffer + self.bufferedData.append(data) + + // Check if the buffer is full and flush + if self.bufferedData.count >= self.maxBufferSize { + self.flushBuffer() + } + } + + private func flushBuffer() { + // Write all buffered data to the stream + ostream.write(bufferedData) + bufferedData.removeAll() + } + + override func stopCapturing() { + // Flush buffer and close the file handles first + flushBuffer() + super.stopCapturing() + } +} From fba5ca4e1219d63ee3172eccb751104c32f6d2f4 Mon Sep 17 00:00:00 2001 From: Magesh K <47920326+mahee96@users.noreply.github.com> Date: Mon, 30 Dec 2024 00:48:40 +0530 Subject: [PATCH 03/59] [apps-v2]: updated to latest commit --- SideStore/apps-v2.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SideStore/apps-v2.json b/SideStore/apps-v2.json index 9166c28c..655c26c7 160000 --- a/SideStore/apps-v2.json +++ b/SideStore/apps-v2.json @@ -1 +1 @@ -Subproject commit 9166c28c455326d31cafe89acf2f7c9caa9384c1 +Subproject commit 655c26c7212512273fb86f50885de9da8ba62ffb From a547d2bc8ae04264749a43fe32271b62150cc6ac Mon Sep 17 00:00:00 2001 From: Magesh K <47920326+mahee96@users.noreply.github.com> Date: Mon, 30 Dec 2024 00:49:06 +0530 Subject: [PATCH 04/59] [apps-v2]: updated to push beta(nightly) updates to sources --- .github/workflows/nightly.yml | 57 +++++++++++++++++------------------ 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 6fbd6c5b..bed65fc6 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -249,37 +249,36 @@ jobs: echo "LOCALIZED_DESCRIPTION=This is nightly release for revision: ${{ github.sha }}" >> $GITHUB_ENV echo "DOWNLOAD_URL=https://github.com/SideStore/SideStore/releases/download/nightly/SideStore.ipa" >> $GITHUB_ENV - # - name: Checkout SideStore/apps-v2.json - # if: ${{ steps.check_publish.outcome == 'success' }} - # uses: actions/checkout@v4 - # with: - # # Repository name with owner. For example, actions/checkout - # # Default: ${{ github.repository }} - # repository: 'SideStore/apps-v2.json' - # # ref: 'main' # TODO: use branches for alpha and beta tracks? so as to avoid push collision? - # ref: 'nightly' # TODO: use branches for alpha and beta tracks? so as to avoid push collision? - # # token: ${{ github.token }} - # token: ${{ secrets.APPS_DEPLOY_KEY }} - # path: 'SideStore/apps-v2.json' + - name: Checkout SideStore/apps-v2.json + if: ${{ steps.check_publish.outcome == 'success' }} + uses: actions/checkout@v4 + with: + # Repository name with owner. For example, actions/checkout + # Default: ${{ github.repository }} + repository: 'SideStore/apps-v2.json' + # ref: 'main' # TODO: use branches for alpha and beta tracks? so as to avoid push collision? + ref: 'nightly' # TODO: use branches for alpha and beta tracks? so as to avoid push collision? + # token: ${{ github.token }} + token: ${{ secrets.APPS_DEPLOY_KEY }} + path: 'SideStore/apps-v2.json' - # - name: Publish to SideStore/apps-v2.json - # if: ${{ steps.check_publish.outcome == 'success' }} - # run: | - # # Copy and execute the update script - # pushd SideStore/apps-v2.json/ + - name: Publish to SideStore/apps-v2.json + if: ${{ steps.check_publish.outcome == 'success' }} + run: | + # Copy and execute the update script + pushd SideStore/apps-v2.json/ - # # Configure Git user (committer details) - # git config user.name "GitHub Actions" - # git config user.email "github-actions@github.com" + # Configure Git user (committer details) + # git config user.name "GitHub Actions" + # git config user.email "github-actions@github.com" - # # Make the update script executable and run it - # python3 ../../update_apps.py "./_includes/source.json" + # update the source.json + python3 ../../update_apps.py "./_includes/source.json" - # # Commit changes and push using SSH - # git add ./_includes/source.json - # git commit -m " - updated for $SHORT_COMMIT deployment" || echo "No changes to commit" + # Commit changes and push using SSH + git add ./_includes/source.json + git commit -m " - updated for $SHORT_COMMIT deployment" || echo "No changes to commit" - # git status - # # git push origin HEAD:main - # git push origin HEAD:nightly - # popd + git status + git push origin HEAD:nightly + popd From 7ae10c6022133c958e08bfa0336b7ab6da288077 Mon Sep 17 00:00:00 2001 From: Magesh K <47920326+mahee96@users.noreply.github.com> Date: Mon, 30 Dec 2024 03:24:31 +0530 Subject: [PATCH 05/59] [CI]: updated nightly workflow to match apps-v2 --- .github/workflows/nightly.yml | 9 +-- update_apps.py | 109 ++++++++++++++++++++++------------ 2 files changed, 75 insertions(+), 43 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index bed65fc6..bb225c85 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -21,7 +21,9 @@ jobs: steps: - name: Set current build as BETA - run: echo "IS_BETA=1" >> $GITHUB_ENV + run: | + echo "IS_BETA=1" >> $GITHUB_ENV + echo "RELEASE_CHANNEL=beta" >> $GITHUB_ENV - name: Checkout code uses: actions/checkout@v4 @@ -242,7 +244,6 @@ jobs: run: | echo "VERSION_IPA=$VERSION_IPA" >> $GITHUB_ENV echo "VERSION_DATE=$FORMATTED_DATE" >> $GITHUB_ENV - echo "BETA=true" >> $GITHUB_ENV echo "COMMIT_ID=$SHORT_COMMIT" >> $GITHUB_ENV echo "SIZE=$IPA_SIZE" >> $GITHUB_ENV echo "SHA256=$SHA256_HASH" >> $GITHUB_ENV @@ -269,8 +270,8 @@ jobs: pushd SideStore/apps-v2.json/ # Configure Git user (committer details) - # git config user.name "GitHub Actions" - # git config user.email "github-actions@github.com" + git config user.name "GitHub Actions" + git config user.email "github-actions@github.com" # update the source.json python3 ../../update_apps.py "./_includes/source.json" diff --git a/update_apps.py b/update_apps.py index 931fbd98..f719938f 100755 --- a/update_apps.py +++ b/update_apps.py @@ -5,9 +5,19 @@ import json import sys # Set environment variables with default values +# VERSION_IPA = os.getenv("VERSION_IPA") +# VERSION_DATE = os.getenv("VERSION_DATE") +# RELEASE_CHANNEL = os.getenv("RELEASE_CHANNEL") +# COMMIT_ID = os.getenv("COMMIT_ID") +# SIZE = os.getenv("SIZE") +# SHA256 = os.getenv("SHA256") +# LOCALIZED_DESCRIPTION = os.getenv("LOCALIZED_DESCRIPTION") +# DOWNLOAD_URL = os.getenv("DOWNLOAD_URL") +# BUNDLE_IDENTIFIER = os.getenv("BUNDLE_IDENTIFIER") + VERSION_IPA = os.getenv("VERSION_IPA", "0.0.0") VERSION_DATE = os.getenv("VERSION_DATE", "2000-12-18T00:00:00Z") -BETA = os.getenv("BETA", "true").lower() == "true" # Convert to boolean +RELEASE_CHANNEL = os.getenv("RELEASE_CHANNEL", "alpha") COMMIT_ID = os.getenv("COMMIT_ID", "1234567") SIZE = int(os.getenv("SIZE", "0")) # Convert to integer SHA256 = os.getenv("SHA256", "") @@ -24,9 +34,10 @@ input_file = sys.argv[1] print(f"Input File: {input_file}") # Debugging the environment variables +print(" ====> Required parameter list <====") print("Version:", VERSION_IPA) print("Version Date:", VERSION_DATE) -print("Beta:", BETA) +print("ReleaseChannel:", RELEASE_CHANNEL) print("Commit ID:", COMMIT_ID) print("Size:", SIZE) print("Sha256:", SHA256) @@ -41,50 +52,70 @@ except Exception as e: print(f"Error reading the input file: {e}") sys.exit(1) +if (VERSION_IPA == None or \ + VERSION_DATE == None or \ + RELEASE_CHANNEL == None or \ + SIZE == None or \ + SHA256 == None or \ + LOCALIZED_DESCRIPTION == None or \ + DOWNLOAD_URL == None): + print("One or more required parameter(s) were not defined as environment variable(s)") + sys.exit(1) + +# make it lowecase +RELEASE_CHANNEL = RELEASE_CHANNEL.lower() +# Convert to integer +SIZE = int(SIZE) + +if RELEASE_CHANNEL != 'stable' and COMMIT_ID is None: + print("Commit ID cannot be empty when ReleaseChannel is not 'stable' ") + sys.exit(1) + +version = data.get("version") +if int(version) < 2: + print("Only v2 and above are supported for direct updates to sources.json on push") + sys.exit(1) + # Process the JSON data updated = False for app in data.get("apps", []): if app.get("bundleIdentifier") == BUNDLE_IDENTIFIER: - # Update app-level metadata - app.update({ - "version": VERSION_IPA, - "versionDate": VERSION_DATE, - "beta": BETA, - "commitID": COMMIT_ID, - "size": SIZE, - "sha256": SHA256, - "localizedDescription": LOCALIZED_DESCRIPTION, - "downloadURL": DOWNLOAD_URL, - }) + if RELEASE_CHANNEL == "stable" : + # Update app-level metadata for store front page + app.update({ + "version": VERSION_IPA, + "versionDate": VERSION_DATE, + "size": SIZE, + "sha256": SHA256, + "localizedDescription": LOCALIZED_DESCRIPTION, + "downloadURL": DOWNLOAD_URL, + }) # Process the versions array - versions = app.get("versions", []) - if not versions or not (versions[0].get("version") == VERSION_IPA and versions[0].get("beta") == BETA): - # Prepend a new version if no matching version exists - new_version = { - "version": VERSION_IPA, - "date": VERSION_DATE, - "localizedDescription": LOCALIZED_DESCRIPTION, - "downloadURL": DOWNLOAD_URL, - "beta": BETA, - "commitID": COMMIT_ID, - "size": SIZE, - "sha256": SHA256, - } - versions.insert(0, new_version) + channels = app.get("releaseChannels", {}) + if not channels: + app["releaseChannels"] = channels + + # create an entry and keep ready + new_version = { + "version": VERSION_IPA, + "date": VERSION_DATE, + "localizedDescription": LOCALIZED_DESCRIPTION, + "downloadURL": DOWNLOAD_URL, + "size": SIZE, + "sha256": SHA256, + } + # add commit ID if release is not stable + if RELEASE_CHANNEL != 'stable': + new_version["commitID"] = COMMIT_ID + + if not channels.get(RELEASE_CHANNEL): + # there was no entries in this release channel so create one + channels[RELEASE_CHANNEL] = [new_version] else: - # Update the existing version object - versions[0].update({ - "version": VERSION_IPA, - "date": VERSION_DATE, - "localizedDescription": LOCALIZED_DESCRIPTION, - "downloadURL": DOWNLOAD_URL, - "beta": BETA, - "commitID": COMMIT_ID, - "size": SIZE, - "sha256": SHA256, - }) - app["versions"] = versions + # Update the existing TOP version object entry + channels[RELEASE_CHANNEL][0] = new_version + updated = True break From 1666c40bd859dd2f1f878f1bcff250fed9fa6442 Mon Sep 17 00:00:00 2001 From: Magesh K <47920326+mahee96@users.noreply.github.com> Date: Mon, 30 Dec 2024 03:25:26 +0530 Subject: [PATCH 06/59] [apps-v2]: sync submodule to latest commit --- SideStore/apps-v2.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SideStore/apps-v2.json b/SideStore/apps-v2.json index 655c26c7..e0914d34 160000 --- a/SideStore/apps-v2.json +++ b/SideStore/apps-v2.json @@ -1 +1 @@ -Subproject commit 655c26c7212512273fb86f50885de9da8ba62ffb +Subproject commit e0914d346315338d1600acda48d18982bb902fef From 82c04cd4237f0c388e7ac3421c40807edfd59925 Mon Sep 17 00:00:00 2001 From: Magesh K <47920326+mahee96@users.noreply.github.com> Date: Mon, 30 Dec 2024 03:27:03 +0530 Subject: [PATCH 07/59] [CI]: updated sources.json updater script --- update_apps.py | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/update_apps.py b/update_apps.py index f719938f..b053816b 100755 --- a/update_apps.py +++ b/update_apps.py @@ -5,25 +5,26 @@ import json import sys # Set environment variables with default values -# VERSION_IPA = os.getenv("VERSION_IPA") -# VERSION_DATE = os.getenv("VERSION_DATE") -# RELEASE_CHANNEL = os.getenv("RELEASE_CHANNEL") -# COMMIT_ID = os.getenv("COMMIT_ID") -# SIZE = os.getenv("SIZE") -# SHA256 = os.getenv("SHA256") -# LOCALIZED_DESCRIPTION = os.getenv("LOCALIZED_DESCRIPTION") -# DOWNLOAD_URL = os.getenv("DOWNLOAD_URL") -# BUNDLE_IDENTIFIER = os.getenv("BUNDLE_IDENTIFIER") +VERSION_IPA = os.getenv("VERSION_IPA") +VERSION_DATE = os.getenv("VERSION_DATE") +RELEASE_CHANNEL = os.getenv("RELEASE_CHANNEL") +COMMIT_ID = os.getenv("COMMIT_ID") +SIZE = os.getenv("SIZE") +SHA256 = os.getenv("SHA256") +LOCALIZED_DESCRIPTION = os.getenv("LOCALIZED_DESCRIPTION") +DOWNLOAD_URL = os.getenv("DOWNLOAD_URL") +BUNDLE_IDENTIFIER = os.getenv("BUNDLE_IDENTIFIER") -VERSION_IPA = os.getenv("VERSION_IPA", "0.0.0") -VERSION_DATE = os.getenv("VERSION_DATE", "2000-12-18T00:00:00Z") -RELEASE_CHANNEL = os.getenv("RELEASE_CHANNEL", "alpha") -COMMIT_ID = os.getenv("COMMIT_ID", "1234567") -SIZE = int(os.getenv("SIZE", "0")) # Convert to integer -SHA256 = os.getenv("SHA256", "") -LOCALIZED_DESCRIPTION = os.getenv("LOCALIZED_DESCRIPTION", "Invalid Update") -DOWNLOAD_URL = os.getenv("DOWNLOAD_URL", "https://github.com/SideStore/SideStore/releases/download/0.0.0/SideStore.ipa") -BUNDLE_IDENTIFIER = os.getenv("BUNDLE_IDENTIFIER", "com.SideStore.SideStore") +# Uncomment to debug/test by simulating dummy input locally +# VERSION_IPA = os.getenv("VERSION_IPA", "0.0.0") +# VERSION_DATE = os.getenv("VERSION_DATE", "2000-12-18T00:00:00Z") +# RELEASE_CHANNEL = os.getenv("RELEASE_CHANNEL", "alpha") +# COMMIT_ID = os.getenv("COMMIT_ID", "1234567") +# SIZE = int(os.getenv("SIZE", "0")) # Convert to integer +# SHA256 = os.getenv("SHA256", "") +# LOCALIZED_DESCRIPTION = os.getenv("LOCALIZED_DESCRIPTION", "Invalid Update") +# DOWNLOAD_URL = os.getenv("DOWNLOAD_URL", "https://github.com/SideStore/SideStore/releases/download/0.0.0/SideStore.ipa") +# BUNDLE_IDENTIFIER = os.getenv("BUNDLE_IDENTIFIER", "com.SideStore.SideStore") # Check if input file is provided if len(sys.argv) < 2: From 3525fb4afaf6391e8643631a48429aa3a8590a64 Mon Sep 17 00:00:00 2001 From: Magesh K <47920326+mahee96@users.noreply.github.com> Date: Mon, 30 Dec 2024 03:36:28 +0530 Subject: [PATCH 08/59] [CI]: apps-v2 sources.json updater - use default bundleID as sidestore --- update_apps.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/update_apps.py b/update_apps.py index b053816b..791e0077 100755 --- a/update_apps.py +++ b/update_apps.py @@ -4,6 +4,8 @@ import os import json import sys +SIDESTORE_BUNDLE_ID = "com.SideStore.SideStore" + # Set environment variables with default values VERSION_IPA = os.getenv("VERSION_IPA") VERSION_DATE = os.getenv("VERSION_DATE") @@ -13,7 +15,7 @@ SIZE = os.getenv("SIZE") SHA256 = os.getenv("SHA256") LOCALIZED_DESCRIPTION = os.getenv("LOCALIZED_DESCRIPTION") DOWNLOAD_URL = os.getenv("DOWNLOAD_URL") -BUNDLE_IDENTIFIER = os.getenv("BUNDLE_IDENTIFIER") +BUNDLE_IDENTIFIER = os.getenv("BUNDLE_IDENTIFIER", SIDESTORE_BUNDLE_ID) # Uncomment to debug/test by simulating dummy input locally # VERSION_IPA = os.getenv("VERSION_IPA", "0.0.0") @@ -24,7 +26,6 @@ BUNDLE_IDENTIFIER = os.getenv("BUNDLE_IDENTIFIER") # SHA256 = os.getenv("SHA256", "") # LOCALIZED_DESCRIPTION = os.getenv("LOCALIZED_DESCRIPTION", "Invalid Update") # DOWNLOAD_URL = os.getenv("DOWNLOAD_URL", "https://github.com/SideStore/SideStore/releases/download/0.0.0/SideStore.ipa") -# BUNDLE_IDENTIFIER = os.getenv("BUNDLE_IDENTIFIER", "com.SideStore.SideStore") # Check if input file is provided if len(sys.argv) < 2: From 5416deddbea2a6dde16f348cd4b1932efa1deacc Mon Sep 17 00:00:00 2001 From: Magesh K <47920326+mahee96@users.noreply.github.com> Date: Mon, 30 Dec 2024 03:54:18 +0530 Subject: [PATCH 09/59] [CI]: use main branch for updates to apps-v2 sources.json --- .github/workflows/nightly.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index bb225c85..ed886ea6 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -281,5 +281,5 @@ jobs: git commit -m " - updated for $SHORT_COMMIT deployment" || echo "No changes to commit" git status - git push origin HEAD:nightly + git push origin HEAD:main popd From 51f2588d3cab911cd588dd4a7e6214a417e40d17 Mon Sep 17 00:00:00 2001 From: Magesh K <47920326+mahee96@users.noreply.github.com> Date: Mon, 30 Dec 2024 04:21:46 +0530 Subject: [PATCH 10/59] [CI]: post apps-v2 on main instead of nightly --- .github/workflows/nightly.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index ed886ea6..79aaaf32 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -257,8 +257,8 @@ jobs: # Repository name with owner. For example, actions/checkout # Default: ${{ github.repository }} repository: 'SideStore/apps-v2.json' - # ref: 'main' # TODO: use branches for alpha and beta tracks? so as to avoid push collision? - ref: 'nightly' # TODO: use branches for alpha and beta tracks? so as to avoid push collision? + ref: 'main' # TODO: use branches for alpha and beta tracks? so as to avoid push collision? + # ref: 'nightly' # TODO: use branches for alpha and beta tracks? so as to avoid push collision? # token: ${{ github.token }} token: ${{ secrets.APPS_DEPLOY_KEY }} path: 'SideStore/apps-v2.json' From ee03d9fa519a085cdc847ccbbdfb4114d0194a6e Mon Sep 17 00:00:00 2001 From: Magesh K <47920326+mahee96@users.noreply.github.com> Date: Thu, 2 Jan 2025 20:05:16 +0530 Subject: [PATCH 11/59] [diagnostics]: Added exporting of the coredata sqlite for debugging --- AltStore.xcodeproj/project.pbxproj | 24 ++++ AltStore/Managing Apps/AppManager.swift | 2 +- AltStore/Settings/Settings.storyboard | 103 +++++++++----- .../Settings/SettingsViewController.swift | 43 +++++- .../Extensions/UserDefaults+AltStore.swift | 4 +- SideStore/Utils/common/DateTimeUtil.swift | 25 ++++ .../Utils/debug/database/CoreDataHelper.swift | 130 ++++++++++++++++++ SideStore/Utils/iostreams/ConsoleLog.swift | 14 +- 8 files changed, 292 insertions(+), 53 deletions(-) create mode 100644 SideStore/Utils/common/DateTimeUtil.swift create mode 100644 SideStore/Utils/debug/database/CoreDataHelper.swift diff --git a/AltStore.xcodeproj/project.pbxproj b/AltStore.xcodeproj/project.pbxproj index fe7ce1b3..e22cccb8 100644 --- a/AltStore.xcodeproj/project.pbxproj +++ b/AltStore.xcodeproj/project.pbxproj @@ -60,6 +60,8 @@ A859ED5D2D1EE827003DCC58 /* OpenSSL.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A859ED5B2D1EE80D003DCC58 /* OpenSSL.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; A8945AA62D059B6100D86CBE /* Roxas.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A8945AA52D059B6100D86CBE /* Roxas.framework */; }; A8A543302D04F14400D72399 /* libfragmentzip.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A8A5432F2D04F0C100D72399 /* libfragmentzip.a */; }; + A8B516E32D2666CA0047047C /* CoreDataHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8B516E22D2666CA0047047C /* CoreDataHelper.swift */; }; + A8B516E62D2668170047047C /* DateTimeUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8B516E52D2668020047047C /* DateTimeUtil.swift */; }; A8BB34E52D04EC8E000A8B4D /* minimuxer-helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = A809F6A52D04DA1900F0F0F3 /* minimuxer-helpers.swift */; }; A8C38C242D206A3A00E83DBD /* ConsoleLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C38C1D2D206A3A00E83DBD /* ConsoleLogger.swift */; }; A8C38C262D206A3A00E83DBD /* ConsoleLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C38C1E2D206A3A00E83DBD /* ConsoleLog.swift */; }; @@ -630,6 +632,8 @@ A86202322D1F35640091187B /* AltStore.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AltStore.xcconfig; sourceTree = ""; }; A86202332D1F35640091187B /* AltStoreCore.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AltStoreCore.xcconfig; sourceTree = ""; }; A8945AA52D059B6100D86CBE /* Roxas.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Roxas.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A8B516E22D2666CA0047047C /* CoreDataHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataHelper.swift; sourceTree = ""; }; + A8B516E52D2668020047047C /* DateTimeUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTimeUtil.swift; sourceTree = ""; }; A8C38C1D2D206A3A00E83DBD /* ConsoleLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsoleLogger.swift; sourceTree = ""; }; A8C38C1E2D206A3A00E83DBD /* ConsoleLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsoleLog.swift; sourceTree = ""; }; A8C38C282D206AC100E83DBD /* OutputStream.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutputStream.swift; sourceTree = ""; }; @@ -1173,9 +1177,26 @@ name = Products; sourceTree = ""; }; + A8B516DE2D2666900047047C /* debug */ = { + isa = PBXGroup; + children = ( + A8B516DF2D2666A00047047C /* database */, + ); + path = debug; + sourceTree = ""; + }; + A8B516DF2D2666A00047047C /* database */ = { + isa = PBXGroup; + children = ( + A8B516E22D2666CA0047047C /* CoreDataHelper.swift */, + ); + path = database; + sourceTree = ""; + }; A8C38C1C2D2068D100E83DBD /* Utils */ = { isa = PBXGroup; children = ( + A8B516DE2D2666900047047C /* debug */, A8C38C272D206AA500E83DBD /* common */, A8C38C202D206A3A00E83DBD /* iostreams */, ); @@ -1194,6 +1215,7 @@ A8C38C272D206AA500E83DBD /* common */ = { isa = PBXGroup; children = ( + A8B516E52D2668020047047C /* DateTimeUtil.swift */, A8C38C282D206AC100E83DBD /* OutputStream.swift */, A8C38C312D206B2500E83DBD /* FileOutputStream.swift */, A8C38C2B2D206AD900E83DBD /* AbstractClassError.swift */, @@ -2912,6 +2934,7 @@ A8C38C262D206A3A00E83DBD /* ConsoleLog.swift in Sources */, D5935AED29C39DE300C157EF /* SourceComponents.swift in Sources */, D5935AED29C39DE300C157EF /* SourceComponents.swift in Sources */, + A8B516E62D2668170047047C /* DateTimeUtil.swift in Sources */, A8FD917C2D0478D200322782 /* VerificationError.swift in Sources */, D5A0537329B91DB400997551 /* SourceDetailContentViewController.swift in Sources */, BF770E5422BC044E002A40FE /* OperationContexts.swift in Sources */, @@ -2986,6 +3009,7 @@ BFF0B6942321CB85007A79E1 /* AuthenticationViewController.swift in Sources */, 0EE7FDCD2BE9124400D1E390 /* ErrorDetailsViewController.swift in Sources */, D561AF822B21669400BF59C6 /* VerifyAppPledgeOperation.swift in Sources */, + A8B516E32D2666CA0047047C /* CoreDataHelper.swift in Sources */, BF3432FB246B894F0052F4A1 /* BackupAppOperation.swift in Sources */, BF9ABA4922DD0742008935CF /* ScreenshotCollectionViewCell.swift in Sources */, BF9ABA4D22DD16DE008935CF /* PillButton.swift in Sources */, diff --git a/AltStore/Managing Apps/AppManager.swift b/AltStore/Managing Apps/AppManager.swift index bea66ab7..b75ae610 100644 --- a/AltStore/Managing Apps/AppManager.swift +++ b/AltStore/Managing Apps/AppManager.swift @@ -1763,7 +1763,7 @@ private extension AppManager private func exportResginedAppsToDocsDir(_ resignedApp: ALTApplication) { // Check if the user has enabled exporting resigned apps to the Documents directory and continue - guard UserDefaults.standard.isResignedAppExportEnabled else { + guard UserDefaults.standard.isExportResignedAppEnabled else { return } diff --git a/AltStore/Settings/Settings.storyboard b/AltStore/Settings/Settings.storyboard index beede7ed..52194d8a 100644 --- a/AltStore/Settings/Settings.storyboard +++ b/AltStore/Settings/Settings.storyboard @@ -21,7 +21,7 @@ - +