Skip to content

Commit

Permalink
Display single alert and/or success message when upscaling in batch
Browse files Browse the repository at this point in the history
  • Loading branch information
joneavila committed Mar 24, 2024
1 parent 9910214 commit 259ca98
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 46 deletions.
2 changes: 1 addition & 1 deletion Superres/Utilities/ImageState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ struct ImageState: Identifiable {
}
}

func saveUpscaledImage(to url: URL) throws {
private func saveUpscaledImage(to url: URL) throws {
guard let upscaledImage = upscaledImage else {
return
}
Expand Down
6 changes: 4 additions & 2 deletions Superres/Views/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ struct ContentView: View {
Spacer()

Button("Upscale") {
viewModel.upscaleImages()
Task {
await viewModel.upscaleImages()
}
}

.buttonStyle(CustomButtonStyle(isProminent: true, useMaxWidth: true))
Expand All @@ -52,7 +54,7 @@ struct ContentView: View {
.padding()
.frame(width: 200, alignment: .leading)
.background(Color("BgColor"))
.disabled(viewModel.isWorking)
.disabled(viewModel.isUpscaling)

DividerView()

Expand Down
115 changes: 72 additions & 43 deletions Superres/Views/ContentViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ final class ContentViewModel: ObservableObject {
@Published var alertMessage = ""
@Published var automaticallySave = false
@Published var showSuccessMessage = false
@Published var isWorking = false
@Published var isUpscaling = false
@Published var imagesNeedUpscaling: Bool = false
private var outputFolderUrl: URL?

Expand All @@ -30,51 +30,67 @@ final class ContentViewModel: ObservableObject {
let homeDirectory = FileManager.default.homeDirectoryForCurrentUser.path
return url.path.replacingOccurrences(of: homeDirectory, with: "~")
}

func upscaleImages() {
isWorking = true

for index in imageStates.indices where imageStates[index].upscaledImage == nil {
self.imageStates[index].isUpscaling = true
}

Task {
let saveImageSuccess = await withTaskGroup(of: Bool.self) { group -> Bool in
for index in self.imageStates.indices where self.imageStates[index].upscaledImage == nil {
group.addTask {
do {
let upscaledImageData = try await upscale(self.imageStates[index].originalImageUrl)
try await MainActor.run {
self.imageStates[index].isUpscaling = false
self.imageStates[index].upscaledImage = NSImage(data: upscaledImageData!)
if self.automaticallySave, let outputFolderUrl = self.outputFolderUrl {
try self.imageStates[index].saveUpscaledImageToFolder(folderUrl: outputFolderUrl)
}
}
return true // Image was saved.

} catch {
await MainActor.run {
self.imageStates[index].isUpscaling = false
self.displayAlert(title: "Error", message: error.localizedDescription)
}

/// Upscale images asynchronously.
@MainActor
func upscaleImages() async {
isUpscaling = true

var taskResults: [(String?, Bool)] = []

// Create a task group to execute upscaling tasks concurrently. The task result is a tuple of an optional string (an error description if upscaling fails) and a boolean (whether the upscaled image was automatically saved).
await withTaskGroup(of: (String?, Bool).self) { group in

// Process images that have not been upscaled.
for index in imageStates.indices where imageStates[index].upscaledImage == nil {
group.addTask {
do {
await MainActor.run {
self.imageStates[index].isUpscaling = true
}

let upscaledImageData = try await upscale(self.imageStates[index].originalImageUrl)

await MainActor.run {
self.imageStates[index].upscaledImage = NSImage(data: upscaledImageData!)
self.imageStates[index].isUpscaling = false
}
return false // Image was not saved.

if self.automaticallySave, let outputFolderUrl = self.outputFolderUrl {
try self.imageStates[index].saveUpscaledImageToFolder(folderUrl: outputFolderUrl)
return (nil, true)
}

return (nil, false)
} catch {
await MainActor.run {
self.imageStates[index].isUpscaling = false
}
return ("Error upscaling image \(self.imageStates[index].originalImageUrl): \(error.localizedDescription)", false)
}
}
return await group.contains(true)
}

if saveImageSuccess {
await MainActor.run {
triggerSuccessMessage()
}
}

await MainActor.run {
self.isWorking = false

// Collect results once all tasks have completed.
for await result in group {
taskResults.append(result)
}
}

isUpscaling = false

// Display any error messages in a single alert.
let errorMessages = taskResults.compactMap { $0.0 }
if !errorMessages.isEmpty {
let errorMessage = errorMessages.joined(separator: "\n")
displayAlert(title: "Error", message: errorMessage)
}

// Display success message if any upscaled images where saved.
let imageWasSaved = taskResults.contains { $0.1 }
if imageWasSaved {
triggerSuccessMessage()
}
}

private func displayAlert(title: String, message: String) {
Expand All @@ -93,30 +109,41 @@ final class ContentViewModel: ObservableObject {
}
}

/// Handles the drop of images onto the application.
/// - Parameter providers: An array of `NSItemProvider` objects representing the dropped items.
/// - Returns: `true` if the drop was handled successfully, `false` otherwise.
func handleDropOfImages(providers: [NSItemProvider]) -> Bool {
var errorMessages = [String]()

for provider in providers {
if provider.canLoadObject(ofClass: URL.self) {
_ = provider.loadObject(ofClass: URL.self) { url, _ in
_ = provider.loadObject(ofClass: URL.self) { url, error in

DispatchQueue.main.async {
if let error = error {
errorMessages.append("Error loading URL: \(error.localizedDescription)")
return
}

guard let url = url else {
return
}

// Get the UTType from the URL.
guard let typeIdentifier = try? url.resourceValues(forKeys: [.typeIdentifierKey]).typeIdentifier,
let fileUTType = UTType(typeIdentifier)
else {
errorMessages.append("Uknown file type: \(url.path())")
return
}

// Check if the file type is supported.
if !ImageState.supportedImageTypes.contains(fileUTType) {
errorMessages.append("Unsupported image type: \(url.path())")
errorMessages.append("Unsupported file type: \(url.path())")
return
}

// Load the image from the URL.
guard let nsImage = NSImage(contentsOf: url) else {
errorMessages.append("Unable to load image: \(url.path())")
return
Expand All @@ -130,9 +157,11 @@ final class ContentViewModel: ObservableObject {
}

DispatchQueue.main.async {
// Display any error messages in a single alert.
if !errorMessages.isEmpty {
let title = "Error loading image\(errorMessages.count > 1 ? "s" : "")"
let message = errorMessages.joined(separator: "\n")
self.displayAlert(title: "Error", message: message)
self.displayAlert(title: title, message: message)
}
}

Expand Down

0 comments on commit 259ca98

Please sign in to comment.