diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cdc682d..a90fa85 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -55,7 +55,7 @@ jobs: - uses: vimtor/action-zip@v1 with: - files: target/release/mediarepo-daemon target/release/mediarepo-daemon.exe + files: mediarepo-daemon/target/release/mediarepo-daemon mediarepo-daemon/target/release/mediarepo-daemon.exe dest: mediarepo-daemon-${{ runner.os }}.zip - name: Upload Release Asset @@ -128,7 +128,7 @@ jobs: - uses: vimtor/action-zip@v1 with: - files: src-tauri/target/release/bundle src-tauri/target/release/mediarepo-ui src-tauri/target/release/mediarepo-ui.exe + files: mediarepo-ui/src-tauri/target/release/bundle mediarepo-ui/src-tauri/target/release/mediarepo-ui mediarepo-ui/src-tauri/target/release/mediarepo-ui.exe dest: mediarepo-ui-${{ runner.os }}.zip - name: Upload Release Asset diff --git a/mediarepo-api/Cargo.toml b/mediarepo-api/Cargo.toml index e350b59..5a8a80e 100644 --- a/mediarepo-api/Cargo.toml +++ b/mediarepo-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mediarepo-api" -version = "0.27.0" +version = "0.28.0" edition = "2018" license = "gpl-3" diff --git a/mediarepo-api/src/client_api/tag.rs b/mediarepo-api/src/client_api/tag.rs index 45e5098..e4b7f79 100644 --- a/mediarepo-api/src/client_api/tag.rs +++ b/mediarepo-api/src/client_api/tag.rs @@ -1,6 +1,7 @@ +use std::collections::HashMap; use crate::client_api::error::ApiResult; use crate::client_api::IPCApi; -use crate::types::files::{GetFileTagsRequest, GetFilesTagsRequest}; +use crate::types::files::{GetFileTagsRequest, GetFilesTagsRequest, GetFileTagMapRequest}; use crate::types::identifier::FileIdentifier; use crate::types::tags::{ChangeFileTagsRequest, NamespaceResponse, TagResponse}; use async_trait::async_trait; @@ -72,6 +73,12 @@ impl TagApi { .await } + /// Returns a map from files to assigned tags + #[tracing::instrument(level = "debug", skip_all)] + pub async fn get_file_tag_map(&self, cds: Vec) -> ApiResult>> { + self.emit_and_get("file_tag_map", GetFileTagMapRequest{cds}, Some(Duration::from_secs(10))).await + } + /// Creates a new tag and returns the created tag object #[tracing::instrument(level = "debug", skip(self))] pub async fn create_tags(&self, tags: Vec) -> ApiResult> { diff --git a/mediarepo-api/src/tauri_plugin/commands/tag.rs b/mediarepo-api/src/tauri_plugin/commands/tag.rs index 809e5b0..3633282 100644 --- a/mediarepo-api/src/tauri_plugin/commands/tag.rs +++ b/mediarepo-api/src/tauri_plugin/commands/tag.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use crate::tauri_plugin::commands::ApiAccess; use crate::tauri_plugin::error::PluginResult; use crate::types::identifier::FileIdentifier; @@ -41,6 +42,14 @@ pub async fn get_tags_for_files( Ok(tags) } +#[tauri::command] +pub async fn get_file_tag_map(cds: Vec, api_state: ApiAccess<'_>) -> PluginResult>> { + let api = api_state.api().await?; + let mappings = api.tag.get_file_tag_map(cds).await?; + + Ok(mappings) +} + #[tauri::command] pub async fn create_tags( api_state: ApiAccess<'_>, diff --git a/mediarepo-api/src/tauri_plugin/mod.rs b/mediarepo-api/src/tauri_plugin/mod.rs index 5779dbe..37e11c6 100644 --- a/mediarepo-api/src/tauri_plugin/mod.rs +++ b/mediarepo-api/src/tauri_plugin/mod.rs @@ -69,7 +69,8 @@ impl MediarepoPlugin { get_file_metadata, run_job, update_file_status, - delete_file + delete_file, + get_file_tag_map ]), } } diff --git a/mediarepo-api/src/types/files.rs b/mediarepo-api/src/types/files.rs index bb8146e..ff9153e 100644 --- a/mediarepo-api/src/types/files.rs +++ b/mediarepo-api/src/types/files.rs @@ -30,6 +30,11 @@ pub struct GetFilesTagsRequest { pub cds: Vec, } +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct GetFileTagMapRequest { + pub cds: Vec, +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct FileBasicDataResponse { pub id: i64, diff --git a/mediarepo-api/src/types/jobs.rs b/mediarepo-api/src/types/jobs.rs index b6260fd..e8b9d3b 100644 --- a/mediarepo-api/src/types/jobs.rs +++ b/mediarepo-api/src/types/jobs.rs @@ -11,4 +11,5 @@ pub enum JobType { MigrateContentDescriptors, CalculateSizes, CheckIntegrity, + Vacuum, } diff --git a/mediarepo-daemon/Cargo.lock b/mediarepo-daemon/Cargo.lock index fda4ce2..63c2385 100644 --- a/mediarepo-daemon/Cargo.lock +++ b/mediarepo-daemon/Cargo.lock @@ -57,9 +57,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.52" +version = "1.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84450d0b4a8bd1ba4144ce8ce718fbc5d071358b1e5384bace6536b3d1f2d5b3" +checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0" [[package]] name = "arrayref" @@ -85,9 +85,9 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7d78656ba01f1b93024b7c3a0467f1608e4be67d725749fdcd7d2c7678fd7a2" dependencies = [ - "proc-macro2 1.0.35", - "quote 1.0.10", - "syn 1.0.84", + "proc-macro2 1.0.36", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] @@ -106,9 +106,9 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308" dependencies = [ - "proc-macro2 1.0.35", - "quote 1.0.10", - "syn 1.0.84", + "proc-macro2 1.0.36", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] @@ -117,9 +117,9 @@ version = "0.1.52" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" dependencies = [ - "proc-macro2 1.0.35", - "quote 1.0.10", - "syn 1.0.84", + "proc-macro2 1.0.36", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] @@ -156,9 +156,9 @@ checksum = "33b8de67cc41132507eeece2584804efcb15f85ba516e34c944b7667f480397a" dependencies = [ "heck", "proc-macro-error", - "proc-macro2 1.0.35", - "quote 1.0.10", - "syn 1.0.84", + "proc-macro2 1.0.36", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] @@ -179,7 +179,7 @@ version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ - "serde 1.0.132", + "serde 1.0.136", ] [[package]] @@ -195,8 +195,8 @@ dependencies = [ "lazy_static", "lazycell", "peeking_take_while", - "proc-macro2 1.0.35", - "quote 1.0.10", + "proc-macro2 1.0.36", + "quote 1.0.15", "regex", "rustc-hash", "shlex", @@ -232,9 +232,9 @@ dependencies = [ [[package]] name = "blake3" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "526c210b4520e416420759af363083471656e819a75e831b8d2c9d5a584f2413" +checksum = "a08e53fc5a564bb15bfe6fae56bd71522205f1f91893f9c0116edad6496c183f" dependencies = [ "arrayref", "arrayvec 0.7.2", @@ -270,19 +270,13 @@ dependencies = [ "byteorder", "futures 0.3.19", "lazy_static", - "serde 1.0.132", + "serde 1.0.136", "thiserror", "tokio", "tracing", "typemap_rev", ] -[[package]] -name = "bumpalo" -version = "3.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" - [[package]] name = "bytemuck" version = "1.7.3" @@ -340,7 +334,7 @@ dependencies = [ "libc", "num-integer", "num-traits 0.2.14", - "serde 1.0.132", + "serde 1.0.136", "time 0.1.44", "winapi", ] @@ -392,7 +386,7 @@ dependencies = [ "lazy_static", "nom 5.1.2", "rust-ini", - "serde 1.0.132", + "serde 1.0.136", "serde-hjson", "serde_json", "toml", @@ -401,9 +395,9 @@ dependencies = [ [[package]] name = "console-api" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "033fddce299c93dd44ae21d5f5a6e749baa5d103784bcdde65701c07272a9fde" +checksum = "14f67643a7d716307ad10b3e3aef02826382acbe349a3e7605ac57556148bc87" dependencies = [ "prost", "prost-types", @@ -414,15 +408,16 @@ dependencies = [ [[package]] name = "console-subscriber" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2380cc150266375aeda8f9aeadc5527395c1a8807ecf9fa97a46d1bb760ec5b" +checksum = "829835c211a0247cd11e65e13cec8696b879374879c35ce162ce8098b23c90d4" dependencies = [ "console-api", + "crossbeam-channel", "futures 0.3.19", "hdrhistogram", "humantime", - "serde 1.0.132", + "serde 1.0.136", "serde_json", "thread_local", "tokio", @@ -481,21 +476,21 @@ checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403" [[package]] name = "crc32fast" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "738c290dfaea84fc1ca15ad9c168d083b05a714e1efddd8edaab678dc28d2836" +checksum = "a2209c310e29876f7f0b2721e7e26b84aff178aa3da5d091f9bfbf47669e60e3" dependencies = [ "cfg-if 1.0.0", ] [[package]] name = "crossbeam-channel" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" +checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.5", + "crossbeam-utils 0.8.6", ] [[package]] @@ -506,17 +501,17 @@ checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", - "crossbeam-utils 0.8.5", + "crossbeam-utils 0.8.6", ] [[package]] name = "crossbeam-epoch" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" +checksum = "97242a70df9b89a65d0b6df3c4bf5b9ce03c5b7309019777fbde37e7537f8762" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.5", + "crossbeam-utils 0.8.6", "lazy_static", "memoffset", "scopeguard", @@ -524,12 +519,12 @@ dependencies = [ [[package]] name = "crossbeam-queue" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b10ddc024425c88c2ad148c1b0fd53f4c6d38db9697c9f1588381212fa657c9" +checksum = "b979d76c9fcb84dffc80a73f7290da0f83e4c95773494674cb44b76d13a7a110" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.5", + "crossbeam-utils 0.8.6", ] [[package]] @@ -545,9 +540,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" +checksum = "cfcae03edb34f947e64acdb1c33ec169824e20657e9ecb61cef6c8c74dcb8120" dependencies = [ "cfg-if 1.0.0", "lazy_static", @@ -576,7 +571,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5bbed42daaa95e780b60a50546aa345b8413a1e46f9a40a12907d3598f038db" dependencies = [ "data-encoding", - "syn 1.0.84", + "syn 1.0.86", ] [[package]] @@ -610,6 +605,15 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +[[package]] +name = "fastrand" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", +] + [[package]] name = "ffmpeg-next" version = "4.4.0" @@ -637,9 +641,9 @@ dependencies = [ [[package]] name = "fixedbitset" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "398ea4fabe40b9b0d885340a2a991a44c8a645624075ad966d21f88688e2b69e" +checksum = "279fb028e20b3c4c320317955b77c5e0c9701f05a1d309905d6fc702cdc5053e" [[package]] name = "flate2" @@ -653,6 +657,18 @@ dependencies = [ "miniz_oxide 0.4.4", ] +[[package]] +name = "flume" +version = "0.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d04dafd11240188e146b6f6476a898004cace3be31d4ec5e08e216bf4947ac0" +dependencies = [ + "futures-core", + "futures-sink", + "pin-project", + "spin", +] + [[package]] name = "fnv" version = "1.0.7" @@ -755,9 +771,9 @@ version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbd947adfffb0efc70599b3ddcf7b5597bb5fa9e245eb99f62b3a5f7bb8bd3c" dependencies = [ - "proc-macro2 1.0.35", - "quote 1.0.10", - "syn 1.0.84", + "proc-macro2 1.0.36", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] @@ -792,9 +808,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.4" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" dependencies = [ "typenum", "version_check", @@ -802,9 +818,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" dependencies = [ "cfg-if 1.0.0", "libc", @@ -829,9 +845,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "h2" -version = "0.3.9" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f072413d126e57991455e0a922b31e4c8ba7c2ffbebf6b78b4f8521397d65cd" +checksum = "d9f1f717ddc7b2ba36df7e871fd88db79326551d3d6f1fc406fbfd28b582ff8e" dependencies = [ "bytes", "fnv", @@ -903,13 +919,13 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "http" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1323096b05d41827dadeaee54c9981958c0f94e670bc94ed80037d1a7b8b186b" +checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" dependencies = [ "bytes", "fnv", - "itoa 0.4.8", + "itoa 1.0.1", ] [[package]] @@ -1009,9 +1025,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" +checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" dependencies = [ "autocfg", "hashbrown", @@ -1065,15 +1081,6 @@ dependencies = [ "rayon", ] -[[package]] -name = "js-sys" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" -dependencies = [ - "wasm-bindgen", -] - [[package]] name = "keccak" version = "0.1.0" @@ -1107,9 +1114,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.112" +version = "0.2.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" +checksum = "565dbd88872dbe4cc8a46e527f26483c1d1f7afa6b884a3bd6cd893d4f98da74" [[package]] name = "libloading" @@ -1123,9 +1130,9 @@ dependencies = [ [[package]] name = "libsqlite3-sys" -version = "0.22.2" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290b64917f8b0cb885d9de0f9959fe1f775d7fa12f1da2db9001c1c8ab60f89d" +checksum = "d2cafc7c74096c336d9d27145f7ebd4f4b6f95ba16aa5a282387267e6925cb58" dependencies = [ "cc", "pkg-config", @@ -1149,9 +1156,9 @@ checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" [[package]] name = "lock_api" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" +checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" dependencies = [ "scopeguard", ] @@ -1182,11 +1189,11 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "mediarepo-api" -version = "0.27.0" +version = "0.28.0" dependencies = [ "bromine", "chrono", - "serde 1.0.132", + "serde 1.0.136", "serde_piecewise_default", "thiserror", "tracing", @@ -1206,7 +1213,7 @@ dependencies = [ "multibase", "multihash", "sea-orm", - "serde 1.0.132", + "serde 1.0.136", "sqlx", "thiserror", "thumbnailer", @@ -1219,13 +1226,13 @@ dependencies = [ [[package]] name = "mediarepo-daemon" -version = "0.13.0" +version = "0.13.2" dependencies = [ "console-subscriber", "glob", "log", "mediarepo-core", - "mediarepo-model", + "mediarepo-logic", "mediarepo-socket", "num-integer", "rolling-file", @@ -1251,7 +1258,7 @@ dependencies = [ ] [[package]] -name = "mediarepo-model" +name = "mediarepo-logic" version = "0.1.0" dependencies = [ "async-trait", @@ -1261,7 +1268,7 @@ dependencies = [ "mime", "mime_guess", "sea-orm", - "serde 1.0.132", + "serde 1.0.136", "tokio", "tracing", "typemap_rev", @@ -1275,10 +1282,10 @@ dependencies = [ "compare", "mediarepo-core", "mediarepo-database", - "mediarepo-model", + "mediarepo-logic", "port_check", "rayon", - "serde 1.0.132", + "serde 1.0.136", "tokio", "tracing", "tracing-futures", @@ -1398,9 +1405,9 @@ checksum = "424f6e86263cd5294cbd7f1e95746b95aca0e0d66bff31e5a40d6baa87b4aa99" dependencies = [ "proc-macro-crate", "proc-macro-error", - "proc-macro2 1.0.35", - "quote 1.0.10", - "syn 1.0.84", + "proc-macro2 1.0.36", + "quote 1.0.15", + "syn 1.0.86", "synstructure", ] @@ -1530,6 +1537,15 @@ dependencies = [ "libc", ] +[[package]] +name = "num_threads" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ba99ba6393e2c3734791401b66902d981cb03bf190af674ca69949b6d5fb15" +dependencies = [ + "libc", +] + [[package]] name = "once_cell" version = "1.9.0" @@ -1558,9 +1574,9 @@ dependencies = [ [[package]] name = "openssl-probe" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" @@ -1594,9 +1610,9 @@ checksum = "39cdc8f765173c2dad6c1f371d0997c2d21003bd14949da99d910ad409d88a85" dependencies = [ "Inflector", "proc-macro-error", - "proc-macro2 1.0.35", - "quote 1.0.10", - "syn 1.0.84", + "proc-macro2 1.0.36", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] @@ -1648,29 +1664,29 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1622113ce508488160cff04e6abc60960e676d330e1ca0f77c0b8df17c81438f" +checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b95af56fee93df76d721d356ac1ca41fccf168bc448eb14049234df764ba3e76" +checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" dependencies = [ - "proc-macro2 1.0.35", - "quote 1.0.10", - "syn 1.0.84", + "proc-macro2 1.0.36", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] name = "pin-project-lite" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" +checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" [[package]] name = "pin-utils" @@ -1704,9 +1720,9 @@ checksum = "f6519412c9e0d4be579b9f0618364d19cb434b324fc6ddb1b27b1e682c7105ed" [[package]] name = "ppv-lite86" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" [[package]] name = "proc-macro-crate" @@ -1725,9 +1741,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", - "proc-macro2 1.0.35", - "quote 1.0.10", - "syn 1.0.84", + "proc-macro2 1.0.36", + "quote 1.0.15", + "syn 1.0.86", "version_check", ] @@ -1737,8 +1753,8 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ - "proc-macro2 1.0.35", - "quote 1.0.10", + "proc-macro2 1.0.36", + "quote 1.0.15", "version_check", ] @@ -1753,9 +1769,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "392a54546fda6b7cc663379d0e6ce8b324cf88aecc5a499838e1be9781bdce2e" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" dependencies = [ "unicode-xid 0.2.2", ] @@ -1798,9 +1814,9 @@ checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" dependencies = [ "anyhow", "itertools", - "proc-macro2 1.0.35", - "quote 1.0.10", - "syn 1.0.84", + "proc-macro2 1.0.36", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] @@ -1824,11 +1840,11 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.10" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" dependencies = [ - "proc-macro2 1.0.35", + "proc-macro2 1.0.36", ] [[package]] @@ -1891,7 +1907,7 @@ checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" dependencies = [ "crossbeam-channel", "crossbeam-deque", - "crossbeam-utils 0.8.5", + "crossbeam-utils 0.8.6", "lazy_static", "num_cpus", ] @@ -1957,13 +1973,13 @@ checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" [[package]] name = "rust_decimal" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c2d4912d369fb95a351c221475657970678d344d70c1a788223f6e74d1e3732" +checksum = "e0593ce4677e3800ddafb3de917e8397b1348e06e688128ade722d88fbe11ebf" dependencies = [ "arrayvec 0.7.2", "num-traits 0.2.14", - "serde 1.0.132", + "serde 1.0.136", ] [[package]] @@ -2008,9 +2024,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "sea-orm" -version = "0.5.0-rc.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83b28888a93992e03d8da07693fac6502d05ce353589de94dd0b1d3087fc8410" +checksum = "5452736ac11d11f9dcf1980897d3a6302d78ee2bfcb928b0f9c03569f2e6b12c" dependencies = [ "async-stream", "async-trait", @@ -2023,7 +2039,7 @@ dependencies = [ "sea-orm-macros", "sea-query", "sea-strum", - "serde 1.0.132", + "serde 1.0.136", "serde_json", "sqlx", "tracing", @@ -2033,15 +2049,15 @@ dependencies = [ [[package]] name = "sea-orm-macros" -version = "0.5.0-rc.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd60216c3182debc49b317b878c10c301e9b52cdf965c38ce33161fe0f4f03ee" +checksum = "c8b7b6d423b591bf447685e9ea7cecd65e0c282e18dc5ddc7438425cd296faa8" dependencies = [ "bae", "heck", - "proc-macro2 1.0.35", - "quote 1.0.10", - "syn 1.0.84", + "proc-macro2 1.0.36", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] @@ -2064,9 +2080,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34cdc022b4f606353fe5dc85b09713a04e433323b70163e81513b141c6ae6eb5" dependencies = [ "heck", - "proc-macro2 1.0.35", - "quote 1.0.10", - "syn 1.0.84", + "proc-macro2 1.0.36", + "quote 1.0.15", + "syn 1.0.86", "thiserror", ] @@ -2086,17 +2102,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69b4397b825df6ccf1e98bcdabef3bbcfc47ff5853983467850eeab878384f21" dependencies = [ "heck", - "proc-macro2 1.0.35", - "quote 1.0.10", + "proc-macro2 1.0.36", + "quote 1.0.15", "rustversion", - "syn 1.0.84", + "syn 1.0.86", ] [[package]] name = "security-framework" -version = "2.4.2" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525bc1abfda2e1998d152c45cf13e696f76d0a4972310b22fac1658b05df7c87" +checksum = "3fed7948b6c68acbb6e20c334f55ad635dc0f75506963de4464289fbd3b051ac" dependencies = [ "bitflags", "core-foundation", @@ -2107,9 +2123,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.4.2" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9dd14d83160b528b7bfd66439110573efcfbe281b17fc2ca9f39f550d619c7e" +checksum = "a57321bf8bc2362081b2599912d2961fe899c0efadf1b4b2f8d48b3e253bb96c" dependencies = [ "core-foundation-sys", "libc", @@ -2123,9 +2139,9 @@ checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" [[package]] name = "serde" -version = "1.0.132" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9875c23cf305cd1fd7eb77234cbb705f21ea6a72c637a5c6db5fe4b8e7f008" +checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" dependencies = [ "serde_derive", ] @@ -2144,25 +2160,25 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.132" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc0db5cb2556c0e558887d9bbdcf6ac4471e83ff66cf696e5419024d1606276" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" dependencies = [ - "proc-macro2 1.0.35", - "quote 1.0.10", - "syn 1.0.84", + "proc-macro2 1.0.36", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] name = "serde_json" -version = "1.0.73" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcbd0344bc6533bc7ec56df11d42fb70f1b912351c0825ccb7211b59d8af7cf5" +checksum = "d23c1ba4cf0efd44be32017709280b32d1cea5c3f1275c3b6d9e8bc54f758085" dependencies = [ "indexmap", "itoa 1.0.1", "ryu", - "serde 1.0.132", + "serde 1.0.136", ] [[package]] @@ -2171,7 +2187,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d91a44b0f51aedd237f8f25c831e1f629982a187a5045c08ce4bccccce17b4b0" dependencies = [ - "serde 1.0.132", + "serde 1.0.136", "serde_piecewise_default_derive", ] @@ -2183,15 +2199,15 @@ checksum = "19446953e7b22342c23c79ede938c04b1c12f4eb7513db30cda94193ce30ff2a" dependencies = [ "proc-macro2 0.4.30", "quote 0.6.13", - "serde 1.0.132", + "serde 1.0.136", "syn 0.15.44", ] [[package]] name = "sha2" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer", "cfg-if 1.0.0", @@ -2244,20 +2260,29 @@ checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" [[package]] name = "smallvec" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" [[package]] name = "socket2" -version = "0.4.2" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" dependencies = [ "libc", "winapi", ] +[[package]] +name = "spin" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "511254be0c5bcf062b019a6c89c01a664aa359ded62f78aa72c6fc137c0590e5" +dependencies = [ + "lock_api", +] + [[package]] name = "sqlformat" version = "0.1.8" @@ -2271,9 +2296,9 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7911b0031a0247af40095838002999c7a52fba29d9739e93326e71a5a1bc9d43" +checksum = "692749de69603d81e016212199d73a2e14ee20e2def7d7914919e8db5d4d48b9" dependencies = [ "sqlx-core", "sqlx-macros", @@ -2281,9 +2306,9 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aec89bfaca8f7737439bad16d52b07f1ccd0730520d3bf6ae9d069fe4b641fb1" +checksum = "518be6f6fff5ca76f985d434f9c37f3662af279642acf730388f271dff7b9016" dependencies = [ "ahash", "atoi", @@ -2294,16 +2319,18 @@ dependencies = [ "crc", "crossbeam-channel", "crossbeam-queue", - "crossbeam-utils 0.8.5", + "crossbeam-utils 0.8.6", "either", + "flume", "futures-channel", "futures-core", + "futures-executor", "futures-intrusive", "futures-util", "hashlink", "hex", "indexmap", - "itoa 0.4.8", + "itoa 1.0.1", "libc", "libsqlite3-sys", "log", @@ -2313,7 +2340,7 @@ dependencies = [ "parking_lot", "percent-encoding", "rust_decimal", - "serde 1.0.132", + "serde 1.0.136", "serde_json", "sha2", "smallvec", @@ -2324,34 +2351,33 @@ dependencies = [ "tokio-stream", "url", "uuid", - "whoami", ] [[package]] name = "sqlx-macros" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "584866c833511b1a152e87a7ee20dee2739746f60c858b3c5209150bc4b466f5" +checksum = "38e45140529cf1f90a5e1c2e561500ca345821a1c513652c8f486bbf07407cc8" dependencies = [ "dotenv", "either", "heck", "once_cell", - "proc-macro2 1.0.35", - "quote 1.0.10", + "proc-macro2 1.0.36", + "quote 1.0.15", "serde_json", "sha2", "sqlx-core", "sqlx-rt", - "syn 1.0.84", + "syn 1.0.86", "url", ] [[package]] name = "sqlx-rt" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d1bd069de53442e7a320f525a6d4deb8bb0621ac7a55f7eccbc2b58b57f43d0" +checksum = "8061cbaa91ee75041514f67a09398c65a64efed72c90151ecd47593bad53da99" dependencies = [ "native-tls", "once_cell", @@ -2389,9 +2415,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "structopt" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b9788f4202aa75c240ecc9c15c65185e6a39ccdeb0fd5d008b98825464c87c" +checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" dependencies = [ "clap", "lazy_static", @@ -2406,9 +2432,9 @@ checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" dependencies = [ "heck", "proc-macro-error", - "proc-macro2 1.0.35", - "quote 1.0.10", - "syn 1.0.84", + "proc-macro2 1.0.36", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] @@ -2424,12 +2450,12 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.84" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecb2e6da8ee5eb9a61068762a32fa9619cc591ceb055b3687f4cd4051ec2e06b" +checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" dependencies = [ - "proc-macro2 1.0.35", - "quote 1.0.10", + "proc-macro2 1.0.36", + "quote 1.0.15", "unicode-xid 0.2.2", ] @@ -2439,21 +2465,21 @@ version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ - "proc-macro2 1.0.35", - "quote 1.0.10", - "syn 1.0.84", + "proc-macro2 1.0.36", + "quote 1.0.15", + "syn 1.0.86", "unicode-xid 0.2.2", ] [[package]] name = "tempfile" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" dependencies = [ "cfg-if 1.0.0", + "fastrand", "libc", - "rand", "redox_syscall", "remove_dir_all", "winapi", @@ -2483,25 +2509,25 @@ version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" dependencies = [ - "proc-macro2 1.0.35", - "quote 1.0.10", - "syn 1.0.84", + "proc-macro2 1.0.36", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] name = "thread_local" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" dependencies = [ "once_cell", ] [[package]] name = "thumbnailer" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8981b60fe29b8213c829340b7d5bea9d0bfb145fc460450ea3db01cf54d8643a" +checksum = "6017341c89a0c406e38801119f67dd0b67d045ff0e50aa2cf8fc1de4a1b48c3b" dependencies = [ "ffmpeg-next", "image", @@ -2535,12 +2561,13 @@ dependencies = [ [[package]] name = "time" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41effe7cfa8af36f439fac33861b66b049edc6f9a32331e2312660529c1c24ad" +checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d" dependencies = [ - "itoa 0.4.8", + "itoa 1.0.1", "libc", + "num_threads", ] [[package]] @@ -2560,9 +2587,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.15.0" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbbf1c778ec206785635ce8ad57fe52b3009ae9e0c9f574a728f3049d3e55838" +checksum = "0c27a64b625de6d309e8c57716ba93021dccf1b3b5c97edd6d3dd2d2135afc0a" dependencies = [ "bytes", "libc", @@ -2603,9 +2630,9 @@ dependencies = [ [[package]] name = "tokio-io-timeout" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90c49f106be240de154571dd31fbe48acb10ba6c6dd6f6517ad603abffa42de9" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" dependencies = [ "pin-project-lite", "tokio", @@ -2617,9 +2644,9 @@ version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" dependencies = [ - "proc-macro2 1.0.35", - "quote 1.0.10", - "syn 1.0.84", + "proc-macro2 1.0.36", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] @@ -2663,7 +2690,7 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" dependencies = [ - "serde 1.0.132", + "serde 1.0.136", ] [[package]] @@ -2703,10 +2730,10 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9403f1bafde247186684b230dc6f38b5cd514584e8bec1dd32514be4745fa757" dependencies = [ - "proc-macro2 1.0.35", + "proc-macro2 1.0.36", "prost-build", - "quote 1.0.10", - "syn 1.0.84", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] @@ -2762,7 +2789,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94571df2eae3ed4353815ea5a90974a594a1792d8782ff2cbcc9392d1101f366" dependencies = [ "crossbeam-channel", - "time 0.3.5", + "time 0.3.7", "tracing-subscriber", ] @@ -2772,9 +2799,9 @@ version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e" dependencies = [ - "proc-macro2 1.0.35", - "quote 1.0.10", - "syn 1.0.84", + "proc-macro2 1.0.36", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] @@ -2825,21 +2852,21 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb65ea441fbb84f9f6748fd496cf7f63ec9af5bca94dd86456978d055e8eb28b" dependencies = [ - "serde 1.0.132", + "serde 1.0.136", "tracing-core", ] [[package]] name = "tracing-subscriber" -version = "0.3.4" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5e6136799e1079699e0d9784c883e03af55cf6a1bee48fe1d79ca552c1bc36f" +checksum = "5312f325fe3588e277415f5a6cca1f4ccad0f248c4cd5a4bd33032d7286abc22" dependencies = [ "ansi_term", "lazy_static", "matchers", "regex", - "serde 1.0.132", + "serde 1.0.136", "serde_json", "sharded-slab", "smallvec", @@ -2947,7 +2974,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ "getrandom", - "serde 1.0.132", + "serde 1.0.136", ] [[package]] @@ -2964,9 +2991,9 @@ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "version_check" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "want" @@ -2984,70 +3011,6 @@ version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" -[[package]] -name = "wasm-bindgen" -version = "0.2.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" -dependencies = [ - "cfg-if 1.0.0", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" -dependencies = [ - "bumpalo", - "lazy_static", - "log", - "proc-macro2 1.0.35", - "quote 1.0.10", - "syn 1.0.84", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" -dependencies = [ - "quote 1.0.10", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" -dependencies = [ - "proc-macro2 1.0.35", - "quote 1.0.10", - "syn 1.0.84", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" - -[[package]] -name = "web-sys" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "webp" version = "0.2.0" @@ -3066,25 +3029,15 @@ checksum = "d8b77fdfd5a253be4ab714e4ffa3c49caf146b4de743e97510c0656cf90f1e8e" [[package]] name = "which" -version = "4.2.2" +version = "4.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea187a8ef279bc014ec368c27a920da2024d2a711109bfbe3440585d5cf27ad9" +checksum = "2a5a7e487e921cf220206864a94a89b6c6905bfc19f1057fa26a4cb360e5c1d2" dependencies = [ "either", "lazy_static", "libc", ] -[[package]] -name = "whoami" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524b58fa5a20a2fb3014dd6358b70e6579692a56ef6fce928834e488f42f65e8" -dependencies = [ - "wasm-bindgen", - "web-sys", -] - [[package]] name = "winapi" version = "0.3.9" diff --git a/mediarepo-daemon/Cargo.toml b/mediarepo-daemon/Cargo.toml index acca829..208826f 100644 --- a/mediarepo-daemon/Cargo.toml +++ b/mediarepo-daemon/Cargo.toml @@ -1,10 +1,10 @@ [workspace] -members = ["mediarepo-core", "mediarepo-database", "mediarepo-model", "mediarepo-socket", "."] -default-members = ["mediarepo-core", "mediarepo-database", "mediarepo-model", "mediarepo-socket", "."] +members = ["mediarepo-core", "mediarepo-database", "mediarepo-logic", "mediarepo-socket", "."] +default-members = ["mediarepo-core", "mediarepo-database", "mediarepo-logic", "mediarepo-socket", "."] [package] name = "mediarepo-daemon" -version = "0.13.1" +version = "0.13.2" edition = "2018" license = "gpl-3" repository = "https://github.com/Trivernis/mediarepo-daemon" @@ -31,8 +31,8 @@ log = "^0.4.14" [dependencies.mediarepo-core] path = "./mediarepo-core" -[dependencies.mediarepo-model] -path = "./mediarepo-model" +[dependencies.mediarepo-logic] +path = "mediarepo-logic" [dependencies.mediarepo-socket] path = "./mediarepo-socket" @@ -47,4 +47,4 @@ features = ["env-filter", "ansi", "json"] [features] default = ["ffmpeg"] -ffmpeg = ["mediarepo-core/ffmpeg", "mediarepo-model/ffmpeg"] \ No newline at end of file +ffmpeg = ["mediarepo-core/ffmpeg", "mediarepo-logic/ffmpeg"] \ No newline at end of file diff --git a/mediarepo-daemon/mediarepo-core/Cargo.toml b/mediarepo-daemon/mediarepo-core/Cargo.toml index 7c2339a..71c1d40 100644 --- a/mediarepo-daemon/mediarepo-core/Cargo.toml +++ b/mediarepo-daemon/mediarepo-core/Cargo.toml @@ -22,7 +22,7 @@ data-encoding = "^2.3.2" tokio-graceful-shutdown = "^0.4.3" [dependencies.thumbnailer] -version = "^0.2.4" +version = "^0.2.5" default-features = false [dependencies.sea-orm] diff --git a/mediarepo-daemon/mediarepo-core/src/content_descriptor.rs b/mediarepo-daemon/mediarepo-core/src/content_descriptor.rs index 73c1ea3..e89f47e 100644 --- a/mediarepo-daemon/mediarepo-core/src/content_descriptor.rs +++ b/mediarepo-daemon/mediarepo-core/src/content_descriptor.rs @@ -1,6 +1,7 @@ -use crate::error::RepoResult; use multihash::{Code, MultihashDigest}; +use crate::error::RepoResult; + /// Creates a new content descriptor for the given file pub fn create_content_descriptor(bytes: &[u8]) -> Vec { Code::Sha2_256.digest(bytes).to_bytes() diff --git a/mediarepo-daemon/mediarepo-core/src/context.rs b/mediarepo-daemon/mediarepo-core/src/context.rs index cc3e52d..77b1e0f 100644 --- a/mediarepo-daemon/mediarepo-core/src/context.rs +++ b/mediarepo-daemon/mediarepo-core/src/context.rs @@ -1,8 +1,10 @@ -use crate::settings::Settings; -use sea_orm::DatabaseConnection; use std::sync::Arc; + +use sea_orm::DatabaseConnection; use tokio::sync::Mutex; +use crate::settings::Settings; + #[derive(Clone, Default)] pub struct Context { pub settings: Arc>, diff --git a/mediarepo-daemon/mediarepo-core/src/error.rs b/mediarepo-daemon/mediarepo-core/src/error.rs index b3e1d82..7f5dfbc 100644 --- a/mediarepo-daemon/mediarepo-core/src/error.rs +++ b/mediarepo-daemon/mediarepo-core/src/error.rs @@ -1,5 +1,6 @@ -use sea_orm::DbErr; use std::fmt::{Debug, Formatter}; + +use sea_orm::DbErr; use thiserror::Error; pub type RepoResult = Result; @@ -37,8 +38,11 @@ pub enum RepoError { #[error("failed to decode data {0}")] Decode(#[from] data_encoding::DecodeError), - #[error("Failed to read repo.toml configuration file {0}")] + #[error("failed to read repo.toml configuration file {0}")] Config(#[from] config::ConfigError), + + #[error("the database file is corrupted {0}")] + Corrupted(String), } #[derive(Error, Debug)] diff --git a/mediarepo-daemon/mediarepo-core/src/fs/drop_file.rs b/mediarepo-daemon/mediarepo-core/src/fs/drop_file.rs index 53fb266..ee6bdb3 100644 --- a/mediarepo-daemon/mediarepo-core/src/fs/drop_file.rs +++ b/mediarepo-daemon/mediarepo-core/src/fs/drop_file.rs @@ -1,5 +1,6 @@ use std::io::Result; use std::path::{Path, PathBuf}; + use tokio::fs::{File, OpenOptions}; /// A file that only exists while being owned. diff --git a/mediarepo-daemon/mediarepo-core/src/fs/file_hash_store.rs b/mediarepo-daemon/mediarepo-core/src/fs/file_hash_store.rs index b23fcf7..71034dc 100644 --- a/mediarepo-daemon/mediarepo-core/src/fs/file_hash_store.rs +++ b/mediarepo-daemon/mediarepo-core/src/fs/file_hash_store.rs @@ -1,11 +1,13 @@ -use crate::content_descriptor::{create_content_descriptor, encode_content_descriptor}; -use crate::error::RepoResult; -use crate::utils::get_folder_size; use std::path::PathBuf; + use tokio::fs; use tokio::fs::{File, OpenOptions}; use tokio::io::{AsyncRead, AsyncReadExt, BufReader}; +use crate::content_descriptor::{create_content_descriptor, encode_content_descriptor}; +use crate::error::RepoResult; +use crate::utils::get_folder_size; + #[derive(Clone, Debug)] pub struct FileHashStore { path: PathBuf, diff --git a/mediarepo-daemon/mediarepo-core/src/fs/thumbnail_store.rs b/mediarepo-daemon/mediarepo-core/src/fs/thumbnail_store.rs index 6a500ea..7b150c2 100644 --- a/mediarepo-daemon/mediarepo-core/src/fs/thumbnail_store.rs +++ b/mediarepo-daemon/mediarepo-core/src/fs/thumbnail_store.rs @@ -1,12 +1,14 @@ -use crate::error::RepoResult; -use crate::utils::get_folder_size; use std::fmt::Debug; use std::io::Result; use std::path::PathBuf; + use tokio::fs; use tokio::fs::OpenOptions; use tokio::io::{AsyncWriteExt, BufWriter}; +use crate::error::RepoResult; +use crate::utils::get_folder_size; + #[derive(Clone, Debug)] pub struct ThumbnailStore { path: PathBuf, diff --git a/mediarepo-daemon/mediarepo-core/src/settings/mod.rs b/mediarepo-daemon/mediarepo-core/src/settings/mod.rs index 329e32a..66a8960 100644 --- a/mediarepo-daemon/mediarepo-core/src/settings/mod.rs +++ b/mediarepo-daemon/mediarepo-core/src/settings/mod.rs @@ -1,19 +1,21 @@ -mod logging; -mod paths; -mod server; -pub mod v1; +use std::fs; +use std::path::PathBuf; -use crate::error::RepoResult; -use crate::settings::v1::SettingsV1; use config::{Config, FileFormat}; use serde::{Deserialize, Serialize}; -use std::fs; -use std::path::PathBuf; pub use logging::*; pub use paths::*; pub use server::*; +use crate::error::RepoResult; +use crate::settings::v1::SettingsV1; + +mod logging; +mod paths; +mod server; +pub mod v1; + #[derive(Clone, Debug, Deserialize, Serialize, Default)] pub struct Settings { pub server: ServerSettings, diff --git a/mediarepo-daemon/mediarepo-core/src/settings/paths.rs b/mediarepo-daemon/mediarepo-core/src/settings/paths.rs index 5e14e75..162e096 100644 --- a/mediarepo-daemon/mediarepo-core/src/settings/paths.rs +++ b/mediarepo-daemon/mediarepo-core/src/settings/paths.rs @@ -1,6 +1,7 @@ -use serde::{Deserialize, Serialize}; use std::path::PathBuf; +use serde::{Deserialize, Serialize}; + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct PathSettings { pub(crate) database_directory: String, diff --git a/mediarepo-daemon/mediarepo-core/src/settings/server.rs b/mediarepo-daemon/mediarepo-core/src/settings/server.rs index c722a11..32de932 100644 --- a/mediarepo-daemon/mediarepo-core/src/settings/server.rs +++ b/mediarepo-daemon/mediarepo-core/src/settings/server.rs @@ -1,6 +1,7 @@ -use serde::{Deserialize, Serialize}; use std::net::IpAddr; +use serde::{Deserialize, Serialize}; + #[derive(Clone, Debug, Deserialize, Serialize, Default)] pub struct ServerSettings { pub tcp: TcpServerSettings, diff --git a/mediarepo-daemon/mediarepo-core/src/settings/v1.rs b/mediarepo-daemon/mediarepo-core/src/settings/v1.rs index ccbb35e..f14cb68 100644 --- a/mediarepo-daemon/mediarepo-core/src/settings/v1.rs +++ b/mediarepo-daemon/mediarepo-core/src/settings/v1.rs @@ -1,7 +1,9 @@ -use crate::error::RepoResult; -use serde::{Deserialize, Serialize}; use std::net::IpAddr; +use serde::{Deserialize, Serialize}; + +use crate::error::RepoResult; + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct SettingsV1 { pub listen_address: IpAddr, diff --git a/mediarepo-daemon/mediarepo-core/src/type_keys.rs b/mediarepo-daemon/mediarepo-core/src/type_keys.rs index 5aa447d..8cebd2c 100644 --- a/mediarepo-daemon/mediarepo-core/src/type_keys.rs +++ b/mediarepo-daemon/mediarepo-core/src/type_keys.rs @@ -1,10 +1,12 @@ -use crate::settings::Settings; -use mediarepo_api::types::repo::SizeType; use std::collections::HashMap; use std::path::PathBuf; + +use mediarepo_api::types::repo::SizeType; use tokio_graceful_shutdown::SubsystemHandle; use typemap_rev::TypeMapKey; +use crate::settings::Settings; + pub struct SettingsKey; impl TypeMapKey for SettingsKey { diff --git a/mediarepo-daemon/mediarepo-core/src/utils.rs b/mediarepo-daemon/mediarepo-core/src/utils.rs index 609f7c2..4d901e8 100644 --- a/mediarepo-daemon/mediarepo-core/src/utils.rs +++ b/mediarepo-daemon/mediarepo-core/src/utils.rs @@ -1,9 +1,11 @@ -use crate::error::RepoResult; -use futures::future; use std::path::PathBuf; + +use futures::future; use tokio::fs::{self, OpenOptions}; use tokio::io::{AsyncBufReadExt, BufReader}; +use crate::error::RepoResult; + /// Parses a normalized tag into its two components of namespace and tag pub fn parse_namespace_and_tag(norm_tag: String) -> (Option, String) { norm_tag diff --git a/mediarepo-daemon/mediarepo-database/src/lib.rs b/mediarepo-daemon/mediarepo-database/src/lib.rs index ea5fb18..4e946a3 100644 --- a/mediarepo-daemon/mediarepo-database/src/lib.rs +++ b/mediarepo-daemon/mediarepo-database/src/lib.rs @@ -1,7 +1,9 @@ -use mediarepo_core::error::RepoDatabaseResult; +use std::time::Duration; + use sea_orm::{ConnectOptions, Database, DatabaseConnection}; use sqlx::migrate::MigrateDatabase; -use std::time::Duration; + +use mediarepo_core::error::RepoDatabaseResult; pub mod entities; pub mod queries; diff --git a/mediarepo-daemon/mediarepo-database/src/queries/tags.rs b/mediarepo-daemon/mediarepo-database/src/queries/tags.rs index d3726c5..c901031 100644 --- a/mediarepo-daemon/mediarepo-database/src/queries/tags.rs +++ b/mediarepo-daemon/mediarepo-database/src/queries/tags.rs @@ -1,11 +1,13 @@ -use mediarepo_core::error::RepoResult; -use sea_orm::DbBackend; -use sea_orm::FromQueryResult; -use sea_orm::{DatabaseConnection, Statement}; use std::collections::HashMap; use std::fmt::Display; use std::iter::FromIterator; +use sea_orm::{DatabaseConnection, Statement}; +use sea_orm::DbBackend; +use sea_orm::FromQueryResult; + +use mediarepo_core::error::RepoResult; + #[derive(Debug, FromQueryResult)] struct CIDNamespaceTag { cd_id: i64, diff --git a/mediarepo-daemon/mediarepo-model/.gitignore b/mediarepo-daemon/mediarepo-logic/.gitignore similarity index 100% rename from mediarepo-daemon/mediarepo-model/.gitignore rename to mediarepo-daemon/mediarepo-logic/.gitignore diff --git a/mediarepo-daemon/mediarepo-model/Cargo.toml b/mediarepo-daemon/mediarepo-logic/Cargo.toml similarity index 91% rename from mediarepo-daemon/mediarepo-model/Cargo.toml rename to mediarepo-daemon/mediarepo-logic/Cargo.toml index 0f14d81..75cab7b 100644 --- a/mediarepo-daemon/mediarepo-model/Cargo.toml +++ b/mediarepo-daemon/mediarepo-logic/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "mediarepo-model" +name = "mediarepo-logic" version = "0.1.0" edition = "2018" workspace = ".." diff --git a/mediarepo-daemon/mediarepo-logic/src/dao/file/add.rs b/mediarepo-daemon/mediarepo-logic/src/dao/file/add.rs new file mode 100644 index 0000000..6bb93c8 --- /dev/null +++ b/mediarepo-daemon/mediarepo-logic/src/dao/file/add.rs @@ -0,0 +1,73 @@ +use std::io::Cursor; + +use chrono::{Local, NaiveDateTime}; +use sea_orm::ActiveValue::Set; +use sea_orm::{ActiveModelTrait, ConnectionTrait, DatabaseTransaction}; + +use mediarepo_core::error::RepoResult; +use mediarepo_database::entities::{content_descriptor, file, file_metadata}; + +use crate::dao::file::FileDao; +use crate::dto::{AddFileDto, FileDto}; + +impl FileDao { + #[tracing::instrument(level = "debug", skip(self))] + pub async fn add(&self, add_dto: AddFileDto) -> RepoResult { + let trx = self.ctx.db.begin().await?; + let file_size = add_dto.content.len(); + let cd_bin = self + .ctx + .main_storage + .add_file(Cursor::new(add_dto.content), None) + .await?; + let cd_model = content_descriptor::ActiveModel { + descriptor: Set(cd_bin), + ..Default::default() + }; + let cd = cd_model.insert(&trx).await?; + + let model = file::ActiveModel { + cd_id: Set(cd.id), + mime_type: Set(add_dto.mime_type), + ..Default::default() + }; + let file: file::Model = model.insert(&trx).await?; + + let metadata = add_file_metadata( + &trx, + file.id, + file_size as i64, + add_dto.creation_time, + add_dto.change_time, + add_dto.name, + ) + .await?; + + trx.commit().await?; + + Ok(FileDto::new(file, cd, Some(metadata))) + } +} + +async fn add_file_metadata( + trx: &DatabaseTransaction, + file_id: i64, + size: i64, + creation_time: NaiveDateTime, + change_time: NaiveDateTime, + name: Option, +) -> RepoResult { + let metadata_model = file_metadata::ActiveModel { + file_id: Set(file_id), + size: Set(size), + import_time: Set(Local::now().naive_local()), + creation_time: Set(creation_time), + change_time: Set(change_time), + name: Set(name), + ..Default::default() + }; + + let metadata = metadata_model.insert(trx).await?; + + Ok(metadata) +} diff --git a/mediarepo-daemon/mediarepo-logic/src/dao/file/delete.rs b/mediarepo-daemon/mediarepo-logic/src/dao/file/delete.rs new file mode 100644 index 0000000..81c914a --- /dev/null +++ b/mediarepo-daemon/mediarepo-logic/src/dao/file/delete.rs @@ -0,0 +1,43 @@ +use sea_orm::ConnectionTrait; +use sea_orm::prelude::*; + +use mediarepo_core::error::{RepoResult}; +use mediarepo_database::entities::{ + content_descriptor, content_descriptor_tag, file, file_metadata, +}; + +use crate::dao::file::{FileDao}; +use crate::dto::FileDto; + +impl FileDao { + #[tracing::instrument(level = "debug", skip(self))] + pub async fn delete(&self, file: FileDto) -> RepoResult<()> { + let trx = self.ctx.db.begin().await?; + + file_metadata::Entity::delete_many() + .filter(file_metadata::Column::FileId.eq(file.id())) + .exec(&trx) + .await?; + file::Entity::delete_many() + .filter(file::Column::Id.eq(file.id())) + .exec(&trx) + .await?; + content_descriptor_tag::Entity::delete_many() + .filter(content_descriptor_tag::Column::CdId.eq(file.cd_id())) + .exec(&trx) + .await?; + content_descriptor::Entity::delete_many() + .filter(content_descriptor::Column::Id.eq(file.cd_id())) + .exec(&trx) + .await?; + + self.ctx + .thumbnail_storage + .delete_parent(&file.encoded_cd()) + .await?; + self.ctx.main_storage.delete_file(file.cd()).await?; + trx.commit().await?; + + Ok(()) + } +} diff --git a/mediarepo-daemon/mediarepo-model/src/file/filter.rs b/mediarepo-daemon/mediarepo-logic/src/dao/file/find.rs similarity index 90% rename from mediarepo-daemon/mediarepo-model/src/file/filter.rs rename to mediarepo-daemon/mediarepo-logic/src/dao/file/find.rs index c3a9313..f96457d 100644 --- a/mediarepo-daemon/mediarepo-model/src/file/filter.rs +++ b/mediarepo-daemon/mediarepo-logic/src/dao/file/find.rs @@ -1,11 +1,16 @@ use chrono::NaiveDateTime; +use sea_orm::{ColumnTrait, EntityTrait, QueryFilter, QuerySelect}; +use sea_orm::Condition; +use sea_orm::sea_query::{Alias, Expr, Query, SimpleExpr}; + +use mediarepo_core::error::RepoResult; use mediarepo_database::entities::content_descriptor; use mediarepo_database::entities::content_descriptor_tag; use mediarepo_database::entities::file; use mediarepo_database::entities::file_metadata; -use sea_orm::sea_query::{Alias, Expr, Query, SimpleExpr}; -use sea_orm::ColumnTrait; -use sea_orm::Condition; + +use crate::dao::file::{FileDao, map_cd_and_file}; +use crate::dto::FileDto; macro_rules! apply_ordering_comparator { ($column:expr, $filter:expr) => { @@ -53,8 +58,28 @@ pub enum NegatableComparator { IsNot(T), } +impl FileDao { + /// Finds files by filters + #[tracing::instrument(level = "debug", skip(self))] + pub async fn find(&self, filters: Vec>) -> RepoResult> { + let main_condition = build_find_filter_conditions(filters); + + let files = content_descriptor::Entity::find() + .find_also_related(file::Entity) + .filter(main_condition) + .group_by(file::Column::Id) + .all(&self.ctx.db) + .await? + .into_iter() + .filter_map(map_cd_and_file) + .collect(); + + Ok(files) + } +} + #[tracing::instrument(level = "debug")] -pub fn build_find_filter_conditions(filters: Vec>) -> Condition { +fn build_find_filter_conditions(filters: Vec>) -> Condition { filters .into_iter() .fold(Condition::all(), |all_cond, mut expression| { diff --git a/mediarepo-daemon/mediarepo-logic/src/dao/file/mod.rs b/mediarepo-daemon/mediarepo-logic/src/dao/file/mod.rs new file mode 100644 index 0000000..210cf50 --- /dev/null +++ b/mediarepo-daemon/mediarepo-logic/src/dao/file/mod.rs @@ -0,0 +1,151 @@ +use sea_orm::prelude::*; +use tokio::io::AsyncReadExt; + +use mediarepo_core::error::RepoResult; +use mediarepo_database::entities::{content_descriptor, file, file_metadata}; + +use crate::dao::{DaoContext, DaoProvider}; +use crate::dto::{FileDto, FileMetadataDto, ThumbnailDto}; + +pub mod add; +pub mod delete; +pub mod find; +pub mod update; + +pub struct FileDao { + ctx: DaoContext, +} + +impl DaoProvider for FileDao { + fn dao_ctx(&self) -> DaoContext { + self.ctx.clone() + } +} + +impl FileDao { + pub fn new(ctx: DaoContext) -> Self { + Self { ctx } + } + + #[tracing::instrument(level = "debug", skip(self))] + pub async fn all(&self) -> RepoResult> { + let files = file::Entity::find() + .find_also_related(content_descriptor::Entity) + .all(&self.ctx.db) + .await? + .into_iter() + .filter_map(map_file_and_cd) + .collect(); + + Ok(files) + } + + #[tracing::instrument(level = "debug", skip(self))] + #[inline] + pub async fn by_id(&self, id: i64) -> RepoResult> { + self.all_by_id(vec![id]).await.map(|f| f.into_iter().next()) + } + + #[tracing::instrument(level = "debug", skip(self))] + #[inline] + pub async fn by_cd(&self, cd: Vec) -> RepoResult> { + self.all_by_cd(vec![cd]).await.map(|f| f.into_iter().next()) + } + + #[tracing::instrument(level = "debug", skip(self))] + pub async fn all_by_cd(&self, cds: Vec>) -> RepoResult> { + if cds.is_empty() { + return Ok(vec![]); + } + + let files = file::Entity::find() + .find_also_related(content_descriptor::Entity) + .filter(content_descriptor::Column::Descriptor.is_in(cds)) + .all(&self.ctx.db) + .await? + .into_iter() + .filter_map(map_file_and_cd) + .collect(); + + Ok(files) + } + + #[tracing::instrument(level = "debug", skip(self))] + pub async fn all_by_id(&self, ids: Vec) -> RepoResult> { + if ids.is_empty() { + return Ok(vec![]); + } + + let files = file::Entity::find() + .find_also_related(content_descriptor::Entity) + .filter(file::Column::Id.is_in(ids)) + .all(&self.ctx.db) + .await? + .into_iter() + .filter_map(map_file_and_cd) + .collect(); + + Ok(files) + } + + pub async fn metadata(&self, file_id: i64) -> RepoResult> { + self.all_metadata(vec![file_id]) + .await + .map(|m| m.into_iter().next()) + } + + #[tracing::instrument(level = "debug", skip(self))] + pub async fn all_metadata(&self, file_ids: Vec) -> RepoResult> { + if file_ids.is_empty() { + return Ok(vec![]); + } + + let metadata = file_metadata::Entity::find() + .filter(file_metadata::Column::FileId.is_in(file_ids)) + .all(&self.ctx.db) + .await? + .into_iter() + .map(|m| FileMetadataDto::new(m)) + .collect(); + + Ok(metadata) + } + + /// Returns all thumbnails for a cd + #[tracing::instrument(level = "debug", skip(self))] + pub async fn thumbnails(&self, encoded_cd: String) -> RepoResult> { + let thumbnails = self + .ctx + .thumbnail_storage + .get_thumbnails(&encoded_cd) + .await? + .into_iter() + .map(|(size, path)| { + ThumbnailDto::new(path, encoded_cd.clone(), size, String::from("image/png")) + }) + .collect(); + + Ok(thumbnails) + } + + #[tracing::instrument(level = "debug", skip(self))] + pub async fn get_bytes(&self, cd: &[u8]) -> RepoResult> { + let mut buf = Vec::new(); + let mut reader = self.ctx.main_storage.get_file(cd).await?.1; + reader.read_to_end(&mut buf).await?; + + Ok(buf) + } +} + +fn map_file_and_cd( + (file, cd): (file::Model, Option), +) -> Option { + cd.map(|c| FileDto::new(file, c, None)) +} + +fn map_cd_and_file( + (cd, file): (content_descriptor::Model, Option), +) -> Option { + file.map(|f| FileDto::new(f, cd, None)) +} diff --git a/mediarepo-daemon/mediarepo-logic/src/dao/file/update.rs b/mediarepo-daemon/mediarepo-logic/src/dao/file/update.rs new file mode 100644 index 0000000..c143964 --- /dev/null +++ b/mediarepo-daemon/mediarepo-logic/src/dao/file/update.rs @@ -0,0 +1,95 @@ +use std::fmt::Debug; +use std::io::Cursor; +use std::str::FromStr; + +use sea_orm::prelude::*; +use sea_orm::ActiveValue::{Set, Unchanged}; +use sea_orm::{ConnectionTrait, NotSet}; + +use mediarepo_core::error::{RepoError, RepoResult}; +use mediarepo_core::fs::thumbnail_store::Dimensions; +use mediarepo_core::thumbnailer; +use mediarepo_core::thumbnailer::{ThumbnailSize}; +use mediarepo_database::entities::{content_descriptor, file, file_metadata}; + +use crate::dao::file::FileDao; +use crate::dao::opt_to_active_val; +use crate::dto::{FileDto, FileMetadataDto, ThumbnailDto, UpdateFileDto, UpdateFileMetadataDto}; + +impl FileDao { + #[tracing::instrument(level = "debug", skip(self))] + pub async fn update(&self, update_dto: UpdateFileDto) -> RepoResult { + let trx = self.ctx.db.begin().await?; + let model = file::ActiveModel { + id: Set(update_dto.id), + cd_id: update_dto.cd_id.map(|v| Set(v)).unwrap_or(NotSet), + mime_type: update_dto.mime_type.map(|v| Set(v)).unwrap_or(NotSet), + status: update_dto.status.map(|v| Set(v as i32)).unwrap_or(NotSet), + }; + let file_model = model.update(&trx).await?; + let cd = file_model + .find_related(content_descriptor::Entity) + .one(&trx) + .await? + .ok_or_else(|| RepoError::from("Content descriptor not found"))?; + trx.commit().await?; + + Ok(FileDto::new(file_model, cd, None)) + } + + #[tracing::instrument(level = "debug", skip(self))] + pub async fn update_metadata( + &self, + update_dto: UpdateFileMetadataDto, + ) -> RepoResult { + let model = file_metadata::ActiveModel { + file_id: Unchanged(update_dto.file_id), + name: opt_to_active_val(update_dto.name), + comment: opt_to_active_val(update_dto.comment), + size: opt_to_active_val(update_dto.size), + change_time: opt_to_active_val(update_dto.change_time), + ..Default::default() + }; + let metadata = model.update(&self.ctx.db).await?; + + Ok(FileMetadataDto::new(metadata)) + } + + #[tracing::instrument(level = "debug", skip(self))] + pub async fn create_thumbnails + Debug>( + &self, + file: FileDto, + sizes: I, + ) -> RepoResult> { + let bytes = self.get_bytes(file.cd()).await?; + let mime_type = mime::Mime::from_str(file.mime_type()) + .unwrap_or_else(|_| mime::APPLICATION_OCTET_STREAM); + let thumbnails = + thumbnailer::create_thumbnails(Cursor::new(bytes), mime_type.clone(), sizes)?; + let mut dtos = Vec::new(); + + for thumbnail in thumbnails { + let mut buf = Vec::new(); + let size = thumbnail.size(); + let size = Dimensions { + height: size.1, + width: size.0, + }; + thumbnail.write_png(&mut buf)?; + + let path = self + .ctx + .thumbnail_storage + .add_thumbnail(file.encoded_cd(), size.clone(), &buf) + .await?; + dtos.push(ThumbnailDto::new( + path, + file.encoded_cd(), + size, + mime_type.to_string(), + )) + } + + Ok(dtos) + } +} diff --git a/mediarepo-daemon/mediarepo-logic/src/dao/job/migrate_content_descriptors.rs b/mediarepo-daemon/mediarepo-logic/src/dao/job/migrate_content_descriptors.rs new file mode 100644 index 0000000..6c395fe --- /dev/null +++ b/mediarepo-daemon/mediarepo-logic/src/dao/job/migrate_content_descriptors.rs @@ -0,0 +1,46 @@ +use crate::dao::job::JobDao; +use mediarepo_core::content_descriptor::{ + convert_v1_descriptor_to_v2, encode_content_descriptor, is_v1_content_descriptor, +}; +use mediarepo_core::error::RepoResult; +use mediarepo_database::entities::content_descriptor; +use sea_orm::prelude::*; +use sea_orm::ActiveValue::Set; +use sea_orm::ConnectionTrait; + +impl JobDao { + #[tracing::instrument(level = "debug", skip(self))] + pub async fn migrate_content_descriptors(&self) -> RepoResult<()> { + let cds: Vec = + content_descriptor::Entity::find().all(&self.ctx.db).await?; + + tracing::info!("Converting content descriptors to v2 format..."); + let mut converted_count = 0; + + for cd in cds { + if is_v1_content_descriptor(&cd.descriptor) { + let trx = self.ctx.db.begin().await?; + let src_cd = cd.descriptor; + let dst_cd = convert_v1_descriptor_to_v2(&src_cd)?; + + let _active_model = content_descriptor::ActiveModel { + id: Set(cd.id), + descriptor: Set(dst_cd.clone()), + }; + self.ctx.main_storage.rename_file(&src_cd, &dst_cd).await?; + self.ctx + .thumbnail_storage + .rename_parent( + encode_content_descriptor(&src_cd), + encode_content_descriptor(&dst_cd), + ) + .await?; + trx.commit().await?; + converted_count += 1; + } + } + tracing::info!("Converted {} descriptors", converted_count); + + Ok(()) + } +} diff --git a/mediarepo-daemon/mediarepo-logic/src/dao/job/mod.rs b/mediarepo-daemon/mediarepo-logic/src/dao/job/mod.rs new file mode 100644 index 0000000..c47b28a --- /dev/null +++ b/mediarepo-daemon/mediarepo-logic/src/dao/job/mod.rs @@ -0,0 +1,20 @@ +pub mod migrate_content_descriptors; +pub mod sqlite_operations; + +use crate::dao::{DaoContext, DaoProvider}; + +pub struct JobDao { + ctx: DaoContext, +} + +impl DaoProvider for JobDao { + fn dao_ctx(&self) -> DaoContext { + self.ctx.clone() + } +} + +impl JobDao { + pub fn new(ctx: DaoContext) -> JobDao { + Self { ctx } + } +} diff --git a/mediarepo-daemon/mediarepo-logic/src/dao/job/sqlite_operations.rs b/mediarepo-daemon/mediarepo-logic/src/dao/job/sqlite_operations.rs new file mode 100644 index 0000000..132bdf9 --- /dev/null +++ b/mediarepo-daemon/mediarepo-logic/src/dao/job/sqlite_operations.rs @@ -0,0 +1,44 @@ +use crate::dao::job::JobDao; +use mediarepo_core::error::RepoError::Corrupted; +use mediarepo_core::error::RepoResult; +use sea_orm::DatabaseBackend::Sqlite; +use sea_orm::{ConnectionTrait, FromQueryResult, Statement}; + +#[derive(Debug, FromQueryResult)] +struct IntegrityCheckResult { + integrity_check: String, +} + +impl JobDao { + #[tracing::instrument(level = "debug", skip(self))] + pub async fn check_integrity(&self) -> RepoResult<()> { + let check_result: Option = IntegrityCheckResult::find_by_statement( + Statement::from_string(Sqlite, String::from("PRAGMA integrity_check;")), + ) + .one(&self.ctx.db) + .await?; + tracing::debug!("check result = {:?}", check_result); + + check_result + .ok_or_else(|| Corrupted(String::from("no check result"))) + .and_then(map_check_result) + } + + #[tracing::instrument(level = "debug", skip(self))] + pub async fn vacuum(&self) -> RepoResult<()> { + self.ctx + .db + .execute(Statement::from_string(Sqlite, String::from("VACUUM;"))) + .await?; + + Ok(()) + } +} + +fn map_check_result(result: IntegrityCheckResult) -> RepoResult<()> { + if result.integrity_check == "ok" { + Ok(()) + } else { + Err(Corrupted(result.integrity_check)) + } +} diff --git a/mediarepo-daemon/mediarepo-logic/src/dao/mod.rs b/mediarepo-daemon/mediarepo-logic/src/dao/mod.rs new file mode 100644 index 0000000..f3cf620 --- /dev/null +++ b/mediarepo-daemon/mediarepo-logic/src/dao/mod.rs @@ -0,0 +1,41 @@ +use sea_orm::{ActiveValue, DatabaseConnection}; + +use mediarepo_core::fs::file_hash_store::FileHashStore; +use mediarepo_core::fs::thumbnail_store::ThumbnailStore; + +use crate::dao::file::FileDao; +use crate::dao::job::JobDao; +use crate::dao::tag::TagDao; + +pub mod file; +pub mod job; +pub mod repo; +pub mod tag; + +#[derive(Clone)] +pub struct DaoContext { + pub db: DatabaseConnection, + pub main_storage: FileHashStore, + pub thumbnail_storage: ThumbnailStore, +} + +pub trait DaoProvider { + fn dao_ctx(&self) -> DaoContext; + + fn file(&self) -> FileDao { + FileDao::new(self.dao_ctx()) + } + + fn tag(&self) -> TagDao { + TagDao::new(self.dao_ctx()) + } + + fn job(&self) -> JobDao { + JobDao::new(self.dao_ctx()) + } +} + +fn opt_to_active_val>(opt: Option) -> ActiveValue { + opt.map(|v| ActiveValue::Set(v)) + .unwrap_or(ActiveValue::NotSet) +} diff --git a/mediarepo-daemon/mediarepo-logic/src/dao/repo/mod.rs b/mediarepo-daemon/mediarepo-logic/src/dao/repo/mod.rs new file mode 100644 index 0000000..9157345 --- /dev/null +++ b/mediarepo-daemon/mediarepo-logic/src/dao/repo/mod.rs @@ -0,0 +1,81 @@ +use std::fmt::Debug; + +use std::path::PathBuf; + +use sea_orm::DatabaseConnection; + +use mediarepo_core::error::RepoResult; +use mediarepo_core::fs::file_hash_store::FileHashStore; +use mediarepo_core::fs::thumbnail_store::ThumbnailStore; + +use crate::dao::{DaoContext, DaoProvider}; +use mediarepo_database::get_database; +use mediarepo_database::queries::analysis::{get_all_counts, Counts}; + +#[derive(Clone)] +pub struct Repo { + db: DatabaseConnection, + main_storage: FileHashStore, + thumbnail_storage: ThumbnailStore, +} + +impl DaoProvider for Repo { + fn dao_ctx(&self) -> DaoContext { + DaoContext { + db: self.db.clone(), + main_storage: self.main_storage.clone(), + thumbnail_storage: self.thumbnail_storage.clone(), + } + } +} + +impl Repo { + pub(crate) fn new( + db: DatabaseConnection, + file_store_path: PathBuf, + thumb_store_path: PathBuf, + ) -> Self { + Self { + db, + main_storage: FileHashStore::new(file_store_path), + thumbnail_storage: ThumbnailStore::new(thumb_store_path), + } + } + + /// Connects to the database with the given uri + #[tracing::instrument(level = "debug")] + pub async fn connect + Debug>( + uri: S, + file_store_path: PathBuf, + thumb_store_path: PathBuf, + ) -> RepoResult { + let db = get_database(uri).await?; + Ok(Self::new(db, file_store_path, thumb_store_path)) + } + + /// Returns the database of the repo for raw sql queries + pub fn db(&self) -> &DatabaseConnection { + &self.db + } + + /// Returns the size of the main storage + #[inline] + #[tracing::instrument(level = "debug", skip(self))] + pub async fn get_main_store_size(&self) -> RepoResult { + self.main_storage.get_size().await + } + + /// Returns the size of the thumbnail storage + #[inline] + #[tracing::instrument(level = "debug", skip(self))] + pub async fn get_thumb_store_size(&self) -> RepoResult { + self.thumbnail_storage.get_size().await + } + + /// Returns all entity counts + #[inline] + #[tracing::instrument(level = "debug", skip(self))] + pub async fn get_counts(&self) -> RepoResult { + get_all_counts(&self.db).await + } +} diff --git a/mediarepo-daemon/mediarepo-logic/src/dao/tag/add.rs b/mediarepo-daemon/mediarepo-logic/src/dao/tag/add.rs new file mode 100644 index 0000000..602a7ea --- /dev/null +++ b/mediarepo-daemon/mediarepo-logic/src/dao/tag/add.rs @@ -0,0 +1,137 @@ +use crate::dao::tag::{map_tag_dto, TagDao}; +use crate::dto::{AddTagDto, NamespaceDto, TagDto}; +use mediarepo_core::error::RepoResult; +use mediarepo_database::entities::{namespace, tag}; +use sea_orm::prelude::*; +use sea_orm::ActiveValue::Set; +use sea_orm::{Condition, ConnectionTrait, DatabaseTransaction}; +use std::collections::HashMap; +use std::iter::FromIterator; + +impl TagDao { + #[tracing::instrument(level = "debug", skip(self))] + pub async fn add_all(&self, mut tags: Vec) -> RepoResult> { + let namespaces = tags.iter().filter_map(|t| t.namespace.clone()).collect(); + let trx = self.ctx.db.begin().await?; + let existing_tags = tags_by_name(&trx, tags.clone()).await?; + + if existing_tags.len() == tags.len() { + return Ok(existing_tags); + } + let existing_tag_map: HashMap = + HashMap::from_iter(existing_tags.into_iter().map(|t| (t.normalized_name(), t))); + + tags.retain(|dto| !existing_tag_map.contains_key(&dto.normalized_name())); + let namespace_map = add_or_get_all_namespaces(&trx, namespaces).await?; + + if tags.is_empty() { + return Ok(existing_tag_map.into_values().collect()); + } + + let tag_models: Vec = tags + .iter() + .map(|t| tag::ActiveModel { + name: Set(t.name.to_owned()), + namespace_id: Set(t + .namespace + .as_ref() + .and_then(|n| namespace_map.get(n)) + .map(|n| n.id())), + ..Default::default() + }) + .collect(); + tag::Entity::insert_many(tag_models).exec(&trx).await?; + let mut tag_dtos = tags_by_name(&trx, tags).await?; + trx.commit().await?; + tag_dtos.append(&mut existing_tag_map.into_values().collect()); + + Ok(tag_dtos) + } +} + +async fn add_or_get_all_namespaces( + trx: &DatabaseTransaction, + mut namespaces: Vec, +) -> RepoResult> { + if namespaces.is_empty() { + return Ok(HashMap::with_capacity(0)); + } + let existing_namespaces = namespaces_by_name(trx, namespaces.clone()).await?; + let mut namespace_map = HashMap::from_iter( + existing_namespaces + .into_iter() + .map(|nsp| (nsp.name().to_owned(), nsp)), + ); + if namespaces.len() == namespace_map.len() { + return Ok(namespace_map); + } + namespaces.retain(|nsp| !namespace_map.contains_key(nsp)); + if namespaces.is_empty() { + return Ok(namespace_map); + } + let namespace_models: Vec = namespaces + .iter() + .map(|nsp| namespace::ActiveModel { + name: Set(nsp.to_owned()), + ..Default::default() + }) + .collect(); + namespace::Entity::insert_many(namespace_models) + .exec(trx) + .await?; + let additional_namespaces = namespaces_by_name(trx, namespaces.clone()).await?; + + for nsp in additional_namespaces { + namespace_map.insert(nsp.name().to_owned(), nsp); + } + + Ok(namespace_map) +} + +async fn namespaces_by_name( + trx: &DatabaseTransaction, + names: Vec, +) -> RepoResult> { + if names.is_empty() { + return Ok(vec![]); + } + let namespaces: Vec = namespace::Entity::find() + .filter(namespace::Column::Name.is_in(names)) + .all(trx) + .await? + .into_iter() + .map(NamespaceDto::new) + .collect(); + + Ok(namespaces) +} + +async fn tags_by_name(trx: &DatabaseTransaction, tags: Vec) -> RepoResult> { + if tags.is_empty() { + return Ok(vec![]); + } + let condition = tags + .into_iter() + .map(build_tag_condition) + .fold(Condition::any(), Condition::add); + let tags = tag::Entity::find() + .find_also_related(namespace::Entity) + .filter(condition) + .all(trx) + .await? + .into_iter() + .map(map_tag_dto) + .collect(); + + Ok(tags) +} + +fn build_tag_condition(tag: AddTagDto) -> Condition { + if let Some(namespace) = tag.namespace { + Condition::all() + .add(tag::Column::Name.eq(tag.name)) + .add(namespace::Column::Name.eq(namespace)) + } else { + Condition::all().add(tag::Column::Name.eq(tag.name)) + } +} diff --git a/mediarepo-daemon/mediarepo-logic/src/dao/tag/by_name.rs b/mediarepo-daemon/mediarepo-logic/src/dao/tag/by_name.rs new file mode 100644 index 0000000..1ae99bc --- /dev/null +++ b/mediarepo-daemon/mediarepo-logic/src/dao/tag/by_name.rs @@ -0,0 +1,64 @@ +use crate::dao::tag::{map_tag_dto, TagDao}; +use crate::dto::TagDto; +use mediarepo_core::error::RepoResult; +use mediarepo_database::entities::{namespace, tag}; +use sea_orm::prelude::*; +use sea_orm::sea_query::Expr; +use sea_orm::{Condition, QuerySelect}; + +#[derive(Clone, Debug)] +pub struct TagByNameQuery { + pub namespace: Option, + pub name: String, +} + +impl TagDao { + /// Filters all tags by names + /// wildcards are supported + #[tracing::instrument(level = "debug", skip(self))] + pub async fn all_by_name(&self, names: Vec) -> RepoResult> { + let mut condition_count = 0; + let condition = names + .into_iter() + .filter_map(name_query_to_condition) + .inspect(|_| condition_count += 1) + .fold(Condition::any(), Condition::add); + if condition_count == 0 { + return Ok(vec![]); + } + + let tags = tag::Entity::find() + .find_also_related(namespace::Entity) + .filter(condition) + .group_by(tag::Column::Id) + .all(&self.ctx.db) + .await? + .into_iter() + .map(map_tag_dto) + .collect(); + + Ok(tags) + } +} + +fn name_query_to_condition(query: TagByNameQuery) -> Option { + let TagByNameQuery { namespace, name } = query; + let mut condition = Condition::all(); + + if !name.ends_with('*') { + condition = condition.add(tag::Column::Name.eq(name)) + } else if name.len() > 1 { + condition = + condition.add(tag::Column::Name.like(&*format!("{}%", name.trim_end_matches("*")))) + } else if namespace.is_none() { + return None; + } + + condition = if let Some(namespace) = namespace { + condition.add(namespace::Column::Name.eq(namespace)) + } else { + condition.add(Expr::tbl(tag::Entity, tag::Column::NamespaceId).is_null()) + }; + + Some(condition) +} diff --git a/mediarepo-daemon/mediarepo-logic/src/dao/tag/mappings.rs b/mediarepo-daemon/mediarepo-logic/src/dao/tag/mappings.rs new file mode 100644 index 0000000..0e2f488 --- /dev/null +++ b/mediarepo-daemon/mediarepo-logic/src/dao/tag/mappings.rs @@ -0,0 +1,66 @@ +use sea_orm::{ConnectionTrait, DatabaseTransaction}; +use sea_orm::ActiveValue::Set; +use sea_orm::prelude::*; + +use mediarepo_core::error::RepoResult; +use mediarepo_database::entities::content_descriptor_tag; + +use crate::dao::tag::TagDao; + +impl TagDao { + #[tracing::instrument(level = "debug", skip(self))] + pub async fn upsert_mappings(&self, cd_ids: Vec, tag_ids: Vec) -> RepoResult<()> { + let trx = self.ctx.db.begin().await?; + + let existing_mappings = get_existing_mappings(&trx, &cd_ids, &tag_ids).await?; + + let active_models: Vec = cd_ids + .into_iter() + .flat_map(|cd_id: i64| { + tag_ids + .iter() + .filter(|tag_id| !existing_mappings.contains(&(cd_id, **tag_id))) + .map(move |tag_id| content_descriptor_tag::ActiveModel { + cd_id: Set(cd_id), + tag_id: Set(*tag_id), + }) + .collect::>() + }) + .collect(); + + content_descriptor_tag::Entity::insert_many(active_models) + .exec(&trx) + .await?; + + trx.commit().await?; + + Ok(()) + } + + #[tracing::instrument(level = "debug", skip(self))] + pub async fn remove_mappings(&self, cd_ids: Vec, tag_ids: Vec) -> RepoResult<()> { + content_descriptor_tag::Entity::delete_many() + .filter(content_descriptor_tag::Column::CdId.is_in(cd_ids)) + .filter(content_descriptor_tag::Column::TagId.is_in(tag_ids)) + .exec(&self.ctx.db) + .await?; + + Ok(()) + } +} + +async fn get_existing_mappings( + trx: &DatabaseTransaction, + cd_ids: &Vec, + tag_ids: &Vec, +) -> RepoResult> { + let existing_mappings: Vec<(i64, i64)> = content_descriptor_tag::Entity::find() + .filter(content_descriptor_tag::Column::CdId.is_in(cd_ids.clone())) + .filter(content_descriptor_tag::Column::TagId.is_in(tag_ids.clone())) + .all(trx) + .await? + .into_iter() + .map(|model: content_descriptor_tag::Model| (model.tag_id, model.cd_id)) + .collect(); + Ok(existing_mappings) +} diff --git a/mediarepo-daemon/mediarepo-logic/src/dao/tag/mod.rs b/mediarepo-daemon/mediarepo-logic/src/dao/tag/mod.rs new file mode 100644 index 0000000..0f44309 --- /dev/null +++ b/mediarepo-daemon/mediarepo-logic/src/dao/tag/mod.rs @@ -0,0 +1,174 @@ +use sea_orm::prelude::*; +use sea_orm::JoinType; +use sea_orm::QuerySelect; +use std::collections::HashMap; +use std::iter::FromIterator; + +use mediarepo_core::error::RepoResult; +use mediarepo_core::itertools::Itertools; + +use mediarepo_core::utils::parse_namespace_and_tag; +use mediarepo_database::entities::{content_descriptor, content_descriptor_tag, namespace, tag}; + +use crate::dao::tag::by_name::TagByNameQuery; +use crate::dao::{DaoContext, DaoProvider}; +use crate::dto::{NamespaceDto, TagDto}; + +pub mod add; +pub mod by_name; +pub mod mappings; + +pub struct TagDao { + ctx: DaoContext, +} + +impl DaoProvider for TagDao { + fn dao_ctx(&self) -> DaoContext { + self.ctx.clone() + } +} + +impl TagDao { + pub fn new(ctx: DaoContext) -> Self { + Self { ctx } + } + + #[tracing::instrument(level = "debug", skip(self))] + pub async fn all(&self) -> RepoResult> { + let tags = tag::Entity::find() + .find_also_related(namespace::Entity) + .all(&self.ctx.db) + .await? + .into_iter() + .map(map_tag_dto) + .collect(); + + Ok(tags) + } + + #[tracing::instrument(level = "debug", skip(self))] + pub async fn all_namespaces(&self) -> RepoResult> { + let namespaces = namespace::Entity::find() + .all(&self.ctx.db) + .await? + .into_iter() + .map(NamespaceDto::new) + .collect(); + + Ok(namespaces) + } + + #[tracing::instrument(level = "debug", skip(self, cds))] + pub async fn all_for_cds(&self, cds: Vec>) -> RepoResult> { + let tags = tag::Entity::find() + .find_also_related(namespace::Entity) + .join( + JoinType::LeftJoin, + content_descriptor_tag::Relation::Tag.def().rev(), + ) + .join( + JoinType::InnerJoin, + content_descriptor_tag::Relation::ContentDescriptorId.def(), + ) + .filter(content_descriptor::Column::Descriptor.is_in(cds)) + .group_by(tag::Column::Id) + .all(&self.ctx.db) + .await? + .into_iter() + .map(map_tag_dto) + .collect(); + + Ok(tags) + } + + #[tracing::instrument(level = "debug", skip(self, cds))] + pub async fn all_for_cds_map( + &self, + cds: Vec>, + ) -> RepoResult, Vec>> { + let tag_cd_entries: Vec<( + content_descriptor_tag::Model, + Option, + )> = content_descriptor_tag::Entity::find() + .find_also_related(content_descriptor::Entity) + .filter(content_descriptor::Column::Descriptor.is_in(cds)) + .all(&self.ctx.db) + .await?; + + let tag_ids: Vec = tag_cd_entries + .iter() + .map(|(t, _)| t.tag_id) + .unique() + .collect(); + + let tags: Vec = tag::Entity::find() + .find_also_related(namespace::Entity) + .filter(tag::Column::Id.is_in(tag_ids)) + .all(&self.ctx.db) + .await? + .into_iter() + .map(map_tag_dto) + .collect(); + + let tag_id_map = tags + .into_iter() + .map(|t| (t.id(), t)) + .collect::>(); + let cd_tag_map = tag_cd_entries + .into_iter() + .filter_map(|(t, cd)| Some((cd?, tag_id_map.get(&t.tag_id)?.clone()))) + .sorted_by_key(|(cd, _)| cd.id) + .group_by(|(cd, _)| cd.descriptor.to_owned()) + .into_iter() + .map(|(key, group)| (key, group.map(|(_, t)| t).collect::>())) + .collect(); + + Ok(cd_tag_map) + } + + #[tracing::instrument(level = "debug", skip(self))] + pub async fn tags_for_cd(&self, cd_id: i64) -> RepoResult> { + let tags = tag::Entity::find() + .find_also_related(namespace::Entity) + .join( + JoinType::LeftJoin, + content_descriptor_tag::Relation::Tag.def().rev(), + ) + .join( + JoinType::InnerJoin, + content_descriptor_tag::Relation::ContentDescriptorId.def(), + ) + .filter(content_descriptor::Column::Id.eq(cd_id)) + .all(&self.ctx.db) + .await? + .into_iter() + .map(map_tag_dto) + .collect(); + + Ok(tags) + } + + /// Returns a map mapping tag names to ids + #[tracing::instrument(level = "debug", skip(self))] + pub async fn normalized_tags_to_ids( + &self, + names: Vec, + ) -> RepoResult> { + let queries = names + .into_iter() + .map(parse_namespace_and_tag) + .map(|(namespace, name)| TagByNameQuery { namespace, name }) + .collect(); + let tags = self.all_by_name(queries).await?; + let tag_map = HashMap::from_iter( + tags.into_iter() + .map(|tag| (tag.normalized_name(), tag.id())), + ); + + Ok(tag_map) + } +} + +fn map_tag_dto(result: (tag::Model, Option)) -> TagDto { + TagDto::new(result.0, result.1) +} diff --git a/mediarepo-daemon/mediarepo-logic/src/dto/file.rs b/mediarepo-daemon/mediarepo-logic/src/dto/file.rs new file mode 100644 index 0000000..ced7cca --- /dev/null +++ b/mediarepo-daemon/mediarepo-logic/src/dto/file.rs @@ -0,0 +1,112 @@ +use chrono::NaiveDateTime; + +use mediarepo_core::content_descriptor::encode_content_descriptor; +use mediarepo_core::mediarepo_api::types::files::FileStatus as ApiFileStatus; +use mediarepo_database::entities::content_descriptor; +use mediarepo_database::entities::file; +use mediarepo_database::entities::file_metadata; + +use crate::dto::FileMetadataDto; + +#[derive(Clone, Debug)] +pub struct FileDto { + model: file::Model, + content_descriptor: content_descriptor::Model, + metadata: Option, +} + +impl FileDto { + pub(crate) fn new( + model: file::Model, + content_descriptor: content_descriptor::Model, + metadata: Option, + ) -> Self { + Self { + model, + content_descriptor, + metadata: metadata.map(FileMetadataDto::new), + } + } + + pub fn id(&self) -> i64 { + self.model.id + } + + pub fn cd_id(&self) -> i64 { + self.model.cd_id + } + + pub fn cd(&self) -> &[u8] { + &self.content_descriptor.descriptor + } + + pub fn encoded_cd(&self) -> String { + encode_content_descriptor(&self.content_descriptor.descriptor) + } + + pub fn status(&self) -> FileStatus { + match self.model.status { + 10 => FileStatus::Imported, + 20 => FileStatus::Archived, + 30 => FileStatus::Deleted, + _ => FileStatus::Imported, + } + } + + pub fn mime_type(&self) -> &String { + &self.model.mime_type + } + + pub fn metadata(&self) -> Option<&FileMetadataDto> { + self.metadata.as_ref() + } + + pub fn into_metadata(self) -> Option { + self.metadata + } +} + +#[derive(Clone, Debug)] +pub struct AddFileDto { + pub content: Vec, + pub mime_type: String, + pub creation_time: NaiveDateTime, + pub change_time: NaiveDateTime, + pub name: Option, +} + +#[derive(Clone, Debug)] +pub struct UpdateFileDto { + pub id: i64, + pub cd_id: Option, + pub mime_type: Option, + pub status: Option, +} + +impl Default for UpdateFileDto { + fn default() -> Self { + Self { + id: 0, + cd_id: None, + mime_type: None, + status: None, + } + } +} + +#[derive(Copy, Clone, Debug)] +pub enum FileStatus { + Imported = 10, + Archived = 20, + Deleted = 30, +} + +impl From for FileStatus { + fn from(s: ApiFileStatus) -> Self { + match s { + ApiFileStatus::Imported => Self::Imported, + ApiFileStatus::Archived => Self::Archived, + ApiFileStatus::Deleted => Self::Deleted, + } + } +} diff --git a/mediarepo-daemon/mediarepo-logic/src/dto/file_metadata.rs b/mediarepo-daemon/mediarepo-logic/src/dto/file_metadata.rs new file mode 100644 index 0000000..1bfb161 --- /dev/null +++ b/mediarepo-daemon/mediarepo-logic/src/dto/file_metadata.rs @@ -0,0 +1,51 @@ +use chrono::NaiveDateTime; + +use mediarepo_database::entities::file_metadata; + +#[derive(Clone, Debug)] +pub struct FileMetadataDto { + model: file_metadata::Model, +} + +impl FileMetadataDto { + pub(crate) fn new(model: file_metadata::Model) -> Self { + Self { model } + } + + pub fn file_id(&self) -> i64 { + self.model.file_id + } + + pub fn name(&self) -> Option<&String> { + self.model.name.as_ref() + } + + pub fn comment(&self) -> Option<&String> { + self.model.comment.as_ref() + } + + pub fn size(&self) -> i64 { + self.model.size + } + + pub fn import_time(&self) -> NaiveDateTime { + self.model.import_time + } + + pub fn creation_time(&self) -> NaiveDateTime { + self.model.creation_time + } + + pub fn change_time(&self) -> NaiveDateTime { + self.model.change_time + } +} + +#[derive(Clone, Debug, Default)] +pub struct UpdateFileMetadataDto { + pub file_id: i64, + pub name: Option>, + pub comment: Option>, + pub size: Option, + pub change_time: Option, +} diff --git a/mediarepo-daemon/mediarepo-logic/src/dto/mod.rs b/mediarepo-daemon/mediarepo-logic/src/dto/mod.rs new file mode 100644 index 0000000..0d7a962 --- /dev/null +++ b/mediarepo-daemon/mediarepo-logic/src/dto/mod.rs @@ -0,0 +1,12 @@ +pub use file::*; +pub use file_metadata::*; +pub use namespace::*; +pub use tag::*; +pub use thumbnail::*; + +mod file; +mod file_metadata; +mod tag; +mod namespace; +mod thumbnail; + diff --git a/mediarepo-daemon/mediarepo-logic/src/dto/namespace.rs b/mediarepo-daemon/mediarepo-logic/src/dto/namespace.rs new file mode 100644 index 0000000..2729ecc --- /dev/null +++ b/mediarepo-daemon/mediarepo-logic/src/dto/namespace.rs @@ -0,0 +1,20 @@ +use mediarepo_database::entities::namespace; + +#[derive(Clone, Debug)] +pub struct NamespaceDto { + model: namespace::Model, +} + +impl NamespaceDto { + pub(crate) fn new(model: namespace::Model) -> Self { + Self {model} + } + + pub fn id(&self) -> i64 { + self.model.id + } + + pub fn name(&self) -> &String { + &self.model.name + } +} \ No newline at end of file diff --git a/mediarepo-daemon/mediarepo-logic/src/dto/tag.rs b/mediarepo-daemon/mediarepo-logic/src/dto/tag.rs new file mode 100644 index 0000000..d45dcd4 --- /dev/null +++ b/mediarepo-daemon/mediarepo-logic/src/dto/tag.rs @@ -0,0 +1,62 @@ +pub use mediarepo_database::entities::namespace; +pub use mediarepo_database::entities::tag; + +use crate::dto::NamespaceDto; + +#[derive(Clone, Debug)] +pub struct TagDto { + model: tag::Model, + namespace: Option, +} + +impl TagDto { + pub(crate) fn new(model: tag::Model, namespace_model: Option) -> Self { + Self { + model, + namespace: namespace_model.map(NamespaceDto::new), + } + } + + pub fn id(&self) -> i64 { + self.model.id + } + + pub fn name(&self) -> &String { + &self.model.name + } + + pub fn namespace(&self) -> Option<&NamespaceDto> { + self.namespace.as_ref() + } + + /// Returns the normalized name of the tag (namespace:tag) + pub fn normalized_name(&self) -> String { + if let Some(namespace) = &self.namespace { + format!("{}:{}", namespace.name(), self.name()) + } else { + self.name().to_owned() + } + } +} + +#[derive(Clone, Debug)] +pub struct AddTagDto { + pub namespace: Option, + pub name: String, +} + +impl AddTagDto { + pub fn from_tuple(tuple: (Option, String)) -> Self { + let (namespace, name) = tuple; + Self { namespace, name } + } + + /// Returns the normalized name of the tag (namespace:tag) + pub fn normalized_name(&self) -> String { + if let Some(namespace) = &self.namespace { + format!("{}:{}", namespace, &self.name) + } else { + self.name.to_owned() + } + } +} diff --git a/mediarepo-daemon/mediarepo-logic/src/dto/thumbnail.rs b/mediarepo-daemon/mediarepo-logic/src/dto/thumbnail.rs new file mode 100644 index 0000000..328302b --- /dev/null +++ b/mediarepo-daemon/mediarepo-logic/src/dto/thumbnail.rs @@ -0,0 +1,53 @@ +use std::path::PathBuf; + +use tokio::fs; +use tokio::fs::{File, OpenOptions}; +use tokio::io::BufReader; + +use mediarepo_core::error::RepoResult; +use mediarepo_core::fs::thumbnail_store::Dimensions; + +#[derive(Clone, Debug)] +pub struct ThumbnailDto { + path: PathBuf, + parent_cd: String, + size: Dimensions, + mime_type: String, +} + +impl ThumbnailDto { + pub fn new(path: PathBuf, parent_cd: String, size: Dimensions, mime_type: String) -> Self { + Self { + path, + parent_cd, + size, + mime_type, + } + } + + pub fn parent_cd(&self) -> &String { + &self.parent_cd + } + + pub fn size(&self) -> &Dimensions { + &self.size + } + + pub fn mime_type(&self) -> &String { + &self.mime_type + } + + #[tracing::instrument(level = "debug")] + pub async fn get_reader(&self) -> RepoResult> { + let file = OpenOptions::new().read(true).open(&self.path).await?; + Ok(BufReader::new(file)) + } + + /// Deletes the thumbnail + #[tracing::instrument(level = "debug")] + pub async fn delete(self) -> RepoResult<()> { + fs::remove_file(&self.path).await?; + + Ok(()) + } +} diff --git a/mediarepo-daemon/mediarepo-logic/src/lib.rs b/mediarepo-daemon/mediarepo-logic/src/lib.rs new file mode 100644 index 0000000..054053a --- /dev/null +++ b/mediarepo-daemon/mediarepo-logic/src/lib.rs @@ -0,0 +1,3 @@ +pub mod dao; +pub mod dto; +pub mod type_keys; diff --git a/mediarepo-daemon/mediarepo-model/src/type_keys.rs b/mediarepo-daemon/mediarepo-logic/src/type_keys.rs similarity index 81% rename from mediarepo-daemon/mediarepo-model/src/type_keys.rs rename to mediarepo-daemon/mediarepo-logic/src/type_keys.rs index a604c7f..bd12d0c 100644 --- a/mediarepo-daemon/mediarepo-model/src/type_keys.rs +++ b/mediarepo-daemon/mediarepo-logic/src/type_keys.rs @@ -1,7 +1,9 @@ -use crate::repo::Repo; use std::sync::Arc; + use typemap_rev::TypeMapKey; +use crate::dao::repo::Repo; + pub struct RepoKey; impl TypeMapKey for RepoKey { diff --git a/mediarepo-daemon/mediarepo-model/src/content_descriptor.rs b/mediarepo-daemon/mediarepo-model/src/content_descriptor.rs deleted file mode 100644 index 2762278..0000000 --- a/mediarepo-daemon/mediarepo-model/src/content_descriptor.rs +++ /dev/null @@ -1,101 +0,0 @@ -use crate::file::File; -use mediarepo_core::content_descriptor::convert_v1_descriptor_to_v2; -use mediarepo_core::error::RepoResult; -use mediarepo_database::entities::content_descriptor; -use mediarepo_database::entities::file; -use sea_orm::prelude::*; -use sea_orm::{DatabaseConnection, Set}; -use std::fmt::Debug; - -pub struct ContentDescriptor { - db: DatabaseConnection, - model: content_descriptor::Model, -} - -impl ContentDescriptor { - #[tracing::instrument(level = "trace")] - pub(crate) fn new(db: DatabaseConnection, model: content_descriptor::Model) -> Self { - Self { db, model } - } - - pub async fn all(db: DatabaseConnection) -> RepoResult> { - let descriptors = content_descriptor::Entity::find() - .all(&db) - .await? - .into_iter() - .map(|model| Self::new(db.clone(), model)) - .collect(); - - Ok(descriptors) - } - - /// Searches for the hash by id - #[tracing::instrument(level = "debug", skip(db))] - pub async fn by_id(db: DatabaseConnection, id: i64) -> RepoResult> { - let hash = content_descriptor::Entity::find_by_id(id) - .one(&db) - .await? - .map(|model| Self::new(db, model)); - - Ok(hash) - } - - /// Returns the hash by value - #[tracing::instrument(level = "debug", skip(db))] - pub async fn by_value + Debug>( - db: DatabaseConnection, - descriptor: D, - ) -> RepoResult> { - let cid = content_descriptor::Entity::find() - .filter(content_descriptor::Column::Descriptor.eq(descriptor.as_ref())) - .one(&db) - .await? - .map(|model| Self::new(db, model)); - - Ok(cid) - } - - /// Adds a new hash to the database - #[tracing::instrument(level = "debug", skip(db))] - pub async fn add(db: DatabaseConnection, descriptor: Vec) -> RepoResult { - let active_model = content_descriptor::ActiveModel { - descriptor: Set(descriptor), - ..Default::default() - }; - let model = active_model.insert(&db).await?; - - Ok(Self::new(db, model)) - } - - pub fn id(&self) -> i64 { - self.model.id - } - - pub fn descriptor(&self) -> &[u8] { - &self.model.descriptor[..] - } - - /// Returns the file associated with the hash - #[tracing::instrument(level = "debug", skip(self))] - pub async fn file(&self) -> RepoResult> { - let file = self - .model - .find_related(file::Entity) - .one(&self.db) - .await? - .map(|file_model| File::new(self.db.clone(), file_model, self.model.clone())); - - Ok(file) - } - - pub async fn convert_v1_to_v2(&mut self) -> RepoResult<()> { - let descriptor = convert_v1_descriptor_to_v2(&self.model.descriptor)?; - let active_model = content_descriptor::ActiveModel { - id: Set(self.id()), - descriptor: Set(descriptor), - }; - self.model = active_model.update(&self.db).await?; - - Ok(()) - } -} diff --git a/mediarepo-daemon/mediarepo-model/src/file/mod.rs b/mediarepo-daemon/mediarepo-model/src/file/mod.rs deleted file mode 100644 index dc7dfa8..0000000 --- a/mediarepo-daemon/mediarepo-model/src/file/mod.rs +++ /dev/null @@ -1,334 +0,0 @@ -pub mod filter; - -use std::fmt::Debug; -use std::io::Cursor; -use std::str::FromStr; - -use mediarepo_core::content_descriptor::encode_content_descriptor; -use sea_orm::prelude::*; -use sea_orm::{ConnectionTrait, DatabaseConnection, Set}; -use sea_orm::{JoinType, QuerySelect}; -use tokio::io::{AsyncReadExt, BufReader}; - -use crate::file::filter::FilterProperty; -use crate::file_metadata::FileMetadata; -use mediarepo_core::error::{RepoError, RepoResult}; -use mediarepo_core::fs::file_hash_store::FileHashStore; -use mediarepo_core::mediarepo_api::types::files::FileStatus as ApiFileStatus; -use mediarepo_core::thumbnailer::{self, Thumbnail as ThumbnailerThumb, ThumbnailSize}; -use mediarepo_database::entities::content_descriptor; -use mediarepo_database::entities::content_descriptor_tag; -use mediarepo_database::entities::file; -use mediarepo_database::entities::file_metadata; -use mediarepo_database::entities::namespace; -use mediarepo_database::entities::tag; - -use crate::tag::Tag; - -pub enum FileStatus { - Imported = 10, - Archived = 20, - Deleted = 30, -} - -impl From for FileStatus { - fn from(s: ApiFileStatus) -> Self { - match s { - ApiFileStatus::Imported => Self::Imported, - ApiFileStatus::Archived => Self::Archived, - ApiFileStatus::Deleted => Self::Deleted, - } - } -} - -#[derive(Clone)] -pub struct File { - db: DatabaseConnection, - model: file::Model, - content_descriptor: content_descriptor::Model, -} - -impl File { - #[tracing::instrument(level = "trace")] - pub(crate) fn new( - db: DatabaseConnection, - model: file::Model, - hash: content_descriptor::Model, - ) -> Self { - Self { - db, - model, - content_descriptor: hash, - } - } - - /// Returns a list of all known stored files - #[tracing::instrument(level = "debug", skip(db))] - pub async fn all(db: DatabaseConnection) -> RepoResult> { - let files: Vec<(file::Model, Option)> = file::Entity::find() - .find_also_related(content_descriptor::Entity) - .all(&db) - .await?; - let files = files - .into_iter() - .filter_map(|(f, h)| { - let h = h?; - Some(Self::new(db.clone(), f, h)) - }) - .collect(); - - Ok(files) - } - - /// Fetches the file by id - #[tracing::instrument(level = "debug", skip(db))] - pub async fn by_id(db: DatabaseConnection, id: i64) -> RepoResult> { - if let Some((model, Some(hash))) = file::Entity::find_by_id(id) - .find_also_related(content_descriptor::Entity) - .one(&db) - .await? - { - let file = File::new(db, model, hash); - Ok(Some(file)) - } else { - Ok(None) - } - } - - /// Finds the file by hash - #[tracing::instrument(level = "debug", skip(db))] - pub async fn by_cd(db: DatabaseConnection, cd: &[u8]) -> RepoResult> { - if let Some((hash, Some(model))) = content_descriptor::Entity::find() - .filter(content_descriptor::Column::Descriptor.eq(cd)) - .find_also_related(file::Entity) - .one(&db) - .await? - { - let file = File::new(db, model, hash); - Ok(Some(file)) - } else { - Ok(None) - } - } - - /// Finds the file by tags - #[tracing::instrument(level = "debug", skip(db))] - pub(crate) async fn find_by_filters( - db: DatabaseConnection, - filters: Vec>, - ) -> RepoResult> { - let main_condition = filter::build_find_filter_conditions(filters); - - let results: Vec<(content_descriptor::Model, Option)> = - content_descriptor::Entity::find() - .find_also_related(file::Entity) - .filter(main_condition) - .group_by(file::Column::Id) - .all(&db) - .await?; - let files: Vec = results - .into_iter() - .filter_map(|(hash, tag)| Some(Self::new(db.clone(), tag?, hash))) - .collect(); - - Ok(files) - } - - /// Adds a file with its hash to the database - #[tracing::instrument(level = "debug", skip(db))] - pub(crate) async fn add( - db: DatabaseConnection, - cd_id: i64, - mime_type: String, - ) -> RepoResult { - let file = file::ActiveModel { - cd_id: Set(cd_id), - mime_type: Set(mime_type), - ..Default::default() - }; - let file: file::ActiveModel = file.insert(&db).await?.into(); - let file = Self::by_id(db, file.id.unwrap()) - .await? - .expect("Inserted file does not exist"); - - Ok(file) - } - - /// Returns the unique identifier of the file - pub fn id(&self) -> i64 { - self.model.id - } - - /// Returns the hash of the file (content identifier) - pub fn cd(&self) -> &[u8] { - &self.content_descriptor.descriptor - } - - /// Returns the encoded content descriptor - pub fn encoded_cd(&self) -> String { - encode_content_descriptor(self.cd()) - } - - /// Returns the id of the civ (content identifier value) of the file - pub fn cd_id(&self) -> i64 { - self.content_descriptor.id - } - - /// Returns the mime type of the file - pub fn mime_type(&self) -> &String { - &self.model.mime_type - } - - /// Returns the status of the file - pub fn status(&self) -> FileStatus { - match self.model.status { - 10 => FileStatus::Imported, - 20 => FileStatus::Archived, - 30 => FileStatus::Deleted, - _ => FileStatus::Imported, - } - } - - pub async fn set_status(&mut self, status: FileStatus) -> RepoResult<()> { - let active_model = file::ActiveModel { - id: Set(self.model.id), - status: Set(status as i32), - ..Default::default() - }; - self.model = active_model.update(&self.db).await?; - - Ok(()) - } - - /// Returns the metadata associated with this file - /// A file MUST always have metadata associated - pub async fn metadata(&self) -> RepoResult { - FileMetadata::by_id(self.db.clone(), self.model.id) - .await - .and_then(|f| f.ok_or_else(|| RepoError::from("missing file metadata"))) - } - - /// Returns the list of tags of the file - #[tracing::instrument(level = "debug", skip(self))] - pub async fn tags(&self) -> RepoResult> { - let tags: Vec<(tag::Model, Option)> = tag::Entity::find() - .find_also_related(namespace::Entity) - .join( - JoinType::LeftJoin, - content_descriptor_tag::Relation::Tag.def().rev(), - ) - .join( - JoinType::InnerJoin, - content_descriptor_tag::Relation::ContentDescriptorId.def(), - ) - .filter(content_descriptor::Column::Id.eq(self.content_descriptor.id)) - .all(&self.db) - .await?; - let tags = tags - .into_iter() - .map(|(tag, namespace)| Tag::new(self.db.clone(), tag, namespace)) - .collect(); - - Ok(tags) - } - - /// Adds a single tag to the file - #[tracing::instrument(level = "debug", skip(self))] - pub async fn add_tag(&mut self, tag_id: i64) -> RepoResult<()> { - let cd_id = self.content_descriptor.id; - let active_model = content_descriptor_tag::ActiveModel { - cd_id: Set(cd_id), - tag_id: Set(tag_id), - }; - active_model.insert(&self.db).await?; - Ok(()) - } - - /// Adds multiple tags to the file at once - #[tracing::instrument(level = "debug", skip(self))] - pub async fn add_tags(&self, tag_ids: Vec) -> RepoResult<()> { - if tag_ids.is_empty() { - return Ok(()); - } - let cd_id = self.content_descriptor.id; - let models: Vec = tag_ids - .into_iter() - .map(|tag_id| content_descriptor_tag::ActiveModel { - cd_id: Set(cd_id), - tag_id: Set(tag_id), - }) - .collect(); - content_descriptor_tag::Entity::insert_many(models) - .exec(&self.db) - .await?; - - Ok(()) - } - - /// Removes multiple tags from the file - #[tracing::instrument(level = "debug", skip(self))] - pub async fn remove_tags(&self, tag_ids: Vec) -> RepoResult<()> { - let hash_id = self.content_descriptor.id; - content_descriptor_tag::Entity::delete_many() - .filter(content_descriptor_tag::Column::CdId.eq(hash_id)) - .filter(content_descriptor_tag::Column::TagId.is_in(tag_ids)) - .exec(&self.db) - .await?; - - Ok(()) - } - - /// Returns the reader for the file - #[tracing::instrument(level = "debug", skip(self))] - pub async fn get_reader( - &self, - storage: &FileHashStore, - ) -> RepoResult> { - storage - .get_file(&self.content_descriptor.descriptor) - .await - .map(|(_, f)| f) - } - - /// Creates a thumbnail for the file - #[tracing::instrument(level = "debug", skip(self))] - pub async fn create_thumbnail + Debug>( - &self, - storage: &FileHashStore, - sizes: I, - ) -> RepoResult> { - let mut buf = Vec::new(); - self.get_reader(storage) - .await? - .read_to_end(&mut buf) - .await?; - let mime_type = self.model.mime_type.clone(); - let mime_type = - mime::Mime::from_str(&mime_type).unwrap_or_else(|_| mime::APPLICATION_OCTET_STREAM); - let thumbs = thumbnailer::create_thumbnails(Cursor::new(buf), mime_type, sizes)?; - - Ok(thumbs) - } - - /// Deletes the file as well as the content descriptor, tag mappings and metadata about the file - #[tracing::instrument(level = "debug", skip(self))] - pub async fn delete(self) -> RepoResult<()> { - let trx = self.db.begin().await?; - file_metadata::Entity::delete_many() - .filter(file_metadata::Column::FileId.eq(self.model.id)) - .exec(&trx) - .await?; - self.model.delete(&trx).await?; - content_descriptor_tag::Entity::delete_many() - .filter(content_descriptor_tag::Column::CdId.eq(self.content_descriptor.id)) - .exec(&trx) - .await?; - content_descriptor::Entity::delete_many() - .filter(content_descriptor::Column::Id.eq(self.content_descriptor.id)) - .exec(&trx) - .await?; - trx.commit().await?; - - Ok(()) - } -} diff --git a/mediarepo-daemon/mediarepo-model/src/file_metadata.rs b/mediarepo-daemon/mediarepo-model/src/file_metadata.rs deleted file mode 100644 index 0af2fbb..0000000 --- a/mediarepo-daemon/mediarepo-model/src/file_metadata.rs +++ /dev/null @@ -1,124 +0,0 @@ -use std::fmt::Debug; - -use chrono::{Local, NaiveDateTime}; -use sea_orm::prelude::*; -use sea_orm::{DatabaseConnection, Set}; - -use mediarepo_core::error::RepoResult; -use mediarepo_database::entities::file_metadata; - -#[derive(Clone)] -pub struct FileMetadata { - db: DatabaseConnection, - model: file_metadata::Model, -} - -impl FileMetadata { - #[tracing::instrument(level = "trace")] - pub(crate) fn new(db: DatabaseConnection, model: file_metadata::Model) -> Self { - Self { db, model } - } - - /// Fetches the file by id - #[tracing::instrument(level = "debug", skip(db))] - pub async fn by_id(db: DatabaseConnection, id: i64) -> RepoResult> { - let file_metadata = file_metadata::Entity::find_by_id(id) - .one(&db) - .await? - .map(|m| FileMetadata::new(db, m)); - - Ok(file_metadata) - } - - /// Fetches metadata for all given file ids - #[tracing::instrument(level = "debug", skip(db))] - pub async fn all_by_ids(db: DatabaseConnection, ids: Vec) -> RepoResult> { - let file_metadata = file_metadata::Entity::find() - .filter(file_metadata::Column::FileId.is_in(ids)) - .all(&db) - .await? - .into_iter() - .map(|m| FileMetadata::new(db.clone(), m)) - .collect(); - - Ok(file_metadata) - } - - /// Adds a file with its hash to the database - #[tracing::instrument(level = "debug", skip(db))] - pub(crate) async fn add( - db: DatabaseConnection, - file_id: i64, - size: i64, - creation_time: NaiveDateTime, - change_time: NaiveDateTime, - ) -> RepoResult { - let file = file_metadata::ActiveModel { - file_id: Set(file_id), - size: Set(size), - import_time: Set(Local::now().naive_local()), - creation_time: Set(creation_time), - change_time: Set(change_time), - ..Default::default() - }; - let model = file.insert(&db).await?; - - Ok(Self::new(db, model)) - } - - pub fn file_id(&self) -> i64 { - self.model.file_id - } - - pub fn size(&self) -> i64 { - self.model.size - } - - pub fn name(&self) -> &Option { - &self.model.name - } - - pub fn comment(&self) -> &Option { - &self.model.comment - } - - pub fn import_time(&self) -> &NaiveDateTime { - &self.model.import_time - } - - pub fn creation_time(&self) -> &NaiveDateTime { - &self.model.creation_time - } - - pub fn change_time(&self) -> &NaiveDateTime { - &self.model.change_time - } - - /// Changes the name of the file - #[tracing::instrument(level = "debug", skip(self))] - pub async fn set_name(&mut self, name: S) -> RepoResult<()> { - let mut active_model = self.get_active_model(); - active_model.name = Set(Some(name.to_string())); - self.model = active_model.update(&self.db).await?; - - Ok(()) - } - - /// Changes the comment of the file - #[tracing::instrument(level = "debug", skip(self))] - pub async fn set_comment(&mut self, comment: S) -> RepoResult<()> { - let mut active_file = self.get_active_model(); - active_file.comment = Set(Some(comment.to_string())); - self.model = active_file.update(&self.db).await?; - - Ok(()) - } - - /// Returns the active model of the file with only the id set - fn get_active_model(&self) -> file_metadata::ActiveModel { - file_metadata::ActiveModel { - file_id: Set(self.file_id()), - ..Default::default() - } - } -} diff --git a/mediarepo-daemon/mediarepo-model/src/lib.rs b/mediarepo-daemon/mediarepo-model/src/lib.rs deleted file mode 100644 index 7706ef2..0000000 --- a/mediarepo-daemon/mediarepo-model/src/lib.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub mod content_descriptor; -pub mod file; -pub mod file_metadata; -pub mod namespace; -pub mod repo; -pub mod tag; -pub mod thumbnail; -pub mod type_keys; diff --git a/mediarepo-daemon/mediarepo-model/src/namespace.rs b/mediarepo-daemon/mediarepo-model/src/namespace.rs deleted file mode 100644 index 1b67661..0000000 --- a/mediarepo-daemon/mediarepo-model/src/namespace.rs +++ /dev/null @@ -1,141 +0,0 @@ -use mediarepo_core::error::RepoResult; -use mediarepo_database::entities::namespace; -use sea_orm::prelude::*; -use sea_orm::{ - Condition, ConnectionTrait, DatabaseBackend, DatabaseConnection, InsertResult, Set, Statement, -}; -use std::fmt::Debug; - -#[derive(Clone)] -pub struct Namespace { - #[allow(dead_code)] - db: DatabaseConnection, - model: namespace::Model, -} - -impl Namespace { - #[tracing::instrument(level = "trace")] - pub(crate) fn new(db: DatabaseConnection, model: namespace::Model) -> Self { - Self { db, model } - } - - /// Retrieves a list of all namespaces - #[tracing::instrument(level = "debug", skip(db))] - pub async fn all(db: DatabaseConnection) -> RepoResult> { - let namespaces = namespace::Entity::find() - .all(&db) - .await? - .into_iter() - .map(|model| Self::new(db.clone(), model)) - .collect(); - - Ok(namespaces) - } - - /// Retrieves the namespace by id - #[tracing::instrument(level = "debug", skip(db))] - pub async fn by_id(db: DatabaseConnection, id: i64) -> RepoResult> { - let namespace = namespace::Entity::find_by_id(id) - .one(&db) - .await? - .map(|model| Self::new(db, model)); - - Ok(namespace) - } - - /// Retrieves a namespace by its name - #[tracing::instrument(level = "debug", skip(db))] - pub async fn by_name + Debug>( - db: DatabaseConnection, - name: S, - ) -> RepoResult> { - let namespace = namespace::Entity::find() - .filter(namespace::Column::Name.eq(name.as_ref())) - .one(&db) - .await? - .map(|model| Self::new(db, model)); - - Ok(namespace) - } - - /// Returns all namespaces by name - #[tracing::instrument(level = "debug", skip(db))] - pub async fn all_by_name(db: DatabaseConnection, names: Vec) -> RepoResult> { - if names.is_empty() { - return Ok(Vec::with_capacity(0)); - } - let mut condition = Condition::any(); - for name in names { - condition = condition.add(namespace::Column::Name.eq(name)); - } - - let namespaces = namespace::Entity::find() - .filter(condition) - .all(&db) - .await? - .into_iter() - .map(|model| Self::new(db.clone(), model)) - .collect(); - - Ok(namespaces) - } - - /// Adds all namespaces to the database - #[tracing::instrument(level = "debug", skip(db))] - pub async fn add_all(db: DatabaseConnection, names: Vec) -> RepoResult> { - if names.is_empty() { - return Ok(vec![]); - } - let models: Vec = names - .into_iter() - .map(|name| namespace::ActiveModel { - name: Set(name), - ..Default::default() - }) - .collect(); - let txn = db.begin().await?; - let last_id = txn - .query_one(Statement::from_string( - DatabaseBackend::Sqlite, - r#"SELECT MAX(id) AS "max_id" FROM namespaces;"#.to_owned(), - )) - .await? - .and_then(|result| result.try_get("", "max_id").ok()) - .unwrap_or(-1); - let result: InsertResult = - namespace::Entity::insert_many(models).exec(&txn).await?; - - let namespaces = namespace::Entity::find() - .filter(namespace::Column::Id.between(last_id, result.last_insert_id + 1)) - .all(&txn) - .await? - .into_iter() - .map(|model| Self::new(db.clone(), model)) - .collect(); - txn.commit().await?; - - Ok(namespaces) - } - - /// Adds a namespace to the database - #[tracing::instrument(level = "debug", skip(db))] - pub async fn add(db: DatabaseConnection, name: S) -> RepoResult { - let active_model = namespace::ActiveModel { - name: Set(name.to_string()), - ..Default::default() - }; - let model = active_model.insert(&db).await?; - - Ok(Self::new(db, model)) - } - - /// The ID of the namespace - pub fn id(&self) -> i64 { - self.model.id - } - - /// The name of the namespace - pub fn name(&self) -> &String { - &self.model.name - } -} diff --git a/mediarepo-daemon/mediarepo-model/src/repo.rs b/mediarepo-daemon/mediarepo-model/src/repo.rs deleted file mode 100644 index f81d37a..0000000 --- a/mediarepo-daemon/mediarepo-model/src/repo.rs +++ /dev/null @@ -1,432 +0,0 @@ -use crate::content_descriptor::ContentDescriptor; -use crate::file::filter::FilterProperty; -use crate::file::File; -use crate::file_metadata::FileMetadata; -use crate::namespace::Namespace; -use crate::tag::Tag; -use crate::thumbnail::Thumbnail; -use chrono::{Local, NaiveDateTime}; -use mediarepo_core::content_descriptor::{encode_content_descriptor, is_v1_content_descriptor}; -use mediarepo_core::error::{RepoError, RepoResult}; -use mediarepo_core::fs::file_hash_store::FileHashStore; -use mediarepo_core::fs::thumbnail_store::{Dimensions, ThumbnailStore}; -use mediarepo_core::itertools::Itertools; -use mediarepo_core::thumbnailer::ThumbnailSize; -use mediarepo_core::utils::parse_namespace_and_tag; -use mediarepo_database::get_database; -use mediarepo_database::queries::analysis::{get_all_counts, Counts}; -use sea_orm::DatabaseConnection; -use std::collections::{HashMap, HashSet}; -use std::fmt::Debug; -use std::io::Cursor; -use std::iter::FromIterator; -use std::path::PathBuf; -use std::str::FromStr; -use tokio::fs::OpenOptions; -use tokio::io::AsyncReadExt; - -#[derive(Clone)] -pub struct Repo { - db: DatabaseConnection, - main_storage: FileHashStore, - thumbnail_storage: ThumbnailStore, -} - -impl Repo { - pub(crate) fn new( - db: DatabaseConnection, - file_store_path: PathBuf, - thumb_store_path: PathBuf, - ) -> Self { - Self { - db, - main_storage: FileHashStore::new(file_store_path), - thumbnail_storage: ThumbnailStore::new(thumb_store_path), - } - } - - /// Connects to the database with the given uri - #[tracing::instrument(level = "debug")] - pub async fn connect + Debug>( - uri: S, - file_store_path: PathBuf, - thumb_store_path: PathBuf, - ) -> RepoResult { - let db = get_database(uri).await?; - Ok(Self::new(db, file_store_path, thumb_store_path)) - } - - /// Returns the database of the repo for raw sql queries - pub fn db(&self) -> &DatabaseConnection { - &self.db - } - - /// Returns a file by its mapped hash - #[tracing::instrument(level = "debug", skip(self))] - pub async fn file_by_cd(&self, cd: &[u8]) -> RepoResult> { - File::by_cd(self.db.clone(), cd).await - } - - /// Returns a file by id - #[tracing::instrument(level = "debug", skip(self))] - pub async fn file_by_id(&self, id: i64) -> RepoResult> { - File::by_id(self.db.clone(), id).await - } - - /// Returns a list of all stored files - #[tracing::instrument(level = "debug", skip(self))] - pub async fn files(&self) -> RepoResult> { - File::all(self.db.clone()).await - } - - /// Finds all files by a list of tags - #[tracing::instrument(level = "debug", skip(self))] - pub async fn find_files_by_filters( - &self, - filters: Vec>, - ) -> RepoResult> { - File::find_by_filters(self.db.clone(), filters).await - } - - /// Returns all file metadata entries for the given file ids - #[tracing::instrument(level = "debug", skip(self))] - pub async fn get_file_metadata_for_ids(&self, ids: Vec) -> RepoResult> { - FileMetadata::all_by_ids(self.db.clone(), ids).await - } - - /// Adds a file from bytes to the database - #[tracing::instrument(level = "debug", skip(self, content))] - pub async fn add_file( - &self, - mime_type: Option, - content: Vec, - creation_time: NaiveDateTime, - change_time: NaiveDateTime, - ) -> RepoResult { - let file_size = content.len(); - let reader = Cursor::new(content); - let cd_binary = self.main_storage.add_file(reader, None).await?; - let cd = ContentDescriptor::add(self.db.clone(), cd_binary).await?; - - let mime_type = mime_type - .and_then(|m| mime::Mime::from_str(&m).ok()) - .unwrap_or_else(|| mime::APPLICATION_OCTET_STREAM) - .to_string(); - - let file = File::add(self.db.clone(), cd.id(), mime_type).await?; - FileMetadata::add( - self.db.clone(), - file.id(), - file_size as i64, - creation_time, - change_time, - ) - .await?; - - Ok(file) - } - - /// Adds a file to the database by its readable path in the file system - #[tracing::instrument(level = "debug", skip(self))] - pub async fn add_file_by_path(&self, path: PathBuf) -> RepoResult { - let mime_type = mime_guess::from_path(&path).first().map(|m| m.to_string()); - - let mut os_file = OpenOptions::new().read(true).open(&path).await?; - let mut buf = Vec::new(); - os_file.read_to_end(&mut buf).await?; - - self.add_file( - mime_type, - buf, - Local::now().naive_local(), - Local::now().naive_local(), - ) - .await - } - - /// Deletes a file from the database and disk - #[tracing::instrument(level = "debug", skip(self, file))] - pub async fn delete_file(&self, file: File) -> RepoResult<()> { - let cd = file.cd().to_owned(); - let cd_string = file.encoded_cd(); - file.delete().await?; - self.main_storage.delete_file(&cd).await?; - self.thumbnail_storage.delete_parent(&cd_string).await?; - - Ok(()) - } - - /// Returns all thumbnails of a file - pub async fn get_file_thumbnails(&self, file_cd: &[u8]) -> RepoResult> { - let file_cd = encode_content_descriptor(file_cd); - let thumbnails = self - .thumbnail_storage - .get_thumbnails(&file_cd) - .await? - .into_iter() - .map(|(size, path)| Thumbnail { - file_hash: file_cd.to_owned(), - path, - size, - mime_type: mime::IMAGE_PNG.to_string(), - }) - .collect_vec(); - - Ok(thumbnails) - } - - pub async fn get_file_bytes(&self, file: &File) -> RepoResult> { - let mut buf = Vec::new(); - let mut reader = file.get_reader(&self.main_storage).await?; - reader.read_to_end(&mut buf).await?; - - Ok(buf) - } - - /// Creates thumbnails of all sizes for a file - #[tracing::instrument(level = "debug", skip(self, file))] - pub async fn create_thumbnails_for_file(&self, file: &File) -> RepoResult> { - let size = ThumbnailSize::Medium; - let (height, width) = size.dimensions(); - let thumbs = file.create_thumbnail(&self.main_storage, [size]).await?; - let mut created_thumbs = Vec::with_capacity(1); - - for thumb in thumbs { - let entry = self - .store_single_thumbnail(file.encoded_cd(), height, width, thumb) - .await?; - created_thumbs.push(entry); - } - - Ok(created_thumbs) - } - - #[tracing::instrument(level = "debug", skip(self, file))] - pub async fn create_file_thumbnail( - &self, - file: &File, - size: ThumbnailSize, - ) -> RepoResult { - let (height, width) = size.dimensions(); - let thumb = file - .create_thumbnail(&self.main_storage, [size]) - .await? - .pop() - .ok_or_else(|| RepoError::from("Failed to create thumbnail"))?; - let thumbnail = self - .store_single_thumbnail(file.encoded_cd(), height, width, thumb) - .await?; - - Ok(thumbnail) - } - - /// Stores a single thumbnail - async fn store_single_thumbnail( - &self, - file_hash: String, - height: u32, - width: u32, - thumb: mediarepo_core::thumbnailer::Thumbnail, - ) -> RepoResult { - let mut buf = Vec::new(); - thumb.write_png(&mut buf)?; - let size = Dimensions { height, width }; - let path = self - .thumbnail_storage - .add_thumbnail(&file_hash, size.clone(), &buf) - .await?; - - let thumbnail = Thumbnail { - file_hash, - path, - size, - mime_type: mime::IMAGE_PNG.to_string(), - }; - - Ok(thumbnail) - } - - /// Returns all tags stored in the database - #[tracing::instrument(level = "debug", skip(self))] - pub async fn tags(&self) -> RepoResult> { - Tag::all(self.db.clone()).await - } - - /// Returns all namespaces stored in the database - #[tracing::instrument(level = "debug", skip(self))] - pub async fn namespaces(&self) -> RepoResult> { - Namespace::all(self.db.clone()).await - } - - /// Converts a list of tag names to tag ids - #[tracing::instrument(level = "debug", skip(self))] - pub async fn tag_names_to_ids(&self, tags: Vec) -> RepoResult> { - let parsed_tags = tags - .iter() - .map(|tag| parse_namespace_and_tag(tag.clone())) - .unique() - .collect(); - - let db_tags = self.tags_by_names(parsed_tags).await?; - let tag_map: HashMap = - HashMap::from_iter(db_tags.into_iter().map(|t| (t.normalized_name(), t.id()))); - - Ok(tag_map) - } - - /// Finds all tags by name - #[tracing::instrument(level = "debug", skip(self))] - pub async fn tags_by_names(&self, tags: Vec<(Option, String)>) -> RepoResult> { - Tag::all_by_name(self.db.clone(), tags).await - } - - /// Finds all tags that are assigned to the given list of hashes - #[tracing::instrument(level = "debug", skip_all)] - pub async fn find_tags_for_file_identifiers(&self, cds: Vec>) -> RepoResult> { - Tag::for_cd_list(self.db.clone(), cds).await - } - - /// Adds all tags that are not in the database to the database and returns the ones already existing as well - #[tracing::instrument(level = "debug", skip_all)] - pub async fn add_all_tags(&self, tags: Vec<(Option, String)>) -> RepoResult> { - let mut tags_to_add = tags.into_iter().unique().collect_vec(); - let mut namespaces_to_add = tags_to_add - .iter() - .filter_map(|(namespace, _)| namespace.clone()) - .unique() - .collect_vec(); - - let mut existing_namespaces = - Namespace::all_by_name(self.db.clone(), namespaces_to_add.clone()).await?; - { - let existing_namespaces_set = existing_namespaces - .iter() - .map(|n| n.name().clone()) - .collect::>(); - namespaces_to_add.retain(|namespace| !existing_namespaces_set.contains(namespace)); - } - existing_namespaces - .append(&mut Namespace::add_all(self.db.clone(), namespaces_to_add).await?); - - let mut existing_tags = self.tags_by_names(tags_to_add.clone()).await?; - { - let existing_tags_set = existing_tags - .iter() - .map(|t| (t.namespace().map(|n| n.name().clone()), t.name().clone())) - .collect::, String)>>(); - - tags_to_add.retain(|t| !existing_tags_set.contains(t)); - } - let namespace_map = existing_namespaces - .into_iter() - .map(|namespace| (namespace.name().clone(), namespace.id())) - .collect::>(); - let tags_to_add = tags_to_add - .into_iter() - .map(|(nsp, name)| (nsp.and_then(|n| namespace_map.get(&n)).map(|i| *i), name)) - .collect_vec(); - existing_tags.append(&mut Tag::add_all(self.db.clone(), tags_to_add).await?); - - Ok(existing_tags) - } - - /// Adds or finds a tag - #[tracing::instrument(level = "debug", skip(self))] - pub async fn add_or_find_tag(&self, tag: S) -> RepoResult { - let (namespace, name) = parse_namespace_and_tag(tag.to_string()); - if let Some(namespace) = namespace { - self.add_or_find_namespaced_tag(name, namespace).await - } else { - self.add_or_find_unnamespaced_tag(name).await - } - } - - /// Adds or finds an unnamespaced tag - #[tracing::instrument(level = "debug", skip(self))] - pub async fn add_or_find_unnamespaced_tag(&self, name: String) -> RepoResult { - if let Some(tag) = Tag::by_name(self.db.clone(), &name, None).await? { - Ok(tag) - } else { - self.add_unnamespaced_tag(name).await - } - } - - /// Adds an unnamespaced tag - #[tracing::instrument(level = "debug", skip(self))] - pub async fn add_unnamespaced_tag(&self, name: String) -> RepoResult { - Tag::add(self.db.clone(), name, None).await - } - - /// Adds or finds a namespaced tag - #[tracing::instrument(level = "debug", skip(self))] - pub async fn add_or_find_namespaced_tag( - &self, - name: String, - namespace: String, - ) -> RepoResult { - if let Some(tag) = Tag::by_name(self.db.clone(), &name, Some(namespace.clone())).await? { - Ok(tag) - } else { - self.add_namespaced_tag(name, namespace).await - } - } - - /// Adds a namespaced tag - #[tracing::instrument(level = "debug", skip(self))] - pub async fn add_namespaced_tag(&self, name: String, namespace: String) -> RepoResult { - let namespace = - if let Some(namespace) = Namespace::by_name(self.db.clone(), &namespace).await? { - namespace - } else { - Namespace::add(self.db.clone(), namespace).await? - }; - Tag::add(self.db.clone(), name, Some(namespace.id())).await - } - - /// Returns the size of the main storage - #[inline] - #[tracing::instrument(level = "debug", skip(self))] - pub async fn get_main_store_size(&self) -> RepoResult { - self.main_storage.get_size().await - } - - /// Returns the size of the thumbnail storage - #[inline] - #[tracing::instrument(level = "debug", skip(self))] - pub async fn get_thumb_store_size(&self) -> RepoResult { - self.thumbnail_storage.get_size().await - } - - /// Returns all entity counts - #[inline] - #[tracing::instrument(level = "debug", skip(self))] - pub async fn get_counts(&self) -> RepoResult { - get_all_counts(&self.db).await - } - - pub async fn migrate(&self) -> RepoResult<()> { - let cds = ContentDescriptor::all(self.db.clone()).await?; - - tracing::info!("Converting content descriptors to v2 format..."); - let mut converted_count = 0; - - for mut cd in cds { - if is_v1_content_descriptor(cd.descriptor()) { - let src_cd = cd.descriptor().to_owned(); - cd.convert_v1_to_v2().await?; - let dst_cd = cd.descriptor().to_owned(); - self.main_storage.rename_file(&src_cd, &dst_cd).await?; - self.thumbnail_storage - .rename_parent( - encode_content_descriptor(&src_cd), - encode_content_descriptor(&dst_cd), - ) - .await?; - converted_count += 1; - } - } - tracing::info!("Converted {} descriptors", converted_count); - - Ok(()) - } -} diff --git a/mediarepo-daemon/mediarepo-model/src/tag.rs b/mediarepo-daemon/mediarepo-model/src/tag.rs deleted file mode 100644 index 95b30d5..0000000 --- a/mediarepo-daemon/mediarepo-model/src/tag.rs +++ /dev/null @@ -1,226 +0,0 @@ -use std::fmt::Debug; - -use mediarepo_core::error::RepoResult; -use mediarepo_database::entities::content_descriptor; -use mediarepo_database::entities::content_descriptor_tag; -use mediarepo_database::entities::namespace; -use mediarepo_database::entities::tag; -use sea_orm::prelude::*; -use sea_orm::query::ConnectionTrait; -use sea_orm::sea_query::Expr; -use sea_orm::{Condition, DatabaseBackend, DatabaseConnection, JoinType, Set, Statement}; -use sea_orm::{InsertResult, QuerySelect}; - -use crate::namespace::Namespace; - -#[derive(Clone)] -pub struct Tag { - db: DatabaseConnection, - model: tag::Model, - namespace: Option, -} - -impl Tag { - #[tracing::instrument(level = "trace")] - pub(crate) fn new( - db: DatabaseConnection, - model: tag::Model, - namespace: Option, - ) -> Self { - Self { - db, - model, - namespace, - } - } - - /// Returns all tags stored in the database - #[tracing::instrument(level = "debug", skip(db))] - pub async fn all(db: DatabaseConnection) -> RepoResult> { - let tags: Vec = tag::Entity::find() - .left_join(namespace::Entity) - .select_also(namespace::Entity) - .all(&db) - .await? - .into_iter() - .map(|(tag, namespace)| Self::new(db.clone(), tag, namespace)) - .collect(); - - Ok(tags) - } - - /// Returns the tag by id - #[tracing::instrument(level = "debug", skip(db))] - pub async fn by_id(db: DatabaseConnection, id: i64) -> RepoResult> { - let tag = tag::Entity::find_by_id(id) - .find_also_related(namespace::Entity) - .one(&db) - .await? - .map(|(model, namespace)| Self::new(db, model, namespace)); - - Ok(tag) - } - - /// Returns one tag by name and namespace - #[tracing::instrument(level = "debug", skip(db))] - pub async fn by_name( - db: DatabaseConnection, - name: S1, - namespace: Option, - ) -> RepoResult> { - let mut entries = Self::all_by_name(db, vec![(namespace, name.to_string())]).await?; - - Ok(entries.pop()) - } - - /// Retrieves the namespaced tags by name and namespace - #[tracing::instrument(level = "debug", skip(db))] - pub async fn all_by_name( - db: DatabaseConnection, - namespaces_with_names: Vec<(Option, String)>, - ) -> RepoResult> { - if namespaces_with_names.is_empty() { - return Ok(vec![]); - } - let mut or_condition = Condition::any(); - - for (namespace, name) in namespaces_with_names { - let mut all_condition = Condition::all(); - if !name.ends_with('*') { - all_condition = all_condition.add(tag::Column::Name.eq(name)) - } else if name.len() > 1 { - all_condition = all_condition - .add(tag::Column::Name.like(&*format!("{}%", name.trim_end_matches("*")))) - } else if namespace.is_none() { - continue; // would result in an empty condition otherwise - } - - all_condition = if let Some(namespace) = namespace { - all_condition.add(namespace::Column::Name.eq(namespace)) - } else { - all_condition.add(Expr::tbl(tag::Entity, tag::Column::NamespaceId).is_null()) - }; - or_condition = or_condition.add(all_condition); - } - - let tags: Vec = tag::Entity::find() - .find_also_related(namespace::Entity) - .filter(or_condition) - .group_by(tag::Column::Id) - .all(&db) - .await? - .into_iter() - .map(|(t, n)| Self::new(db.clone(), t, n)) - .collect(); - - Ok(tags) - } - - /// Returns all tags that are assigned to any of the passed hashes - #[tracing::instrument(level = "debug", skip_all)] - pub async fn for_cd_list(db: DatabaseConnection, cds: Vec>) -> RepoResult> { - let tags: Vec = tag::Entity::find() - .find_also_related(namespace::Entity) - .join( - JoinType::LeftJoin, - content_descriptor_tag::Relation::Tag.def().rev(), - ) - .join( - JoinType::InnerJoin, - content_descriptor_tag::Relation::ContentDescriptorId.def(), - ) - .filter(content_descriptor::Column::Descriptor.is_in(cds)) - .group_by(tag::Column::Id) - .all(&db) - .await? - .into_iter() - .map(|(t, n)| Self::new(db.clone(), t, n)) - .collect(); - - Ok(tags) - } - - pub async fn add_all( - db: DatabaseConnection, - namespaces_with_names: Vec<(Option, String)>, - ) -> RepoResult> { - if namespaces_with_names.is_empty() { - return Ok(vec![]); - } - let models: Vec = namespaces_with_names - .into_iter() - .map(|(namespace_id, name)| tag::ActiveModel { - name: Set(name), - namespace_id: Set(namespace_id), - ..Default::default() - }) - .collect(); - let txn = db.begin().await?; - let last_id: i64 = txn - .query_one(Statement::from_string( - DatabaseBackend::Sqlite, - r#"SELECT MAX(id) as "max_id" FROM tags"#.to_owned(), - )) - .await? - .and_then(|res| res.try_get("", "max_id").ok()) - .unwrap_or(-1); - - let result: InsertResult = - tag::Entity::insert_many(models).exec(&txn).await?; - let tags: Vec = tag::Entity::find() - .find_also_related(namespace::Entity) - .filter(tag::Column::Id.between(last_id, result.last_insert_id + 1)) - .all(&txn) - .await? - .into_iter() - .map(|(t, n)| Self::new(db.clone(), t, n)) - .collect(); - txn.commit().await?; - - Ok(tags) - } - - /// Adds a new tag to the database - #[tracing::instrument(level = "debug", skip(db))] - pub async fn add( - db: DatabaseConnection, - name: S, - namespace_id: Option, - ) -> RepoResult { - let active_model = tag::ActiveModel { - name: Set(name.to_string()), - namespace_id: Set(namespace_id), - ..Default::default() - }; - let model: tag::Model = active_model.insert(&db).await?; - let namespace = model.find_related(namespace::Entity).one(&db).await?; - - Ok(Self::new(db, model, namespace)) - } - - /// The ID of the tag - pub fn id(&self) -> i64 { - self.model.id - } - - /// The name of the tag - pub fn name(&self) -> &String { - &self.model.name - } - - /// The namespace of the tag - pub fn namespace(&self) -> Option { - self.namespace - .clone() - .map(|n| Namespace::new(self.db.clone(), n)) - } - - /// Returns the normalized name of the tag (namespace:tag) - pub fn normalized_name(&self) -> String { - if let Some(namespace) = &self.namespace { - format!("{}:{}", namespace.name, self.model.name) - } else { - self.model.name.to_owned() - } - } -} diff --git a/mediarepo-daemon/mediarepo-model/src/thumbnail.rs b/mediarepo-daemon/mediarepo-model/src/thumbnail.rs deleted file mode 100644 index be45be5..0000000 --- a/mediarepo-daemon/mediarepo-model/src/thumbnail.rs +++ /dev/null @@ -1,30 +0,0 @@ -use mediarepo_core::error::RepoResult; -use mediarepo_core::fs::thumbnail_store::Dimensions; -use std::path::PathBuf; -use tokio::fs::{self, File, OpenOptions}; -use tokio::io::BufReader; - -#[derive(Debug)] -pub struct Thumbnail { - pub file_hash: String, - pub path: PathBuf, - pub size: Dimensions, - pub mime_type: String, -} - -impl Thumbnail { - /// Returns the reader of the thumbnail file - #[tracing::instrument(level = "debug")] - pub async fn get_reader(&self) -> RepoResult> { - let file = OpenOptions::new().read(true).open(&self.path).await?; - Ok(BufReader::new(file)) - } - - /// Deletes the thumbnail - #[tracing::instrument(level = "debug")] - pub async fn delete(self) -> RepoResult<()> { - fs::remove_file(&self.path).await?; - - Ok(()) - } -} diff --git a/mediarepo-daemon/mediarepo-socket/Cargo.toml b/mediarepo-daemon/mediarepo-socket/Cargo.toml index a804747..c1f51ee 100644 --- a/mediarepo-daemon/mediarepo-socket/Cargo.toml +++ b/mediarepo-daemon/mediarepo-socket/Cargo.toml @@ -19,8 +19,8 @@ path = "../mediarepo-core" [dependencies.mediarepo-database] path = "../mediarepo-database" -[dependencies.mediarepo-model] -path = "../mediarepo-model" +[dependencies.mediarepo-logic] +path = "../mediarepo-logic" [dependencies.tokio] version = "^1.15.0" diff --git a/mediarepo-daemon/mediarepo-socket/src/from_model.rs b/mediarepo-daemon/mediarepo-socket/src/from_model.rs index dc0f59f..eeffd64 100644 --- a/mediarepo-daemon/mediarepo-socket/src/from_model.rs +++ b/mediarepo-daemon/mediarepo-socket/src/from_model.rs @@ -2,36 +2,34 @@ use mediarepo_core::mediarepo_api::types::files::{ FileBasicDataResponse, FileMetadataResponse, FileStatus, ThumbnailMetadataResponse, }; use mediarepo_core::mediarepo_api::types::tags::{NamespaceResponse, TagResponse}; -use mediarepo_model::file::{File, FileStatus as FileStatusModel}; -use mediarepo_model::file_metadata::FileMetadata; -use mediarepo_model::namespace::Namespace; -use mediarepo_model::tag::Tag; -use mediarepo_model::thumbnail::Thumbnail; +use mediarepo_logic::dto::{ + FileDto, FileMetadataDto, FileStatus as FileStatusModel, NamespaceDto, TagDto, ThumbnailDto, +}; pub trait FromModel { fn from_model(model: M) -> Self; } -impl FromModel for FileMetadataResponse { - fn from_model(metadata: FileMetadata) -> Self { +impl FromModel for FileMetadataResponse { + fn from_model(model: FileMetadataDto) -> Self { Self { - file_id: metadata.file_id(), - name: metadata.name().to_owned(), - comment: metadata.comment().to_owned(), - creation_time: metadata.creation_time().to_owned(), - change_time: metadata.change_time().to_owned(), - import_time: metadata.import_time().to_owned(), + file_id: model.file_id(), + name: model.name().cloned(), + comment: model.comment().cloned(), + creation_time: model.creation_time().to_owned(), + change_time: model.change_time().to_owned(), + import_time: model.import_time().to_owned(), } } } -impl FromModel for FileBasicDataResponse { - fn from_model(file: File) -> Self { +impl FromModel for FileBasicDataResponse { + fn from_model(model: FileDto) -> Self { FileBasicDataResponse { - id: file.id(), - status: FileStatus::from_model(file.status()), - cd: file.encoded_cd(), - mime_type: file.mime_type().to_owned(), + id: model.id(), + status: FileStatus::from_model(model.status()), + cd: model.encoded_cd(), + mime_type: model.mime_type().to_owned(), } } } @@ -46,8 +44,8 @@ impl FromModel for FileStatus { } } -impl FromModel for TagResponse { - fn from_model(model: Tag) -> Self { +impl FromModel for TagResponse { + fn from_model(model: TagDto) -> Self { Self { id: model.id(), namespace: model.namespace().map(|n| n.name().to_owned()), @@ -56,19 +54,19 @@ impl FromModel for TagResponse { } } -impl FromModel for ThumbnailMetadataResponse { - fn from_model(model: Thumbnail) -> Self { +impl FromModel for ThumbnailMetadataResponse { + fn from_model(model: ThumbnailDto) -> Self { Self { - file_hash: model.file_hash, - height: model.size.height, - width: model.size.width, - mime_type: model.mime_type.to_owned(), + file_hash: model.parent_cd().to_owned(), + height: model.size().height, + width: model.size().width, + mime_type: model.mime_type().to_owned(), } } } -impl FromModel for NamespaceResponse { - fn from_model(model: Namespace) -> Self { +impl FromModel for NamespaceResponse { + fn from_model(model: NamespaceDto) -> Self { Self { id: model.id(), name: model.name().to_owned(), diff --git a/mediarepo-daemon/mediarepo-socket/src/lib.rs b/mediarepo-daemon/mediarepo-socket/src/lib.rs index 50f6918..4979eda 100644 --- a/mediarepo-daemon/mediarepo-socket/src/lib.rs +++ b/mediarepo-daemon/mediarepo-socket/src/lib.rs @@ -1,16 +1,18 @@ +use std::net::SocketAddr; +use std::path::PathBuf; +use std::sync::Arc; + +use tokio::net::TcpListener; +use tokio::task::JoinHandle; + use mediarepo_core::bromine::prelude::*; use mediarepo_core::error::{RepoError, RepoResult}; use mediarepo_core::mediarepo_api::types::misc::InfoResponse; use mediarepo_core::settings::{PortSetting, Settings}; use mediarepo_core::tokio_graceful_shutdown::SubsystemHandle; use mediarepo_core::type_keys::{RepoPathKey, SettingsKey, SizeMetadataKey, SubsystemKey}; -use mediarepo_model::repo::Repo; -use mediarepo_model::type_keys::RepoKey; -use std::net::SocketAddr; -use std::path::PathBuf; -use std::sync::Arc; -use tokio::net::TcpListener; -use tokio::task::JoinHandle; +use mediarepo_logic::dao::repo::Repo; +use mediarepo_logic::type_keys::RepoKey; mod from_model; mod namespaces; diff --git a/mediarepo-daemon/mediarepo-socket/src/namespaces/files/mod.rs b/mediarepo-daemon/mediarepo-socket/src/namespaces/files/mod.rs index f0d07a6..644a6f0 100644 --- a/mediarepo-daemon/mediarepo-socket/src/namespaces/files/mod.rs +++ b/mediarepo-daemon/mediarepo-socket/src/namespaces/files/mod.rs @@ -1,11 +1,8 @@ -mod searching; -mod sorting; +use tokio::io::AsyncReadExt; -use crate::from_model::FromModel; -use crate::namespaces::files::searching::find_files_for_filters; -use crate::namespaces::files::sorting::sort_files_by_properties; -use crate::utils::{cd_by_identifier, file_by_identifier, get_repo_from_context}; use mediarepo_core::bromine::prelude::*; +use mediarepo_core::content_descriptor::{create_content_descriptor, encode_content_descriptor}; +use mediarepo_core::error::RepoError; use mediarepo_core::fs::thumbnail_store::Dimensions; use mediarepo_core::itertools::Itertools; use mediarepo_core::mediarepo_api::types::files::{ @@ -17,7 +14,16 @@ use mediarepo_core::mediarepo_api::types::filtering::FindFilesRequest; use mediarepo_core::mediarepo_api::types::identifier::FileIdentifier; use mediarepo_core::thumbnailer::ThumbnailSize; use mediarepo_core::utils::parse_namespace_and_tag; -use tokio::io::AsyncReadExt; +use mediarepo_logic::dao::DaoProvider; +use mediarepo_logic::dto::{AddFileDto, AddTagDto, UpdateFileDto, UpdateFileMetadataDto}; + +use crate::from_model::FromModel; +use crate::namespaces::files::searching::find_files_for_filters; +use crate::namespaces::files::sorting::sort_files_by_properties; +use crate::utils::{cd_by_identifier, file_by_identifier, get_repo_from_context}; + +mod searching; +mod sorting; pub struct FilesNamespace; @@ -50,7 +56,7 @@ impl FilesNamespace { #[tracing::instrument(skip_all)] async fn all_files(ctx: &Context, _event: Event) -> IPCResult<()> { let repo = get_repo_from_context(ctx).await; - let files = repo.files().await?; + let files = repo.file().all().await?; let responses: Vec = files .into_iter() @@ -80,7 +86,17 @@ impl FilesNamespace { let id = event.payload::()?; let repo = get_repo_from_context(ctx).await; let file = file_by_identifier(id, &repo).await?; - let metadata = file.metadata().await?; + let file_id = file.id(); + + let metadata = if let Some(metadata) = file.into_metadata() { + metadata + } else { + repo.file() + .metadata(file_id) + .await? + .ok_or_else(|| RepoError::from("file metadata not found"))? + }; + ctx.emit_to( Self::name(), "get_file_metadata", @@ -135,22 +151,38 @@ impl FilesNamespace { .into_inner(); let AddFileRequestHeader { metadata, tags } = request; let repo = get_repo_from_context(ctx).await; + let bytes = bytes.into_inner(); + let cd = create_content_descriptor(&bytes); - let file = repo - .add_file( - metadata.mime_type, - bytes.into_inner(), - metadata.creation_time, - metadata.change_time, - ) - .await?; - file.metadata().await?.set_name(metadata.name).await?; + let file = if let Some(file) = repo.file().by_cd(cd).await? { + tracing::debug!("Inserted file already exists"); + file + } else { + let add_dto = AddFileDto { + content: bytes, + mime_type: metadata + .mime_type + .unwrap_or(String::from("application/octet-stream")), + creation_time: metadata.creation_time, + change_time: metadata.change_time, + name: Some(metadata.name), + }; + repo.file().add(add_dto).await? + }; let tags = repo - .add_all_tags(tags.into_iter().map(parse_namespace_and_tag).collect()) + .tag() + .add_all( + tags.into_iter() + .map(parse_namespace_and_tag) + .map(AddTagDto::from_tuple) + .collect(), + ) .await?; let tag_ids: Vec = tags.into_iter().map(|t| t.id()).unique().collect(); - file.add_tags(tag_ids).await?; + repo.tag() + .upsert_mappings(vec![file.cd_id()], tag_ids) + .await?; ctx.emit_to( Self::name(), @@ -167,7 +199,14 @@ impl FilesNamespace { let request = event.payload::()?; let repo = get_repo_from_context(ctx).await; let mut file = file_by_identifier(request.file_id, &repo).await?; - file.set_status(request.status.into()).await?; + file = repo + .file() + .update(UpdateFileDto { + id: file.id(), + status: Some(request.status.into()), + ..Default::default() + }) + .await?; ctx.emit_to( Self::name(), "update_file_status", @@ -184,7 +223,7 @@ impl FilesNamespace { let request = event.payload::()?; let repo = get_repo_from_context(ctx).await; let file = file_by_identifier(request.id, &repo).await?; - let bytes = repo.get_file_bytes(&file).await?; + let bytes = repo.file().get_bytes(file.cd()).await?; ctx.emit_to(Self::name(), "read_file", BytePayload::new(bytes)) .await?; @@ -198,7 +237,7 @@ impl FilesNamespace { let id = event.payload::()?; let repo = get_repo_from_context(ctx).await; let file = file_by_identifier(id, &repo).await?; - repo.delete_file(file).await?; + repo.file().delete(file).await?; ctx.emit_to(Self::name(), "delete_file", ()).await?; @@ -211,12 +250,18 @@ impl FilesNamespace { let request = event.payload::()?; let repo = get_repo_from_context(ctx).await; let file_cd = cd_by_identifier(request.id.clone(), &repo).await?; - let mut thumbnails = repo.get_file_thumbnails(&file_cd).await?; + let mut thumbnails = repo + .file() + .thumbnails(encode_content_descriptor(&file_cd)) + .await?; if thumbnails.is_empty() { tracing::debug!("No thumbnails for file found. Creating thumbnails..."); let file = file_by_identifier(request.id, &repo).await?; - thumbnails = repo.create_thumbnails_for_file(&file).await?; + thumbnails = repo + .file() + .create_thumbnails(file, vec![ThumbnailSize::Medium]) + .await?; tracing::debug!("Thumbnails for file created."); } @@ -236,17 +281,20 @@ impl FilesNamespace { let request = event.payload::()?; let repo = get_repo_from_context(ctx).await; let file_cd = cd_by_identifier(request.id.clone(), &repo).await?; - let thumbnails = repo.get_file_thumbnails(&file_cd).await?; let min_size = request.min_size; let max_size = request.max_size; + let thumbnails = repo + .file() + .thumbnails(encode_content_descriptor(&file_cd)) + .await?; let found_thumbnail = thumbnails.into_iter().find(|thumb| { - let Dimensions { height, width } = thumb.size; + let Dimensions { height, width } = thumb.size(); - height >= min_size.0 - && height <= max_size.0 - && width >= min_size.1 - && width <= max_size.1 + *height >= min_size.0 + && *height <= max_size.0 + && *width >= min_size.1 + && *width <= max_size.1 }); let thumbnail = if let Some(thumbnail) = found_thumbnail { @@ -255,10 +303,14 @@ impl FilesNamespace { let file = file_by_identifier(request.id, &repo).await?; let middle_size = ((max_size.0 + min_size.0) / 2, (max_size.1 + min_size.1) / 2); let thumbnail = repo - .create_file_thumbnail(&file, ThumbnailSize::Custom(middle_size)) + .file() + .create_thumbnails(file, vec![ThumbnailSize::Custom(middle_size)]) .await?; thumbnail + .into_iter() + .next() + .ok_or_else(|| RepoError::from("thumbnail could not be created"))? }; let mut buf = Vec::new(); thumbnail.get_reader().await?.read_to_end(&mut buf).await?; @@ -280,8 +332,15 @@ impl FilesNamespace { let repo = get_repo_from_context(ctx).await; let request = event.payload::()?; let file = file_by_identifier(request.file_id, &repo).await?; - let mut metadata = file.metadata().await?; - metadata.set_name(request.name).await?; + + let metadata = repo + .file() + .update_metadata(UpdateFileMetadataDto { + file_id: file.id(), + name: Some(Some(request.name)), + ..Default::default() + }) + .await?; ctx.emit_to( Self::name(), @@ -299,7 +358,7 @@ impl FilesNamespace { let repo = get_repo_from_context(ctx).await; let id = event.payload::()?; let file = file_by_identifier(id, &repo).await?; - let thumbnails = repo.get_file_thumbnails(file.cd()).await?; + let thumbnails = repo.file().thumbnails(file.encoded_cd()).await?; for thumb in thumbnails { thumb.delete().await?; diff --git a/mediarepo-daemon/mediarepo-socket/src/namespaces/files/searching.rs b/mediarepo-daemon/mediarepo-socket/src/namespaces/files/searching.rs index e15c6df..fb78e4d 100644 --- a/mediarepo-daemon/mediarepo-socket/src/namespaces/files/searching.rs +++ b/mediarepo-daemon/mediarepo-socket/src/namespaces/files/searching.rs @@ -1,25 +1,27 @@ +use std::collections::HashMap; + use mediarepo_core::content_descriptor::decode_content_descriptor; use mediarepo_core::error::RepoResult; use mediarepo_core::mediarepo_api::types::files::FileStatus as ApiFileStatus; use mediarepo_core::mediarepo_api::types::filtering::{ FilterExpression, FilterQuery, PropertyQuery, TagQuery, ValueComparator, }; -use mediarepo_model::file::filter::NegatableComparator::{Is, IsNot}; -use mediarepo_model::file::filter::{FilterFileProperty, FilterProperty, OrderingComparator}; -use mediarepo_model::file::{File, FileStatus}; -use mediarepo_model::repo::Repo; -use std::collections::HashMap; +use mediarepo_logic::dao::file::find::NegatableComparator::{Is, IsNot}; +use mediarepo_logic::dao::file::find::{FilterFileProperty, FilterProperty, OrderingComparator}; +use mediarepo_logic::dao::repo::Repo; +use mediarepo_logic::dao::DaoProvider; +use mediarepo_logic::dto::{FileDto, FileStatus}; #[tracing::instrument(level = "debug", skip(repo))] pub async fn find_files_for_filters( repo: &Repo, expressions: Vec, -) -> RepoResult> { +) -> RepoResult> { let tag_names = get_tag_names_from_expressions(&expressions); - let tag_id_map = repo.tag_names_to_ids(tag_names).await?; + let tag_id_map = repo.tag().normalized_tags_to_ids(tag_names).await?; let filters = build_filters_from_expressions(expressions, &tag_id_map); - repo.find_files_by_filters(filters).await + repo.file().find(filters).await } #[tracing::instrument(level = "debug")] diff --git a/mediarepo-daemon/mediarepo-socket/src/namespaces/files/sorting.rs b/mediarepo-daemon/mediarepo-socket/src/namespaces/files/sorting.rs index 1f36770..5f56c5d 100644 --- a/mediarepo-daemon/mediarepo-socket/src/namespaces/files/sorting.rs +++ b/mediarepo-daemon/mediarepo-socket/src/namespaces/files/sorting.rs @@ -1,17 +1,20 @@ +use std::cmp::Ordering; +use std::collections::HashMap; +use std::iter::FromIterator; + use chrono::NaiveDateTime; use compare::Compare; +use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; + use mediarepo_core::error::RepoResult; use mediarepo_core::mediarepo_api::types::filtering::{SortDirection, SortKey}; use mediarepo_database::queries::tags::{ get_cids_with_namespaced_tags, get_content_descriptors_with_tag_count, }; -use mediarepo_model::file::File; -use mediarepo_model::file_metadata::FileMetadata; -use mediarepo_model::repo::Repo; -use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; -use std::cmp::Ordering; -use std::collections::HashMap; -use std::iter::FromIterator; +use mediarepo_logic::dao::DaoProvider; +use mediarepo_logic::dao::repo::Repo; +use mediarepo_logic::dto::{FileDto, FileMetadataDto}; + pub struct FileSortContext { name: Option, @@ -28,7 +31,7 @@ pub struct FileSortContext { pub async fn sort_files_by_properties( repo: &Repo, sort_expression: Vec, - files: &mut Vec, + files: &mut Vec, ) -> RepoResult<()> { let contexts = build_sort_context(repo, files).await?; @@ -45,7 +48,7 @@ pub async fn sort_files_by_properties( async fn build_sort_context( repo: &Repo, - files: &Vec, + files: &Vec, ) -> RepoResult> { let hash_ids: Vec = files.par_iter().map(|f| f.cd_id()).collect(); let file_ids: Vec = files.par_iter().map(|f| f.id()).collect(); @@ -54,9 +57,9 @@ async fn build_sort_context( get_cids_with_namespaced_tags(repo.db(), hash_ids.clone()).await?; let mut cid_tag_counts = get_content_descriptors_with_tag_count(repo.db(), hash_ids).await?; - let files_metadata = repo.get_file_metadata_for_ids(file_ids).await?; + let files_metadata = repo.file().all_metadata(file_ids).await?; - let mut file_metadata_map: HashMap = + let mut file_metadata_map: HashMap = HashMap::from_iter(files_metadata.into_iter().map(|m| (m.file_id(), m))); let mut contexts = HashMap::new(); @@ -64,7 +67,7 @@ async fn build_sort_context( for file in files { if let Some(metadata) = file_metadata_map.remove(&file.id()) { let context = FileSortContext { - name: metadata.name().to_owned(), + name: metadata.name().cloned(), size: metadata.size() as u64, mime_type: file.mime_type().to_owned(), namespaces: cid_nsp diff --git a/mediarepo-daemon/mediarepo-socket/src/namespaces/jobs.rs b/mediarepo-daemon/mediarepo-socket/src/namespaces/jobs.rs index 393fed8..52e1ef4 100644 --- a/mediarepo-daemon/mediarepo-socket/src/namespaces/jobs.rs +++ b/mediarepo-daemon/mediarepo-socket/src/namespaces/jobs.rs @@ -1,9 +1,11 @@ -use crate::utils::{calculate_size, get_repo_from_context}; use mediarepo_core::bromine::prelude::*; use mediarepo_core::error::RepoResult; use mediarepo_core::mediarepo_api::types::jobs::{JobType, RunJobRequest}; use mediarepo_core::mediarepo_api::types::repo::SizeType; use mediarepo_core::type_keys::SizeMetadataKey; +use mediarepo_logic::dao::DaoProvider; + +use crate::utils::{calculate_size, get_repo_from_context}; pub struct JobsNamespace; @@ -23,12 +25,13 @@ impl JobsNamespace { #[tracing::instrument(skip_all)] pub async fn run_job(ctx: &Context, event: Event) -> IPCResult<()> { let run_request = event.payload::()?; - let repo = get_repo_from_context(ctx).await; + let job_dao = get_repo_from_context(ctx).await.job(); match run_request.job_type { - JobType::MigrateContentDescriptors => repo.migrate().await?, + JobType::MigrateContentDescriptors => job_dao.migrate_content_descriptors().await?, JobType::CalculateSizes => calculate_all_sizes(ctx).await?, - JobType::CheckIntegrity => {} + JobType::CheckIntegrity => job_dao.check_integrity().await?, + JobType::Vacuum => job_dao.vacuum().await?, } ctx.emit_to(Self::name(), "run_job", ()).await?; diff --git a/mediarepo-daemon/mediarepo-socket/src/namespaces/tags.rs b/mediarepo-daemon/mediarepo-socket/src/namespaces/tags.rs index cf2de6d..ef6543b 100644 --- a/mediarepo-daemon/mediarepo-socket/src/namespaces/tags.rs +++ b/mediarepo-daemon/mediarepo-socket/src/namespaces/tags.rs @@ -1,12 +1,20 @@ -use crate::from_model::FromModel; -use crate::utils::{file_by_identifier, get_repo_from_context}; +use rayon::iter::{IntoParallelIterator, ParallelIterator}; +use std::collections::HashMap; + use mediarepo_core::bromine::prelude::*; -use mediarepo_core::content_descriptor::decode_content_descriptor; -use mediarepo_core::mediarepo_api::types::files::{GetFileTagsRequest, GetFilesTagsRequest}; +use mediarepo_core::content_descriptor::{decode_content_descriptor, encode_content_descriptor}; +use mediarepo_core::mediarepo_api::types::files::{ + GetFileTagMapRequest, GetFileTagsRequest, GetFilesTagsRequest, +}; use mediarepo_core::mediarepo_api::types::tags::{ ChangeFileTagsRequest, NamespaceResponse, TagResponse, }; -use rayon::iter::{IntoParallelIterator, ParallelIterator}; +use mediarepo_core::utils::parse_namespace_and_tag; +use mediarepo_logic::dao::DaoProvider; +use mediarepo_logic::dto::AddTagDto; + +use crate::from_model::FromModel; +use crate::utils::{file_by_identifier, get_repo_from_context}; pub struct TagsNamespace; @@ -21,6 +29,7 @@ impl NamespaceProvider for TagsNamespace { "all_namespaces" => Self::all_namespaces, "tags_for_file" => Self::tags_for_file, "tags_for_files" => Self::tags_for_files, + "file_tag_map" => Self::tag_cd_map_for_files, "create_tags" => Self::create_tags, "change_file_tags" => Self::change_file_tags ); @@ -33,7 +42,8 @@ impl TagsNamespace { async fn all_tags(ctx: &Context, _event: Event) -> IPCResult<()> { let repo = get_repo_from_context(ctx).await; let tags: Vec = repo - .tags() + .tag() + .all() .await? .into_iter() .map(TagResponse::from_model) @@ -48,7 +58,8 @@ impl TagsNamespace { async fn all_namespaces(ctx: &Context, _event: Event) -> IPCResult<()> { let repo = get_repo_from_context(ctx).await; let namespaces: Vec = repo - .namespaces() + .tag() + .all_namespaces() .await? .into_iter() .map(NamespaceResponse::from_model) @@ -65,7 +76,7 @@ impl TagsNamespace { let repo = get_repo_from_context(ctx).await; let request = event.payload::()?; let file = file_by_identifier(request.id, &repo).await?; - let tags = file.tags().await?; + let tags = repo.tag().tags_for_cd(file.cd_id()).await?; let responses: Vec = tags.into_iter().map(TagResponse::from_model).collect(); ctx.emit_to(Self::name(), "tags_for_file", responses) @@ -80,7 +91,8 @@ impl TagsNamespace { let repo = get_repo_from_context(ctx).await; let request = event.payload::()?; let tag_responses: Vec = repo - .find_tags_for_file_identifiers( + .tag() + .all_for_cds( request .cds .into_par_iter() @@ -97,17 +109,53 @@ impl TagsNamespace { Ok(()) } - /// Creates all tags given as input or returns the existing tag + /// Returns a map of content descriptors to assigned tags + #[tracing::instrument(skip_all)] + async fn tag_cd_map_for_files(ctx: &Context, event: Event) -> IPCResult<()> { + let request = event.payload::()?; + let repo = get_repo_from_context(ctx).await; + let cds = request + .cds + .into_iter() + .filter_map(|c| decode_content_descriptor(c).ok()) + .collect(); + + let mappings = repo + .tag() + .all_for_cds_map(cds) + .await? + .into_iter() + .map(|(cd, tags)| (encode_content_descriptor(&cd), tags)) + .map(|(cd, tags)| { + ( + cd, + tags.into_iter() + .map(TagResponse::from_model) + .collect::>(), + ) + }) + .collect::>>(); + + ctx.emit_to(Self::name(), "file_tag_map", mappings).await?; + + Ok(()) + } + + /// Creates all tags given as input or returns the existing tags #[tracing::instrument(skip_all)] async fn create_tags(ctx: &Context, event: Event) -> IPCResult<()> { let repo = get_repo_from_context(ctx).await; let tags = event.payload::>()?; - let mut created_tags = Vec::new(); + let created_tags = repo + .tag() + .add_all( + tags.into_iter() + .map(parse_namespace_and_tag) + .map(AddTagDto::from_tuple) + .collect(), + ) + .await?; - for tag in tags { - let created_tag = repo.add_or_find_tag(tag).await?; - created_tags.push(created_tag); - } let responses: Vec = created_tags .into_iter() .map(TagResponse::from_model) @@ -126,14 +174,19 @@ impl TagsNamespace { let file = file_by_identifier(request.file_id, &repo).await?; if !request.added_tags.is_empty() { - file.add_tags(request.added_tags).await?; + repo.tag() + .upsert_mappings(vec![file.cd_id()], request.added_tags) + .await?; } if !request.removed_tags.is_empty() { - file.remove_tags(request.removed_tags).await?; + repo.tag() + .remove_mappings(vec![file.cd_id()], request.removed_tags) + .await?; } - let responses: Vec = file - .tags() + let responses: Vec = repo + .tag() + .tags_for_cd(file.cd_id()) .await? .into_iter() .map(TagResponse::from_model) diff --git a/mediarepo-daemon/mediarepo-socket/src/utils.rs b/mediarepo-daemon/mediarepo-socket/src/utils.rs index 6c07756..5e48cdc 100644 --- a/mediarepo-daemon/mediarepo-socket/src/utils.rs +++ b/mediarepo-daemon/mediarepo-socket/src/utils.rs @@ -1,3 +1,7 @@ +use std::sync::Arc; + +use tokio::fs; + use mediarepo_core::bromine::ipc::context::Context; use mediarepo_core::content_descriptor::decode_content_descriptor; use mediarepo_core::error::{RepoError, RepoResult}; @@ -5,11 +9,10 @@ use mediarepo_core::mediarepo_api::types::identifier::FileIdentifier; use mediarepo_core::mediarepo_api::types::repo::SizeType; use mediarepo_core::type_keys::{RepoPathKey, SettingsKey}; use mediarepo_core::utils::get_folder_size; -use mediarepo_model::file::File; -use mediarepo_model::repo::Repo; -use mediarepo_model::type_keys::RepoKey; -use std::sync::Arc; -use tokio::fs; +use mediarepo_logic::dao::DaoProvider; +use mediarepo_logic::dao::repo::Repo; +use mediarepo_logic::dto::FileDto; +use mediarepo_logic::type_keys::RepoKey; pub async fn get_repo_from_context(ctx: &Context) -> Arc { let data = ctx.data.read().await; @@ -17,10 +20,10 @@ pub async fn get_repo_from_context(ctx: &Context) -> Arc { Arc::clone(repo) } -pub async fn file_by_identifier(identifier: FileIdentifier, repo: &Repo) -> RepoResult { +pub async fn file_by_identifier(identifier: FileIdentifier, repo: &Repo) -> RepoResult { let file = match identifier { - FileIdentifier::ID(id) => repo.file_by_id(id).await, - FileIdentifier::CD(cd) => repo.file_by_cd(&decode_content_descriptor(cd)?).await, + FileIdentifier::ID(id) => repo.file().by_id(id).await, + FileIdentifier::CD(cd) => repo.file().by_cd(decode_content_descriptor(cd)?).await, }?; file.ok_or_else(|| RepoError::from("File not found")) } @@ -29,7 +32,8 @@ pub async fn cd_by_identifier(identifier: FileIdentifier, repo: &Repo) -> RepoRe match identifier { FileIdentifier::ID(id) => { let file = repo - .file_by_id(id) + .file() + .by_id(id) .await? .ok_or_else(|| "Thumbnail not found")?; Ok(file.cd().to_owned()) diff --git a/mediarepo-daemon/src/logging.rs b/mediarepo-daemon/src/logging.rs index 9e1692a..dfb5846 100644 --- a/mediarepo-daemon/src/logging.rs +++ b/mediarepo-daemon/src/logging.rs @@ -1,21 +1,22 @@ -use console_subscriber::ConsoleLayer; -use rolling_file::RollingConditionBasic; use std::fs; use std::path::PathBuf; -use mediarepo_core::settings::LoggingSettings; +use console_subscriber::ConsoleLayer; +use rolling_file::RollingConditionBasic; use tracing::Level; use tracing_appender::non_blocking::{NonBlocking, WorkerGuard}; use tracing_flame::FlameLayer; use tracing_log::LogTracer; -use tracing_subscriber::filter::{self, Targets}; -use tracing_subscriber::fmt::format::FmtSpan; -use tracing_subscriber::layer::SubscriberExt; -use tracing_subscriber::util::SubscriberInitExt; use tracing_subscriber::{ fmt::{self}, Layer, Registry, }; +use tracing_subscriber::filter::{self, Targets}; +use tracing_subscriber::fmt::format::FmtSpan; +use tracing_subscriber::layer::SubscriberExt; +use tracing_subscriber::util::SubscriberInitExt; + +use mediarepo_core::settings::LoggingSettings; #[allow(dyn_drop)] pub type DropGuard = Box; diff --git a/mediarepo-daemon/src/main.rs b/mediarepo-daemon/src/main.rs index 3e60d09..757dffe 100644 --- a/mediarepo-daemon/src/main.rs +++ b/mediarepo-daemon/src/main.rs @@ -1,7 +1,10 @@ +use std::env; use std::path::PathBuf; +use std::time::Duration; use structopt::StructOpt; use tokio::fs; +use tokio::io::AsyncWriteExt; use tokio::runtime; use tokio::runtime::Runtime; @@ -9,11 +12,8 @@ use mediarepo_core::error::RepoResult; use mediarepo_core::fs::drop_file::DropFile; use mediarepo_core::settings::{PathSettings, Settings}; use mediarepo_core::tokio_graceful_shutdown::{SubsystemHandle, Toplevel}; -use mediarepo_model::repo::Repo; +use mediarepo_logic::dao::repo::Repo; use mediarepo_socket::start_tcp_server; -use std::env; -use std::time::Duration; -use tokio::io::AsyncWriteExt; use crate::utils::{create_paths_for_repo, get_repo, load_settings}; diff --git a/mediarepo-daemon/src/utils.rs b/mediarepo-daemon/src/utils.rs index 2abb7e1..130947a 100644 --- a/mediarepo-daemon/src/utils.rs +++ b/mediarepo-daemon/src/utils.rs @@ -1,10 +1,12 @@ -use mediarepo_core::error::RepoResult; -use mediarepo_core::settings::v1::SettingsV1; -use mediarepo_core::settings::{PathSettings, Settings}; -use mediarepo_model::repo::Repo; use std::path::PathBuf; + use tokio::fs; +use mediarepo_core::error::RepoResult; +use mediarepo_core::settings::{PathSettings, Settings}; +use mediarepo_core::settings::v1::SettingsV1; +use mediarepo_logic::dao::repo::Repo; + /// Loads the settings from a toml path pub fn load_settings(root_path: &PathBuf) -> RepoResult { let contents = std::fs::read_to_string(root_path.join("repo.toml"))?; diff --git a/mediarepo-ui/package.json b/mediarepo-ui/package.json index e673757..b2ec35b 100644 --- a/mediarepo-ui/package.json +++ b/mediarepo-ui/package.json @@ -1,6 +1,6 @@ { "name": "mediarepo-ui", - "version": "0.13.1", + "version": "0.13.2", "scripts": { "ng": "ng", "start": "ng serve", @@ -59,4 +59,4 @@ "karma-jasmine-html-reporter": "~1.7.0", "typescript": "~4.5.4" } -} \ No newline at end of file +} diff --git a/mediarepo-ui/src-tauri/Cargo.lock b/mediarepo-ui/src-tauri/Cargo.lock index 190bbe3..52488e0 100644 --- a/mediarepo-ui/src-tauri/Cargo.lock +++ b/mediarepo-ui/src-tauri/Cargo.lock @@ -34,13 +34,13 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.52" +version = "1.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84450d0b4a8bd1ba4144ce8ce718fbc5d071358b1e5384bace6536b3d1f2d5b3" +checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0" [[package]] name = "app" -version = "0.13.1" +version = "0.13.2" dependencies = [ "mediarepo-api", "serde", @@ -71,8 +71,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" dependencies = [ "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] @@ -140,9 +140,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "blake3" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "882e99e4a0cb2ae6cb6e442102e8e6b7131718d94110e64c3e6a34ea9b106f37" +checksum = "a08e53fc5a564bb15bfe6fae56bd71522205f1f91893f9c0116edad6496c183f" dependencies = [ "arrayref", "arrayvec", @@ -366,7 +366,7 @@ checksum = "7606b05842fea68ddcc89e8053b8860ebcb2a0ba8d6abfe3a148e5d5a8d3f0c1" dependencies = [ "com_macros_support", "proc-macro2 1.0.36", - "syn 1.0.85", + "syn 1.0.86", ] [[package]] @@ -376,8 +376,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97e9a6d20f4ac8830e309a455d7e9416e65c6af5a97c88c55fbb4c2012e107da" dependencies = [ "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] @@ -476,9 +476,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "738c290dfaea84fc1ca15ad9c168d083b05a714e1efddd8edaab678dc28d2836" +checksum = "a2209c310e29876f7f0b2721e7e26b84aff178aa3da5d091f9bfbf47669e60e3" dependencies = [ "cfg-if 1.0.0", ] @@ -548,9 +548,9 @@ dependencies = [ "matches", "phf 0.8.0", "proc-macro2 1.0.36", - "quote 1.0.14", + "quote 1.0.15", "smallvec", - "syn 1.0.85", + "syn 1.0.86", ] [[package]] @@ -559,8 +559,8 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfae75de57f2b2e85e8768c3ea840fd159c8f33e2b6522c7835b7abac81be16e" dependencies = [ - "quote 1.0.14", - "syn 1.0.85", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] @@ -588,9 +588,9 @@ dependencies = [ "fnv", "ident_case", "proc-macro2 1.0.36", - "quote 1.0.14", + "quote 1.0.15", "strsim", - "syn 1.0.85", + "syn 1.0.86", ] [[package]] @@ -600,8 +600,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" dependencies = [ "darling_core", - "quote 1.0.14", - "syn 1.0.85", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] @@ -632,9 +632,9 @@ checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ "convert_case", "proc-macro2 1.0.36", - "quote 1.0.14", + "quote 1.0.15", "rustc_version 0.4.0", - "syn 1.0.85", + "syn 1.0.86", ] [[package]] @@ -725,9 +725,9 @@ checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" [[package]] name = "fastrand" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "779d043b6a0b90cc4c0ed7ee380a6504394cee7efd7db050e3774eee387324b2" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" dependencies = [ "instant", ] @@ -877,8 +877,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbd947adfffb0efc70599b3ddcf7b5597bb5fa9e245eb99f62b3a5f7bb8bd3c" dependencies = [ "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] @@ -1096,8 +1096,8 @@ dependencies = [ "proc-macro-crate 1.1.0", "proc-macro-error", "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] @@ -1207,8 +1207,8 @@ dependencies = [ "proc-macro-crate 1.1.0", "proc-macro-error", "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] @@ -1239,8 +1239,8 @@ dependencies = [ "mac", "markup5ever", "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] @@ -1389,9 +1389,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.55" +version = "0.3.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" +checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" dependencies = [ "wasm-bindgen", ] @@ -1416,15 +1416,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.112" +version = "0.2.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" +checksum = "565dbd88872dbe4cc8a46e527f26483c1d1f7afa6b884a3bd6cd893d4f98da74" [[package]] name = "lock_api" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" +checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" dependencies = [ "scopeguard", ] @@ -1499,7 +1499,7 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "mediarepo-api" -version = "0.27.0" +version = "0.28.0" dependencies = [ "async-trait", "bromine", @@ -1646,8 +1646,8 @@ dependencies = [ "darling", "proc-macro-crate 0.1.5", "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] @@ -1734,8 +1734,8 @@ checksum = "0d992b768490d7fe0d8586d9b5745f6c49f557da6d81dc982b1d167ad4edbb21" dependencies = [ "proc-macro-crate 1.1.0", "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] @@ -1975,8 +1975,8 @@ dependencies = [ "phf_shared 0.8.0", "proc-macro-hack", "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] @@ -1989,8 +1989,8 @@ dependencies = [ "phf_shared 0.10.0", "proc-macro-hack", "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] @@ -2092,8 +2092,8 @@ checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", + "quote 1.0.15", + "syn 1.0.86", "version_check", ] @@ -2104,7 +2104,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ "proc-macro2 1.0.36", - "quote 1.0.14", + "quote 1.0.15", "version_check", ] @@ -2143,9 +2143,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d" +checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" dependencies = [ "proc-macro2 1.0.36", ] @@ -2425,9 +2425,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "security-framework" -version = "2.4.2" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525bc1abfda2e1998d152c45cf13e696f76d0a4972310b22fac1658b05df7c87" +checksum = "3fed7948b6c68acbb6e20c334f55ad635dc0f75506963de4464289fbd3b051ac" dependencies = [ "bitflags", "core-foundation 0.9.2", @@ -2438,9 +2438,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.4.2" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9dd14d83160b528b7bfd66439110573efcfbe281b17fc2ca9f39f550d619c7e" +checksum = "a57321bf8bc2362081b2599912d2961fe899c0efadf1b4b2f8d48b3e253bb96c" dependencies = [ "core-foundation-sys 0.8.3", "libc", @@ -2492,29 +2492,29 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.133" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97565067517b60e2d1ea8b268e59ce036de907ac523ad83a0475da04e818989a" +checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.133" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed201699328568d8d08208fdd080e3ff594e6c422e438b6705905da01005d537" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" dependencies = [ "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] name = "serde_json" -version = "1.0.74" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee2bb9cd061c5865d345bb02ca49fcef1391741b672b54a0bf7b679badec3142" +checksum = "d23c1ba4cf0efd44be32017709280b32d1cea5c3f1275c3b6d9e8bc54f758085" dependencies = [ "itoa 1.0.1", "ryu", @@ -2550,8 +2550,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98d0516900518c29efa217c298fa1f4e6c6ffc85ae29fd7f4ee48f176e1a9ed5" dependencies = [ "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] @@ -2606,9 +2606,9 @@ dependencies = [ [[package]] name = "siphasher" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b" +checksum = "a86232ab60fa71287d7f2ddae4a7073f6b7aac33631c3015abb556f08c6d0a3e" [[package]] name = "slab" @@ -2675,7 +2675,7 @@ dependencies = [ "phf_generator 0.8.0", "phf_shared 0.8.0", "proc-macro2 1.0.36", - "quote 1.0.14", + "quote 1.0.15", ] [[package]] @@ -2704,8 +2704,8 @@ checksum = "87c85aa3f8ea653bfd3ddf25f7ee357ee4d204731f6aa9ad04002306f6e2774c" dependencies = [ "heck", "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] @@ -2716,8 +2716,8 @@ checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec" dependencies = [ "heck", "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] @@ -2739,12 +2739,12 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.85" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a684ac3dcd8913827e18cd09a68384ee66c1de24157e3c556c9ab16d85695fb7" +checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" dependencies = [ "proc-macro2 1.0.36", - "quote 1.0.14", + "quote 1.0.15", "unicode-xid 0.2.2", ] @@ -2884,7 +2884,7 @@ checksum = "3c9c9a9bea25b9d6f5845b8662e18447e17218f99860cab37e39e2b57a9fcd49" dependencies = [ "anyhow", "proc-macro2 1.0.36", - "quote 1.0.14", + "quote 1.0.15", "serde_json", "tauri-utils", "winres", @@ -2899,7 +2899,7 @@ dependencies = [ "blake3", "kuchiki", "proc-macro2 1.0.36", - "quote 1.0.14", + "quote 1.0.15", "regex", "serde", "serde_json", @@ -2916,8 +2916,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bddf9f5868402323f35ef94fa6ab1d5d10b29aea9de598d829723aa1db5693b4" dependencies = [ "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", + "quote 1.0.15", + "syn 1.0.86", "tauri-codegen", ] @@ -2966,7 +2966,7 @@ dependencies = [ "kuchiki", "phf 0.10.1", "proc-macro2 1.0.36", - "quote 1.0.14", + "quote 1.0.15", "serde", "serde_json", "thiserror", @@ -3021,15 +3021,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" dependencies = [ "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] name = "thread_local" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" dependencies = [ "once_cell", ] @@ -3062,9 +3062,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.15.0" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbbf1c778ec206785635ce8ad57fe52b3009ae9e0c9f574a728f3049d3e55838" +checksum = "0c27a64b625de6d309e8c57716ba93021dccf1b3b5c97edd6d3dd2d2135afc0a" dependencies = [ "bytes", "libc", @@ -3105,8 +3105,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e" dependencies = [ "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] @@ -3131,9 +3131,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d81bfa81424cc98cb034b837c985b7a290f592e5b4322f353f94a0ab0f9f594" +checksum = "5312f325fe3588e277415f5a6cca1f4ccad0f248c4cd5a4bd33032d7286abc22" dependencies = [ "ansi_term", "lazy_static", @@ -3290,9 +3290,9 @@ checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" [[package]] name = "wasm-bindgen" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -3300,24 +3300,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" dependencies = [ "bumpalo", "lazy_static", "log", "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", + "quote 1.0.15", + "syn 1.0.86", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39" +checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -3327,38 +3327,38 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" dependencies = [ - "quote 1.0.14", + "quote 1.0.15", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" dependencies = [ "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", + "quote 1.0.15", + "syn 1.0.86", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" +checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" [[package]] name = "web-sys" -version = "0.3.55" +version = "0.3.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" +checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/mediarepo-ui/src-tauri/Cargo.toml b/mediarepo-ui/src-tauri/Cargo.toml index 52b21b8..afc9bec 100644 --- a/mediarepo-ui/src-tauri/Cargo.toml +++ b/mediarepo-ui/src-tauri/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "app" -version = "0.13.1" -description = "A Tauri App" +version = "0.13.2" +description = "The UI for the mediarepo media management tool" authors = ["you"] license = "" repository = "" diff --git a/mediarepo-ui/src-tauri/tauri.conf.json b/mediarepo-ui/src-tauri/tauri.conf.json index af9e242..668c202 100644 --- a/mediarepo-ui/src-tauri/tauri.conf.json +++ b/mediarepo-ui/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "package": { "productName": "mediarepo-ui", - "version": "0.13.1" + "version": "0.13.2" }, "build": { "distDir": "../dist/mediarepo-ui", @@ -73,4 +73,4 @@ "csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self' img-src: 'self' once: thumb: content:" } } -} \ No newline at end of file +} diff --git a/mediarepo-ui/src/api/Api.ts b/mediarepo-ui/src/api/Api.ts index 6faeec7..292cd8f 100644 --- a/mediarepo-ui/src/api/Api.ts +++ b/mediarepo-ui/src/api/Api.ts @@ -13,6 +13,7 @@ import { DeleteThumbnailsRequest, FindFilesRequest, GetFileMetadataRequest, + GetFileTagMapRequest, GetSizeRequest, GetTagsForFilesRequest, InitRepositoryRequest, @@ -28,7 +29,7 @@ import { UpdateFileStatusRequest } from "./api-types/requests"; import {RepositoryData, RepositoryMetadata, SizeMetadata} from "./api-types/repo"; -import {NamespaceData, TagData} from "./api-types/tags"; +import {CdTagMappings, NamespaceData, TagData} from "./api-types/tags"; import {ShortCache} from "./ShortCache"; export class MediarepoApi { @@ -146,6 +147,15 @@ export class MediarepoApi { ); } + public static async getFileTagMap(request: GetFileTagMapRequest): Promise { + return ShortCache.cached( + request, + () => this.invokePlugin(ApiFunction.GetFileTagMap, request), + 1000, + "getFileTagMap" + ); + } + public static async createTags(request: CreateTagsRequest): Promise { return this.invokePlugin(ApiFunction.CreateTags, request); } diff --git a/mediarepo-ui/src/api/api-types/functions.ts b/mediarepo-ui/src/api/api-types/functions.ts index 33a5140..5f61ed0 100644 --- a/mediarepo-ui/src/api/api-types/functions.ts +++ b/mediarepo-ui/src/api/api-types/functions.ts @@ -29,6 +29,7 @@ export enum ApiFunction { GetAllTags = "get_all_tags", GetAllNamespace = "get_all_namespaces", GetTagsForFiles = "get_tags_for_files", + GetFileTagMap = "get_file_tag_map", CreateTags = "create_tags", ChangeFileTags = "change_file_tags", // import diff --git a/mediarepo-ui/src/api/api-types/job.ts b/mediarepo-ui/src/api/api-types/job.ts index c61458a..cda973f 100644 --- a/mediarepo-ui/src/api/api-types/job.ts +++ b/mediarepo-ui/src/api/api-types/job.ts @@ -1,3 +1,4 @@ export type JobType = "MigrateContentDescriptors" | "CalculateSizes" - | "CheckIntegrity"; + | "CheckIntegrity" + | "Vacuum"; diff --git a/mediarepo-ui/src/api/api-types/requests.ts b/mediarepo-ui/src/api/api-types/requests.ts index 5a261b5..c6c0d24 100644 --- a/mediarepo-ui/src/api/api-types/requests.ts +++ b/mediarepo-ui/src/api/api-types/requests.ts @@ -73,6 +73,10 @@ export type GetTagsForFilesRequest = { cds: string[] }; +export type GetFileTagMapRequest = { + cds: string[] +}; + export type CreateTagsRequest = { tags: string[] }; diff --git a/mediarepo-ui/src/api/api-types/tags.ts b/mediarepo-ui/src/api/api-types/tags.ts index 6b81aba..fcd8d8f 100644 --- a/mediarepo-ui/src/api/api-types/tags.ts +++ b/mediarepo-ui/src/api/api-types/tags.ts @@ -8,3 +8,7 @@ export type NamespaceData = { id: number, name: string, }; + +export type CdTagMappings = { + [key: string]: TagData[], +}; diff --git a/mediarepo-ui/src/app/app.component.scss b/mediarepo-ui/src/app/app.component.scss index 8b79f48..bfa0fc0 100644 --- a/mediarepo-ui/src/app/app.component.scss +++ b/mediarepo-ui/src/app/app.component.scss @@ -1,3 +1,5 @@ +@import "src/colors"; + ::ng-deep .mat-button-wrapper > ng-icon { font-size: 26px; } @@ -5,3 +7,12 @@ ::ng-deep ng-icon { font-size: 24px; } + +::ng-deep .app-warn { + background-color: $warn-chill; +} + +::ng-deep .app-error { + background-color: $warn; + color: $text; +} diff --git a/mediarepo-ui/src/app/app.component.ts b/mediarepo-ui/src/app/app.component.ts index 3885fcc..0665228 100644 --- a/mediarepo-ui/src/app/app.component.ts +++ b/mediarepo-ui/src/app/app.component.ts @@ -1,7 +1,9 @@ import {Component, OnInit} from "@angular/core"; import {RepositoryService} from "./services/repository/repository.service"; import {MatSnackBar} from "@angular/material/snack-bar"; -import {ErrorBrokerService} from "./services/error-broker/error-broker.service"; +import {LoggingService} from "./services/logging/logging.service"; +import {LogEntry, LogLevel} from "./services/logging/LogEntry"; +import {environment} from "../environments/environment"; @Component({ selector: "app-root", @@ -13,21 +15,39 @@ export class AppComponent implements OnInit { constructor( private snackBar: MatSnackBar, - private errorBroker: ErrorBrokerService, + private logger: LoggingService, private repoService: RepositoryService, ) { } async ngOnInit() { - this.errorBroker.errorCb = (err: { message: string }) => this.showError( - err); - this.errorBroker.infoCb = (info: string) => this.showInfo(info); + this.logger.logs.subscribe(entry => { + this.logEntry(entry); + switch (entry.getLevel()) { + case LogLevel.Info: + this.showInfo(entry.getMessage()); + break; + case LogLevel.Warn: + this.showWarning(entry.getMessage()); + break; + case LogLevel.Error: + this.showError(entry.getMessage()); + break; + } + }); await this.repoService.loadRepositories(); } - private showError(err: { message: string }) { - this.snackBar.open(err.message, undefined, { - panelClass: "warn", + private showError(err: string) { + this.snackBar.open(err, undefined, { + panelClass: "app-error", + duration: 2000, + }); + } + + private showWarning(err: string) { + this.snackBar.open(err, undefined, { + panelClass: "app-warn", duration: 2000, }); } @@ -38,4 +58,26 @@ export class AppComponent implements OnInit { duration: 2000, }); } + + private logEntry(entry: LogEntry) { + if (!environment.production) { + switch (entry.getLevel()) { + case LogLevel.Trace: + console.trace(entry.getMessage()); + break; + case LogLevel.Debug: + console.debug(entry.getMessage()); + break; + case LogLevel.Info: + console.info(entry.getMessage()); + break; + case LogLevel.Warn: + console.warn(entry.getMessage()); + break; + } + } + if (entry.getLevel() == LogLevel.Error) { + console.error(entry.getMessage(), entry.getError()); + } + } } diff --git a/mediarepo-ui/src/app/components/core/files-tab/files-tab.component.ts b/mediarepo-ui/src/app/components/core/files-tab/files-tab.component.ts index a64a857..7a588f3 100644 --- a/mediarepo-ui/src/app/components/core/files-tab/files-tab.component.ts +++ b/mediarepo-ui/src/app/components/core/files-tab/files-tab.component.ts @@ -30,6 +30,7 @@ export class FilesTabComponent implements OnInit { } else { this.state.selectedCD.next(undefined); } + console.debug(this.selectedFiles); } public getStateSelectedFile(): File | undefined { diff --git a/mediarepo-ui/src/app/components/core/repositories-tab/repositories-tab.component.ts b/mediarepo-ui/src/app/components/core/repositories-tab/repositories-tab.component.ts index c98d9ae..ecb4843 100644 --- a/mediarepo-ui/src/app/components/core/repositories-tab/repositories-tab.component.ts +++ b/mediarepo-ui/src/app/components/core/repositories-tab/repositories-tab.component.ts @@ -6,7 +6,7 @@ import {DownloadDaemonDialogComponent} from "./download-daemon-dialog/download-d import { AddRepositoryDialogComponent } from "../../shared/repository/repository/add-repository-dialog/add-repository-dialog.component"; -import {ErrorBrokerService} from "../../../services/error-broker/error-broker.service"; +import {LoggingService} from "../../../services/logging/logging.service"; import {BehaviorSubject} from "rxjs"; import {BusyDialogComponent} from "../../shared/app-common/busy-dialog/busy-dialog.component"; import {JobService} from "../../../services/job/job.service"; @@ -24,7 +24,7 @@ export class RepositoriesTabComponent implements OnInit, AfterViewInit { public selectedRepository?: Repository; constructor( - private errorBroker: ErrorBrokerService, + private logger: LoggingService, private repoService: RepositoryService, private jobService: JobService, private stateService: StateService, @@ -60,8 +60,8 @@ export class RepositoriesTabComponent implements OnInit, AfterViewInit { }); } await this.selectRepository(repository, dialogContext); - } catch (err) { - this.errorBroker.showError(err); + } catch (err: any) { + this.logger.error(err); } } @@ -75,7 +75,7 @@ export class RepositoriesTabComponent implements OnInit, AfterViewInit { await this.repoService.loadRepositories(); dialogContext.dialog.close(true); } catch (err: any) { - this.errorBroker.showError(err); + this.logger.error(err); dialogContext.message.next( "Failed to open repository: " + err.toString()); await this.forceCloseRepository(); @@ -111,13 +111,15 @@ export class RepositoriesTabComponent implements OnInit, AfterViewInit { } private async runRepositoryStartupTasks(dialogContext: BusyDialogContext): Promise { + dialogContext.message.next("Checking integrity..."); + await this.jobService.runJob("CheckIntegrity"); + dialogContext.message.next("Running a vacuum on the database..."); + await this.jobService.runJob("Vacuum"); dialogContext.message.next( "Migrating content descriptors to new format..."); await this.jobService.runJob("MigrateContentDescriptors"); dialogContext.message.next("Calculating repository sizes..."); await this.jobService.runJob("CalculateSizes"); - dialogContext.message.next("Checking integrity..."); - await this.jobService.runJob("CheckIntegrity"); dialogContext.message.next("Finished repository startup"); } diff --git a/mediarepo-ui/src/app/components/shared/app-base/file-action-base/file-action-base.component.ts b/mediarepo-ui/src/app/components/shared/app-base/file-action-base/file-action-base.component.ts index 1063fc6..e912a41 100644 --- a/mediarepo-ui/src/app/components/shared/app-base/file-action-base/file-action-base.component.ts +++ b/mediarepo-ui/src/app/components/shared/app-base/file-action-base/file-action-base.component.ts @@ -9,7 +9,7 @@ import {BehaviorSubject} from "rxjs"; import {BusyDialogComponent} from "../../app-common/busy-dialog/busy-dialog.component"; import {ConfirmDialogComponent, ConfirmDialogData} from "../../app-common/confirm-dialog/confirm-dialog.component"; import {MatDialog, MatDialogConfig, MatDialogRef} from "@angular/material/dialog"; -import {ErrorBrokerService} from "../../../../services/error-broker/error-broker.service"; +import {LoggingService} from "../../../../services/logging/logging.service"; type ProgressDialogContext = { dialog: MatDialogRef, @@ -22,7 +22,7 @@ type ProgressDialogContext = { template: "

Do not use

", }) export class FileActionBaseComponent { - constructor(private dialog: MatDialog, private errorBroker: ErrorBrokerService, private fileService: FileService) { + constructor(private dialog: MatDialog, private errorBroker: LoggingService, private fileService: FileService) { } public async copyFileContentDescriptor(file: File): Promise { diff --git a/mediarepo-ui/src/app/components/shared/app-common/busy-dialog/busy-dialog.component.ts b/mediarepo-ui/src/app/components/shared/app-common/busy-dialog/busy-dialog.component.ts index 2e330b4..d422f31 100644 --- a/mediarepo-ui/src/app/components/shared/app-common/busy-dialog/busy-dialog.component.ts +++ b/mediarepo-ui/src/app/components/shared/app-common/busy-dialog/busy-dialog.component.ts @@ -23,13 +23,18 @@ export class BusyDialogComponent { public progress = 0; public mode: ProgressBarMode = "indeterminate"; - constructor(public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) data: BusyDialogData) { + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) data: BusyDialogData + ) { this.title = data.title; if (data.message) { data.message.subscribe(m => this.message = m); } if (data.progress) { - data.progress.subscribe(p => this.progress = p); + data.progress.subscribe(p => { + this.progress = Math.floor(p * 100); + }); this.mode = "determinate"; } this.allowCancel = data.allowCancel ?? false; diff --git a/mediarepo-ui/src/app/components/shared/file/content-viewer/content-viewer.component.ts b/mediarepo-ui/src/app/components/shared/file/content-viewer/content-viewer.component.ts index 2f52ce5..cc83300 100644 --- a/mediarepo-ui/src/app/components/shared/file/content-viewer/content-viewer.component.ts +++ b/mediarepo-ui/src/app/components/shared/file/content-viewer/content-viewer.component.ts @@ -12,7 +12,7 @@ import {SafeResourceUrl} from "@angular/platform-browser"; import {File} from "../../../../../api/models/File"; import {FileService} from "../../../../services/file/file.service"; import {FileHelper} from "../../../../services/file/file.helper"; -import {ErrorBrokerService} from "../../../../services/error-broker/error-broker.service"; +import {LoggingService} from "../../../../services/logging/logging.service"; import {BusyIndicatorComponent} from "../../app-common/busy-indicator/busy-indicator.component"; type ContentType = "image" | "video" | "audio" | "other"; @@ -33,7 +33,7 @@ export class ContentViewerComponent implements AfterViewInit, OnChanges, OnDestr @ViewChild(BusyIndicatorComponent) busyIndicator!: BusyIndicatorComponent; constructor( - private errorBroker: ErrorBrokerService, + private errorBroker: LoggingService, private fileService: FileService ) { } @@ -70,8 +70,8 @@ export class ContentViewerComponent implements AfterViewInit, OnChanges, OnDestr if (path) { try { await this.fileService.saveFile(this.file, path); - } catch (err) { - this.errorBroker.showError(err); + } catch (err: any) { + this.errorBroker.error(err); } } } diff --git a/mediarepo-ui/src/app/components/shared/file/file-context-menu/file-context-menu.component.ts b/mediarepo-ui/src/app/components/shared/file/file-context-menu/file-context-menu.component.ts index 83040bc..90a2151 100644 --- a/mediarepo-ui/src/app/components/shared/file/file-context-menu/file-context-menu.component.ts +++ b/mediarepo-ui/src/app/components/shared/file/file-context-menu/file-context-menu.component.ts @@ -2,19 +2,11 @@ import {Component, EventEmitter, OnChanges, Output, SimpleChanges, ViewChild} fr import {File} from "../../../../../api/models/File"; import {ContextMenuComponent} from "../../app-common/context-menu/context-menu.component"; import {FileService} from "../../../../services/file/file.service"; -import {ErrorBrokerService} from "../../../../services/error-broker/error-broker.service"; -import {MatDialog, MatDialogRef} from "@angular/material/dialog"; -import {BusyDialogComponent} from "../../app-common/busy-dialog/busy-dialog.component"; -import {BehaviorSubject} from "rxjs"; +import {LoggingService} from "../../../../services/logging/logging.service"; +import {MatDialog} from "@angular/material/dialog"; import {FileActionBaseComponent} from "../../app-base/file-action-base/file-action-base.component"; import {FileStatus} from "../../../../../api/api-types/files"; -type ProgressDialogContext = { - dialog: MatDialogRef, - progress: BehaviorSubject, - message: BehaviorSubject, -}; - @Component({ selector: "app-file-context-menu", templateUrl: "./file-context-menu.component.html", @@ -34,7 +26,7 @@ export class FileContextMenuComponent extends FileActionBaseComponent implements @Output() fileDeleted = new EventEmitter(); @Output() fileStatusChange = new EventEmitter(); - constructor(fileService: FileService, errorBroker: ErrorBrokerService, dialog: MatDialog) { + constructor(fileService: FileService, errorBroker: LoggingService, dialog: MatDialog) { super(dialog, errorBroker, fileService); } diff --git a/mediarepo-ui/src/app/components/shared/file/file-multiview/file-grid/file-grid.component.html b/mediarepo-ui/src/app/components/shared/file/file-multiview/file-grid/file-grid.component.html index 13dafcc..be22ac3 100644 --- a/mediarepo-ui/src/app/components/shared/file/file-multiview/file-grid/file-grid.component.html +++ b/mediarepo-ui/src/app/components/shared/file/file-multiview/file-grid/file-grid.component.html @@ -5,7 +5,7 @@ class="file-gallery-inner"> -
+
- +