Skip to content

Commit

Permalink
Split DB's struct from Tauri plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
martpie committed Nov 4, 2024
1 parent fe2bf6e commit 0679990
Show file tree
Hide file tree
Showing 8 changed files with 276 additions and 496 deletions.
243 changes: 1 addition & 242 deletions src-tauri/Cargo.lock

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ m3u = "1.0.0"
memoize = "0.4.2"
nosleep = "0.2.1"
ormlite = { version = "0.22.2", features = ["default-sqlite"] }
ormlite-cli = { version = "0.22.2" }
pathdiff = "0.2.2"
rayon = "1.10.0"
serde = { version = "1.0.211", features = ["derive"] }
Expand Down
244 changes: 244 additions & 0 deletions src-tauri/src/libs/database.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
use ormlite::model::ModelBuilder;
use ormlite::sqlite::SqliteConnection;
use ormlite::Model;
use std::collections::HashMap;
use std::path::PathBuf;

use super::error::AnyResult;
use super::playlist::Playlist;
use super::track::Track;
use super::utils::TimeLogger;

// KEEP THAT IN SYNC with Tauri's file associations in tauri.conf.json
pub const SUPPORTED_TRACKS_EXTENSIONS: [&str; 9] = [
"mp3", "aac", "m4a", "3gp", "wav", /* mp3 / mp4 */
"ogg", "opus", /* Opus */
"flac", /* Flac */
"weba", /* Web media */
];

pub const SUPPORTED_PLAYLISTS_EXTENSIONS: [&str; 1] = ["m3u"];

/** ----------------------------------------------------------------------------
* Databases
* exposes databases for tracks and playlists
* * -------------------------------------------------------------------------- */

pub struct DB {
pub connection: SqliteConnection,
}

impl DB {
/**
* Create tables within a SQLite connection
*/
pub async fn create_tables(&mut self) -> AnyResult<()> {
// TODO: move that to SQL files, or derive that from the struct itself, probably need to create a PR for ormlite-cli
ormlite::query(
"CREATE TABLE IF NOT EXISTS tracks (
id TEXT PRIMARY KEY NOT NULL,
path TEXT NOT NULL UNIQUE, -- Path as a string and unique
title TEXT NOT NULL,
album TEXT NOT NULL,
artists JSON NOT NULL, -- Array of strings
genres JSON NOT NULL, -- Array of strings
year INTEGER,
duration INTEGER NOT NULL,
track_no INTEGER,
track_of INTEGER,
disk_no INTEGER,
disk_of INTEGER
);",
)
.execute(&mut self.connection)
.await?;

// Index for the path column in Track
ormlite::query("CREATE INDEX IF NOT EXISTS index_track_path ON tracks (path);")
.execute(&mut self.connection)
.await?;

ormlite::query(
"CREATE TABLE IF NOT EXISTS playlists (
id TEXT PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
tracks JSON NOT NULL DEFAULT '[]', -- Array of track IDs
import_path TEXT UNIQUE -- Path of the playlist file, unique if it exists
);",
)
.execute(&mut self.connection)
.await?;

Ok(())
}

/**
* Get all the tracks (and their content) from the database
*/
pub async fn get_all_tracks(&mut self) -> AnyResult<Vec<Track>> {
let timer = TimeLogger::new("Retrieved and decoded tracks".into());
let tracks = Track::select().fetch_all(&mut self.connection).await?;
timer.complete();
Ok(tracks)
}

/**
* Get tracks (and their content) given a set of document IDs
*/
pub async fn get_tracks(&mut self, track_ids: &Vec<String>) -> AnyResult<Vec<Track>> {
// TODO: Can this be improved somehow?
// Improve me once https://github.com/launchbadge/sqlx/issues/875 is fixed
let placeholders = track_ids.iter().map(|_| "?").collect::<Vec<_>>().join(", ");
let where_statement = format!("id IN ({})", placeholders);

let mut query_builder = Track::select().dangerous_where(&where_statement);

for id in track_ids {
query_builder = query_builder.bind(id);
}

let mut tracks: Vec<Track> = query_builder.fetch_all(&mut self.connection).await?;

// document may not ordered the way we want, so let's ensure they map to track_ids
let track_id_positions: HashMap<&String, usize> = track_ids
.iter()
.enumerate()
.map(|(i, id)| (id, i))
.collect();
tracks.sort_by_key(|track| track_id_positions.get(&track.id));

Ok(tracks)
}

/**
* Get tracks (and their content) given a set of document IDs
*/
pub async fn update_track(&mut self, track: Track) -> AnyResult<Track> {
let updated_track = track.update_all_fields(&mut self.connection).await?;
Ok(updated_track)
}

