refactor: Go back to cli-only app
Signed-off-by: SindreKjelsrud <sindre@kjelsrud.dev>
This commit is contained in:
parent
758076fe32
commit
f946dbb9ce
6 changed files with 29 additions and 2446 deletions
2107
Cargo.lock
generated
2107
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
10
Cargo.toml
10
Cargo.toml
|
|
@ -46,16 +46,6 @@ id3 = "1.13"
|
|||
# UUID generation
|
||||
uuid = { version = "1.0", features = ["v4"] }
|
||||
|
||||
# GUI
|
||||
eframe = { version = "0.27", default-features = false, features = ["default_fonts", "glow"] }
|
||||
egui = "0.27"
|
||||
rfd = "0.14"
|
||||
home = "0.5.11" # Pin to version compatible with Rust 1.86
|
||||
|
||||
[[bin]]
|
||||
name = "navipod-gui"
|
||||
path = "src/gui_main.rs"
|
||||
|
||||
[dev-dependencies]
|
||||
mockito = "1.0"
|
||||
tokio-test = "0.4"
|
||||
|
|
|
|||
31
shell.nix
31
shell.nix
|
|
@ -11,31 +11,9 @@ pkgs.mkShell {
|
|||
pkg-config # useful for C-dependencies
|
||||
openssl # if crates are dependent on openssl
|
||||
openssl.dev # openssl development headers
|
||||
# GUI dependencies - X11
|
||||
libGL
|
||||
xorg.libX11
|
||||
xorg.libXcursor
|
||||
xorg.libXi
|
||||
xorg.libXrandr
|
||||
libxkbcommon
|
||||
# GUI dependencies - Wayland (for Wayland support)
|
||||
wayland
|
||||
];
|
||||
|
||||
# Set up library paths for runtime
|
||||
shellHook = let
|
||||
libPath = pkgs.lib.makeLibraryPath (with pkgs; [
|
||||
libGL
|
||||
xorg.libX11
|
||||
xorg.libXcursor
|
||||
xorg.libXi
|
||||
xorg.libXrandr
|
||||
libxkbcommon
|
||||
wayland
|
||||
]);
|
||||
in ''
|
||||
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:${libPath}"
|
||||
|
||||
shellHook = ''
|
||||
echo "NaviPod Development Environment"
|
||||
|
||||
# Verify Rust is available
|
||||
|
|
@ -50,7 +28,12 @@ pkgs.mkShell {
|
|||
echo ""
|
||||
echo "Welcome to NaviPod development shell!"
|
||||
echo "Run 'cargo build' to build the project."
|
||||
echo "Run 'cargo run' to run the CLI."
|
||||
echo "Run 'cargo check' to check for compilation errors."
|
||||
echo "Run './target/debug/navipod-gui' to launch the GUI (from within this shell)."
|
||||
echo ""
|
||||
echo "Available commands:"
|
||||
echo " navipod sync [--album ALBUM] [--artist ARTIST] - Sync content to iPod"
|
||||
echo " navipod list-albums - List available albums"
|
||||
echo " navipod check - Check iPod connection"
|
||||
'';
|
||||
}
|
||||
|
|
|
|||
297
src/gui.rs
297
src/gui.rs
|
|
@ -1,297 +0,0 @@
|
|||
use crate::config::{Config, FirmwareType, IpodConfig, NavidromeConfig, SyncConfig};
|
||||
use crate::sync;
|
||||
use eframe::egui;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
pub struct NaviPodApp {
|
||||
// Configuration inputs
|
||||
navidrome_url: String,
|
||||
navidrome_username: String,
|
||||
navidrome_password: String,
|
||||
firmware_type: FirmwareType,
|
||||
mount_point: String,
|
||||
|
||||
// Status
|
||||
status: Arc<Mutex<SyncStatus>>,
|
||||
runtime: Option<Runtime>,
|
||||
}
|
||||
|
||||
impl Default for NaviPodApp {
|
||||
fn default() -> Self {
|
||||
// Try to load config from file
|
||||
let (url, username, password, firmware, mount) = if let Ok(config) = crate::config::Config::load() {
|
||||
(
|
||||
config.navidrome.url,
|
||||
config.navidrome.username,
|
||||
config.navidrome.password,
|
||||
config.ipod.firmware,
|
||||
config.ipod.mount_point.to_string_lossy().to_string(),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
String::new(),
|
||||
String::new(),
|
||||
String::new(),
|
||||
FirmwareType::Rockbox,
|
||||
String::new(),
|
||||
)
|
||||
};
|
||||
|
||||
Self {
|
||||
navidrome_url: url,
|
||||
navidrome_username: username,
|
||||
navidrome_password: password,
|
||||
firmware_type: firmware,
|
||||
mount_point: mount,
|
||||
status: Arc::new(Mutex::new(SyncStatus::Idle)),
|
||||
runtime: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum SyncStatus {
|
||||
Idle,
|
||||
Connecting,
|
||||
Syncing {
|
||||
current_album: String,
|
||||
current_song: String,
|
||||
albums_total: usize,
|
||||
albums_done: usize,
|
||||
},
|
||||
Success {
|
||||
message: String,
|
||||
},
|
||||
Error {
|
||||
message: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl Default for SyncStatus {
|
||||
fn default() -> Self {
|
||||
SyncStatus::Idle
|
||||
}
|
||||
}
|
||||
|
||||
impl NaviPodApp {
|
||||
fn start_sync(&mut self) {
|
||||
if self.runtime.is_some() {
|
||||
return; // Already syncing
|
||||
}
|
||||
|
||||
// Validate inputs
|
||||
if self.navidrome_url.is_empty() {
|
||||
*self.status.lock().unwrap() = SyncStatus::Error {
|
||||
message: "Navidrome URL is required".to_string(),
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
if self.navidrome_username.is_empty() {
|
||||
*self.status.lock().unwrap() = SyncStatus::Error {
|
||||
message: "Navidrome username is required".to_string(),
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
if self.navidrome_password.is_empty() {
|
||||
*self.status.lock().unwrap() = SyncStatus::Error {
|
||||
message: "Navidrome password is required".to_string(),
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
if self.mount_point.is_empty() {
|
||||
*self.status.lock().unwrap() = SyncStatus::Error {
|
||||
message: "iPod mount point is required".to_string(),
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
// Create runtime for async operations
|
||||
let rt = Runtime::new().unwrap();
|
||||
self.runtime = Some(rt);
|
||||
|
||||
// Build config
|
||||
let config = Config {
|
||||
navidrome: NavidromeConfig {
|
||||
url: self.navidrome_url.clone(),
|
||||
username: self.navidrome_username.clone(),
|
||||
password: self.navidrome_password.clone(),
|
||||
},
|
||||
ipod: IpodConfig {
|
||||
mount_point: PathBuf::from(&self.mount_point),
|
||||
firmware: self.firmware_type,
|
||||
},
|
||||
sync: SyncConfig {
|
||||
albums: true,
|
||||
playlists: false,
|
||||
format: "mp3".to_string(),
|
||||
},
|
||||
};
|
||||
|
||||
let status_clone_progress = self.status.clone();
|
||||
let status_clone_final = self.status.clone();
|
||||
let runtime = self.runtime.as_ref().unwrap();
|
||||
|
||||
*self.status.lock().unwrap() = SyncStatus::Connecting;
|
||||
|
||||
// Create progress callback
|
||||
let progress_callback: sync::ProgressCallback = Arc::new(move |album, song, albums_done, albums_total| {
|
||||
*status_clone_progress.lock().unwrap() = SyncStatus::Syncing {
|
||||
current_album: album,
|
||||
current_song: song,
|
||||
albums_total,
|
||||
albums_done,
|
||||
};
|
||||
});
|
||||
|
||||
// Spawn sync task
|
||||
runtime.spawn(async move {
|
||||
match sync::sync_content_with_progress(config, None, None, Some(progress_callback)).await {
|
||||
Ok(_) => {
|
||||
*status_clone_final.lock().unwrap() = SyncStatus::Success {
|
||||
message: "Sync completed successfully!".to_string(),
|
||||
};
|
||||
}
|
||||
Err(e) => {
|
||||
*status_clone_final.lock().unwrap() = SyncStatus::Error {
|
||||
message: format!("Sync failed: {}", e),
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn stop_sync(&mut self) {
|
||||
self.runtime = None;
|
||||
*self.status.lock().unwrap() = SyncStatus::Idle;
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for NaviPodApp {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.heading("NaviPod - Sync Navidrome to iPod");
|
||||
ui.separator();
|
||||
|
||||
// Configuration section
|
||||
ui.group(|ui| {
|
||||
ui.label("Navidrome Configuration");
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("URL:");
|
||||
ui.text_edit_singleline(&mut self.navidrome_url);
|
||||
});
|
||||
if self.navidrome_url.is_empty() {
|
||||
ui.label(egui::RichText::new(" (e.g., https://music.example.com)").weak());
|
||||
}
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Username:");
|
||||
ui.text_edit_singleline(&mut self.navidrome_username);
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Password:");
|
||||
ui.add(egui::TextEdit::singleline(&mut self.navidrome_password).password(true));
|
||||
});
|
||||
});
|
||||
|
||||
ui.add_space(10.0);
|
||||
|
||||
// iPod Configuration
|
||||
ui.group(|ui| {
|
||||
ui.label("iPod Configuration");
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Firmware:");
|
||||
egui::ComboBox::from_id_source("firmware")
|
||||
.selected_text(match self.firmware_type {
|
||||
FirmwareType::Rockbox => "Rockbox",
|
||||
FirmwareType::Stock => "Stock",
|
||||
})
|
||||
.show_ui(ui, |ui| {
|
||||
ui.selectable_value(&mut self.firmware_type, FirmwareType::Rockbox, "Rockbox");
|
||||
ui.selectable_value(&mut self.firmware_type, FirmwareType::Stock, "Stock");
|
||||
});
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Mount Point:");
|
||||
ui.text_edit_singleline(&mut self.mount_point);
|
||||
if ui.button("Browse...").clicked() {
|
||||
// Use native file dialog to select directory
|
||||
if let Some(path) = rfd::FileDialog::new()
|
||||
.set_directory("/")
|
||||
.pick_folder()
|
||||
{
|
||||
self.mount_point = path.to_string_lossy().to_string();
|
||||
}
|
||||
}
|
||||
});
|
||||
if self.mount_point.is_empty() {
|
||||
ui.label(egui::RichText::new(" (e.g., /run/media/user/IPOD)").weak());
|
||||
}
|
||||
});
|
||||
|
||||
ui.add_space(10.0);
|
||||
|
||||
// Sync button
|
||||
let is_syncing = matches!(
|
||||
*self.status.lock().unwrap(),
|
||||
SyncStatus::Connecting | SyncStatus::Syncing { .. }
|
||||
);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
if is_syncing {
|
||||
if ui.button("Stop Sync").clicked() {
|
||||
self.stop_sync();
|
||||
}
|
||||
} else {
|
||||
if ui.button("Start Sync").clicked() {
|
||||
self.start_sync();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ui.add_space(10.0);
|
||||
|
||||
// Status display
|
||||
ui.group(|ui| {
|
||||
ui.label("Status:");
|
||||
let status = self.status.lock().unwrap().clone();
|
||||
match status {
|
||||
SyncStatus::Idle => {
|
||||
ui.label(egui::RichText::new("Ready").color(egui::Color32::GRAY));
|
||||
}
|
||||
SyncStatus::Connecting => {
|
||||
ui.label(egui::RichText::new("Connecting to Navidrome...").color(egui::Color32::BLUE));
|
||||
ui.spinner();
|
||||
}
|
||||
SyncStatus::Syncing {
|
||||
current_album,
|
||||
current_song,
|
||||
albums_total,
|
||||
albums_done,
|
||||
} => {
|
||||
ui.label(egui::RichText::new(format!(
|
||||
"Syncing: {} / {} albums",
|
||||
albums_done, albums_total
|
||||
)).color(egui::Color32::BLUE));
|
||||
ui.label(format!("Album: {}", current_album));
|
||||
ui.label(format!("Song: {}", current_song));
|
||||
ui.spinner();
|
||||
}
|
||||
SyncStatus::Success { message } => {
|
||||
ui.label(egui::RichText::new(&message).color(egui::Color32::GREEN));
|
||||
}
|
||||
SyncStatus::Error { message } => {
|
||||
ui.label(egui::RichText::new(&message).color(egui::Color32::RED));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Request repaint to update status
|
||||
ctx.request_repaint();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
use eframe::egui;
|
||||
use navipod::gui::NaviPodApp;
|
||||
|
||||
fn main() -> eframe::Result<()> {
|
||||
// Force X11 backend - set before any winit code runs
|
||||
// This must be set before eframe initializes
|
||||
if std::env::var("WINIT_UNIX_BACKEND").is_err() {
|
||||
std::env::set_var("WINIT_UNIX_BACKEND", "x11");
|
||||
}
|
||||
|
||||
// Also try setting WAYLAND_DISPLAY to empty to prevent Wayland detection
|
||||
if std::env::var("WAYLAND_DISPLAY").is_ok() {
|
||||
std::env::remove_var("WAYLAND_DISPLAY");
|
||||
}
|
||||
|
||||
let options = eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default()
|
||||
.with_inner_size([600.0, 500.0])
|
||||
.with_title("NaviPod - Sync Navidrome to iPod"),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
eframe::run_native(
|
||||
"NaviPod",
|
||||
options,
|
||||
Box::new(|_cc| Box::new(NaviPodApp::default())),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
pub mod config;
|
||||
pub mod error;
|
||||
pub mod gui;
|
||||
pub mod ipod;
|
||||
pub mod navidrome;
|
||||
pub mod sync;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue