commit fa12b068886107bb42ea3ca2674cd8539d284fa6
parent 0047eb9494f60479a0b16bd0c80fe2ad56c43253
Author: MTRNord <mtrnord1@gmail.com>
Date: Sun, 10 Nov 2024 01:20:07 +0100
Prepare worker
Diffstat:
4 files changed, 143 insertions(+), 4 deletions(-)
diff --git a/src/app.rs b/src/app.rs
@@ -1,4 +1,5 @@
use crate::config::{APP_ID, PROFILE};
+use crate::conversion_worker::{ConversionWorker, ConversionWorkerInputMsg, ConversionWorkerMsg};
use crate::modals::about::AboutDialog;
use crate::select_folder::{InOut, SelectFolder, SelectFolderOut};
use gtk::prelude::*;
@@ -6,7 +7,7 @@ use gtk::{gio, glib};
use relm4::{
actions::{RelmAction, RelmActionGroup},
adw, gtk, main_application, Component, ComponentController, ComponentParts, ComponentSender,
- Controller, SimpleComponent,
+ Controller, SimpleComponent, WorkerController,
};
use std::path::PathBuf;
@@ -16,6 +17,7 @@ pub(super) struct App {
output_folder_selector: Controller<SelectFolder>,
input_folder: Option<PathBuf>,
output_folder: Option<PathBuf>,
+ conversion_worker: WorkerController<ConversionWorker>,
}
#[derive(Debug)]
@@ -24,6 +26,7 @@ pub(super) enum AppMsg {
OutputFolderSelected(PathBuf),
DeselectInputFolder,
DeselectOutputFolder,
+ Convert,
Quit,
Noop,
}
@@ -105,8 +108,7 @@ impl SimpleComponent for App {
gtk::Button {
set_label: "Convert",
connect_clicked[sender] => move |_| {
- // TODO: Implement conversion using worker and imagick
- sender.input(AppMsg::Quit);
+ sender.input(AppMsg::Convert);
}
}
}
@@ -153,11 +155,21 @@ impl SimpleComponent for App {
SelectFolderOut::FolderSelected(path) => AppMsg::OutputFolderSelected(path),
SelectFolderOut::AbortLast => AppMsg::DeselectInputFolder,
});
+ let conversion_worker =
+ ConversionWorker::builder()
+ .detach_worker(())
+ .forward(sender.input_sender(), |msg| match msg {
+ ConversionWorkerMsg::ConversionStarted => AppMsg::Noop,
+ ConversionWorkerMsg::ProgressUpdate(_) => AppMsg::Noop,
+ ConversionWorkerMsg::ConversionComplete => AppMsg::Noop,
+ ConversionWorkerMsg::ConversionFailed(_) => AppMsg::Noop,
+ });
let model = Self {
about_dialog,
input_folder_selector,
output_folder_selector,
+ conversion_worker,
input_folder: None,
output_folder: None,
};
@@ -196,6 +208,18 @@ impl SimpleComponent for App {
AppMsg::OutputFolderSelected(path) => self.output_folder = Some(path),
AppMsg::DeselectOutputFolder => self.output_folder = None,
AppMsg::Quit => main_application().quit(),
+ AppMsg::Convert => {
+ if let (Some(input_folder), Some(output_folder)) =
+ (&self.input_folder, &self.output_folder)
+ {
+ let _ = self.conversion_worker.sender().send(
+ ConversionWorkerInputMsg::ConvertFolder(
+ input_folder.clone(),
+ output_folder.clone(),
+ ),
+ );
+ }
+ }
AppMsg::Noop => {}
}
}
diff --git a/src/conversion_worker.rs b/src/conversion_worker.rs
@@ -0,0 +1,114 @@
+use magick_rust::{MagickError, MagickWand};
+use std::path::PathBuf;
+use std::result::Result;
+use walkdir::WalkDir;
+
+use relm4::{ComponentSender, Worker};
+
+#[derive(Debug)]
+pub enum ConversionWorkerInputMsg {
+ ConvertFolder(PathBuf, PathBuf),
+}
+
+#[derive(Debug)]
+pub enum ConversionWorkerMsg {
+ ConversionStarted,
+ ProgressUpdate(f64),
+ ConversionComplete,
+ ConversionFailed(String),
+}
+
+pub struct ConversionWorker;
+
+impl Worker for ConversionWorker {
+ type Init = ();
+ type Input = ConversionWorkerInputMsg;
+ type Output = ConversionWorkerMsg;
+
+ fn init(_init: Self::Init, _sender: ComponentSender<Self>) -> Self {
+ Self
+ }
+
+ fn update(&mut self, msg: ConversionWorkerInputMsg, sender: ComponentSender<Self>) {
+ match msg {
+ ConversionWorkerInputMsg::ConvertFolder(input_path, output_path) => {
+ // Start the conversion
+ sender
+ .output(ConversionWorkerMsg::ConversionStarted)
+ .unwrap();
+
+ // Walk directory, find all heic files, convert them to jpg and update progress
+ let result = self.convert_folder(input_path, output_path, |progress| {
+ sender
+ .output(ConversionWorkerMsg::ProgressUpdate(progress))
+ .unwrap();
+ });
+
+ // Send the result of the conversion back
+ match result {
+ Ok(_) => sender
+ .output(ConversionWorkerMsg::ConversionComplete)
+ .unwrap(),
+ Err(e) => sender
+ .output(ConversionWorkerMsg::ConversionFailed(e.to_string()))
+ .unwrap(),
+ }
+ }
+ }
+ }
+}
+
+impl ConversionWorker {
+ fn convert_folder<F: Fn(f64)>(
+ &self,
+ input_path: PathBuf,
+ output_path: PathBuf,
+ progress_callback: F,
+ ) -> Result<(), MagickError> {
+ // Use walkdir to find all heic files in the input directory
+ let heic_files: Vec<PathBuf> = WalkDir::new(input_path)
+ .into_iter()
+ .filter_map(|entry| {
+ let entry = entry.ok()?;
+ let path = entry.path();
+ if path.is_file() && path.extension().map_or(false, |ext| ext == "heic") {
+ Some(path.to_path_buf())
+ } else {
+ None
+ }
+ })
+ .collect();
+
+ // Convert each heic file to jpg
+ for (index, heic_file) in heic_files.iter().enumerate() {
+ let output_file = output_path
+ .join(heic_file.file_stem().unwrap())
+ .with_extension("jpg");
+
+ // Convert the file
+ self.convert_heic_to_jpg(heic_file.to_path_buf(), output_file)?;
+
+ // Update the progress
+ progress_callback((index + 1) as f64 / heic_files.len() as f64);
+ }
+ Ok(())
+ }
+
+ fn convert_heic_to_jpg(
+ &self,
+ input_file: PathBuf,
+ output_file: PathBuf,
+ ) -> Result<(), MagickError> {
+ // Create a MagickWand
+ let mut wand = MagickWand::new();
+
+ // Read the input file
+ wand.read_image(input_file.to_str().unwrap())?;
+
+ // Convert the image to jpg
+ wand.set_image_format("jpg")?;
+ wand.write_image(output_file.to_str().unwrap())?;
+
+ Ok(())
+ }
+}
diff --git a/src/main.rs b/src/main.rs
@@ -1,6 +1,7 @@
#[rustfmt::skip]
mod config;
mod app;
+mod conversion_worker;
mod modals;
mod select_folder;
diff --git a/src/select_folder.rs b/src/select_folder.rs
@@ -3,7 +3,7 @@ use relm4::{
adw, gtk, Component, ComponentController, ComponentParts, ComponentSender, Controller,
SimpleComponent,
};
-use relm4_components::{open_dialog::*, save_dialog::*};
+use relm4_components::open_dialog::*;
use std::path::PathBuf;
#[derive(Debug, PartialEq)]