/** Delete multiple tracks by ID */
pub async fn remove_tracks(&mut self, track_ids: &Vec<String>) -> AnyResult<()> {
// TODO: batch that, use DELETE statement instead
let tracks = self.get_tracks(track_ids).await?;

for track in tracks {
track.delete(&mut self.connection).await?
}

Ok(())
}

/**
* Insert a new track in the DB, will fail in case there is a duplicate unique
* key (like track.path)
*
* Doc: https://github.com/khonsulabs/bonsaidb/blob/main/examples/basic-local/examples/basic-local-multidb.rs
*/
pub async fn insert_tracks(&mut self, tracks: Vec<Track>) -> AnyResult<()> {
// Weirdly, this is fast enough with SQLite, no need to create transactions
for track in tracks {
track.insert(&mut self.connection).await?;
}

Ok(())
}

/** Get all the playlists (and their content) from the database */
pub async fn get_all_playlists(&mut self) -> AnyResult<Vec<Playlist>> {
let timer = TimeLogger::new("Retrieved and decoded playlists".into());
let mut playlists = Playlist::select()
.order_asc("name")
.fetch_all(&mut self.connection)
.await?;

// Ensure the playlists are sorted alphabetically (case-insensitive) for better UX
playlists.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase()));

timer.complete();
Ok(playlists)
}

/** Get a single playlist by ID */
pub async fn get_playlist(&mut self, playlist_id: &String) -> AnyResult<Option<Playlist>> {
let playlist = Playlist::select()
.where_bind("id = ?", playlist_id)
.fetch_one(&mut self.connection)
.await?;

Ok(Some(playlist))
}

/** Create a playlist given a name and a set of track IDs */
pub async fn create_playlist(
&mut self,
name: String,
tracks_ids: Vec<String>,
import_path: Option<PathBuf>,
) -> AnyResult<Playlist> {
let playlist_path: Option<String> = match import_path {
Some(path) => Some(path.to_str().unwrap().to_string()),
None => None,
};

let playlist = Playlist {
id: uuid::Uuid::new_v4().to_string(),
name,
tracks: tracks_ids,
import_path: playlist_path,
};

let playlist = playlist.insert(&mut self.connection).await?;

Ok(playlist)
}

/** Set the tracks of a playlist given its ID and tracks IDs */
pub async fn set_playlist_tracks(
&mut self,
id: &String,
tracks: Vec<String>,
) -> AnyResult<Playlist> {
let playlist = Playlist::select()
.where_bind("id = ?", id)
.fetch_one(&mut self.connection)
.await?;

let updated_playlist = playlist
.update_partial()
.tracks(tracks)
.update(&mut self.connection)
.await?;

Ok(updated_playlist)
}

/** Update a playlist name by ID */
pub async fn rename_playlist(&mut self, id: &String, name: String) -> AnyResult<Playlist> {
let playlist = Playlist::select()
.where_bind("id = ?", id)
.fetch_one(&mut self.connection)
.await?;

let updated_playlist = playlist
.update_partial()
.name(name)
.update(&mut self.connection)
.await?;

Ok(updated_playlist)
}

/** Delete a playlist by ID */
pub async fn delete_playlist(&mut self, id: &String) -> AnyResult<()> {
let playlist = Playlist::select()
.where_bind("id = ?", id)
.fetch_one(&mut self.connection)
.await?;

playlist.delete(&mut self.connection).await?;

Ok(())
}
}
2 changes: 1 addition & 1 deletion src-tauri/src/libs/file_associations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ use std::path::PathBuf;
use tauri::{AppHandle, Emitter, Manager};
use tauri_plugin_dialog::{DialogExt, MessageDialogKind};

use crate::libs::database::SUPPORTED_TRACKS_EXTENSIONS;
use crate::libs::track::get_track_from_file;
use crate::libs::utils::is_file_valid;
use crate::plugins::database::SUPPORTED_TRACKS_EXTENSIONS;

use super::events::IPCEvent;

Expand Down
1 change: 1 addition & 0 deletions src-tauri/src/libs/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod database;
pub mod error;
pub mod events;
pub mod file_associations;
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ fn main() {
.plugin(plugins::app_menu::init())
.plugin(plugins::config::init())
.plugin(plugins::cover::init())
.plugin(plugins::database::init())
.plugin(plugins::db::init())
.plugin(plugins::debug::init())
.plugin(plugins::default_view::init())
.plugin(plugins::shell_extension::init())
Expand Down
Loading

0 comments on commit 0679990

Please sign in to comment.