Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Download checksum #705

Merged
merged 4 commits into from Jan 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions PlayCover.xcodeproj/project.pbxproj
Expand Up @@ -81,6 +81,7 @@
B6603E1328E2257800DEFA3F /* Uninstaller.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6603E1228E2257800DEFA3F /* Uninstaller.swift */; };
B6825C3528F3D23600E3015A /* InstallSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6825C3428F3D23600E3015A /* InstallSettings.swift */; };
B6AB53C929232B4F00039B2E /* KeyCodeNames.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AB53C829232B4F00039B2E /* KeyCodeNames.swift */; };
B6ABDA2A2971EEF700A46E80 /* ProgressVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6ABDA292971EEF700A46E80 /* ProgressVM.swift */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
Expand Down Expand Up @@ -179,6 +180,7 @@
B6603E1228E2257800DEFA3F /* Uninstaller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Uninstaller.swift; sourceTree = "<group>"; };
B6825C3428F3D23600E3015A /* InstallSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallSettings.swift; sourceTree = "<group>"; };
B6AB53C829232B4F00039B2E /* KeyCodeNames.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyCodeNames.swift; sourceTree = "<group>"; };
B6ABDA292971EEF700A46E80 /* ProgressVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressVM.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -297,6 +299,7 @@
6E66B0BE289DE6240099B907 /* StoreVM.swift */,
6E66B0D828A135360099B907 /* ToastVM.swift */,
53F4D29F26C43C690020167C /* Log.swift */,
B6ABDA292971EEF700A46E80 /* ProgressVM.swift */,
);
path = ViewModel;
sourceTree = "<group>";
Expand Down Expand Up @@ -652,6 +655,7 @@
53F4D29E26C43C040020167C /* UserIntentFlow.swift in Sources */,
6E66B0BF289DE6240099B907 /* StoreVM.swift in Sources */,
B6603E1328E2257800DEFA3F /* Uninstaller.swift in Sources */,
B6ABDA2A2971EEF700A46E80 /* ProgressVM.swift in Sources */,
AA71964B287A0EA800623C15 /* PlayRules.swift in Sources */,
6ECB1D0E29798DFA00CD92AA /* DataExtensions.swift in Sources */,
6E66B0D928A135360099B907 /* ToastVM.swift in Sources */,
Expand Down
72 changes: 59 additions & 13 deletions PlayCover/AppInstaller/Downloader.swift
Expand Up @@ -6,6 +6,7 @@
//

import Foundation
import CryptoKit
import DownloadManager

