Skip to content

Commit

Permalink
Report created track and playlist count at the end of a scan
Browse files Browse the repository at this point in the history
  • Loading branch information
martpie committed Sep 15, 2024
1 parent 67525ae commit 370f492
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 65 deletions.
139 changes: 81 additions & 58 deletions src-tauri/src/plugins/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -341,32 +341,43 @@ pub struct Playlist {
*/
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export, export_to = "../../src/generated/typings/index.ts")]
pub struct Progress {
pub struct ScanProgress {
current: usize,
total: usize,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export, export_to = "../../src/generated/typings/index.ts")]
pub struct ScanResult {
track_count: usize,
track_failures: usize,
playlist_count: usize,
playlist_failures: usize,
}

/** ----------------------------------------------------------------------------
* Commands
* -------------------------------------------------------------------------- */

/**
* Popup a directory picker dialog, scan the selected folders, extract all
* ID3 tags from it, and update the DB accordingly.
* Scan the selected folders, extract all ID3 tags from it, and update the DB
* accordingly.
*/
#[tauri::command]
async fn import_tracks_to_library<R: Runtime>(
window: tauri::Window<R>,
db: State<'_, DB>,
import_paths: Vec<PathBuf>,
) -> AnyResult<Vec<Track>> {
) -> AnyResult<ScanResult> {
let webview_window = window.get_webview_window("main").unwrap();

info!("Importing paths to library:");
for path in &import_paths {
info!(" - {:?}", path)
}

let mut scan_result = ScanResult::default();

// Scan all directories for valid files to be scanned and imported
let mut track_paths = scan_dirs(&import_paths, &SUPPORTED_TRACKS_EXTENSIONS);
let scanned_paths_count = track_paths.len();
Expand All @@ -393,7 +404,7 @@ async fn import_tracks_to_library<R: Runtime>(
webview_window
.emit(
IPCEvent::LibraryScanProgress.as_ref(),
Progress {
ScanProgress {
current: 0,
total: track_paths.len(),
},
Expand All @@ -416,7 +427,7 @@ async fn import_tracks_to_library<R: Runtime>(
webview_window
.emit(
IPCEvent::LibraryScanProgress.as_ref(),
Progress {
ScanProgress {
current: p_current,
total: p_total,
},
Expand Down Expand Up @@ -487,14 +498,16 @@ async fn import_tracks_to_library<R: Runtime>(
.flatten()
.collect::<Vec<Track>>();

let track_failures = track_paths.len() - tracks.len();
scan_result.track_count = tracks.len();
scan_result.track_failures = track_failures;
info!("{} tracks successfully scanned", tracks.len());
info!(
"{} tracks failed to be scanned",
track_paths.len() - tracks.len()
);
info!("{} tracks failed to be scanned", track_failures);

scan_logger.complete();

// Insert all tracks in the DB
// Insert all tracks in the DB, we'are kind of assuming it cannot fail (regarding scan progress information), but
// it technically could.
let db_insert_logger: TimeLogger = TimeLogger::new("Inserted tracks".into());
let result = db.insert_tracks(tracks).await;

Expand All @@ -510,59 +523,69 @@ async fn import_tracks_to_library<R: Runtime>(
info!("Found {} playlist(s) to import", playlist_paths.len());

for playlist_path in playlist_paths {
let mut reader = m3u::Reader::open(&playlist_path).unwrap();
let playlist_dir_path = playlist_path.parent().unwrap();

let track_paths: Vec<PathBuf> = reader
.entries()
.filter_map(|entry| {
let Ok(entry) = entry else {
return None;
};
match {
let mut reader = m3u::Reader::open(&playlist_path).unwrap();
let playlist_dir_path = playlist_path.parent().unwrap();

match entry {
m3u::Entry::Path(path) => Some(playlist_dir_path.join(path)),
_ => return None, // We don't support (yet?) URLs in playlists
}
})
.collect();

// Ok, this is sketchy. To avoid having to create a TrackByPath DB View,
// let's guess the ID of the track with UUID::v3
let track_ids = track_paths
.iter()
.flat_map(|path| db.get_track_id_for_path(path))
.collect::<Vec<String>>();

let playlist_name = playlist_path
.file_stem()
.unwrap()
.to_str()
.unwrap_or("unknown playlist")
.to_owned();

let tracks = db.get_tracks(&track_ids).await?;

if tracks.len() != track_ids.len() {
warn!(
"Playlist track mismatch ({} from playlist, {} from library)",
track_paths.len(),
tracks.len()
);
}
let track_paths: Vec<PathBuf> = reader
.entries()
.filter_map(|entry| {
let Ok(entry) = entry else {
return None;
};

match entry {
m3u::Entry::Path(path) => Some(playlist_dir_path.join(path)),
_ => return None, // We don't support (yet?) URLs in playlists
}
})
.collect();

info!(
r#"Creating playlist "{}" ({} tracks)"#,
&playlist_name,
&track_ids.len()
);
// Ok, this is sketchy. To avoid having to create a TrackByPath DB View,
// let's guess the ID of the track with UUID::v3
let track_ids = track_paths
.iter()
.flat_map(|path| db.get_track_id_for_path(path))
.collect::<Vec<String>>();

let playlist_name = playlist_path
.file_stem()
.unwrap()
.to_str()
.unwrap_or("unknown playlist")
.to_owned();

let tracks = db.get_tracks(&track_ids).await?;

if tracks.len() != track_ids.len() {
warn!(
"Playlist track mismatch ({} from playlist, {} from library)",
track_paths.len(),
tracks.len()
);
}

db.create_playlist(playlist_name, track_ids).await?;
info!(
r#"Creating playlist "{}" ({} tracks)"#,
&playlist_name,
&track_ids.len()
);

db.create_playlist(playlist_name, track_ids).await?;
Ok::<(), MuseeksError>(())
} {
Ok(_) => {
scan_result.playlist_count += 1;
}
Err(err) => {
warn!("Failed to import playlist: {}", err);
scan_result.playlist_failures += 1;
}
}
}

// All good :]
let tracks = db.get_all_tracks().await?;
Ok(tracks)
Ok(scan_result)
}

#[tauri::command]
Expand Down
4 changes: 2 additions & 2 deletions src/components/Events/LibraryEvents.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { listen } from '@tauri-apps/api/event';
import { useEffect } from 'react';

import type { IPCEvent, Progress } from '../../generated/typings';
import type { IPCEvent, ScanProgress } from '../../generated/typings';
import { useLibraryAPI } from '../../stores/useLibraryStore';

/**
Expand All @@ -11,7 +11,7 @@ function LibraryEvents() {
const { setRefresh } = useLibraryAPI();

useEffect(() => {
const promise = listen<Progress>(
const promise = listen<ScanProgress>(
'LibraryScanProgress' satisfies IPCEvent,
({ payload }) => {
setRefresh(payload.current, payload.total);
Expand Down
6 changes: 4 additions & 2 deletions src/generated/typings/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ export type NumberOf = { no: number | null, of: number | null, };
* -------------------------------------------------------------------------- */
export type Playlist = { _id: string, name: string, tracks: Array<string>, import_path: string | null, };

export type Repeat = "All" | "One" | "None";

/**
* Scan progress
*/
export type Progress = { current: number, total: number, };
export type ScanProgress = { current: number, total: number, };

export type Repeat = "All" | "One" | "None";
export type ScanResult = { track_count: number, track_failures: number, playlist_count: number, playlist_failures: number, };

export type SortBy = "Artist" | "Album" | "Title" | "Duration" | "Genre";

Expand Down
4 changes: 2 additions & 2 deletions src/lib/database.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { invoke } from '@tauri-apps/api/core';

import type { Playlist, Track } from '../generated/typings';
import type { Playlist, ScanResult, Track } from '../generated/typings';

/**
* Bridge for the UI to communicate with the backend and manipulate the Database
Expand Down Expand Up @@ -32,7 +32,7 @@ const database = {
});
},

async importTracks(importPaths: Array<string>): Promise<void> {
async importTracks(importPaths: Array<string>): Promise<ScanResult> {
return invoke('plugin:database|import_tracks_to_library', {
importPaths,
});
Expand Down
7 changes: 7 additions & 0 deletions src/stores/SettingsAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,17 @@ import { invalidate } from '../lib/query';
import useLibraryStore from './useLibraryStore';
import useToastsStore from './useToastsStore';

// Manual prevention of a useEffect being called twice (to avoid refreshing the
// library twice on startup in dev mode).
let did_init = false;

/**
* Init all settings, then show the app
*/
async function init(): Promise<void> {
if (did_init) return;

did_init = true;
// This is non-blocking
checkForLibraryRefresh().catch(logAndNotifyError);

Expand Down
22 changes: 21 additions & 1 deletion src/stores/useLibraryStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,27 @@ const useLibraryStore = createStore<LibraryState>((set, get) => ({
set({ refreshing: true });

const libraryFolders = await config.get('library_folders');
await database.importTracks(libraryFolders);
const scanResult = await database.importTracks(libraryFolders);

if (scanResult.track_count > 0) {
useToastsStore
.getState()
.api.add(
'success',
`${scanResult.track_count} track(s) were added to the library.`,
5000,
);
}

if (scanResult.playlist_count > 0) {
useToastsStore
.getState()
.api.add(
'success',
`${scanResult.playlist_count} playlist(s) were added to the library.`,
5000,
);
}

invalidate();
} catch (err) {
Expand Down

0 comments on commit 370f492

Please sign in to comment.