Skip to content

Commit

Permalink
✨ feat: multithreading local scanner
Browse files Browse the repository at this point in the history
Signed-off-by: SimonShiki <[email protected]>
  • Loading branch information
SimonShiki committed Aug 12, 2024
1 parent a7957e4 commit de3444c
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 10 deletions.
11 changes: 11 additions & 0 deletions src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,6 @@ tauri-plugin-dialog = "2.0.0-rc.0"
rodio = { git = "https://github.com/SimonShiki/rodio.git", branch = "master", features = ["symphonia-all"] }
souvlaki = "0.6"
walkdir = "2.5.0"
num_cpus = "1.16.0"
lofty = "0.21.0"
base64 = "0.22.1"
68 changes: 59 additions & 9 deletions src-tauri/src/local_scanner.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use std::path::Path;
use std::path::{Path, PathBuf};
use lofty::{file::{AudioFile, TaggedFileExt}, probe::Probe, tag::{Accessor, ItemKey}};
use walkdir::WalkDir;
use serde::{Serialize, Deserialize};
use base64::{Engine as _, engine::general_purpose};
use std::fs;
use std::sync::mpsc;
use std::thread;
use std::sync::{Arc, Mutex};

#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, Clone)]
pub struct Song {
id: String,
name: String,
Expand All @@ -21,16 +24,64 @@ pub struct Song {

#[tauri::command]
pub fn scan_folder(path: &str) -> Result<Vec<Song>, String> {
let mut songs = Vec::new();
let (tx, rx) = mpsc::channel();
let path_clone = path.to_string();

let handle = thread::spawn(move || {
let (file_tx, file_rx) = mpsc::channel::<PathBuf>();
let file_rx = Arc::new(Mutex::new(file_rx));

// Spawn multiple worker threads
let num_threads = num_cpus::get();
let thread_handles: Vec<_> = (0..num_threads).map(|_| {
let file_rx = Arc::clone(&file_rx);
let tx = tx.clone();
thread::spawn(move || {
loop {
let path = {
let rx = file_rx.lock().unwrap();
rx.recv()
};
match path {
Ok(path) => {
if let Some(song) = process_file(&path) {
tx.send(song).unwrap();
}
},
Err(_) => break, // Channel closed, exit the loop
}
}
})
}).collect();

for entry in WalkDir::new(path).follow_links(true).into_iter().filter_map(|e| e.ok()) {
if entry.file_type().is_file() {
if let Some(song) = process_file(entry.path()) {
songs.push(song);
// Collect files
for entry in WalkDir::new(&path_clone).follow_links(true).into_iter().filter_map(|e| e.ok()) {
if entry.file_type().is_file() {
file_tx.send(entry.path().to_path_buf()).unwrap();
}
}

// Close the file channel
drop(file_tx);

// Wait for all worker threads to complete
for handle in thread_handles {
handle.join().unwrap();
}

// Close the song channel
drop(tx);
});

// Collect all songs
let mut songs = Vec::new();
for song in rx {
songs.push(song);
}

// Wait for the main thread to finish
handle.join().unwrap();

Ok(songs)
}

Expand Down Expand Up @@ -61,7 +112,6 @@ fn process_file(path: &Path) -> Option<Song> {
t.get_string(&ItemKey::Lyrics).map(|s| s.to_string())
});


let cover = tag.and_then(|t| t.pictures().first()).map(|p| {
let b64 = general_purpose::STANDARD.encode(&p.data());
format!("data:{};base64,{}", p.mime_type().unwrap().as_str(), b64)
Expand All @@ -73,7 +123,7 @@ fn process_file(path: &Path) -> Option<Song> {
let mtime = metadata.modified().ok()?.duration_since(std::time::UNIX_EPOCH).ok()?.as_secs();

Some(Song {
id: format!("local-{name}-{album}-{artist}", album = album.clone().unwrap(), artist = artist.clone().unwrap()),
id: format!("local-{name}-{album}-{artist}", album = album.clone().unwrap_or_default(), artist = artist.clone().unwrap_or_default()),
name,
artist,
album,
Expand Down
2 changes: 1 addition & 1 deletion src/components/song-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export default function SongItem (props: SongItemProps) {
props.onSelect?.(!props.select);
}} className={`flex flex-row items-center ${props.selectMode ? '' : 'active:scale-99'} py-2 gap-2 hover:!bg-black cursor-pointer hover:!bg-op-5 transition-all ${props.hideBg ? '!border-none !bg-transparent' : ''}`}>
{props.selectMode && (<Checkbox checked={props.select} onChange={props.onSelect} />)}
<img draggable={false} src={props.song.cover ?? defaultCover} alt={props.song.name} className='rounded-md w-10 h-10' />
<img draggable={false} src={props.song.cover ?? defaultCover} alt={props.song.name} className='object-cover rounded-md w-10 h-10' />
<div className='flex flex-col gap-1'>
<span className='color-text-pri font-size-sm font-500'>{props.song.name}</span>
<span className='color-text-sec font-size-xs'>{props.song.album}</span>
Expand Down

0 comments on commit de3444c

Please sign in to comment.