small update
This commit is contained in:
parent
b29ef4901e
commit
92d3f7ba77
102
Cargo.lock
generated
102
Cargo.lock
generated
@ -112,15 +112,6 @@ version = "0.8.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "614aa3f2bac03707e62a84d18a48dd3d9ea6171313fd5e6a53b5054d8ae74601"
|
checksum = "614aa3f2bac03707e62a84d18a48dd3d9ea6171313fd5e6a53b5054d8ae74601"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "block-buffer"
|
|
||||||
version = "0.10.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
|
||||||
dependencies = [
|
|
||||||
"generic-array",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.17.0"
|
version = "3.17.0"
|
||||||
@ -266,16 +257,6 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crypto-common"
|
|
||||||
version = "0.1.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
|
||||||
dependencies = [
|
|
||||||
"generic-array",
|
|
||||||
"typenum",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling"
|
name = "darling"
|
||||||
version = "0.20.10"
|
version = "0.20.10"
|
||||||
@ -320,16 +301,6 @@ dependencies = [
|
|||||||
"powerfmt",
|
"powerfmt",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "digest"
|
|
||||||
version = "0.10.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
|
||||||
dependencies = [
|
|
||||||
"block-buffer",
|
|
||||||
"crypto-common",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dirs"
|
name = "dirs"
|
||||||
version = "6.0.0"
|
version = "6.0.0"
|
||||||
@ -547,16 +518,6 @@ dependencies = [
|
|||||||
"slab",
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "generic-array"
|
|
||||||
version = "0.14.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
|
||||||
dependencies = [
|
|
||||||
"typenum",
|
|
||||||
"version_check",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
@ -975,8 +936,8 @@ checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itunesdb"
|
name = "itunesdb"
|
||||||
version = "0.1.11"
|
version = "0.1.19"
|
||||||
source = "git+https://gitea.awain.net/alterwain/ITunesDB.git#4204fdcda886438d815da7b70f736506e06a22e3"
|
source = "git+https://gitea.awain.net/alterwain/ITunesDB.git#e1e8d0a12ca2c3825191ff1d815645a704e1b646"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bincode",
|
"bincode",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
@ -1071,15 +1032,15 @@ dependencies = [
|
|||||||
name = "lyrica"
|
name = "lyrica"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bincode",
|
|
||||||
"chrono",
|
"chrono",
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
"dirs",
|
"dirs",
|
||||||
"futures",
|
"futures",
|
||||||
"itunesdb",
|
"itunesdb",
|
||||||
"md-5",
|
"mp3-duration",
|
||||||
"puremp3",
|
"puremp3",
|
||||||
|
"rand",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
"regex",
|
"regex",
|
||||||
"rusb",
|
"rusb",
|
||||||
@ -1091,16 +1052,6 @@ dependencies = [
|
|||||||
"toml",
|
"toml",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "md-5"
|
|
||||||
version = "0.10.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"digest",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.7.4"
|
version = "2.7.4"
|
||||||
@ -1134,6 +1085,15 @@ dependencies = [
|
|||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mp3-duration"
|
||||||
|
version = "0.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "348bdc7300502f0801e5b57c448815713cd843b744ef9bda252a2698fdf90a0f"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "native-tls"
|
name = "native-tls"
|
||||||
version = "0.2.13"
|
version = "0.2.13"
|
||||||
@ -1411,7 +1371,7 @@ checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.2.15",
|
"getrandom 0.2.15",
|
||||||
"libredox",
|
"libredox",
|
||||||
"thiserror",
|
"thiserror 2.0.11",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1883,13 +1843,33 @@ dependencies = [
|
|||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl 1.0.69",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "2.0.11"
|
version = "2.0.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc"
|
checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl 2.0.11",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2114,12 +2094,6 @@ version = "0.2.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "typenum"
|
|
||||||
version = "1.17.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.16"
|
version = "1.0.16"
|
||||||
@ -2196,12 +2170,6 @@ version = "0.2.15"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "version_check"
|
|
||||||
version = "0.9.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "want"
|
name = "want"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
|
@ -12,7 +12,6 @@ dirs = "6.0.0"
|
|||||||
toml = "0.8.20"
|
toml = "0.8.20"
|
||||||
serde = "1.0.217"
|
serde = "1.0.217"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
bincode = "1.3.3"
|
|
||||||
regex = "1.11.1"
|
regex = "1.11.1"
|
||||||
ratatui = { version = "0.29.0", features = ["all-widgets"] }
|
ratatui = { version = "0.29.0", features = ["all-widgets"] }
|
||||||
color-eyre = "0.6.3"
|
color-eyre = "0.6.3"
|
||||||
@ -21,6 +20,7 @@ futures = "0.3"
|
|||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
tokio-util = { version = "0.7.12", features = ["codec"] }
|
tokio-util = { version = "0.7.12", features = ["codec"] }
|
||||||
soundcloud = { version = "0.1.8", git = "https://gitea.awain.net/alterwain/soundcloud_api.git" }
|
soundcloud = { version = "0.1.8", git = "https://gitea.awain.net/alterwain/soundcloud_api.git" }
|
||||||
itunesdb = { version = "0.1.11", git = "https://gitea.awain.net/alterwain/ITunesDB.git" }
|
itunesdb = { version = "0.1.19", git = "https://gitea.awain.net/alterwain/ITunesDB.git" }
|
||||||
md-5 = "0.10.6"
|
|
||||||
puremp3 = "0.1.0"
|
puremp3 = "0.1.0"
|
||||||
|
mp3-duration = "0.1.10"
|
||||||
|
rand = "0.8.5"
|
@ -1,15 +1,71 @@
|
|||||||
use crate::{screen::AppScreen, theme::Theme};
|
use crate::{screen::AppScreen, theme::Theme};
|
||||||
|
use ratatui::layout::{Constraint, Direction, Layout, Rect};
|
||||||
|
use ratatui::prelude::{Color, Line, Style, Stylize};
|
||||||
|
use ratatui::widgets::{Block, Borders, Paragraph, Row, Table};
|
||||||
|
use ratatui::Frame;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
pub struct FileSystem {}
|
pub struct FileSystem {}
|
||||||
|
|
||||||
impl AppScreen for FileSystem {
|
impl AppScreen for FileSystem {
|
||||||
fn handle_key_event(&mut self, key_event: crossterm::event::KeyEvent) {}
|
fn handle_key_event(&mut self, key_event: crossterm::event::KeyEvent) {}
|
||||||
|
|
||||||
fn render(&self, frame: &mut ratatui::Frame, theme: &Theme) {
|
fn render(&self, frame: &mut ratatui::Frame, theme: &Theme) {
|
||||||
todo!()
|
let chunks = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints([
|
||||||
|
Constraint::Min(0), // Main content area
|
||||||
|
Constraint::Length(1), // Status bar
|
||||||
|
])
|
||||||
|
.split(frame.area());
|
||||||
|
|
||||||
|
self.render_main(frame, chunks[0]);
|
||||||
|
|
||||||
|
// Render Status Bar
|
||||||
|
let status_bar = Paragraph::new(Line::from(vec![
|
||||||
|
"<F5> SAVE AS PLAYLIST".bold(),
|
||||||
|
" | ".dark_gray(),
|
||||||
|
"<F6> SAVE AS IS".bold(),
|
||||||
|
" | ".dark_gray(),
|
||||||
|
"<F8> SELECT".bold(),
|
||||||
|
" | ".dark_gray(),
|
||||||
|
"<F9> DESELECT".bold(),
|
||||||
|
" | ".dark_gray(),
|
||||||
|
"<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 {
|
fn as_any(&mut self) -> &mut dyn std::any::Any {
|
||||||
todo!()
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileSystem {
|
||||||
|
fn render_main(&self, frame: &mut Frame, area: Rect) {
|
||||||
|
let mut v = vec![Row::new(vec!["Name", "Type", "Size", "Modified"])
|
||||||
|
.style(Style::default().fg(Color::Gray))];
|
||||||
|
|
||||||
|
// move this out to make hdd not suffer
|
||||||
|
let paths = std::fs::read_dir("~/Documents").unwrap();
|
||||||
|
|
||||||
|
for path in paths {
|
||||||
|
v.push();
|
||||||
|
}
|
||||||
|
|
||||||
|
let table = Table::new(
|
||||||
|
v,
|
||||||
|
&[
|
||||||
|
Constraint::Percentage(50),
|
||||||
|
Constraint::Length(5),
|
||||||
|
Constraint::Percentage(20),
|
||||||
|
Constraint::Percentage(30),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.block(Block::default().borders(Borders::ALL).title(" Documents "))
|
||||||
|
.style(Style::default().fg(Color::Black));
|
||||||
|
|
||||||
|
frame.render_widget(table, area);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use std::{collections::HashMap, error::Error, io};
|
use crate::file_system::FileSystem;
|
||||||
|
|
||||||
use crate::theme::Theme;
|
use crate::theme::Theme;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
@ -19,6 +18,7 @@ use ratatui::{
|
|||||||
Frame, Terminal,
|
Frame, Terminal,
|
||||||
};
|
};
|
||||||
use screen::AppScreen;
|
use screen::AppScreen;
|
||||||
|
use std::{collections::HashMap, error::Error, io};
|
||||||
use sync::AppEvent;
|
use sync::AppEvent;
|
||||||
use tokio::sync::mpsc::{self, Receiver, UnboundedSender};
|
use tokio::sync::mpsc::{self, Receiver, UnboundedSender};
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
@ -66,6 +66,7 @@ impl Default for App {
|
|||||||
screens.insert(AppState::IPodWait, Box::new(WaitScreen::default()));
|
screens.insert(AppState::IPodWait, Box::new(WaitScreen::default()));
|
||||||
screens.insert(AppState::MainScreen, Box::new(MainScreen::new(jx.clone())));
|
screens.insert(AppState::MainScreen, Box::new(MainScreen::new(jx.clone())));
|
||||||
screens.insert(AppState::LoadingScreen, Box::new(LoadingScreen::default()));
|
screens.insert(AppState::LoadingScreen, Box::new(LoadingScreen::default()));
|
||||||
|
screens.insert(AppState::FileSystem, Box::new(FileSystem::default()));
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
receiver: rx,
|
receiver: rx,
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
use chrono::{DateTime, TimeZone, Utc};
|
use chrono::{DateTime, TimeZone, Utc};
|
||||||
use crossterm::event::{KeyCode, KeyEvent};
|
use crossterm::event::{KeyCode, KeyEvent};
|
||||||
use itunesdb::xobjects::XPlaylist;
|
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
layout::{Constraint, Direction, Layout, Rect},
|
layout::{Constraint, Direction, Layout, Rect},
|
||||||
style::{Color, Modifier, Style, Stylize},
|
style::{Color, Modifier, Style, Stylize},
|
||||||
@ -11,8 +10,8 @@ use ratatui::{
|
|||||||
use soundcloud::sobjects::{CloudPlaylist, CloudPlaylists};
|
use soundcloud::sobjects::{CloudPlaylist, CloudPlaylists};
|
||||||
use tokio::sync::mpsc::UnboundedSender;
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
|
|
||||||
use crate::{screen::AppScreen, sync::AppEvent, theme::Theme};
|
|
||||||
use crate::sync::DBPlaylist;
|
use crate::sync::DBPlaylist;
|
||||||
|
use crate::{screen::AppScreen, sync::AppEvent, theme::Theme, AppState};
|
||||||
|
|
||||||
pub struct MainScreen {
|
pub struct MainScreen {
|
||||||
mode: bool,
|
mode: bool,
|
||||||
@ -36,6 +35,10 @@ impl AppScreen for MainScreen {
|
|||||||
KeyCode::Down => self.next_row(),
|
KeyCode::Down => self.next_row(),
|
||||||
KeyCode::F(6) => self.download_row(),
|
KeyCode::F(6) => self.download_row(),
|
||||||
KeyCode::Tab => self.switch_mode(),
|
KeyCode::Tab => self.switch_mode(),
|
||||||
|
KeyCode::F(2) => {
|
||||||
|
self.sender
|
||||||
|
.send(AppEvent::SwitchScreen(AppState::FileSystem));
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -82,7 +85,7 @@ impl AppScreen for MainScreen {
|
|||||||
"<Q> QUIT".bold(),
|
"<Q> QUIT".bold(),
|
||||||
]))
|
]))
|
||||||
.centered();
|
.centered();
|
||||||
frame.render_widget(status_bar, chunks[2]); // Render into third chunk
|
frame.render_widget(status_bar, chunks[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_any(&mut self) -> &mut dyn std::any::Any {
|
fn as_any(&mut self) -> &mut dyn std::any::Any {
|
||||||
@ -203,7 +206,7 @@ impl MainScreen {
|
|||||||
self.update_max_rows();
|
self.update_max_rows();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_itunes(&mut self, pl: Vec<DBPlaylist>) {
|
pub fn set_itunes(&mut self, pl: Vec<DBPlaylist>) {
|
||||||
self.playlists = Some(pl);
|
self.playlists = Some(pl);
|
||||||
if self.selected_tab == 2 {
|
if self.selected_tab == 2 {
|
||||||
@ -247,11 +250,11 @@ impl MainScreen {
|
|||||||
);
|
);
|
||||||
if let Some(s) = &self.playlists {
|
if let Some(s) = &self.playlists {
|
||||||
for (i, playlist) in s.iter().enumerate() {
|
for (i, playlist) in s.iter().enumerate() {
|
||||||
let date = Utc.timestamp_millis_opt(playlist.data.timestamp as i64).unwrap();
|
let date = Utc.timestamp_millis_opt(playlist.timestamp as i64).unwrap();
|
||||||
let mut row = Row::new(vec![
|
let mut row = Row::new(vec![
|
||||||
playlist.data.persistent_playlist_id.to_string(),
|
playlist.id.to_string(),
|
||||||
"".to_string(),
|
"".to_string(),
|
||||||
playlist.elems.len().to_string(),
|
playlist.tracks.len().to_string(),
|
||||||
format!("{}", date.format("%Y-%m-%d %H:%M")),
|
format!("{}", date.format("%Y-%m-%d %H:%M")),
|
||||||
"YES".to_string(),
|
"YES".to_string(),
|
||||||
]);
|
]);
|
||||||
@ -320,7 +323,7 @@ impl MainScreen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
v
|
v
|
||||||
},
|
}
|
||||||
2 => {
|
2 => {
|
||||||
// local
|
// local
|
||||||
let mut v = Vec::new();
|
let mut v = Vec::new();
|
||||||
@ -329,14 +332,14 @@ impl MainScreen {
|
|||||||
.style(Style::default().fg(Color::Gray)),
|
.style(Style::default().fg(Color::Gray)),
|
||||||
);
|
);
|
||||||
if let Some(pls) = &self.playlists {
|
if let Some(pls) = &self.playlists {
|
||||||
let s = &pls.get(self.selected_playlist as usize).unwrap().elems;
|
let s = &pls.get(self.selected_playlist as usize).unwrap().tracks;
|
||||||
for (i, track) in s.iter().enumerate() {
|
for (i, track) in s.iter().enumerate() {
|
||||||
let mut row = Row::new(vec![
|
let mut row = Row::new(vec![
|
||||||
track.unique_id.to_string(),
|
track.data.unique_id.to_string(),
|
||||||
track.title.clone(),
|
track.get_title(),
|
||||||
track.location.clone(),
|
track.get_location(),
|
||||||
track.bitrate.to_string(),
|
track.data.bitrate.to_string(),
|
||||||
track.genre.clone(),
|
track.get_genre(),
|
||||||
]);
|
]);
|
||||||
if self.selected_song == i as i32 {
|
if self.selected_song == i as i32 {
|
||||||
row = row.style(Style::default().bg(Color::LightBlue).fg(Color::White));
|
row = row.style(Style::default().bg(Color::LightBlue).fg(Color::White));
|
||||||
|
131
src/sync.rs
131
src/sync.rs
@ -1,7 +1,9 @@
|
|||||||
|
use itunesdb::objects::{ListSortOrder, PlaylistItem};
|
||||||
|
use itunesdb::serializer;
|
||||||
|
use itunesdb::xobjects::{XDatabase, XPlArgument, XPlaylist, XTrackItem};
|
||||||
|
use soundcloud::sobjects::{CloudPlaylist, CloudPlaylists, CloudTrack};
|
||||||
|
use std::io::Write;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use itunesdb::xobjects::{XDatabase, XTrackItem};
|
|
||||||
use soundcloud::sobjects::{CloudPlaylist, CloudPlaylists};
|
|
||||||
use tokio::{
|
use tokio::{
|
||||||
fs::File,
|
fs::File,
|
||||||
io::{AsyncReadExt, AsyncWriteExt},
|
io::{AsyncReadExt, AsyncWriteExt},
|
||||||
@ -9,9 +11,13 @@ use tokio::{
|
|||||||
};
|
};
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
use crate::{config::{
|
use crate::{
|
||||||
get_config_path, get_configs_dir, get_temp_dl_dir, get_temp_itunesdb, LyricaConfiguration,
|
config::{
|
||||||
}, dlp::{self, DownloadProgress}, util, AppState};
|
get_config_path, get_configs_dir, get_temp_dl_dir, get_temp_itunesdb, LyricaConfiguration,
|
||||||
|
},
|
||||||
|
dlp::{self, DownloadProgress},
|
||||||
|
util, AppState,
|
||||||
|
};
|
||||||
|
|
||||||
pub enum AppEvent {
|
pub enum AppEvent {
|
||||||
SearchIPod,
|
SearchIPod,
|
||||||
@ -28,7 +34,54 @@ pub struct DBPlaylist {
|
|||||||
pub id: u64,
|
pub id: u64,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub timestamp: u32,
|
pub timestamp: u32,
|
||||||
pub tracks: Vec<XTrackItem>
|
pub tracks: Vec<XTrackItem>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn track_from_soundcloud(value: &CloudTrack) -> XTrackItem {
|
||||||
|
let mut track_path = get_temp_dl_dir();
|
||||||
|
track_path.push(value.id.to_string());
|
||||||
|
track_path.set_extension("mp3");
|
||||||
|
let f = std::fs::File::open(&track_path).unwrap();
|
||||||
|
let mut data = &std::fs::read(&track_path).unwrap()[..];
|
||||||
|
let (header, _samples) = puremp3::read_mp3(data).unwrap();
|
||||||
|
|
||||||
|
let duration = mp3_duration::from_read(&mut data).unwrap();
|
||||||
|
|
||||||
|
let mut track = XTrackItem::new(
|
||||||
|
value.id as u32,
|
||||||
|
f.metadata().unwrap().len() as u32,
|
||||||
|
duration.as_millis() as u32,
|
||||||
|
0,
|
||||||
|
header.bitrate.bps() / 1000,
|
||||||
|
header.sample_rate.hz(),
|
||||||
|
hash(),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
track.set_title(value.title.clone().unwrap());
|
||||||
|
track.set_artist(
|
||||||
|
value
|
||||||
|
.user
|
||||||
|
.clone()
|
||||||
|
.map_or(String::new(), |a| a.username.unwrap_or(a.permalink)),
|
||||||
|
);
|
||||||
|
track.set_genre(value.genre.clone().unwrap());
|
||||||
|
track.update_arg(6, String::from("MPEG audio file"));
|
||||||
|
track
|
||||||
|
}
|
||||||
|
|
||||||
|
// note: this hash function is used to make unique ids for each track. It doesn't aim to generate secure ones.
|
||||||
|
fn hash() -> u64 {
|
||||||
|
rand::random::<u64>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn overwrite_database(database: &mut XDatabase, ipod_path: &String) {
|
||||||
|
let data = serializer::to_bytes(database);
|
||||||
|
let mut p: PathBuf = Path::new(ipod_path).into();
|
||||||
|
p.push("iPod_Control");
|
||||||
|
p.push("iTunes");
|
||||||
|
p.push("iTunesDB");
|
||||||
|
let mut file = std::fs::File::create(p).unwrap();
|
||||||
|
let _ = file.write(&data);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn initialize_async_service(
|
pub fn initialize_async_service(
|
||||||
@ -60,7 +113,8 @@ pub fn initialize_async_service(
|
|||||||
let _ = sender.send(AppEvent::IPodNotFound).await;
|
let _ = sender.send(AppEvent::IPodNotFound).await;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
AppEvent::DownloadPlaylist(playlist) => download_playlist(playlist, &mut database.as_mut().unwrap(), &sender, ipod_db.clone().unwrap()).await,
|
AppEvent::DownloadPlaylist(playlist) => download_playlist(playlist, database.as_mut().unwrap(), &sender, ipod_db.clone().unwrap()).await,
|
||||||
|
AppEvent::SwitchScreen(state) => { let _ = sender.send(AppEvent::SwitchScreen(state)).await;},
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -74,7 +128,7 @@ async fn download_playlist(
|
|||||||
playlist: CloudPlaylist,
|
playlist: CloudPlaylist,
|
||||||
database: &mut XDatabase,
|
database: &mut XDatabase,
|
||||||
sender: &Sender<AppEvent>,
|
sender: &Sender<AppEvent>,
|
||||||
ipod_path: String
|
ipod_path: String,
|
||||||
) {
|
) {
|
||||||
if let Ok(()) =
|
if let Ok(()) =
|
||||||
dlp::download_from_soundcloud(&playlist.permalink_url, &get_temp_dl_dir(), sender.clone())
|
dlp::download_from_soundcloud(&playlist.permalink_url, &get_temp_dl_dir(), sender.clone())
|
||||||
@ -82,35 +136,61 @@ async fn download_playlist(
|
|||||||
{
|
{
|
||||||
let tracks = playlist.tracks;
|
let tracks = playlist.tracks;
|
||||||
|
|
||||||
let mut p: PathBuf = Path::new(&ipod_path).into();
|
let p: PathBuf = Path::new(&ipod_path).into();
|
||||||
|
|
||||||
|
let mut new_playlist = XPlaylist::new(rand::random(), ListSortOrder::SongTitle);
|
||||||
|
|
||||||
|
new_playlist.set_title(playlist.title);
|
||||||
|
|
||||||
for track in tracks {
|
for track in tracks {
|
||||||
if track.title.is_none() {
|
if track.title.is_none() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let mut t: XTrackItem = track.into();
|
let mut t: XTrackItem = track_from_soundcloud(&track);
|
||||||
t.data.unique_id = database.get_unique_id();
|
t.data.unique_id = database.get_unique_id();
|
||||||
|
new_playlist.add_elem(t.data.unique_id);
|
||||||
let mut tp = PathBuf::new();
|
let mut tp = PathBuf::new();
|
||||||
tp.push("iPod_Control");
|
tp.push(":iPod_Control");
|
||||||
tp.push("Music");
|
tp.push("Music");
|
||||||
tp.push(["F", &format!("{:02}", &(t.data.unique_id % 100))].concat());
|
tp.push(["F", &format!("{:02}", &(t.data.unique_id % 100))].concat());
|
||||||
tp.push(format!("{:X}", t.data.unique_id));
|
tp.push(format!("{:X}", t.data.unique_id));
|
||||||
tp.set_extension("mp3");
|
tp.set_extension("mp3");
|
||||||
t.set_location(tp.to_str().unwrap().to_string().replace("/", ":").to_string());
|
t.set_location(
|
||||||
|
tp.to_str()
|
||||||
|
.unwrap()
|
||||||
|
.to_string()
|
||||||
|
.replace("/", ":")
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
let mut dest = p.clone();
|
let mut dest = p.clone();
|
||||||
dest.push(tp);
|
dest.push("iPod_Control");
|
||||||
|
dest.push("Music");
|
||||||
|
dest.push(["F", &format!("{:02}", &(t.data.unique_id % 100))].concat());
|
||||||
|
let _ = std::fs::create_dir_all(dest.to_str().unwrap());
|
||||||
|
dest.push(format!("{:X}", t.data.unique_id));
|
||||||
|
dest.set_extension("mp3");
|
||||||
|
|
||||||
let mut track_path = get_temp_dl_dir();
|
let mut track_path = get_temp_dl_dir();
|
||||||
track_path.push(track.id.to_string());
|
track_path.push(track.id.to_string());
|
||||||
track_path.set_extension("mp3");
|
track_path.set_extension("mp3");
|
||||||
|
|
||||||
let _ = std::fs::copy(track_path, dest);
|
let _ = std::fs::copy(track_path.to_str().unwrap(), dest.to_str().unwrap());
|
||||||
|
|
||||||
let _ = database.add_track(t);
|
database.add_track(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
database.add_playlist(new_playlist);
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = sender
|
let _ = sender
|
||||||
.send(AppEvent::SwitchScreen(AppState::MainScreen))
|
.send(AppEvent::SwitchScreen(AppState::MainScreen))
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
let _ = sender
|
||||||
|
.send(AppEvent::ITunesParsed(get_playlists(database)))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
overwrite_database(database, &ipod_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_playlists(db: &mut XDatabase) -> Vec<DBPlaylist> {
|
fn get_playlists(db: &mut XDatabase) -> Vec<DBPlaylist> {
|
||||||
@ -120,7 +200,19 @@ fn get_playlists(db: &mut XDatabase) -> Vec<DBPlaylist> {
|
|||||||
id: t.data.persistent_playlist_id,
|
id: t.data.persistent_playlist_id,
|
||||||
title: t.get_title(),
|
title: t.get_title(),
|
||||||
timestamp: t.data.timestamp,
|
timestamp: t.data.timestamp,
|
||||||
tracks: t.elems.iter().map(|(i, _a)| db.get_track(i.track_id)).filter(|t| t.is_some()).map(|t| t.unwrap().clone()).collect()}).collect()
|
tracks: to_tracks(db, t.elems.clone()),
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_tracks(db: &mut XDatabase, elems: Vec<(PlaylistItem, Vec<XPlArgument>)>) -> Vec<XTrackItem> {
|
||||||
|
elems
|
||||||
|
.iter()
|
||||||
|
.map(|(i, _a)| i.track_id)
|
||||||
|
.map(|id| db.get_track(id))
|
||||||
|
.filter(|i| i.is_some())
|
||||||
|
.map(|i| i.unwrap().clone())
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn parse_itunes(sender: &Sender<AppEvent>, path: String) -> XDatabase {
|
async fn parse_itunes(sender: &Sender<AppEvent>, path: String) -> XDatabase {
|
||||||
@ -129,7 +221,6 @@ async fn parse_itunes(sender: &Sender<AppEvent>, path: String) -> XDatabase {
|
|||||||
p.push("iPod_Control");
|
p.push("iPod_Control");
|
||||||
p.push("iTunes");
|
p.push("iTunes");
|
||||||
p.push("iTunesDB");
|
p.push("iTunesDB");
|
||||||
println!("{}", p.to_str().unwrap());
|
|
||||||
let _ = std::fs::copy(p, &cd);
|
let _ = std::fs::copy(p, &cd);
|
||||||
let mut file = File::open(cd).await.unwrap();
|
let mut file = File::open(cd).await.unwrap();
|
||||||
let mut contents = vec![];
|
let mut contents = vec![];
|
||||||
@ -137,9 +228,7 @@ async fn parse_itunes(sender: &Sender<AppEvent>, path: String) -> XDatabase {
|
|||||||
let mut database = itunesdb::deserializer::parse_bytes(&contents);
|
let mut database = itunesdb::deserializer::parse_bytes(&contents);
|
||||||
|
|
||||||
let _ = sender
|
let _ = sender
|
||||||
.send(AppEvent::ITunesParsed(
|
.send(AppEvent::ITunesParsed(get_playlists(&mut database)))
|
||||||
get_playlists(&mut database),
|
|
||||||
))
|
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let p = get_config_path();
|
let p = get_config_path();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user