modified: src/db.rs

new file:   src/file_system.rs
	new file:   src/loading_screen.rs
	modified:   src/main.rs
	modified:   src/main_screen.rs
	modified:   src/sync.rs
This commit is contained in:
Michael Wain 2025-02-12 22:56:56 +03:00
parent b34032d228
commit 1657e328fb
6 changed files with 164 additions and 99 deletions

View File

@ -124,6 +124,17 @@ pub fn get_track(db: &Database, id: u32) -> Result<Track, Error> {
Ok(track)
}
pub fn get_all_tracks(db: &Database) -> Result<Vec<Track>, Error> {
let read_txn = db.begin_read()?;
let table = read_txn.open_table(TRACKS)?;
Ok(table
.iter()
.unwrap()
.flatten()
.map(|d| bincode::deserialize(&d.1.value()).unwrap())
.collect::<Vec<Track>>())
}
pub fn get_last_track_id(db: &Database) -> Result<u32, Error> {
let read_txn = db.begin_read()?;
let table = read_txn.open_table(TRACKS)?;

15
src/file_system.rs Normal file
View File

@ -0,0 +1,15 @@
use crate::screen::AppScreen;
pub struct FileSystem {}
impl AppScreen for FileSystem {
fn handle_key_event(&mut self, key_event: crossterm::event::KeyEvent) {}
fn render(&self, frame: &mut ratatui::Frame) {
todo!()
}
fn as_any(&mut self) -> &mut dyn std::any::Any {
todo!()
}
}

101
src/loading_screen.rs Normal file
View File

@ -0,0 +1,101 @@
use ratatui::{
layout::{Constraint, Direction, Layout, Rect},
style::{Color, Style, Stylize},
text::Line,
widgets::{Block, Borders, Gauge, Paragraph},
Frame,
};
use crate::{dlp::DownloadProgress, screen::AppScreen};
#[derive(Default)]
pub struct LoadingScreen {
pub progress: Option<(u32, u32)>,
pub s_progress: Option<DownloadProgress>,
}
impl AppScreen for LoadingScreen {
fn handle_key_event(&mut self, key_event: crossterm::event::KeyEvent) {}
fn render(&self, frame: &mut ratatui::Frame) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Min(0), // Main content area
Constraint::Length(1), // Status bar
])
.split(frame.area());
self.render_progress(frame, chunks[0]);
// Render Status Bar
let status_bar = Paragraph::new(Line::from(vec!["<Q> QUIT".bold()])).centered();
frame.render_widget(status_bar, chunks[1]); // Render into third chunk
}
fn as_any(&mut self) -> &mut dyn std::any::Any {
self
}
}
impl LoadingScreen {
fn render_progress(&self, frame: &mut Frame, area: Rect) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Min(0), // Main content
Constraint::Length(6), // Progress bar
Constraint::Length(6), // Progress bar
])
.split(area);
let main_content = Paragraph::new("Please wait").block(
Block::default()
.borders(Borders::ALL)
.title("Downloading has started!"),
);
frame.render_widget(main_content, chunks[0]);
let gauge = Gauge::default()
.block(
Block::default()
.borders(Borders::ALL)
.title(" Downloading Playlist "),
)
.gauge_style(Style::default().fg(Color::Green))
.ratio(self.progress.unwrap().0 as f64 / self.progress.unwrap().1 as f64)
.label(format!(
"{:}/{:}",
self.progress.unwrap().0,
self.progress.unwrap().1
));
frame.render_widget(gauge, chunks[1]);
if self.s_progress.is_none() {
return;
}
let s: String = self
.s_progress
.as_ref()
.unwrap()
.progress_percentage
.chars()
.filter(|c| c.is_ascii_digit() || *c == '.')
.collect();
let ratio: f64 = s.parse::<f64>().unwrap_or(0.0);
let gauge = Gauge::default()
.block(Block::default().borders(Borders::ALL).title(format!(
" Downloading Item (ETA: {}) ",
self.s_progress.as_ref().unwrap().eta
)))
.gauge_style(Style::default().fg(Color::Green))
.ratio(ratio / 100.0)
.label(self.s_progress.as_ref().unwrap().progress_total.to_string());
frame.render_widget(gauge, chunks[2]);
}
}

View File

@ -10,6 +10,7 @@ use crossterm::{
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use futures::StreamExt;
use loading_screen::LoadingScreen;
use main_screen::MainScreen;
use ratatui::{
prelude::{Backend, CrosstermBackend},
@ -25,6 +26,8 @@ use wait_screen::WaitScreen;
mod config;
mod db;
mod dlp;
mod file_system;
mod loading_screen;
mod main_screen;
mod screen;
mod sync;
@ -35,6 +38,8 @@ mod wait_screen;
enum AppState {
IPodWait,
MainScreen,
LoadingScreen,
FileSystem,
}
pub struct App {
@ -58,6 +63,7 @@ impl Default for App {
let mut screens: HashMap<AppState, Box<dyn AppScreen>> = HashMap::new();
screens.insert(AppState::IPodWait, Box::new(WaitScreen::default()));
screens.insert(AppState::MainScreen, Box::new(MainScreen::new(jx.clone())));
screens.insert(AppState::LoadingScreen, Box::new(LoadingScreen::default()));
Self {
receiver: rx,
@ -102,25 +108,23 @@ impl App {
AppEvent::IPodNotFound => {
let _ = self.sender.send(AppEvent::SearchIPod);
},
AppEvent::ITunesParsed(xdb) => {
AppEvent::ITunesParsed(tracks) => {
let screen: &mut MainScreen = self.get_screen(&AppState::MainScreen);
screen.tracks = Some(tracks);
},
AppEvent::SoundcloudGot(playlists) => {
let a = self.screens.get_mut(&AppState::MainScreen).unwrap();
let screen: &mut MainScreen = a.as_any().downcast_mut::<MainScreen>().unwrap();
let screen: &mut MainScreen = self.get_screen(&AppState::MainScreen);
screen.set_soundcloud_playlists(playlists);
},
AppEvent::OverallProgress((c, max)) => {
let a = self.screens.get_mut(&AppState::MainScreen).unwrap();
let screen: &mut MainScreen = a.as_any().downcast_mut::<MainScreen>().unwrap();
self.state = AppState::LoadingScreen;
let screen: &mut LoadingScreen = self.get_screen(&AppState::LoadingScreen);
screen.progress = Some((c, max));
screen.download_screen();
},
AppEvent::CurrentProgress(progress) => {
let a = self.screens.get_mut(&AppState::MainScreen).unwrap();
let screen: &mut MainScreen = a.as_any().downcast_mut::<MainScreen>().unwrap();
self.state = AppState::LoadingScreen;
let screen: &mut LoadingScreen = self.get_screen(&AppState::LoadingScreen);
screen.s_progress = Some(progress);
screen.download_screen();
},
_ => {}
}
@ -141,6 +145,14 @@ impl App {
fn exit(&mut self) {
self.token.cancel();
}
fn get_screen<T>(&mut self, state: &AppState) -> &mut T
where
T: 'static + AppScreen,
{
let a = self.screens.get_mut(state).unwrap();
a.as_any().downcast_mut::<T>().unwrap()
}
}
#[tokio::main]

View File

@ -5,22 +5,14 @@ use ratatui::{
layout::{Constraint, Direction, Layout, Rect},
style::{Color, Modifier, Style, Stylize},
text::{Line, Span},
widgets::{Block, Borders, Gauge, Paragraph, Row, Table, Tabs},
widgets::{Block, Borders, Paragraph, Row, Table, Tabs},
Frame,
};
use soundcloud::sobjects::{CloudPlaylist, CloudPlaylists};
use strum::IntoEnumIterator;
use tokio::sync::mpsc::UnboundedSender;
use crate::{dlp::DownloadProgress, screen::AppScreen, sync::AppEvent};
/*struct Playlist {
id: u64,
title: String,
link: String,
created_at: String,
track_count: u32,
}*/
use crate::{db::Track, screen::AppScreen, sync::AppEvent};
pub struct MainScreen {
selected_tab: i8,
@ -28,8 +20,7 @@ pub struct MainScreen {
max_rows: i32,
tab_titles: Vec<String>,
soundcloud: Option<Vec<CloudPlaylist>>,
pub progress: Option<(u32, u32)>,
pub s_progress: Option<DownloadProgress>,
pub tracks: Option<Vec<Track>>,
sender: UnboundedSender<AppEvent>,
}
@ -72,10 +63,7 @@ impl AppScreen for MainScreen {
frame.render_widget(tabs, chunks[0]);
match self.selected_tab {
-1 => self.render_progress(frame, chunks[1]),
_ => self.render_tab(frame, chunks[1]),
}
self.render_tab(frame, chunks[1]);
// Render Status Bar
let status_bar = Paragraph::new(Line::from(vec![
@ -101,11 +89,10 @@ impl AppScreen for MainScreen {
impl MainScreen {
pub fn new(sender: UnboundedSender<AppEvent>) -> Self {
MainScreen {
selected_row: -1,
selected_row: 0,
max_rows: 0,
soundcloud: None,
progress: None,
s_progress: None,
tracks: None,
selected_tab: 0,
tab_titles: vec![
"YouTube".to_string(),
@ -117,10 +104,6 @@ impl MainScreen {
}
}
pub fn download_screen(&mut self) {
self.selected_tab = -1;
}
fn update_max_rows(&mut self) {
self.max_rows = match self.selected_tab {
1 => self.soundcloud.as_deref().unwrap_or(&[]).len(),
@ -175,66 +158,6 @@ impl MainScreen {
self.soundcloud = Some(pl.collection);
}
fn render_progress(&self, frame: &mut Frame, area: Rect) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Min(0), // Main content
Constraint::Length(6), // Progress bar
Constraint::Length(6), // Progress bar
])
.split(area);
let main_content = Paragraph::new("Please wait").block(
Block::default()
.borders(Borders::ALL)
.title("Downloading has started!"),
);
frame.render_widget(main_content, chunks[0]);
let gauge = Gauge::default()
.block(
Block::default()
.borders(Borders::ALL)
.title(" Downloading Playlist "),
)
.gauge_style(Style::default().fg(Color::Green))
.ratio(self.progress.unwrap().0 as f64 / self.progress.unwrap().1 as f64)
.label(format!(
"{:}/{:}",
self.progress.unwrap().0,
self.progress.unwrap().1
));
frame.render_widget(gauge, chunks[1]);
if self.s_progress.is_none() {
return;
}
let s: String = self
.s_progress
.as_ref()
.unwrap()
.progress_percentage
.chars()
.filter(|c| c.is_ascii_digit() || *c == '.')
.collect();
let ratio: f64 = s.parse::<f64>().unwrap_or(0.0);
let gauge = Gauge::default()
.block(Block::default().borders(Borders::ALL).title(format!(
" Downloading Item (ETA: {}) ",
self.s_progress.as_ref().unwrap().eta
)))
.gauge_style(Style::default().fg(Color::Green))
.ratio(ratio / 100.0)
.label(self.s_progress.as_ref().unwrap().progress_total.to_string());
frame.render_widget(gauge, chunks[2]);
}
fn render_tab(&self, frame: &mut Frame, area: Rect) /*-> Table<'_>*/
{
let rows = match self.selected_tab {
@ -265,7 +188,7 @@ impl MainScreen {
}
2 => {
// local
/* let mut v = Vec::new();
let mut v = Vec::new();
v.push(
Row::new(vec!["Id", "Title", "Artist", "Bitrate", "Hash"])
.style(Style::default().fg(Color::Gray)),
@ -286,8 +209,7 @@ impl MainScreen {
v.push(row);
}
}
v*/
Vec::new()
v
}
_ => Vec::new(),
};

View File

@ -1,6 +1,6 @@
use std::path::{Path, PathBuf};
use itunesdb::xobjects::{XDatabase, XSomeList};
use itunesdb::xobjects::XSomeList;
use redb::Database;
use soundcloud::sobjects::{CloudPlaylist, CloudPlaylists};
use tokio::{
@ -23,7 +23,7 @@ pub enum AppEvent {
IPodFound(String),
IPodNotFound,
ParseItunes(String),
ITunesParsed(XDatabase),
ITunesParsed(Vec<Track>),
SoundcloudGot(CloudPlaylists),
DownloadPlaylist(CloudPlaylist),
CurrentProgress(DownloadProgress),
@ -108,7 +108,11 @@ async fn parse_itunes(database: &Database, sender: &Sender<AppEvent>, path: Stri
}
}
let _ = sender.send(AppEvent::ITunesParsed(xdb)).await;
let _ = sender
.send(AppEvent::ITunesParsed(
db::get_all_tracks(database).unwrap(),
))
.await;
let p = get_config_path();
if !p.exists() {