Skip to content

Commit

Permalink
Keep log file in root of volume for controlling installed versions (#8)
Browse files Browse the repository at this point in the history
* Keep log file in root of volume for controlling installed versions.

* Add several new resources
  • Loading branch information
diegotl authored Oct 20, 2023
1 parent af54e63 commit 5393848
Show file tree
Hide file tree
Showing 12 changed files with 348 additions and 157 deletions.
8 changes: 4 additions & 4 deletions Empusa.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@
CODE_SIGN_ENTITLEMENTS = Empusa/Empusa.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 3;
CURRENT_PROJECT_VERSION = 4;
DEVELOPMENT_TEAM = D97MJ3844Q;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
Expand All @@ -422,7 +422,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.0.2;
MARKETING_VERSION = 1.0.3;
PRODUCT_BUNDLE_IDENTIFIER = nl.trevisa.diego.Empusa;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
Expand All @@ -438,7 +438,7 @@
CODE_SIGN_ENTITLEMENTS = Empusa/Empusa.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 3;
CURRENT_PROJECT_VERSION = 4;
DEVELOPMENT_TEAM = D97MJ3844Q;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
Expand All @@ -452,7 +452,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.0.2;
MARKETING_VERSION = 1.0.3;
PRODUCT_BUNDLE_IDENTIFIER = nl.trevisa.diego.Empusa;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
Expand Down
45 changes: 28 additions & 17 deletions Empusa/EmpusaModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ final class EmpusaModel: ObservableObject {
// MARK: - Init
init() {
loadExternalVolumes()
loadResourcesVersions()
}

// MARK: - Public functions
Expand Down Expand Up @@ -107,23 +106,27 @@ final class EmpusaModel: ObservableObject {
.receive(on: RunLoop.main)
.assign(to: &$progress)

do {
try await contentManager.download(
resources: selectedResources,
into: selectedVolume.url,
progressSubject: progressSubject
)
let result = await contentManager.download(
resources: selectedResources,
into: selectedVolume,
progressSubject: progressSubject
)

self.alertData = .init(
isProcessing = false
self.progress = nil
loadResourcesVersions()

if !result.failedResources.isEmpty {
alertData = .init(
title: "Alert",
message: "Failed to install \(result.failedResourceNames):\n\n\(result.failedResources.last!.error.localizedDescription)"
)
} else {
alertData = .init(
title: "Success",
message: "Selected resources have been downloaded into the selected destination."
)
} catch {
alertData = .init(error: error)
}

isProcessing = false
self.progress = nil
}
}

Expand Down Expand Up @@ -181,10 +184,18 @@ final class EmpusaModel: ObservableObject {
}

func loadResourcesVersions() {
Task { [resourceService, weak self] in
self?.isLoadingResources = true
self?.availableResources = await resourceService.fetchResources()
self?.isLoadingResources = false
Task { [weak self] in
guard let self else { return }
self.isLoadingResources = true

self.availableResources = await resourceService
.fetchResources(for: self.selectedVolume)

self.selectedResources = availableResources
.filter{ $0.preChecked }
.map { $0.resource }

self.isLoadingResources = false
}
}
}
16 changes: 14 additions & 2 deletions Empusa/UI Components/ResourcesView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,14 @@ struct ResourcesView: View {
model.selectedResources.remove(at: index)
}
})) {
Text(displayingResource.formattedName)
.frame(maxWidth: .infinity, alignment: .leading)
HStack {
Text(displayingResource.formattedName)
.frame(maxWidth: .infinity, alignment: .leading)

if let version = displayingResource.version {
Text(version)
}
}
}
}
}
Expand All @@ -36,6 +42,12 @@ struct ResourcesView: View {
}
}
.disabled(model.isProcessing)
.onAppear(perform: {
model.loadResourcesVersions()
})
.onChange(of: model.selectedVolume) { _ in
model.loadResourcesVersions()
}
}
}

Expand Down
4 changes: 2 additions & 2 deletions Packages/EmpusaKit/Sources/EmpusaKit/Client/Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ protocol ClientProtocol {
final class Client: NSObject, ClientProtocol {
func request<T: Decodable>(url: URL) async throws -> T {
let request = URLRequest(url: url)
let (data, response) = try await URLSession
let (data, _) = try await URLSession
.shared
.data(for: request)

Expand All @@ -35,7 +35,7 @@ final class Client: NSObject, ClientProtocol {
progressSubject.send(1)
}

let (localUrl, response) = try await URLSession
let (localUrl, _) = try await URLSession
.shared
.download(from: url)

Expand Down
174 changes: 109 additions & 65 deletions Packages/EmpusaKit/Sources/EmpusaKit/ContentManager.swift
Original file line number Diff line number Diff line change
@@ -1,92 +1,136 @@
import Combine
import Foundation
import OSLog

public struct FailedResource {
let resource: SwitchResource
public let error: Error
}

public struct ProcessResult {
public var succeededResources: [SwitchResource] = []
public var failedResources: [FailedResource] = []

public var failedResourceNames: String {
failedResources
.map { $0.resource.displayName }
.joined(separator: ", ")
}
}

public protocol ContentManagerProtocol {
func download(resources: [SwitchResource], into destination: URL, progressSubject: CurrentValueSubject<ProgressData?, Never>) async throws
func download(resources: [SwitchResource], into volume: ExternalVolume, progressSubject: CurrentValueSubject<ProgressData?, Never>) async -> ProcessResult
func backupVolume(at location: URL, progressSubject: CurrentValueSubject<ProgressData?, Never>) async throws -> ZipFile
func restoreBackup(at location: URL, to destination: URL, progressSubject: CurrentValueSubject<ProgressData?, Never>) async throws
}

public final class ContentManager: ContentManagerProtocol {
private let storageService: StorageServiceProtocol = StorageService()
private let githubService: AssetServiceProtocol = AssetService()
private let logger: Logger = .init(subsystem: "nl.trevisa.diego.Empusa.EmpusaKit", category: "ContentManager")

public init() {}

public func download(
resources: [SwitchResource],
into destination: URL,
into volume: ExternalVolume,
progressSubject: CurrentValueSubject<ProgressData?, Never>
) async throws {
) async -> ProcessResult {
let totalProgress = Double(resources.count) * 3
let log: EmpusaLog = storageService.getLog(at: volume) ?? .init()
var result = ProcessResult()

for resource in resources {
let accumulatedProgress = 3 * Double(resources.firstIndex(of: resource)!)
let progressTitleSubject = CurrentValueSubject<String, Never>("")
let downloadProgressSubject = CurrentValueSubject<Double, Never>(0)
let unzipProgressSubject = CurrentValueSubject<Double, Never>(0)
let mergeProgressSubject = CurrentValueSubject<Double, Never>(0)

let cancellable = Publishers.CombineLatest4(
progressTitleSubject,
downloadProgressSubject,
unzipProgressSubject,
mergeProgressSubject
).map { (title, downloadProgress, unzipProgress, mergeProgress) in
ProgressData(
title: title,
progress: accumulatedProgress + downloadProgress + unzipProgress + mergeProgress,
total: totalProgress
do {
let accumulatedProgress = 3 * Double(resources.firstIndex(of: resource)!)
let progressTitleSubject = CurrentValueSubject<String, Never>("")
let downloadProgressSubject = CurrentValueSubject<Double, Never>(0)
let unzipProgressSubject = CurrentValueSubject<Double, Never>(0)
let mergeProgressSubject = CurrentValueSubject<Double, Never>(0)

let cancellable = Publishers.CombineLatest4(
progressTitleSubject,
downloadProgressSubject,
unzipProgressSubject,
mergeProgressSubject
).map { (title, downloadProgress, unzipProgress, mergeProgress) in
ProgressData(
title: title,
progress: accumulatedProgress + downloadProgress + unzipProgress + mergeProgress,
total: totalProgress
)
}.assign(to: \.value, on: progressSubject)

// Download asset
progressTitleSubject.send("Downloading \(resource.assetFileName)...")
let asset = try await githubService.downloadAsset(
for: resource,
progressSubject: downloadProgressSubject
)

// Rename asset file
let assetFileUrl = asset
.url
.deletingLastPathComponent()
.appending(component: resource.assetFileName)

try storageService.moveItem(
at: asset.url,
to: assetFileUrl
)

let extractedUrl = try {
if resource.isAssetZipped {
// Unzip asset
progressTitleSubject.send("Unzipping \(resource.assetFileName)...")
return try storageService.unzipFile(
at: assetFileUrl,
progressSubject: unzipProgressSubject
)
} else {
// Do nothing
unzipProgressSubject.send(1)
return assetFileUrl
}
}()

// Copy asset to SD
progressTitleSubject.send("Copying contents of \(resource.assetFileName) into destination...")
try resource.handleAsset(
at: extractedUrl,
destination: volume.url,
progressSubject: mergeProgressSubject
)

// Delete downloaded files on disk
progressTitleSubject.send("Removing temporary files...")
storageService.removeItem(at: assetFileUrl)
storageService.removeItem(at: extractedUrl)

// Update log
log.add(
resource: resource,
version: asset.version
)
}.assign(to: \.value, on: progressSubject)

// Download asset
progressTitleSubject.send("Downloading \(resource.assetFileName)...")
let assetFileTempUrl = try await githubService.downloadAsset(
for: resource,
progressSubject: downloadProgressSubject
)

// Rename asset file
let assetFileUrl = assetFileTempUrl
.deletingLastPathComponent()
.appending(component: resource.assetFileName)

try storageService.moveItem(
at: assetFileTempUrl,
to: assetFileUrl
)

let extractedUrl = try {
if resource.isAssetZipped {
// Unzip asset
progressTitleSubject.send("Unzipping \(resource.assetFileName)...")
return try storageService.unzipFile(
at: assetFileUrl,
progressSubject: unzipProgressSubject
)
} else {
// Do nothing
unzipProgressSubject.send(1)
return assetFileUrl
}
}()

// Copy asset to SD
progressTitleSubject.send("Copying contents of \(resource.assetFileName) into destination...")
try resource.handleAsset(
at: extractedUrl,
destination: destination,
progressSubject: mergeProgressSubject
)
result.succeededResources.append(resource)
cancellable.cancel()
} catch {
result.failedResources.append(.init(
resource: resource,
error: error
))
logger.error("Fail to execute for resource \(resource.displayName): \(error.localizedDescription)")
}
}

// Delete downloaded files on disk
progressTitleSubject.send("Removing temporary files...")
storageService.removeItem(at: assetFileUrl)
storageService.removeItem(at: extractedUrl)
// Save log file
storageService.saveLog(
log,
at: volume
)

cancellable.cancel()
}
return result
}

public func backupVolume(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Combine

extension FileManager {
private var logger: Logger {
.init(subsystem: "nl.trevisa.diego.Empusa.Services", category: "FileManager")
.init(subsystem: "nl.trevisa.diego.Empusa.EmpusaKit", category: "FileManager")
}

enum ConflictResolution {
Expand Down
6 changes: 6 additions & 0 deletions Packages/EmpusaKit/Sources/EmpusaKit/Models/Asset.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Foundation

public struct DownloadedAsset {
let version: String?
let url: URL
}
22 changes: 22 additions & 0 deletions Packages/EmpusaKit/Sources/EmpusaKit/Models/EmpusaLog.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Foundation

public struct InstalledResource: Codable {
let resource: SwitchResource
let version: String?
var updatedAt: Date = Date()
}

public final class EmpusaLog: Codable {
var resources: [InstalledResource] = []

func add(
resource: SwitchResource,
version: String?
) {
resources.removeAll(where: { $0.resource == resource })
resources.append(.init(
resource: resource,
version: version
))
}
}
Loading

0 comments on commit 5393848

Please sign in to comment.