/// DownloaderManager can be configured through this struct, default values are as the same as below
Expand Down Expand Up @@ -37,7 +38,7 @@ class DownloadApp {

func start() {
if !NetworkVM.isConnectedToNetwork() { return }
if installVM.installing {
if installVM.inProgress {
Log.shared.error(PlayCoverError.waitInstallation)
} else {
if let warningMessage = warning {
Expand All @@ -53,25 +54,61 @@ class DownloadApp {

if alert.runModal() == .alertSecondButtonReturn {
return
} else {
proceedDownload()
}
} else {
proceedDownload()
}

proceedDownload()
}
}

func cancel() {
downloader.cancelAllDownloads()
downloadVM.downloading = false
downloadVM.progress = 0

downloadVM.next(.canceled, 0.95, 1.0)
downloadVM.storeAppData = nil
}

private func checksumAlert(originalSum: String, givenSum: String, completion: @escaping(Bool) -> Void) {
Task { @MainActor in
let alert = NSAlert()
alert.messageText = NSLocalizedString("playapp.download.differentChecksum", comment: "")
alert.informativeText = String(
format: NSLocalizedString("playapp.download.differentChecksumDesc", comment: ""),
arguments: [originalSum, givenSum]
)
alert.alertStyle = .warning
alert.addButton(withTitle: NSLocalizedString("button.Proceed", comment: ""))
alert.addButton(withTitle: NSLocalizedString("button.Cancel", comment: ""))

completion(alert.runModal() == .alertFirstButtonReturn)
}
}

private func verifyChecksum(checksum: String?, file: URL?, completion: @escaping(Bool) -> Void) {
Task {
if let originalSum = self.downloadVM.storeAppData?.checksum, !originalSum.isEmpty, let fileURL = file {
do {
let sha256 = SHA256.hash(data: try Data(contentsOf: fileURL))
.map { String(format: "%02hhx", $0) }
.joined()

if originalSum != sha256 {
checksumAlert(originalSum: originalSum, givenSum: sha256, completion: completion)
return
}
} catch {
print("Error in calculating sha256sum: \(error)")
}
}

completion(true)
}
}

private func proceedDownload() {
self.downloadVM.storeAppData = self.app
self.downloadVM.downloading = true
self.downloadVM.next(.downloading, 0.0, 0.7)

var tmpDir: URL?
do {
tmpDir = try FileManager.default.url(for: .itemReplacementDirectory,
Expand All @@ -84,17 +121,26 @@ class DownloadApp {
// progress is a Float
self.downloadVM.progress = Double(progress)
}, onCompletion: { error, fileURL in
self.downloadVM.next(.integrity, 0.7, 0.95)

guard error == nil else {
self.downloadVM.downloading = false
self.downloadVM.progress = 0
self.downloadVM.next(.failed, 0.95, 1.0)
self.downloadVM.storeAppData = nil
return Log.shared.error(error!)
}
self.downloadVM.downloading = false
self.downloadVM.progress = 0
self.proceedInstall(fileURL)

self.verifyChecksum(checksum: self.downloadVM.storeAppData?.checksum, file: fileURL) { completing in
self.downloadVM.next(completing ? .finish : .failed, 0.95, 1.0)
if completing {
Task { @MainActor in
self.proceedInstall(fileURL)
}
}
}
})
} catch {
self.downloadVM.next(.failed, 0.95, 1.0)

if let tmpDir = tmpDir {
FileManager.default.delete(at: tmpDir)
}
Expand Down
17 changes: 14 additions & 3 deletions PlayCover/ViewModel/DownloadVM.swift
Expand Up @@ -7,10 +7,21 @@

import Foundation

class DownloadVM: ObservableObject {
@Published var progress: Double = 0
@Published var downloading = false
enum DownloadStepsNative: String {
case downloading = "playapp.download.downloading",
integrity = "playapp.download.integrityCheck",
finish = "playapp.progress.finished",
failed = "playapp.progress.failed",
canceled = "playapp.progress.canceled"
}

class DownloadVM: ProgressVM<DownloadStepsNative> {
@Published var storeAppData: StoreAppData?

static let shared = DownloadVM()

init() {
super.init(start: .downloading, ends: [.finish, .failed, .canceled])
}

}
31 changes: 6 additions & 25 deletions PlayCover/ViewModel/InstallVM.swift
Expand Up @@ -12,35 +12,16 @@ enum InstallStepsNative: String {
sign = "playapp.install.signing",
library = "playapp.install.addToLib",
begin = "playapp.install.copy",
finish = "playapp.install.finished",
failed = "playapp.install.failed"
finish = "playapp.progress.finished",
failed = "playapp.progress.failed"
}

class InstallVM: ObservableObject {

@Published var status: String = NSLocalizedString(InstallStepsNative.begin.rawValue, comment: "")
@Published var progress = 0.0
@Published var installing = false
class InstallVM: ProgressVM<InstallStepsNative> {

static let shared = InstallVM()

func next(_ step: InstallStepsNative, _ startProgress: Double, _ stopProgress: Double) {
Task { @MainActor in
self.progress = startProgress
self.status = NSLocalizedString(step.rawValue, comment: "")
if step == .begin {
self.installing = true
} else if step == .finish || step == .failed {
self.progress = 1.0
try await Task.sleep(nanoseconds: 1500000000)
self.installing = false
}
while self.status == NSLocalizedString(step.rawValue, comment: "") {
try await Task.sleep(nanoseconds: 50000000)
if self.progress < stopProgress {
self.progress += 0.002
}
}
}
init() {
super.init(start: .begin, ends: [.finish, .failed])
}

}
48 changes: 48 additions & 0 deletions PlayCover/ViewModel/ProgressVM.swift
@@ -0,0 +1,48 @@
//
// ProgressVM.swift
// PlayCover
//
// Created by TheMoonThatRises on 1/13/23.
//

import Foundation

class ProgressVM<Steps: RawRepresentable & Equatable>: ObservableObject where Steps.RawValue == String {
@Published var progress = 0.0
@Published var inProgress = false
@Published var status: Steps

private let starting: Steps
private let ends: [Steps]

/// - Parameters:
/// - starts: The initial starting value
/// - ends: Ending conditions
init(start: Steps, ends: [Steps]) {
self.status = start
self.starting = start
self.ends = ends
}

func next(_ step: Steps, _ startProgress: Double, _ stopProgress: Double) {
Task { @MainActor in
self.progress = startProgress
self.status = step

if step == self.starting {
self.inProgress = true
} else if self.ends.contains(step) {
self.progress = 1.0
try await Task.sleep(nanoseconds: 1500000000)
self.inProgress = false
} else {
while self.status == step {
try await Task.sleep(nanoseconds: 50000000)
if self.progress < stopProgress {
self.progress += 0.002
}
}
}
}
}
}
1 change: 1 addition & 0 deletions PlayCover/ViewModel/StoreVM.swift
Expand Up @@ -188,4 +188,5 @@ struct StoreAppData: Codable, Equatable {
let version: String
let itunesLookup: String
let link: String
let checksum: String?
}
6 changes: 3 additions & 3 deletions PlayCover/Views/App Views/StoreAppView.swift
Expand Up @@ -34,7 +34,7 @@ struct StoreAppView: View {
warningMessage: $warningMessage)
.gesture(TapGesture(count: 2).onEnded {
if let url = URL(string: app.link) {
if downloadVM.downloading {
if downloadVM.inProgress {
Log.shared.error(PlayCoverError.waitDownload)
} else {
DownloadApp(url: url, app: app,
Expand Down Expand Up @@ -114,7 +114,7 @@ struct StoreAppConditionalView: View {
.shadow(radius: 1)
.padding(.horizontal, 15)
.padding(.vertical, 5)
if downloadVM.downloading && downloadVM.storeAppData == app {
if downloadVM.inProgress && downloadVM.storeAppData == app {
ProgressView(value: downloadVM.progress)
.progressViewStyle(.circular)
}
Expand Down Expand Up @@ -154,7 +154,7 @@ struct StoreAppConditionalView: View {
.frame(width: 60, height: 60)
.cornerRadius(15)
.shadow(radius: 1)
if downloadVM.downloading && downloadVM.storeAppData == app {
if downloadVM.inProgress && downloadVM.storeAppData == app {
VStack {
Spacer()
ProgressView(value: downloadVM.progress)
Expand Down
4 changes: 2 additions & 2 deletions PlayCover/Views/MenuBarView.swift
Expand Up @@ -60,9 +60,9 @@ struct PlayCoverViewMenuView: Commands {
CommandGroup(replacing: .importExport) {
Button("menubar.exportToSideloady") {
Task {
if InstallVM.shared.installing {
if InstallVM.shared.inProgress {
Log.shared.error(PlayCoverError.waitInstallation)
} else if DownloadVM.shared.downloading {
} else if DownloadVM.shared.inProgress {
Log.shared.error(PlayCoverError.waitDownload)
} else {
await NSOpenPanel.selectIPA { result in
Expand Down
1 change: 1 addition & 0 deletions PlayCover/Views/PlayCoverApp.swift
Expand Up @@ -70,6 +70,7 @@ struct PlayCoverApp: App {
WindowGroup {
MainView(isSigningSetupShown: $isSigningSetupShown)
.environmentObject(InstallVM.shared)
.environmentObject(DownloadVM.shared)
.environmentObject(AppsVM.shared)
.environmentObject(storeVM)
.environmentObject(AppIntegrity())
Expand Down
13 changes: 7 additions & 6 deletions PlayCover/Views/Sidebar Views/AppLibraryView.swift
Expand Up @@ -8,6 +8,7 @@ import SwiftUI
struct AppLibraryView: View {
@EnvironmentObject var appsVM: AppsVM
@EnvironmentObject var installVM: InstallVM
@EnvironmentObject var downloadVM: DownloadVM

@Binding var selectedBackgroundColor: Color
@Binding var selectedTextColor: Color
Expand Down Expand Up @@ -61,9 +62,9 @@ struct AppLibraryView: View {
.font(.subheadline)
.foregroundColor(.secondary)
Button("Import IPA") {
if installVM.installing {
if installVM.inProgress {
Log.shared.error(PlayCoverError.waitInstallation)
} else if DownloadVM.shared.downloading {
} else if downloadVM.status == .downloading {
Log.shared.error(PlayCoverError.waitDownload)
} else {
selectFile()
Expand All @@ -88,9 +89,9 @@ struct AppLibraryView: View {
}
ToolbarItem(placement: .primaryAction) {
Button(action: {
if installVM.installing {
if installVM.inProgress {
Log.shared.error(PlayCoverError.waitInstallation)
} else if DownloadVM.shared.downloading {
} else if downloadVM.inProgress {
Log.shared.error(PlayCoverError.waitDownload)
} else {
selectFile()
Expand Down Expand Up @@ -124,10 +125,10 @@ struct AppLibraryView: View {
showLegacyConvertAlert = LegacySettings.doesMonolithExist
}
.onDrop(of: ["public.url", "public.file-url"], isTargeted: nil) { (items) -> Bool in
if installVM.installing {
if installVM.inProgress {
Log.shared.error(PlayCoverError.waitInstallation)
return false
} else if DownloadVM.shared.downloading {
} else if downloadVM.inProgress {
Log.shared.error(PlayCoverError.waitDownload)
return false
} else if let item = items.first {
Expand Down