removing termusic
This commit is contained in:
parent
2a713dce7d
commit
b5aadbf88b
6 changed files with 18 additions and 707 deletions
12
flake.lock
generated
12
flake.lock
generated
|
@ -90,11 +90,11 @@
|
||||||
"nixpkgs-stable": "nixpkgs-stable"
|
"nixpkgs-stable": "nixpkgs-stable"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1749918208,
|
"lastModified": 1749921358,
|
||||||
"narHash": "sha256-lSNJKfE5XvZ0BTbdl/sAZ+3nUxhPnhB1g9Dj1o84MPw=",
|
"narHash": "sha256-mhVXykmXD59JAwcYmnW3lJoavq23ysfSbPQC9gRdzm8=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "emacs-overlay",
|
"repo": "emacs-overlay",
|
||||||
"rev": "90d12e22421147992240f87578dee2e8ff0635af",
|
"rev": "5479bde495cf43766d53b3789a316e83ef7f8905",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -1797,11 +1797,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1749875058,
|
"lastModified": 1749925521,
|
||||||
"narHash": "sha256-LGeVwJLNJ+QPsuQMGsm5atAkFZgrRG3t62bB0+loOi4=",
|
"narHash": "sha256-cbTj62MYbyZFYYTeA5zviOF/tr6ZzRKaJh1BEFsoE6c=",
|
||||||
"owner": "0xc000022070",
|
"owner": "0xc000022070",
|
||||||
"repo": "zen-browser-flake",
|
"repo": "zen-browser-flake",
|
||||||
"rev": "12fbcbb395776b50a848f87434bb786ef4f09b9d",
|
"rev": "bb4ab4a99523e3b4ef1f6ba1fc49d21f41e4921b",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
|
@ -4,8 +4,6 @@
|
||||||
home.packages = with pkgs; [
|
home.packages = with pkgs; [
|
||||||
mpc
|
mpc
|
||||||
nix-config.packages.x86_64-linux.rmpc-latest
|
nix-config.packages.x86_64-linux.rmpc-latest
|
||||||
# termusic-patched
|
|
||||||
termusic-git
|
|
||||||
];
|
];
|
||||||
|
|
||||||
services = {
|
services = {
|
||||||
|
|
|
@ -256,8 +256,11 @@ in
|
||||||
softness = 10;
|
softness = 10;
|
||||||
spread = 8;
|
spread = 8;
|
||||||
};
|
};
|
||||||
min-width = 250;
|
|
||||||
min-height = 250;
|
min-width = 500;
|
||||||
|
min-height = 400;
|
||||||
|
# tiled-state = false;
|
||||||
|
|
||||||
open-focused = true;
|
open-focused = true;
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
|
@ -30,11 +30,17 @@ in {
|
||||||
"wireplumber"
|
"wireplumber"
|
||||||
"bluetooth"
|
"bluetooth"
|
||||||
"power-profiles-daemon"
|
"power-profiles-daemon"
|
||||||
"upower"
|
# "upower"
|
||||||
|
"battery"
|
||||||
"idle_inhibitor"
|
"idle_inhibitor"
|
||||||
"custom/notification"
|
"custom/notification"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
"battery" = {
|
||||||
|
"format" = "{capacity}% {icon}";
|
||||||
|
"format-icons" = ["" "" "" "" ""];
|
||||||
|
};
|
||||||
|
|
||||||
"bluetooth" = {
|
"bluetooth" = {
|
||||||
"format" = "";
|
"format" = "";
|
||||||
"format-off" = "";
|
"format-off" = "";
|
||||||
|
@ -290,12 +296,6 @@ window#waybar {
|
||||||
animation-direction: alternate;
|
animation-direction: alternate;
|
||||||
}
|
}
|
||||||
|
|
||||||
#mpris {
|
|
||||||
}
|
|
||||||
|
|
||||||
#workspaces {
|
|
||||||
}
|
|
||||||
|
|
||||||
#workspaces button {
|
#workspaces button {
|
||||||
color: #666;
|
color: #666;
|
||||||
padding: 3px 6px;
|
padding: 3px 6px;
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
final: prev: {
|
|
||||||
termusic-git = prev.termusic.overrideAttrs (old: rec {
|
|
||||||
version = "HEAD";
|
|
||||||
|
|
||||||
src = prev.fetchFromGitHub {
|
|
||||||
owner = "tramhao";
|
|
||||||
repo = "termusic";
|
|
||||||
rev = "HEAD";
|
|
||||||
hash = "sha256-rK4kZlKXxH4fh7ODCwfx/MfDZaFOPMgw7XLOKHtistc=";
|
|
||||||
};
|
|
||||||
|
|
||||||
cargoDeps = final.rustPlatform.fetchCargoVendor {
|
|
||||||
inherit src;
|
|
||||||
hash = "sha256-q8rfB57sSmzvuY+QomBY8boOADLSYQnt1aRVi6fz1bY=";
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,673 +0,0 @@
|
||||||
From a319c68c62ae93c1604c84d488192ca0e8551cb5 Mon Sep 17 00:00:00 2001
|
|
||||||
From: hasezoey <hasezoey@gmail.com>
|
|
||||||
Date: Wed, 28 May 2025 16:21:25 +0200
|
|
||||||
Subject: [PATCH 1/3] feat(tui::components::music_library): change music
|
|
||||||
library tree loading to be async and non-blocking
|
|
||||||
|
|
||||||
Downside is that the placeholder still acts as a correct path and that multiple loads can be done and overwrite eachother.
|
|
||||||
|
|
||||||
fixes #481
|
|
||||||
---
|
|
||||||
CHANGELOG.md | 1 +
|
|
||||||
lib/src/types.rs | 13 ++
|
|
||||||
tui/src/ui/components/config_editor/view.rs | 2 +-
|
|
||||||
tui/src/ui/components/music_library.rs | 226 +++++++++++---------
|
|
||||||
tui/src/ui/components/playlist.rs | 2 +-
|
|
||||||
tui/src/ui/components/tag_editor/update.rs | 2 +-
|
|
||||||
tui/src/ui/model/mod.rs | 19 +-
|
|
||||||
tui/src/ui/model/update.rs | 32 +--
|
|
||||||
8 files changed, 172 insertions(+), 125 deletions(-)
|
|
||||||
|
|
||||||
diff --git a/CHANGELOG.md b/CHANGELOG.md
|
|
||||||
index 2cd911a25..0641e5f29 100644
|
|
||||||
--- a/CHANGELOG.md
|
|
||||||
+++ b/CHANGELOG.md
|
|
||||||
@@ -21,6 +21,7 @@
|
|
||||||
- Fix(tui): on track change, dont select "current track" playlist item if the old "current track" was not selected.
|
|
||||||
- Fix(tui): re-select the (approximately) same playlist item after a shuffle.
|
|
||||||
- Fix(tui): escape key will now no longer also act as a quit key.
|
|
||||||
+- Fix(tui): changed the music library tree loading to be async.
|
|
||||||
- Fix(server): with `rusty-soundtouch` on `rusty` backend, dont take initial samples until necessary.
|
|
||||||
- Fix(server): on rusty backend, always decode and use `f32` samples. (instead of `i16`)
|
|
||||||
- Fix(server): on rusty backend, update `soundtouch` version to fix build issues on latest arch & gcc 15.
|
|
||||||
diff --git a/lib/src/types.rs b/lib/src/types.rs
|
|
||||||
index d26802f9d..f60080802 100644
|
|
||||||
--- a/lib/src/types.rs
|
|
||||||
+++ b/lib/src/types.rs
|
|
||||||
@@ -1,3 +1,4 @@
|
|
||||||
+use std::path::PathBuf;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use crate::config::v2::tui::{keys::KeyBinding, theme::styles::ColorTermusic};
|
|
||||||
@@ -387,6 +388,14 @@ pub enum KFMsg {
|
|
||||||
PodcastRefreshAllFeedsBlurUp,
|
|
||||||
}
|
|
||||||
|
|
||||||
+/// Basically a Tree Node, but without having to include `tui-realm-treeview` as another dependency for lib
|
|
||||||
+#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
+pub struct RecVec<T, V> {
|
|
||||||
+ pub id: T,
|
|
||||||
+ pub value: V,
|
|
||||||
+ pub children: Vec<RecVec<T, V>>,
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub enum LIMsg {
|
|
||||||
TreeStepInto(String),
|
|
||||||
@@ -397,6 +406,10 @@ pub enum LIMsg {
|
|
||||||
SwitchRoot,
|
|
||||||
AddRoot,
|
|
||||||
RemoveRoot,
|
|
||||||
+
|
|
||||||
+ /// A requested node is ready from loading.
|
|
||||||
+ /// `(Tree, FocusNode)`
|
|
||||||
+ TreeNodeReady(RecVec<PathBuf, String>, Option<String>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
diff --git a/tui/src/ui/components/config_editor/view.rs b/tui/src/ui/components/config_editor/view.rs
|
|
||||||
index de259702a..1a96d2177 100644
|
|
||||||
--- a/tui/src/ui/components/config_editor/view.rs
|
|
||||||
+++ b/tui/src/ui/components/config_editor/view.rs
|
|
||||||
@@ -1732,7 +1732,7 @@ impl Model {
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn umount_config_editor(&mut self) {
|
|
||||||
- self.library_reload_tree();
|
|
||||||
+ self.library_scan_dir(&self.library.tree_path, None);
|
|
||||||
self.playlist_reload();
|
|
||||||
self.database_reload();
|
|
||||||
self.progress_reload();
|
|
||||||
diff --git a/tui/src/ui/components/music_library.rs b/tui/src/ui/components/music_library.rs
|
|
||||||
index afd6cc6ad..6a4d65e5c 100644
|
|
||||||
--- a/tui/src/ui/components/music_library.rs
|
|
||||||
+++ b/tui/src/ui/components/music_library.rs
|
|
||||||
@@ -1,14 +1,15 @@
|
|
||||||
-use crate::ui::model::Model;
|
|
||||||
+use crate::ui::model::{DownloadTracker, Model};
|
|
||||||
use crate::ui::tui_cmd::TuiCmd;
|
|
||||||
use crate::utils::get_pin_yin;
|
|
||||||
use anyhow::{bail, Context, Result};
|
|
||||||
use std::fs::{remove_dir_all, remove_file, rename, DirEntry};
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
+use std::sync::mpsc::Sender;
|
|
||||||
use termusiclib::config::v2::server::config_extra::ServerConfigVersionedDefaulted;
|
|
||||||
use termusiclib::config::v2::server::ScanDepth;
|
|
||||||
use termusiclib::config::SharedTuiSettings;
|
|
||||||
use termusiclib::ids::Id;
|
|
||||||
-use termusiclib::types::{GSMsg, LIMsg, Msg, PLMsg, TEMsg, YSMsg};
|
|
||||||
+use termusiclib::types::{GSMsg, LIMsg, Msg, PLMsg, RecVec, TEMsg, YSMsg};
|
|
||||||
use tui_realm_treeview::{Node, Tree, TreeView, TREE_CMD_CLOSE, TREE_CMD_OPEN, TREE_INITIAL_NODE};
|
|
||||||
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
|
|
||||||
use tuirealm::event::{Key, KeyEvent, KeyModifiers, NoUserEvent};
|
|
||||||
@@ -234,14 +235,6 @@ impl Component<Msg, NoUserEvent> for MusicLibrary {
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Model {
|
|
||||||
- pub fn library_scan_dir<P: Into<PathBuf>>(&mut self, p: P) {
|
|
||||||
- self.library.tree_path = p.into();
|
|
||||||
- self.library.tree = Tree::new(Self::library_dir_tree(
|
|
||||||
- &self.library.tree_path,
|
|
||||||
- self.config_server.read().get_library_scan_depth(),
|
|
||||||
- ));
|
|
||||||
- }
|
|
||||||
-
|
|
||||||
pub fn library_upper_dir(&self) -> Option<PathBuf> {
|
|
||||||
self.library
|
|
||||||
.tree_path
|
|
||||||
@@ -249,12 +242,58 @@ impl Model {
|
|
||||||
.map(std::path::Path::to_path_buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
- pub fn library_dir_tree(p: &Path, depth: ScanDepth) -> Node<String> {
|
|
||||||
- let name: String = match p.file_name() {
|
|
||||||
+ /// Execute [`Self::library_scan`] from a `&self` instance.
|
|
||||||
+ #[inline]
|
|
||||||
+ pub fn library_scan_dir<P: Into<PathBuf>>(&self, path: P, focus_node: Option<String>) {
|
|
||||||
+ Self::library_scan(
|
|
||||||
+ self.tx_to_main.clone(),
|
|
||||||
+ self.download_tracker.clone(),
|
|
||||||
+ path,
|
|
||||||
+ self.config_server.read().get_library_scan_depth(),
|
|
||||||
+ focus_node,
|
|
||||||
+ );
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ pub fn loading_tree() -> Tree<String> {
|
|
||||||
+ Tree::new(Node::new("/dev/null".to_string(), "Loading...".to_string()))
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ /// Execute a library scan on a different thread.
|
|
||||||
+ ///
|
|
||||||
+ /// Executes [`Self::library_dir_tree`] on a different thread and send a [`LIMsg::TreeNodeReady`] on finish
|
|
||||||
+ pub fn library_scan<P: Into<PathBuf>>(
|
|
||||||
+ tx: Sender<Msg>,
|
|
||||||
+ download_tracker: DownloadTracker,
|
|
||||||
+ path: P,
|
|
||||||
+ depth: ScanDepth,
|
|
||||||
+ focus_node: Option<String>,
|
|
||||||
+ ) {
|
|
||||||
+ let path = path.into();
|
|
||||||
+ std::thread::Builder::new()
|
|
||||||
+ .name("library tree scan".to_string())
|
|
||||||
+ .spawn(move || {
|
|
||||||
+ download_tracker.increase_one(path.to_string_lossy());
|
|
||||||
+ let root_node = Self::library_dir_tree(&path, depth);
|
|
||||||
+
|
|
||||||
+ let _ = tx.send(Msg::Library(LIMsg::TreeNodeReady(root_node, focus_node)));
|
|
||||||
+ download_tracker.decrease_one(&path.to_string_lossy());
|
|
||||||
+ })
|
|
||||||
+ .expect("Failed to spawn thread");
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ /// Scan the given `path` for up to `depth`, and return a [`Node`] tree.
|
|
||||||
+ ///
|
|
||||||
+ /// Note: consider using [`Self::library_scan`] instead of this directly.
|
|
||||||
+ fn library_dir_tree(path: &Path, depth: ScanDepth) -> RecVec<PathBuf, String> {
|
|
||||||
+ let name: String = match path.file_name() {
|
|
||||||
None => "/".to_string(),
|
|
||||||
Some(n) => n.to_string_lossy().into_owned(),
|
|
||||||
};
|
|
||||||
- let mut node: Node<String> = Node::new(p.to_string_lossy().into_owned(), name);
|
|
||||||
+ let mut node = RecVec {
|
|
||||||
+ id: path.to_path_buf(),
|
|
||||||
+ value: name,
|
|
||||||
+ children: Vec::new(),
|
|
||||||
+ };
|
|
||||||
|
|
||||||
let depth = match depth {
|
|
||||||
ScanDepth::Limited(v) => v,
|
|
||||||
@@ -262,8 +301,8 @@ impl Model {
|
|
||||||
ScanDepth::Unlimited => u32::MAX,
|
|
||||||
};
|
|
||||||
|
|
||||||
- if depth > 0 && p.is_dir() {
|
|
||||||
- if let Ok(paths) = std::fs::read_dir(p) {
|
|
||||||
+ if depth > 0 && path.is_dir() {
|
|
||||||
+ if let Ok(paths) = std::fs::read_dir(path) {
|
|
||||||
let mut paths: Vec<(String, PathBuf)> = paths
|
|
||||||
.filter_map(std::result::Result::ok)
|
|
||||||
.filter(|p| !p.file_name().to_string_lossy().starts_with('.'))
|
|
||||||
@@ -273,12 +312,14 @@ impl Model {
|
|
||||||
paths.sort_by(|a, b| alphanumeric_sort::compare_str(&a.0, &b.0));
|
|
||||||
|
|
||||||
for p in paths {
|
|
||||||
- node.add_child(Self::library_dir_tree(&p.1, ScanDepth::Limited(depth - 1)));
|
|
||||||
+ node.children
|
|
||||||
+ .push(Self::library_dir_tree(&p.1, ScanDepth::Limited(depth - 1)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
node
|
|
||||||
}
|
|
||||||
+
|
|
||||||
pub fn library_dir_children(p: &Path) -> Vec<String> {
|
|
||||||
let mut children: Vec<String> = vec![];
|
|
||||||
if p.is_dir() {
|
|
||||||
@@ -300,88 +341,69 @@ impl Model {
|
|
||||||
children
|
|
||||||
}
|
|
||||||
|
|
||||||
- pub fn library_reload_with_node_focus(&mut self, node: Option<&str>) {
|
|
||||||
+ /// Reload the library with the given `node` as a focus, also starts a new database sync worker for the current path.
|
|
||||||
+ pub fn library_reload_with_node_focus(&mut self, node: Option<String>) {
|
|
||||||
self.db.sync_database(self.library.tree_path.as_path());
|
|
||||||
self.database_reload();
|
|
||||||
- self.library_reload_tree();
|
|
||||||
- if let Some(n) = node {
|
|
||||||
- assert!(self
|
|
||||||
- .app
|
|
||||||
- .attr(
|
|
||||||
- &Id::Library,
|
|
||||||
- Attribute::Custom(TREE_INITIAL_NODE),
|
|
||||||
- AttrValue::String(n.to_string()),
|
|
||||||
- )
|
|
||||||
- .is_ok());
|
|
||||||
+ self.library_scan_dir(&self.library.tree_path, node);
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ /// Convert a [`RecVec`] to a [`Node`].
|
|
||||||
+ fn recvec_to_node(vec: RecVec<PathBuf, String>) -> Node<String> {
|
|
||||||
+ let mut node = Node::new(vec.id.to_string_lossy().to_string(), vec.value);
|
|
||||||
+
|
|
||||||
+ for val in vec.children {
|
|
||||||
+ node.add_child(Self::recvec_to_node(val));
|
|
||||||
}
|
|
||||||
+
|
|
||||||
+ node
|
|
||||||
}
|
|
||||||
|
|
||||||
- pub fn library_reload_tree(&mut self) {
|
|
||||||
- self.library.tree = Tree::new(Self::library_dir_tree(
|
|
||||||
- self.library.tree_path.as_ref(),
|
|
||||||
- self.config_server.read().get_library_scan_depth(),
|
|
||||||
- ));
|
|
||||||
- let current_node = match self.app.state(&Id::Library).ok().unwrap() {
|
|
||||||
+ /// Apply the given [`RecVec`] as a tree
|
|
||||||
+ pub fn library_apply_as_tree(
|
|
||||||
+ &mut self,
|
|
||||||
+ msg: RecVec<PathBuf, String>,
|
|
||||||
+ focus_node: Option<String>,
|
|
||||||
+ ) {
|
|
||||||
+ let root_path = msg.id.clone();
|
|
||||||
+ let root_node = Self::recvec_to_node(msg);
|
|
||||||
+
|
|
||||||
+ let old_current_node = match self.app.state(&Id::Library).ok().unwrap() {
|
|
||||||
State::One(StateValue::String(id)) => Some(id),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
- let mut focus = false;
|
|
||||||
-
|
|
||||||
- if let Ok(f) = self.app.query(&Id::Library, Attribute::Focus) {
|
|
||||||
- if Some(AttrValue::Flag(true)) == f {
|
|
||||||
- focus = true;
|
|
||||||
- }
|
|
||||||
- }
|
|
||||||
|
|
||||||
- if focus {
|
|
||||||
- self.app.umount(&Id::Library).ok();
|
|
||||||
- assert!(self
|
|
||||||
- .app
|
|
||||||
- .mount(
|
|
||||||
- Id::Library,
|
|
||||||
- Box::new(MusicLibrary::new(
|
|
||||||
- &self.library.tree,
|
|
||||||
- current_node,
|
|
||||||
- self.config_tui.clone(),
|
|
||||||
- ),),
|
|
||||||
- Vec::new()
|
|
||||||
- )
|
|
||||||
- .is_ok());
|
|
||||||
- self.app.active(&Id::Library).ok();
|
|
||||||
- return;
|
|
||||||
+ self.library.tree_path = root_path;
|
|
||||||
+ self.library.tree = Tree::new(root_node);
|
|
||||||
+
|
|
||||||
+ // remount preserves focus
|
|
||||||
+ let _ = self.app.remount(
|
|
||||||
+ Id::Library,
|
|
||||||
+ Box::new(MusicLibrary::new(
|
|
||||||
+ &self.library.tree,
|
|
||||||
+ old_current_node,
|
|
||||||
+ self.config_tui.clone(),
|
|
||||||
+ )),
|
|
||||||
+ Vec::new(),
|
|
||||||
+ );
|
|
||||||
+
|
|
||||||
+ // focus the specified node
|
|
||||||
+ if let Some(id) = focus_node {
|
|
||||||
+ let _ = self.app.attr(
|
|
||||||
+ &Id::Library,
|
|
||||||
+ Attribute::Custom(TREE_INITIAL_NODE),
|
|
||||||
+ AttrValue::String(id),
|
|
||||||
+ );
|
|
||||||
}
|
|
||||||
-
|
|
||||||
- assert!(self
|
|
||||||
- .app
|
|
||||||
- .remount(
|
|
||||||
- Id::Library,
|
|
||||||
- Box::new(MusicLibrary::new(
|
|
||||||
- &self.library.tree,
|
|
||||||
- current_node,
|
|
||||||
- self.config_tui.clone(),
|
|
||||||
- ),),
|
|
||||||
- Vec::new()
|
|
||||||
- )
|
|
||||||
- .is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
- // Kept for debugging focus issue
|
|
||||||
- // if let Ok(f) = self.app.query(&Id::Library, Attribute::Focus) {
|
|
||||||
- // if Some(AttrValue::Flag(true)) == f {
|
|
||||||
- // error!("focus after remount: true");
|
|
||||||
- // } else {
|
|
||||||
- // error!("focus after remount: false");
|
|
||||||
- // }
|
|
||||||
- // }
|
|
||||||
pub fn library_stepinto(&mut self, node_id: &str) {
|
|
||||||
- self.library_scan_dir(PathBuf::from(node_id));
|
|
||||||
- self.library_reload_tree();
|
|
||||||
+ self.library_scan_dir(PathBuf::from(node_id), None);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn library_stepout(&mut self) {
|
|
||||||
if let Some(p) = self.library_upper_dir() {
|
|
||||||
- self.library_scan_dir(p);
|
|
||||||
- self.library_reload_tree();
|
|
||||||
+ self.library_scan_dir(p, None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -396,6 +418,7 @@ impl Model {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
+ /// Delete the currently selected node from the filesystem and reload the tree and remove the deleted paths from the playlist.
|
|
||||||
pub fn library_delete_node(&mut self) -> Result<()> {
|
|
||||||
if let Ok(State::One(StateValue::String(node_id))) = self.app.state(&Id::Library) {
|
|
||||||
if let Some(mut route) = self.library.tree.root().route_by_node(&node_id) {
|
|
||||||
@@ -407,28 +430,27 @@ impl Model {
|
|
||||||
remove_dir_all(p)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
- // this is to keep the state of playlist
|
|
||||||
- self.library_reload_tree();
|
|
||||||
- let tree = self.library.tree.clone();
|
|
||||||
- if let Some(new_node) = tree.root().node_by_route(&route) {
|
|
||||||
- self.library_reload_with_node_focus(Some(new_node.id()));
|
|
||||||
- } else {
|
|
||||||
- //special case 1: old route not available but have siblings
|
|
||||||
- if let Some(last) = route.last_mut() {
|
|
||||||
- if last > &mut 0 {
|
|
||||||
- *last -= 1;
|
|
||||||
- }
|
|
||||||
- }
|
|
||||||
- if let Some(new_node) = tree.root().node_by_route(&route) {
|
|
||||||
- self.library_reload_with_node_focus(Some(new_node.id()));
|
|
||||||
- } else {
|
|
||||||
- //special case 2: old route not available and no siblings
|
|
||||||
- route.truncate(route.len() - 1);
|
|
||||||
- if let Some(new_node) = tree.root().node_by_route(&route) {
|
|
||||||
- self.library_reload_with_node_focus(Some(new_node.id()));
|
|
||||||
+ let mut tree = self.library.tree.clone();
|
|
||||||
+ tree.root_mut().remove_child(&node_id);
|
|
||||||
+ let mut focus_node: Option<String> = None;
|
|
||||||
+ // case 1: the route still exists due to having a sibling beyond the index which now takes the same index
|
|
||||||
+ if let Some(node) = tree.root().node_by_route(&route) {
|
|
||||||
+ focus_node = Some(node.id().to_string());
|
|
||||||
+ } else if !route.is_empty() {
|
|
||||||
+ let _ = route.pop();
|
|
||||||
+ // case 2: the route does not exist anymore, but there is a parent in the route
|
|
||||||
+ if let Some(parent) = tree.root().node_by_route(&route) {
|
|
||||||
+ // case 2.1: the parent has children, select the last of them
|
|
||||||
+ if let Some(last_child) = parent.children().last() {
|
|
||||||
+ focus_node = Some(last_child.id().to_string());
|
|
||||||
+ } else {
|
|
||||||
+ // case 2.2: the parent exists, but has no children
|
|
||||||
+ focus_node = Some(parent.id().to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+
|
|
||||||
+ self.library_scan_dir(&self.library.tree_path, focus_node);
|
|
||||||
}
|
|
||||||
// this line remove the deleted songs from playlist
|
|
||||||
self.playlist_update_library_delete();
|
|
||||||
@@ -459,7 +481,7 @@ impl Model {
|
|
||||||
p_parent.join(pold_filename)
|
|
||||||
};
|
|
||||||
rename(pold, new_node_id.as_path())?;
|
|
||||||
- self.library_reload_with_node_focus(new_node_id.to_str());
|
|
||||||
+ self.library_reload_with_node_focus(Some(new_node_id.to_string_lossy().to_string()));
|
|
||||||
}
|
|
||||||
self.library.yanked_node_id = None;
|
|
||||||
self.playlist_update_library_delete();
|
|
||||||
@@ -520,7 +542,7 @@ impl Model {
|
|
||||||
}
|
|
||||||
if let Some(dir) = vec.get(index) {
|
|
||||||
let pathbuf = PathBuf::from(dir);
|
|
||||||
- self.library_scan_dir(pathbuf);
|
|
||||||
+ self.library_scan_dir(pathbuf, None);
|
|
||||||
self.library_reload_with_node_focus(None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
diff --git a/tui/src/ui/components/playlist.rs b/tui/src/ui/components/playlist.rs
|
|
||||||
index 96254dd8b..a2a333b9e 100644
|
|
||||||
--- a/tui/src/ui/components/playlist.rs
|
|
||||||
+++ b/tui/src/ui/components/playlist.rs
|
|
||||||
@@ -888,7 +888,7 @@ impl Model {
|
|
||||||
// TODO: move this to server?
|
|
||||||
self.playback.playlist.save_m3u(filename)?;
|
|
||||||
|
|
||||||
- self.library_reload_with_node_focus(Some(&filename.to_string_lossy()));
|
|
||||||
+ self.library_reload_with_node_focus(Some(filename.to_string_lossy().to_string()));
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
diff --git a/tui/src/ui/components/tag_editor/update.rs b/tui/src/ui/components/tag_editor/update.rs
|
|
||||||
index ca268cacf..5b2554501 100644
|
|
||||||
--- a/tui/src/ui/components/tag_editor/update.rs
|
|
||||||
+++ b/tui/src/ui/components/tag_editor/update.rs
|
|
||||||
@@ -34,7 +34,7 @@ impl Model {
|
|
||||||
TEMsg::TagEditorClose => {
|
|
||||||
if let Some(s) = self.tageditor_song.clone() {
|
|
||||||
// TODO: this should be re-done and take actual track ids themself, or at least verified to use the same functions to result in the same id
|
|
||||||
- self.library_reload_with_node_focus(Some(&s.path_as_id_str()));
|
|
||||||
+ self.library_reload_with_node_focus(Some(s.path_as_id_str().to_string()));
|
|
||||||
}
|
|
||||||
self.umount_tageditor();
|
|
||||||
}
|
|
||||||
diff --git a/tui/src/ui/model/mod.rs b/tui/src/ui/model/mod.rs
|
|
||||||
index 8916513aa..c5515e81d 100644
|
|
||||||
--- a/tui/src/ui/model/mod.rs
|
|
||||||
+++ b/tui/src/ui/model/mod.rs
|
|
||||||
@@ -35,7 +35,7 @@ use super::components::TETrack;
|
|
||||||
use super::tui_cmd::TuiCmd;
|
|
||||||
use crate::ui::Application;
|
|
||||||
use crate::CombinedSettings;
|
|
||||||
-use download_tracker::DownloadTracker;
|
|
||||||
+pub use download_tracker::DownloadTracker;
|
|
||||||
|
|
||||||
mod download_tracker;
|
|
||||||
mod playlist;
|
|
||||||
@@ -362,10 +362,7 @@ impl Model {
|
|
||||||
} = config;
|
|
||||||
let path = Self::get_full_path_from_config(&config_server.read());
|
|
||||||
// TODO: refactor music library tree to be Paths instead?
|
|
||||||
- let tree = Tree::new(Self::library_dir_tree(
|
|
||||||
- &path,
|
|
||||||
- config_server.read().get_library_scan_depth(),
|
|
||||||
- ));
|
|
||||||
+ let tree = Self::loading_tree();
|
|
||||||
|
|
||||||
let viuer_supported = if config_tui.read().cover_features_enabled() {
|
|
||||||
get_viuer_support()
|
|
||||||
@@ -412,6 +409,16 @@ impl Model {
|
|
||||||
let ce_theme = config_tui.read().settings.theme.clone();
|
|
||||||
let xywh = xywh::Xywh::from(&config_tui.read().settings.coverart);
|
|
||||||
|
|
||||||
+ let download_tracker = DownloadTracker::default();
|
|
||||||
+
|
|
||||||
+ Self::library_scan(
|
|
||||||
+ tx_to_main.clone(),
|
|
||||||
+ download_tracker.clone(),
|
|
||||||
+ &path,
|
|
||||||
+ config_server.read().get_library_scan_depth(),
|
|
||||||
+ None,
|
|
||||||
+ );
|
|
||||||
+
|
|
||||||
Self {
|
|
||||||
app,
|
|
||||||
quit: false,
|
|
||||||
@@ -460,7 +467,7 @@ impl Model {
|
|
||||||
taskpool,
|
|
||||||
tx_to_main,
|
|
||||||
rx_to_main,
|
|
||||||
- download_tracker: DownloadTracker::default(),
|
|
||||||
+ download_tracker,
|
|
||||||
current_track_lyric: None,
|
|
||||||
playback: Playback::new(),
|
|
||||||
cmd_to_server_tx,
|
|
||||||
diff --git a/tui/src/ui/model/update.rs b/tui/src/ui/model/update.rs
|
|
||||||
index 7e9111ec5..066f56f8f 100644
|
|
||||||
--- a/tui/src/ui/model/update.rs
|
|
||||||
+++ b/tui/src/ui/model/update.rs
|
|
||||||
@@ -57,7 +57,7 @@ impl Update<Msg> for Model {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
Msg::Library(m) => {
|
|
||||||
- self.update_library(&m);
|
|
||||||
+ self.update_library(m);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
Msg::GeneralSearch(m) => {
|
|
||||||
@@ -110,7 +110,7 @@ impl Update<Msg> for Model {
|
|
||||||
|
|
||||||
Msg::Podcast(m) => self.update_podcast(m),
|
|
||||||
Msg::LyricMessage(m) => self.update_lyric_textarea(m),
|
|
||||||
- Msg::Download(m) => self.update_download_msg(&m),
|
|
||||||
+ Msg::Download(m) => self.update_download_msg(m),
|
|
||||||
Msg::Xywh(m) => self.update_xywh_msg(m),
|
|
||||||
|
|
||||||
Msg::ForceRedraw => None,
|
|
||||||
@@ -558,13 +558,13 @@ impl Model {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
- fn update_library(&mut self, msg: &LIMsg) {
|
|
||||||
+ fn update_library(&mut self, msg: LIMsg) {
|
|
||||||
match msg {
|
|
||||||
LIMsg::TreeBlur => {
|
|
||||||
assert!(self.app.active(&Id::Playlist).is_ok());
|
|
||||||
}
|
|
||||||
LIMsg::TreeStepInto(path) => {
|
|
||||||
- self.library_stepinto(path);
|
|
||||||
+ self.library_stepinto(&path);
|
|
||||||
}
|
|
||||||
LIMsg::TreeStepOut => {
|
|
||||||
self.library_stepout();
|
|
||||||
@@ -588,6 +588,9 @@ impl Model {
|
|
||||||
self.mount_error_popup(e.context("library remove root"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+ LIMsg::TreeNodeReady(vec, focus_node) => {
|
|
||||||
+ self.library_apply_as_tree(vec, focus_node);
|
|
||||||
+ }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -916,20 +919,20 @@ impl Model {
|
|
||||||
}
|
|
||||||
|
|
||||||
// change status bar text to indicate the downloading state
|
|
||||||
- fn update_download_msg(&mut self, msg: &DLMsg) -> Option<Msg> {
|
|
||||||
+ fn update_download_msg(&mut self, msg: DLMsg) -> Option<Msg> {
|
|
||||||
self.redraw = true;
|
|
||||||
match msg {
|
|
||||||
DLMsg::DownloadRunning(url, title) => {
|
|
||||||
- self.download_tracker.increase_one(&**url);
|
|
||||||
+ self.download_tracker.increase_one(&*url);
|
|
||||||
self.show_message_timeout_label_help(
|
|
||||||
- self.download_tracker.message_download_start(title),
|
|
||||||
+ self.download_tracker.message_download_start(&title),
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
DLMsg::DownloadSuccess(url) => {
|
|
||||||
- self.download_tracker.decrease_one(url);
|
|
||||||
+ self.download_tracker.decrease_one(&url);
|
|
||||||
self.show_message_timeout_label_help(
|
|
||||||
self.download_tracker.message_download_complete(),
|
|
||||||
None,
|
|
||||||
@@ -945,13 +948,14 @@ impl Model {
|
|
||||||
if self.download_tracker.visible() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
- self.library_reload_with_node_focus(file.as_deref());
|
|
||||||
+ self.library_reload_with_node_focus(file);
|
|
||||||
}
|
|
||||||
DLMsg::DownloadErrDownload(url, title, error_message) => {
|
|
||||||
- self.download_tracker.decrease_one(url);
|
|
||||||
+ self.download_tracker.decrease_one(&url);
|
|
||||||
self.mount_error_popup(anyhow!("download failed: {error_message}"));
|
|
||||||
self.show_message_timeout_label_help(
|
|
||||||
- self.download_tracker.message_download_error_response(title),
|
|
||||||
+ self.download_tracker
|
|
||||||
+ .message_download_error_response(&title),
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
@@ -961,17 +965,17 @@ impl Model {
|
|
||||||
self.mount_error_popup(anyhow!("download ok but tag info is not complete."));
|
|
||||||
self.show_message_timeout_label_help(
|
|
||||||
self.download_tracker
|
|
||||||
- .message_download_error_embed_data(title),
|
|
||||||
+ .message_download_error_embed_data(&title),
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
DLMsg::MessageShow((title, text)) => {
|
|
||||||
- self.mount_message(title, text);
|
|
||||||
+ self.mount_message(&title, &text);
|
|
||||||
}
|
|
||||||
DLMsg::MessageHide((title, text)) => {
|
|
||||||
- self.umount_message(title, text);
|
|
||||||
+ self.umount_message(&title, &text);
|
|
||||||
}
|
|
||||||
DLMsg::FetchPhotoSuccess(image_wrapper) => {
|
|
||||||
self.show_image(&image_wrapper.data).ok();
|
|
||||||
|
|
||||||
From 8b1fd1216fb81bfa982958086832c10b52d6e494 Mon Sep 17 00:00:00 2001
|
|
||||||
From: hasezoey <hasezoey@gmail.com>
|
|
||||||
Date: Wed, 28 May 2025 16:25:41 +0200
|
|
||||||
Subject: [PATCH 2/3] fix(tui::components::music_library::library_stepout):
|
|
||||||
focus the last root node in the new tree
|
|
||||||
|
|
||||||
Instead of always focusing the root node of the new tree.
|
|
||||||
---
|
|
||||||
CHANGELOG.md | 1 +
|
|
||||||
tui/src/ui/components/music_library.rs | 5 ++++-
|
|
||||||
2 files changed, 5 insertions(+), 1 deletion(-)
|
|
||||||
|
|
||||||
diff --git a/CHANGELOG.md b/CHANGELOG.md
|
|
||||||
index 0641e5f29..3e05516e9 100644
|
|
||||||
--- a/CHANGELOG.md
|
|
||||||
+++ b/CHANGELOG.md
|
|
||||||
@@ -22,6 +22,7 @@
|
|
||||||
- Fix(tui): re-select the (approximately) same playlist item after a shuffle.
|
|
||||||
- Fix(tui): escape key will now no longer also act as a quit key.
|
|
||||||
- Fix(tui): changed the music library tree loading to be async.
|
|
||||||
+- Fix(tui): in the music library tree, when stepping out now the correct node if focused instead of the root node.
|
|
||||||
- Fix(server): with `rusty-soundtouch` on `rusty` backend, dont take initial samples until necessary.
|
|
||||||
- Fix(server): on rusty backend, always decode and use `f32` samples. (instead of `i16`)
|
|
||||||
- Fix(server): on rusty backend, update `soundtouch` version to fix build issues on latest arch & gcc 15.
|
|
||||||
diff --git a/tui/src/ui/components/music_library.rs b/tui/src/ui/components/music_library.rs
|
|
||||||
index 6a4d65e5c..498a29ffb 100644
|
|
||||||
--- a/tui/src/ui/components/music_library.rs
|
|
||||||
+++ b/tui/src/ui/components/music_library.rs
|
|
||||||
@@ -397,13 +397,16 @@ impl Model {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
+ /// Handle stepping into a node on the tree
|
|
||||||
pub fn library_stepinto(&mut self, node_id: &str) {
|
|
||||||
self.library_scan_dir(PathBuf::from(node_id), None);
|
|
||||||
}
|
|
||||||
|
|
||||||
+ /// Handle stepping out of the current root node on the tree
|
|
||||||
pub fn library_stepout(&mut self) {
|
|
||||||
if let Some(p) = self.library_upper_dir() {
|
|
||||||
- self.library_scan_dir(p, None);
|
|
||||||
+ let focus_node = Some(self.library.tree_path.to_string_lossy().to_string());
|
|
||||||
+ self.library_scan_dir(p, focus_node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
From 47a13fc83e3c353a516d83e892ea87c7e26ed430 Mon Sep 17 00:00:00 2001
|
|
||||||
From: hasezoey <hasezoey@gmail.com>
|
|
||||||
Date: Wed, 28 May 2025 16:32:42 +0200
|
|
||||||
Subject: [PATCH 3/3] style(tui::components::music_library): add a todo to try
|
|
||||||
to load the directory if not loaded yet
|
|
||||||
|
|
||||||
---
|
|
||||||
tui/src/ui/components/music_library.rs | 1 +
|
|
||||||
1 file changed, 1 insertion(+)
|
|
||||||
|
|
||||||
diff --git a/tui/src/ui/components/music_library.rs b/tui/src/ui/components/music_library.rs
|
|
||||||
index 498a29ffb..d98cd6c09 100644
|
|
||||||
--- a/tui/src/ui/components/music_library.rs
|
|
||||||
+++ b/tui/src/ui/components/music_library.rs
|
|
||||||
@@ -90,6 +90,7 @@ impl MusicLibrary {
|
|
||||||
let current_node = self.component.tree_state().selected().unwrap();
|
|
||||||
let p: &Path = Path::new(current_node);
|
|
||||||
if p.is_dir() {
|
|
||||||
+ // TODO: try to load the directory if it is not loaded yet.
|
|
||||||
(self.perform(Cmd::Custom(TREE_CMD_OPEN)), None)
|
|
||||||
} else {
|
|
||||||
(
|
|
Loading…
Add table
Add a link
Reference in a new issue