Merge pull request #13 from Trivernis/develop

Version 0.13.4
main v0.13.4
Julius Riegel 2 years ago committed by GitHub
commit a7da504b10
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,52 +1,52 @@
[package]
name = "mediarepo-api"
version = "0.28.0"
version = "0.28.1"
edition = "2018"
license = "gpl-3"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
tracing = "^0.1.29"
thiserror = "^1.0.30"
async-trait = {version = "^0.1.52", optional=true}
parking_lot = {version="^0.11.2", optional=true}
serde_json = {version="^1.0.73", optional=true}
directories = {version="^4.0.1", optional=true}
mime_guess = {version = "^2.0.3", optional=true}
serde_piecewise_default = "^0.2.0"
futures = {version = "^0.3.19", optional=true}
url = {version = "^2.2.2", optional=true }
pathsearch = {version="^0.2.0", optional=true}
tracing = "0.1.30"
thiserror = "1.0.30"
async-trait = { version = "0.1.52", optional = true }
parking_lot = { version = "0.12.0", optional = true }
serde_json = { version = "1.0.78", optional = true }
directories = { version = "4.0.1", optional = true }
mime_guess = { version = "2.0.3", optional = true }
serde_piecewise_default = "0.2.0"
futures = { version = "0.3.19", optional = true }
url = { version = "2.2.2", optional = true }
pathsearch = { version = "0.2.0", optional = true }
[dependencies.bromine]
version = "^0.17.1"
version = "0.17.1"
optional = true
features = ["serialize_bincode"]
[dependencies.serde]
version = "^1.0.132"
version = "1.0.136"
features = ["serde_derive"]
[dependencies.chrono]
version = "^0.4.19"
version = "0.4.19"
features = ["serde"]
[dependencies.tauri]
version = "^1.0.0-beta.8"
version = "1.0.0-beta.8"
optional=true
default-features = false
features = []
[dependencies.tokio]
version = "^1.15.0"
version = "1.16.1"
optional = true
features = ["sync", "fs", "net", "io-util", "io-std", "time", "rt", "process"]
[dependencies.toml]
version = "^0.5.8"
version = "0.5.8"
optional = true
[features]
tauri-plugin = ["client-api","tauri", "parking_lot", "serde_json", "tokio", "toml", "directories", "mime_guess", "futures", "url"]
client-api = ["bromine", "async-trait", "tokio", "pathsearch"]
client-api = ["bromine", "async-trait", "tokio", "pathsearch"]

@ -4,6 +4,7 @@ pub mod job;
pub mod protocol;
pub mod repo;
pub mod tag;
pub mod preset;
use crate::client_api::error::{ApiError, ApiResult};
use crate::client_api::file::FileApi;
@ -15,6 +16,7 @@ use async_trait::async_trait;
use bromine::ipc::stream_emitter::EmitMetadata;
use bromine::prelude::*;
use tokio::time::Duration;
use crate::client_api::preset::PresetApi;
#[async_trait]
pub trait IPCApi {
@ -48,6 +50,7 @@ pub struct ApiClient {
pub tag: TagApi,
pub repo: RepoApi,
pub job: JobApi,
pub preset: PresetApi,
}
impl Clone for ApiClient {
@ -58,6 +61,7 @@ impl Clone for ApiClient {
tag: self.tag.clone(),
repo: self.repo.clone(),
job: self.job.clone(),
preset: self.preset.clone(),
}
}
}
@ -70,6 +74,7 @@ impl ApiClient {
tag: TagApi::new(ctx.clone()),
repo: RepoApi::new(ctx.clone()),
job: JobApi::new(ctx.clone()),
preset: PresetApi::new(ctx.clone()),
ctx,
}
}

@ -0,0 +1,54 @@
use std::time::Duration;
use bromine::prelude::*;
use crate::client_api::error::ApiResult;
use crate::types::filtering::{SortingPreset, SortKey};
use super::IPCApi;
#[derive(Clone)]
pub struct PresetApi {
ctx: PooledContext,
}
impl IPCApi for PresetApi {
fn namespace() -> &'static str {
"presets"
}
fn ctx(&self) -> PoolGuard<Context> {
self.ctx.acquire()
}
}
impl PresetApi {
pub fn new(ctx: PooledContext) -> Self {
Self { ctx }
}
/// Returns all sorting presets of the repository
#[tracing::instrument(level = "debug", skip(self))]
pub async fn all_sorting_presets(&self) -> ApiResult<Vec<SortingPreset>> {
self.emit_and_get(
"all_sorting_presets",
(),
Some(Duration::from_secs(1))
)
.await
}
/// Adds a new sorting preset with the given keys
#[tracing::instrument(level = "debug", skip(self))]
pub async fn add_sorting_preset(&self, keys: Vec<SortKey>) -> ApiResult<SortingPreset> {
self.emit_and_get(
"add_sorting_preset",
keys,
Some(Duration::from_secs(1))
)
.await
}
/// Deletes a given sorting preset by id
#[tracing::instrument(level = "debug", skip(self))]
pub async fn delete_sorting_preset(&self, id: i32) -> ApiResult<()> {
self.emit_and_get("delete_sorting_preset", id, Some(Duration::from_secs(1))).await
}
}

@ -5,6 +5,7 @@ pub use file::*;
pub use job::*;
pub use repo::*;
pub use tag::*;
pub use preset::*;
use crate::tauri_plugin::state::{ApiState, AppState, BufferState};
@ -13,6 +14,7 @@ pub mod file;
pub mod job;
pub mod repo;
pub mod tag;
pub mod preset;
pub type ApiAccess<'a> = State<'a, ApiState>;
pub type AppAccess<'a> = State<'a, AppState>;

@ -0,0 +1,27 @@
use crate::tauri_plugin::commands::ApiAccess;
use crate::tauri_plugin::error::PluginResult;
use crate::types::filtering::{SortingPreset, SortKey};
#[tauri::command]
pub async fn all_sorting_presets(api_state: ApiAccess<'_>) -> PluginResult<Vec<SortingPreset>> {
let api = api_state.api().await?;
let presets = api.preset.all_sorting_presets().await?;
Ok(presets)
}
#[tauri::command]
pub async fn add_sorting_preset(api_state: ApiAccess<'_>, sort_keys: Vec<SortKey>) -> PluginResult<SortingPreset> {
let api = api_state.api().await?;
let preset = api.preset.add_sorting_preset(sort_keys).await?;
Ok(preset)
}
#[tauri::command]
pub async fn delete_sorting_preset(api_state: ApiAccess<'_>, id: i32) -> PluginResult<()> {
let api = api_state.api().await?;
api.preset.delete_sorting_preset(id).await?;
Ok(())
}

@ -70,7 +70,10 @@ impl<R: Runtime> MediarepoPlugin<R> {
run_job,
update_file_status,
delete_file,
get_file_tag_map
get_file_tag_map,
all_sorting_presets,
add_sorting_preset,
delete_sorting_preset
]),
}
}

@ -71,3 +71,9 @@ pub enum SortDirection {
}
impl Eq for SortDirection {}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SortingPreset {
pub id: i32,
pub keys: Vec<SortKey>,
}

@ -202,6 +202,12 @@ dependencies = [
"shlex",
]
[[package]]
name = "bit_field"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4"
[[package]]
name = "bitflags"
version = "1.3.2"
@ -210,23 +216,23 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "blake2b_simd"
version = "0.5.11"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587"
checksum = "72936ee4afc7f8f736d1c38383b56480b5497b4617b4a77bdbf1d2ababc76127"
dependencies = [
"arrayref",
"arrayvec 0.5.2",
"arrayvec 0.7.2",
"constant_time_eq",
]
[[package]]
name = "blake2s_simd"
version = "0.5.11"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e461a7034e85b211a4acb57ee2e6730b32912b06c08cc242243c39fc21ae6a2"
checksum = "db539cc2b5f6003621f1cd9ef92d7ded8ea5232c7de0f9faa2de251cd98730d4"
dependencies = [
"arrayref",
"arrayvec 0.5.2",
"arrayvec 0.7.2",
"constant_time_eq",
]
@ -249,15 +255,17 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
dependencies = [
"block-padding",
"generic-array",
]
[[package]]
name = "block-padding"
version = "0.2.1"
name = "block-buffer"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
checksum = "03588e54c62ae6d763e2a80090d50353b785795361b4ff5b3bf0a5097fc31c0b"
dependencies = [
"generic-array",
]
[[package]]
name = "bromine"
@ -277,6 +285,12 @@ dependencies = [
"typemap_rev",
]
[[package]]
name = "bumpalo"
version = "3.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899"
[[package]]
name = "bytemuck"
version = "1.7.3"
@ -395,9 +409,9 @@ dependencies = [
[[package]]
name = "console-api"
version = "0.1.1"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14f67643a7d716307ad10b3e3aef02826382acbe349a3e7605ac57556148bc87"
checksum = "cc347c19eb5b940f396ac155822caee6662f850d97306890ac3773ed76c90c5a"
dependencies = [
"prost",
"prost-types",
@ -450,6 +464,15 @@ version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]]
name = "core2"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505"
dependencies = [
"memchr",
]
[[package]]
name = "cpufeatures"
version = "0.2.1"
@ -490,7 +513,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa"
dependencies = [
"cfg-if 1.0.0",
"crossbeam-utils 0.8.6",
"crossbeam-utils 0.8.7",
]
[[package]]
@ -501,17 +524,17 @@ checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e"
dependencies = [
"cfg-if 1.0.0",
"crossbeam-epoch",
"crossbeam-utils 0.8.6",
"crossbeam-utils 0.8.7",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.6"
version = "0.9.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97242a70df9b89a65d0b6df3c4bf5b9ce03c5b7309019777fbde37e7537f8762"
checksum = "c00d6d2ea26e8b151d99093005cb442fb9a37aeaca582a03ec70946f49ab5ed9"
dependencies = [
"cfg-if 1.0.0",
"crossbeam-utils 0.8.6",
"crossbeam-utils 0.8.7",
"lazy_static",
"memoffset",
"scopeguard",
@ -519,12 +542,12 @@ dependencies = [
[[package]]
name = "crossbeam-queue"
version = "0.3.3"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b979d76c9fcb84dffc80a73f7290da0f83e4c95773494674cb44b76d13a7a110"
checksum = "4dd435b205a4842da59efd07628f921c096bc1cc0a156835b4fa0bcb9a19bcce"
dependencies = [
"cfg-if 1.0.0",
"crossbeam-utils 0.8.6",
"crossbeam-utils 0.8.7",
]
[[package]]
@ -540,14 +563,23 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
version = "0.8.6"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcae03edb34f947e64acdb1c33ec169824e20657e9ecb61cef6c8c74dcb8120"
checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6"
dependencies = [
"cfg-if 1.0.0",
"lazy_static",
]
[[package]]
name = "crypto-common"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d6b536309245c849479fba3da410962a43ed8e51c26b729208ec0ac2798d0"
dependencies = [
"generic-array",
]
[[package]]
name = "data-encoding"
version = "2.3.2"
@ -576,12 +608,20 @@ dependencies = [
[[package]]
name = "deflate"
version = "0.8.6"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174"
checksum = "5f95bf05dffba6e6cce8dfbb30def788154949ccd9aed761b472119c21e01c70"
dependencies = [
"adler32",
]
[[package]]
name = "deflate"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c86f7e25f518f4b81808a2cf1c50996a61f5c2eb394b2393bd87f2a4780a432f"
dependencies = [
"adler32",
"byteorder",
]
[[package]]
@ -593,6 +633,17 @@ dependencies = [
"generic-array",
]
[[package]]
name = "digest"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b697d66081d42af4fba142d56918a3cb21dc8eb63372c6b85d14f44fb9c5979b"
dependencies = [
"block-buffer 0.10.1",
"crypto-common",
"generic-array",
]
[[package]]
name = "dotenv"
version = "0.15.0"
@ -605,6 +656,86 @@ version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]]
name = "encoding"
version = "0.2.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec"
dependencies = [
"encoding-index-japanese",
"encoding-index-korean",
"encoding-index-simpchinese",
"encoding-index-singlebyte",
"encoding-index-tradchinese",
]
[[package]]
name = "encoding-index-japanese"
version = "1.20141219.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91"
dependencies = [
"encoding_index_tests",
]
[[package]]
name = "encoding-index-korean"
version = "1.20141219.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81"
dependencies = [
"encoding_index_tests",
]
[[package]]
name = "encoding-index-simpchinese"
version = "1.20141219.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7"
dependencies = [
"encoding_index_tests",
]
[[package]]
name = "encoding-index-singlebyte"
version = "1.20141219.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a"
dependencies = [
"encoding_index_tests",
]
[[package]]
name = "encoding-index-tradchinese"
version = "1.20141219.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18"
dependencies = [
"encoding_index_tests",
]
[[package]]
name = "encoding_index_tests"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569"
[[package]]
name = "exr"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4badb9489a465cb2c555af1f00f0bfd8cecd6fc12ac11da9d5b40c5dd5f0200"
dependencies = [
"bit_field",
"deflate 1.0.0",
"flume",
"half",
"inflate",
"lebe",
"smallvec",
"threadpool",
]
[[package]]
name = "fastrand"
version = "1.7.0"
@ -654,7 +785,7 @@ dependencies = [
"cfg-if 1.0.0",
"crc32fast",
"libc",
"miniz_oxide 0.4.4",
"miniz_oxide",
]
[[package]]
@ -665,6 +796,7 @@ checksum = "5d04dafd11240188e146b6f6476a898004cace3be31d4ec5e08e216bf4947ac0"
dependencies = [
"futures-core",
"futures-sink",
"nanorand",
"pin-project",
"spin",
]
@ -823,8 +955,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c"
dependencies = [
"cfg-if 1.0.0",
"js-sys",
"libc",
"wasi",
"wasm-bindgen",
]
[[package]]
@ -862,6 +996,12 @@ dependencies = [
"tracing",
]
[[package]]
name = "half"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
[[package]]
name = "hashbrown"
version = "0.11.2"
@ -1006,15 +1146,16 @@ dependencies = [
[[package]]
name = "image"
version = "0.23.14"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1"
checksum = "e94ac3d41f882c624a82d7945952032388488681f45f9d4077999a6c85688d61"
dependencies = [
"bytemuck",
"byteorder",
"color_quant",
"exr",
"gif",
"jpeg-decoder",
"jpeg-decoder 0.2.1",
"num-iter",
"num-rational",
"num-traits 0.2.14",
@ -1033,6 +1174,15 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "inflate"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cdb29978cc5797bd8dcc8e5bf7de604891df2a8dc576973d71a281e916db2ff"
dependencies = [
"adler32",
]
[[package]]
name = "instant"
version = "0.1.12"
@ -1077,10 +1227,25 @@ name = "jpeg-decoder"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2"
[[package]]
name = "jpeg-decoder"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbcf0244f6597be39ab8d9203f574cafb529ae8c698afa2182f7b3c3205a4a9c"
dependencies = [
"rayon",
]
[[package]]
name = "js-sys"
version = "0.3.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "keccak"
version = "0.1.0"
@ -1099,6 +1264,12 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "lebe"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7efd1d698db0759e6ef11a7cd44407407399a910c774dd804c64c032da7826ff"
[[package]]
name = "lexical-core"
version = "0.7.6"
@ -1114,9 +1285,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.116"
version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "565dbd88872dbe4cc8a46e527f26483c1d1f7afa6b884a3bd6cd893d4f98da74"
checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c"
[[package]]
name = "libloading"
@ -1141,9 +1312,9 @@ dependencies = [
[[package]]
name = "libwebp-sys"
version = "0.2.0"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e70c064738b35a28fd6f991d27c0d9680353641d167ae3702a8228dd8272ef6"
checksum = "439fd1885aa28937e7edcd68d2e793cb4a22f8733460d2519fbafd2b215672bf"
dependencies = [
"cc",
]
@ -1189,7 +1360,7 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]]
name = "mediarepo-api"
version = "0.28.0"
version = "0.28.1"
dependencies = [
"bromine",
"chrono",
@ -1226,7 +1397,7 @@ dependencies = [
[[package]]
name = "mediarepo-daemon"
version = "0.13.3"
version = "0.13.4"
dependencies = [
"console-subscriber",
"glob",
@ -1328,15 +1499,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435"
dependencies = [
"adler32",
]
[[package]]
name = "miniz_oxide"
version = "0.4.4"
@ -1382,26 +1544,26 @@ dependencies = [
[[package]]
name = "multihash"
version = "0.15.0"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49e540106213dc639fe2b632a7d9e3525169ef081378a7c2da4457b84fec44c0"
checksum = "7392bffd88bc0c4f8297e36a777ab9f80b7127409c4a1acb8fee99c9f27addcd"
dependencies = [
"blake2b_simd",
"blake2s_simd",
"blake3",
"digest",
"generic-array",
"core2",
"digest 0.10.1",
"multihash-derive",
"sha2",
"sha2 0.10.1",
"sha3",
"unsigned-varint",
]
[[package]]
name = "multihash-derive"
version = "0.7.2"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "424f6e86263cd5294cbd7f1e95746b95aca0e0d66bff31e5a40d6baa87b4aa99"
checksum = "fc076939022111618a5026d3be019fd8b366e76314538ff9a1b59ffbcbf98bcd"
dependencies = [
"proc-macro-crate",
"proc-macro-error",
@ -1417,6 +1579,15 @@ version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a"
[[package]]
name = "nanorand"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "729eb334247daa1803e0a094d0a5c55711b85571179f5ec6e53eccfdf7008958"
dependencies = [
"getrandom",
]
[[package]]
name = "native-tls"
version = "0.2.8"
@ -1500,9 +1671,9 @@ dependencies = [
[[package]]
name = "num-rational"
version = "0.3.2"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a"
dependencies = [
"autocfg",
"num-integer",
@ -1702,14 +1873,15 @@ checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe"
[[package]]
name = "png"
version = "0.16.8"
version = "0.17.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6"
checksum = "c845088517daa61e8a57eee40309347cea13f273694d1385c553e7a57127763b"
dependencies = [
"bitflags",
"crc32fast",
"deflate",
"miniz_oxide 0.3.7",
"deflate 0.9.1",
"encoding",
"miniz_oxide",
]
[[package]]
@ -1907,7 +2079,7 @@ checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-utils 0.8.6",
"crossbeam-utils 0.8.7",
"lazy_static",
"num_cpus",
]
@ -1973,9 +2145,9 @@ checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2"
[[package]]
name = "rust_decimal"
version = "1.20.0"
version = "1.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0593ce4677e3800ddafb3de917e8397b1348e06e688128ade722d88fbe11ebf"
checksum = "4214023b1223d02a4aad9f0bb9828317634a56530870a2eaf7200a99c0c10f68"
dependencies = [
"arrayvec 0.7.2",
"num-traits 0.2.14",
@ -2110,9 +2282,9 @@ dependencies = [
[[package]]
name = "security-framework"
version = "2.6.0"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fed7948b6c68acbb6e20c334f55ad635dc0f75506963de4464289fbd3b051ac"
checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc"
dependencies = [
"bitflags",
"core-foundation",
@ -2123,9 +2295,9 @@ dependencies = [
[[package]]
name = "security-framework-sys"
version = "2.6.0"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a57321bf8bc2362081b2599912d2961fe899c0efadf1b4b2f8d48b3e253bb96c"
checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556"
dependencies = [
"core-foundation-sys",
"libc",
@ -2209,23 +2381,32 @@ version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800"
dependencies = [
"block-buffer",
"block-buffer 0.9.0",
"cfg-if 1.0.0",
"cpufeatures",
"digest",
"digest 0.9.0",
"opaque-debug",
]
[[package]]
name = "sha2"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99c3bd8169c58782adad9290a9af5939994036b76187f7b4f0e6de91dbbfc0ec"
dependencies = [
"cfg-if 1.0.0",
"cpufeatures",
"digest 0.10.1",
]
[[package]]
name = "sha3"
version = "0.9.1"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809"
checksum = "31f935e31cf406e8c0e96c2815a5516181b7004ae8c5f296293221e9b1e356bd"
dependencies = [
"block-buffer",
"digest",
"digest 0.10.1",
"keccak",
"opaque-debug",
]
[[package]]
@ -2319,7 +2500,7 @@ dependencies = [
"crc",
"crossbeam-channel",
"crossbeam-queue",
"crossbeam-utils 0.8.6",
"crossbeam-utils 0.8.7",
"either",
"flume",
"futures-channel",
@ -2342,7 +2523,7 @@ dependencies = [
"rust_decimal",
"serde 1.0.136",
"serde_json",
"sha2",
"sha2 0.9.9",
"smallvec",
"sqlformat",
"sqlx-rt",
@ -2366,7 +2547,7 @@ dependencies = [
"proc-macro2 1.0.36",
"quote 1.0.15",
"serde_json",
"sha2",
"sha2 0.9.9",
"sqlx-core",
"sqlx-rt",
"syn 1.0.86",
@ -2523,11 +2704,20 @@ dependencies = [
"once_cell",
]
[[package]]
name = "threadpool"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
dependencies = [
"num_cpus",
]
[[package]]
name = "thumbnailer"
version = "0.2.5"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6017341c89a0c406e38801119f67dd0b67d045ff0e50aa2cf8fc1de4a1b48c3b"
checksum = "be2c8e83220af2d58ff05f00dd28ceeef896e831d263014fd1229099f2d954be"
dependencies = [
"ffmpeg-next",
"image",
@ -2539,12 +2729,12 @@ dependencies = [
[[package]]
name = "tiff"
version = "0.6.1"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437"
checksum = "0247608e998cb6ce39dfc8f4a16c50361ce71e5b52e6d24ea1227ea8ea8ee0b2"
dependencies = [
"jpeg-decoder",
"miniz_oxide 0.4.4",
"flate2",
"jpeg-decoder 0.1.22",
"weezl",
]
@ -2771,9 +2961,9 @@ checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6"
[[package]]
name = "tracing"
version = "0.1.29"
version = "0.1.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105"
checksum = "2d8d93354fe2a8e50d5953f5ae2e47a3fc2ef03292e7ea46e3cc38f549525fb9"
dependencies = [
"cfg-if 1.0.0",
"log",
@ -2795,9 +2985,9 @@ dependencies = [
[[package]]
name = "tracing-attributes"
version = "0.1.18"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e"
checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716"
dependencies = [
"proc-macro2 1.0.36",
"quote 1.0.15",
@ -2806,11 +2996,12 @@ dependencies = [
[[package]]
name = "tracing-core"
version = "0.1.21"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4"
checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23"
dependencies = [
"lazy_static",
"valuable",
]
[[package]]
@ -2848,9 +3039,9 @@ dependencies = [
[[package]]
name = "tracing-serde"
version = "0.1.2"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb65ea441fbb84f9f6748fd496cf7f63ec9af5bca94dd86456978d055e8eb28b"
checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1"
dependencies = [
"serde 1.0.136",
"tracing-core",
@ -2858,9 +3049,9 @@ dependencies = [
[[package]]
name = "tracing-subscriber"
version = "0.3.7"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5312f325fe3588e277415f5a6cca1f4ccad0f248c4cd5a4bd33032d7286abc22"
checksum = "74786ce43333fcf51efe947aed9718fbe46d5c7328ec3f1029e818083966d9aa"
dependencies = [
"ansi_term",
"lazy_static",
@ -2977,6 +3168,12 @@ dependencies = [
"serde 1.0.136",
]
[[package]]
name = "valuable"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "vcpkg"
version = "0.2.15"
@ -3011,11 +3208,65 @@ 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.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06"
dependencies = [
"cfg-if 1.0.0",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca"
dependencies = [
"bumpalo",
"lazy_static",
"log",
"proc-macro2 1.0.36",
"quote 1.0.15",
"syn 1.0.86",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01"
dependencies = [
"quote 1.0.15",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc"
dependencies = [
"proc-macro2 1.0.36",
"quote 1.0.15",
"syn 1.0.86",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2"
[[package]]
name = "webp"
version = "0.2.0"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0041a9fcbdbf6402c3cb0ec7c2c1a1c240efd523519a0763ae07753bc95f7713"
checksum = "3f80f6a431ea17cbe9d6958628e553c17d22df62b301b39940a9dfd60f3dd7c6"
dependencies = [
"image",
"libwebp-sys",

@ -4,7 +4,7 @@ default-members = ["mediarepo-core", "mediarepo-database", "mediarepo-logic", "m
[package]
name = "mediarepo-daemon"
version = "0.13.3"
version = "0.13.4"
edition = "2018"
license = "gpl-3"
repository = "https://github.com/Trivernis/mediarepo-daemon"
@ -16,17 +16,17 @@ name = "mediarepo-daemon"
path = "src/main.rs"
[dependencies]
tracing = "^0.1.29"
toml = "^0.5.8"
structopt ="^0.3.25"
glob = "^0.3.0"
tracing-flame = "^0.2.0"
tracing-appender = "^0.2.0"
tracing-log = "^0.1.2"
rolling-file = "^0.1.0"
num-integer = "^0.1.44"
console-subscriber = "^0.1.0"
log = "^0.4.14"
tracing = "0.1.30"
toml = "0.5.8"
structopt = "0.3.26"
glob = "0.3.0"
tracing-flame = "0.2.0"
tracing-appender = "0.2.0"
tracing-log = "0.1.2"
rolling-file = "0.1.0"
num-integer = "0.1.44"
console-subscriber = "0.1.1"
log = "0.4.14"
[dependencies.mediarepo-core]
path = "./mediarepo-core"
@ -38,13 +38,13 @@ path = "mediarepo-logic"
path = "./mediarepo-socket"
[dependencies.tokio]
version = "1.15.0"
version = "1.16.1"
features = ["macros", "rt-multi-thread", "io-std", "io-util"]
[dependencies.tracing-subscriber]
version="0.3.0"
version= "0.3.8"
features = ["env-filter", "ansi", "json"]
[features]
default = ["ffmpeg"]
ffmpeg = ["mediarepo-core/ffmpeg", "mediarepo-logic/ffmpeg"]
ffmpeg = ["mediarepo-core/ffmpeg", "mediarepo-logic/ffmpeg"]

@ -7,39 +7,39 @@ workspace = ".."
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
thiserror = "^1.0.30"
multihash = "^0.15.0"
multibase = "^0.9.1"
base64 = "^0.13.0"
toml = "^0.5.8"
serde = "^1.0.132"
typemap_rev = "^0.1.5"
futures = "^0.3.19"
itertools = "^0.10.3"
glob = "^0.3.0"
tracing = "^0.1.29"
data-encoding = "^2.3.2"
tokio-graceful-shutdown = "^0.4.3"
thiserror = "1.0.30"
multihash = "0.16.1"
multibase = "0.9.1"
base64 = "0.13.0"
toml = "0.5.8"
serde = "1.0.136"
typemap_rev = "0.1.5"
futures = "0.3.19"
itertools = "0.10.3"
glob = "0.3.0"
tracing = "0.1.30"
data-encoding = "2.3.2"
tokio-graceful-shutdown = "0.4.3"
[dependencies.thumbnailer]
version = "^0.2.5"
version = "0.3.0"
default-features = false
[dependencies.sea-orm]
version = "^0.5.0-rc.1"
version = "0.5.0"
default-features = false
[dependencies.sqlx]
version = "^0.5.9"
version = "0.5.10"
default-features = false
features = ["migrate"]
[dependencies.tokio]
version = "^1.15.0"
version = "1.16.1"
features = ["fs", "io-util", "io-std"]
[dependencies.config]
version = "^0.11.0"
version = "0.11.0"
features = ["toml"]
[dependencies.mediarepo-api]
@ -48,4 +48,4 @@ features = ["bromine"]
[features]
default = []
ffmpeg = ["thumbnailer/ffmpeg"]
ffmpeg = ["thumbnailer/ffmpeg"]

@ -27,7 +27,8 @@ impl ThumbnailStore {
/// Adds a thumbnail to be stored for a parent id
/// if the thumbnail already exists it will be recreated without warning
pub async fn add_thumbnail<S: ToString>(
#[tracing::instrument(level = "debug", skip(self, data))]
pub async fn add_thumbnail<S: ToString + Debug>(
&self,
parent_id: S,
size: Dimensions,
@ -53,7 +54,8 @@ impl ThumbnailStore {
}
/// Returns all thumbnails for a parent id
pub async fn get_thumbnails<S: ToString>(
#[tracing::instrument(level = "debug", skip(self))]
pub async fn get_thumbnails<S: ToString + Debug>(
&self,
parent_id: S,
) -> Result<Vec<(Dimensions, PathBuf)>> {

@ -7,17 +7,17 @@ workspace = ".."
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
chrono = "^0.4.19"
tracing = "^0.1.29"
chrono = "0.4.19"
tracing = "0.1.30"
[dependencies.mediarepo-core]
path = "../mediarepo-core"
[dependencies.sqlx]
version = "^0.5.9"
version = "0.5.10"
features = ["migrate"]
[dependencies.sea-orm]
version = "^0.5.0-rc.1"
version = "0.5.0"
features = ["sqlx-sqlite", "runtime-tokio-native-tls", "macros", "debug-print"]
default-features = false
default-features = false

@ -0,0 +1,20 @@
-- Add migration script here
CREATE TABLE sorting_presets (
id INTEGER PRIMARY KEY AUTOINCREMENT
);
CREATE TABLE sort_keys (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key_type INTEGER NOT NULL DEFAULT 0,
ascending INTEGER NOT NULL CHECK (ascending IN (0, 1)),
value VARCHAR(128)
);
CREATE TABLE sorting_preset_keys (
preset_id INTEGER REFERENCES sorting_presets (id) ON DELETE CASCADE,
key_id INTEGER REFERENCES sort_keys (id),
key_index INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (preset_id, key_id)
);
CREATE INDEX sorting_preset_index ON sorting_preset_keys (preset_id);

@ -0,0 +1,17 @@
PRAGMA foreign_keys= off;
ALTER TABLE sorting_preset_keys
RENAME TO _sorting_preset_keys_old;
CREATE TABLE sorting_preset_keys
(
preset_id INTEGER REFERENCES sorting_presets (id) ON DELETE CASCADE,
key_id INTEGER REFERENCES sort_keys (id),
key_index INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (preset_id, key_id, key_index)
);
INSERT INTO sorting_preset_keys SELECT * FROM _sorting_preset_keys_old;
DROP TABLE _sorting_preset_keys_old;
PRAGMA foreign_keys= on;

@ -4,5 +4,8 @@ pub mod content_descriptor_tag;
pub mod file;
pub mod file_metadata;
pub mod namespace;
pub mod sort_key;
pub mod sorting_preset;
pub mod sorting_preset_key;
pub mod source;
pub mod tag;

@ -0,0 +1,26 @@
use sea_orm::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "sort_keys")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub key_type: i32,
pub ascending: bool,
pub value: Option<String>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
impl Related<super::sorting_preset::Entity> for Entity {
fn to() -> RelationDef {
super::sorting_preset_key::Relation::SortingPreset.def()
}
fn via() -> Option<RelationDef> {
Some(super::sorting_preset_key::Relation::SortingKey.def().rev())
}
}
impl ActiveModelBehavior for ActiveModel {}

@ -0,0 +1,27 @@
use sea_orm::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "sorting_presets")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
impl Related<super::sort_key::Entity> for Entity {
fn to() -> RelationDef {
super::sorting_preset_key::Relation::SortingKey.def()
}
fn via() -> Option<RelationDef> {
Some(
super::sorting_preset_key::Relation::SortingPreset
.def()
.rev(),
)
}
}
impl ActiveModelBehavior for ActiveModel {}

@ -0,0 +1,45 @@
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "sorting_preset_keys")]
pub struct Model {
#[sea_orm(primary_key)]
preset_id: i32,
#[sea_orm(primary_key)]
key_id: i32,
#[sea_orm(primary_key)]
key_index: i32,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::sorting_preset::Entity"
from = "Column::PresetId",
to = "super::sorting_preset::Column::Id"
)]
SortingPreset,
#[sea_orm(
belongs_to = "super::sort_key::Entity",
from = "Column::KeyId",
to = "super::sort_key::Column::Id"
)]
SortingKey,
}
impl Related<super::sorting_preset::Entity> for Entity {
fn to() -> RelationDef {
Relation::SortingPreset.def()
}
}
impl Related<super::sort_key::Entity> for Entity {
fn to() -> RelationDef {
Relation::SortingKey.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

@ -1,61 +1,12 @@
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 sea_orm::{DatabaseConnection, Statement};
use mediarepo_core::error::RepoResult;
#[derive(Debug, FromQueryResult)]
struct CIDNamespaceTag {
cd_id: i64,
namespace: String,
tag: String,
}
#[tracing::instrument(level = "debug", skip_all)]
pub async fn get_cids_with_namespaced_tags(
db: &DatabaseConnection,
hash_ids: Vec<i64>,
) -> RepoResult<HashMap<i64, HashMap<String, Vec<String>>>> {
let hash_namespace_tags: Vec<CIDNamespaceTag> =
CIDNamespaceTag::find_by_statement(Statement::from_sql_and_values(
DbBackend::Sqlite,
format!(
r#"SELECT ctm.cd_id, n.name as namespace, t.name as tag
FROM cd_tag_mappings ctm
INNER JOIN tags t on ctm.tag_id = t.id
JOIN namespaces n on t.namespace_id = n.id
WHERE t.namespace_id IS NOT NULL
AND ctm.cd_id IN ({}) ORDER BY t.namespace_id;"#,
vec_to_query_list(hash_ids)
)
.as_str(),
vec![],
))
.all(db)
.await?;
let mut cd_id_namespaces: HashMap<i64, HashMap<String, Vec<String>>> = HashMap::new();
for hnt in hash_namespace_tags {
if let Some(entry) = cd_id_namespaces.get_mut(&hnt.cd_id) {
if let Some(nsp_entry) = entry.get_mut(&hnt.namespace) {
nsp_entry.push(hnt.tag);
} else {
entry.insert(hnt.namespace, vec![hnt.tag]);
}
} else {
cd_id_namespaces.insert(
hnt.cd_id,
HashMap::from_iter(vec![(hnt.namespace, vec![hnt.tag])].into_iter()),
);
}
}
Ok(cd_id_namespaces)
}
#[derive(Debug, FromQueryResult)]
struct CIDTagCount {
cd_id: i64,

@ -7,13 +7,13 @@ workspace = ".."
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
chrono = "^0.4.19"
typemap_rev = "^0.1.5"
serde = "^1.0.130"
mime_guess = "^2.0.3"
mime = "^0.3.16"
tracing = "^0.1.29"
async-trait = "^0.1.51"
chrono = "0.4.19"
typemap_rev = "0.1.5"
serde = "1.0.136"
mime_guess = "2.0.3"
mime = "0.3.16"
tracing = "0.1.30"
async-trait = "0.1.52"
[dependencies.mediarepo-core]
path = "../mediarepo-core"
@ -22,14 +22,14 @@ path = "../mediarepo-core"
path = "../mediarepo-database"
[dependencies.sea-orm]
version = "^0.5.0-rc.1"
version = "0.5.0"
features = ["runtime-tokio-native-tls", "macros"]
default-features = false
[dependencies.tokio]
version = "^1.15.0"
version = "1.16.1"
features = ["fs", "io-std", "io-util"]
[features]
ffmpeg = ["mediarepo-core/ffmpeg"]
ffmpeg = ["mediarepo-core/ffmpeg"]

@ -1,10 +1,10 @@
use sea_orm::prelude::*;
use tokio::io::AsyncReadExt;
use crate::dao_provider;
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;
@ -12,21 +12,9 @@ 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()
}
}
dao_provider!(FileDao);
impl FileDao {
pub fn new(ctx: DaoContext) -> Self {
Self { ctx }
}
#[tracing::instrument(level = "debug", skip(self))]
pub async fn all(&self) -> RepoResult<Vec<FileDto>> {
let files = file::Entity::find()

@ -9,7 +9,7 @@ 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_core::thumbnailer::ThumbnailSize;
use mediarepo_database::entities::{content_descriptor, file, file_metadata};
use crate::dao::file::FileDao;
@ -69,7 +69,7 @@ impl FileDao {
let mut dtos = Vec::new();
for thumbnail in thumbnails {
let mut buf = Vec::new();
let mut buf = Cursor::new(Vec::new());
let size = thumbnail.size();
let size = Dimensions {
height: size.1,
@ -80,7 +80,7 @@ impl FileDao {
let path = self
.ctx
.thumbnail_storage
.add_thumbnail(file.encoded_cd(), size.clone(), &buf)
.add_thumbnail(file.encoded_cd(), size.clone(), &buf.into_inner())
.await?;
dtos.push(ThumbnailDto::new(
path,

@ -1,20 +1,6 @@
use crate::dao_provider;
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 }
}
}
dao_provider!(JobDao);

@ -5,13 +5,38 @@ use mediarepo_core::fs::thumbnail_store::ThumbnailStore;
use crate::dao::file::FileDao;
use crate::dao::job::JobDao;
use crate::dao::sorting_preset::SortingPresetDao;
use crate::dao::tag::TagDao;
pub mod file;
pub mod job;
pub mod repo;
pub mod sorting_preset;
pub mod tag;
#[macro_export]
macro_rules! dao_provider {
($name:ident) => {
use crate::dao::{DaoContext, DaoProvider};
pub struct $name {
ctx: DaoContext,
}
impl DaoProvider for $name {
fn dao_ctx(&self) -> DaoContext {
self.ctx.clone()
}
}
impl $name {
pub fn new(ctx: DaoContext) -> Self {
Self { ctx }
}
}
};
}
#[derive(Clone)]
pub struct DaoContext {
pub db: DatabaseConnection,
@ -33,6 +58,10 @@ pub trait DaoProvider {
fn job(&self) -> JobDao {
JobDao::new(self.dao_ctx())
}
fn sorting_preset(&self) -> SortingPresetDao {
SortingPresetDao::new(self.dao_ctx())
}
}
fn opt_to_active_val<T: Into<sea_orm::Value>>(opt: Option<T>) -> ActiveValue<T> {

@ -0,0 +1,172 @@
use crate::dao::sorting_preset::SortingPresetDao;
use crate::dto::{AddSortKeyDto, AddSortingPresetDto, SortKeyDto, SortingPresetDto};
use mediarepo_core::error::RepoResult;
use mediarepo_database::entities::{sort_key, sorting_preset, sorting_preset_key};
use sea_orm::prelude::*;
use sea_orm::ActiveValue::Set;
use sea_orm::{
Condition, ConnectionTrait, DatabaseTransaction, DbBackend, FromQueryResult, JoinType,
QuerySelect, Statement,
};
#[allow(unused_imports)]
use sea_orm::TryGetableMany; // otherwise intellijrust hates on me
impl SortingPresetDao {
#[tracing::instrument(level = "debug", skip(self))]
pub async fn add(&self, preset: AddSortingPresetDto) -> RepoResult<SortingPresetDto> {
let trx = self.ctx.db.begin().await?;
let keys = add_keys(&trx, preset.keys).await?;
let key_ids = keys
.iter()
.enumerate()
.map(|(idx, key)| (idx, key.id()))
.collect::<Vec<(usize, i32)>>();
let condition = key_ids
.iter()
.cloned()
.map(create_mapping_condition)
.fold(Condition::any(), |acc, cond| acc.add(cond));
let existing_preset: Option<sorting_preset::Model> = sorting_preset::Entity::find()
.join(
JoinType::InnerJoin,
sorting_preset_key::Relation::SortingPreset.def().rev(),
)
.filter(condition)
.one(&trx)
.await?;
if let Some(model) = existing_preset {
trx.commit().await?;
return Ok(SortingPresetDto::new(model, keys));
}
// sea_orm currently doesn't support all-default-value inserts.
// TODOD: Replace after the change for default inserts has been merged
let preset_model = sorting_preset::Model::find_by_statement(Statement::from_string(
DbBackend::Sqlite,
"INSERT INTO sorting_presets DEFAULT VALUES RETURNING *;".to_string(),
))
.one(&trx)
.await?
.expect("failed to insert new sorting preset");
let mapping_models = key_ids
.into_iter()
.map(|(idx, key)| sorting_preset_key::ActiveModel {
preset_id: Set(preset_model.id),
key_id: Set(key),
key_index: Set(idx as i32),
})
.collect::<Vec<sorting_preset_key::ActiveModel>>();
if !mapping_models.is_empty() {
sorting_preset_key::Entity::insert_many(mapping_models)
.exec(&trx)
.await?;
}
trx.commit().await?;
Ok(SortingPresetDto::new(preset_model, keys))
}
}
async fn add_keys(
trx: &DatabaseTransaction,
keys: Vec<AddSortKeyDto>,
) -> RepoResult<Vec<SortKeyDto>> {
let mut key_dtos = find_sort_keys(trx, &keys).await?;
let mut insert_keys = keys.clone();
key_dtos.iter().for_each(|key| {
insert_keys.retain(|k| {
k.ascending != key.ascending()
|| k.key_type != key.key_type().unwrap()
|| !compare_opts_eq(key.value(), k.value.as_ref())
})
});
if !insert_keys.is_empty() {
let active_models: Vec<sort_key::ActiveModel> = insert_keys
.iter()
.cloned()
.map(|key| sort_key::ActiveModel {
key_type: Set(key.key_type.to_number()),
ascending: Set(key.ascending),
value: Set(key.value),
..Default::default()
})
.collect();
sort_key::Entity::insert_many(active_models)
.exec(trx)
.await?;
let mut new_keys = find_sort_keys(trx, &insert_keys).await?;
key_dtos.append(&mut new_keys);
}
let keys_original_order = keys
.into_iter()
.filter_map(|k| {
key_dtos
.iter()
.find(|key| {
k.ascending == key.ascending()
&& k.key_type == key.key_type().unwrap()
&& compare_opts_eq(key.value(), k.value.as_ref())
})
.cloned()
})
.collect::<Vec<SortKeyDto>>();
Ok(keys_original_order)
}
async fn find_sort_keys(
trx: &DatabaseTransaction,
keys: &Vec<AddSortKeyDto>,
) -> RepoResult<Vec<SortKeyDto>> {
if keys.is_empty() {
return Ok(vec![]);
}
let condition = keys
.iter()
.cloned()
.map(create_sort_key_condition)
.fold(Condition::any(), |acc, cond| acc.add(cond));
let keys = sort_key::Entity::find()
.filter(condition)
.all(trx)
.await?
.into_iter()
.map(SortKeyDto::new)
.collect();
Ok(keys)
}
fn create_sort_key_condition(key: AddSortKeyDto) -> Condition {
let mut condition = Condition::all()
.add(sort_key::Column::KeyType.eq(key.key_type.to_number()))
.add(sort_key::Column::Ascending.eq(key.ascending));
if let Some(value) = key.value {
condition = condition.add(sort_key::Column::Value.eq(value))
} else {
condition = condition.add(sort_key::Column::Value.is_null())
}
condition
}
fn create_mapping_condition(entry: (usize, i32)) -> Condition {
Condition::all()
.add(sorting_preset_key::Column::KeyId.eq(entry.1))
.add(sorting_preset_key::Column::KeyIndex.eq(entry.0 as i32))
}
fn compare_opts_eq<T: Eq>(opt1: Option<T>, opt2: Option<T>) -> bool {
if let (Some(opt1), Some(opt2)) = (&opt1, &opt2) {
opt1 == opt2
} else {
opt1.is_none() && opt2.is_none()
}
}

@ -0,0 +1,42 @@
pub mod add;
use crate::dao_provider;
use crate::dto::{SortKeyDto, SortingPresetDto};
use mediarepo_core::error::RepoResult;
use mediarepo_database::entities::{sort_key, sorting_preset, sorting_preset_key};
use sea_orm::prelude::*;
use sea_orm::QueryOrder;
dao_provider!(SortingPresetDao);
impl SortingPresetDao {
#[tracing::instrument(level = "debug", skip(self))]
pub async fn all(&self) -> RepoResult<Vec<SortingPresetDto>> {
let presets = sorting_preset::Entity::find()
.find_with_related(sort_key::Entity)
.order_by_asc(sorting_preset_key::Column::KeyIndex)
.all(&self.ctx.db)
.await?
.into_iter()
.map(map_sorting_preset_dto)
.collect();
Ok(presets)
}
#[tracing::instrument(level = "debug", skip(self))]
pub async fn delete(&self, id: i32) -> RepoResult<()> {
sorting_preset::Entity::delete_many()
.filter(sorting_preset::Column::Id.eq(id))
.exec(&self.ctx.db)
.await?;
Ok(())
}
}
fn map_sorting_preset_dto(
entry: (sorting_preset::Model, Vec<sort_key::Model>),
) -> SortingPresetDto {
SortingPresetDto::new(entry.0, entry.1.into_iter().map(SortKeyDto::new).collect())
}

@ -0,0 +1,56 @@
use crate::dao::tag::TagDao;
use mediarepo_core::error::RepoResult;
use mediarepo_database::entities::{content_descriptor_tag, namespace, tag};
use sea_orm::prelude::*;
use sea_orm::JoinType;
use sea_orm::{FromQueryResult, QuerySelect};
use std::collections::HashMap;
use std::iter::FromIterator;
#[derive(Debug, FromQueryResult)]
struct CDIDNamespaceTag {
cd_id: i64,
namespace: String,
tag: String,
}
impl TagDao {
#[tracing::instrument(level = "debug", skip(self, cdids))]
pub async fn cdids_with_namespaced_tags(
&self,
cdids: Vec<i64>,
) -> RepoResult<HashMap<i64, HashMap<String, Vec<String>>>> {
let cd_namespace_tags: Vec<CDIDNamespaceTag> = content_descriptor_tag::Entity::find()
.select_only()
.column(content_descriptor_tag::Column::CdId)
.column_as(tag::Column::Name, "tag")
.column_as(namespace::Column::Name, "namespace")
.join(
JoinType::InnerJoin,
content_descriptor_tag::Relation::Tag.def(),
)
.join(JoinType::Join, namespace::Relation::Tag.def().rev())
.filter(content_descriptor_tag::Column::CdId.is_in(cdids))
.into_model::<CDIDNamespaceTag>()
.all(&self.ctx.db)
.await?;
let mut cd_id_namespaces: HashMap<i64, HashMap<String, Vec<String>>> = HashMap::new();
for cnt in cd_namespace_tags {
if let Some(entry) = cd_id_namespaces.get_mut(&cnt.cd_id) {
if let Some(nsp_entry) = entry.get_mut(&cnt.namespace) {
nsp_entry.push(cnt.tag);
} else {
entry.insert(cnt.namespace, vec![cnt.tag]);
}
} else {
cd_id_namespaces.insert(
cnt.cd_id,
HashMap::from_iter(vec![(cnt.namespace, vec![cnt.tag])].into_iter()),
);
}
}
Ok(cd_id_namespaces)
}
}

@ -10,29 +10,18 @@ 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::dao_provider;
use crate::dto::{NamespaceDto, TagDto};
pub mod add;
pub mod all_for_cds_map;
pub mod by_name;
pub mod cdids_with_namespaced_tags;
pub mod mappings;
pub struct TagDao {
ctx: DaoContext,
}
impl DaoProvider for TagDao {
fn dao_ctx(&self) -> DaoContext {
self.ctx.clone()
}
}
dao_provider!(TagDao);
impl TagDao {
pub fn new(ctx: DaoContext) -> Self {
Self { ctx }
}
#[tracing::instrument(level = "debug", skip(self))]
pub async fn all(&self) -> RepoResult<Vec<TagDto>> {
let tags = tag::Entity::find()

@ -1,12 +1,13 @@
pub use file::*;
pub use file_metadata::*;
pub use namespace::*;
pub use sorting_preset::*;
pub use tag::*;
pub use thumbnail::*;
mod file;
mod file_metadata;
mod tag;
mod namespace;
mod sorting_preset;
mod tag;
mod thumbnail;

@ -0,0 +1,101 @@
use crate::dto::KeyType::{
FileChangeTime, FileCreatedTime, FileImportedTime, FileName, FileSize, FileType, Namespace,
NumTags,
};
use mediarepo_database::entities::sort_key;
use mediarepo_database::entities::sorting_preset;
#[derive(Clone, Debug)]
pub struct SortingPresetDto {
model: sorting_preset::Model,
keys: Vec<SortKeyDto>,
}
impl SortingPresetDto {
pub fn new(model: sorting_preset::Model, keys: Vec<SortKeyDto>) -> Self {
Self { model, keys }
}
pub fn id(&self) -> i32 {
self.model.id
}
pub fn keys(&self) -> &Vec<SortKeyDto> {
&self.keys
}
pub fn into_keys(self) -> Vec<SortKeyDto> {
self.keys
}
}
#[derive(Clone, Debug)]
pub struct SortKeyDto {
model: sort_key::Model,
}
impl SortKeyDto {
pub fn new(model: sort_key::Model) -> Self {
Self { model }
}
pub fn id(&self) -> i32 {
self.model.id
}
pub fn key_type(&self) -> Option<KeyType> {
KeyType::from_number(self.model.key_type)
}
pub fn ascending(&self) -> bool {
self.model.ascending
}
pub fn value(&self) -> Option<&String> {
self.model.value.as_ref()
}
}
#[derive(Clone, Copy, Debug, PartialOrd, PartialEq)]
pub enum KeyType {
Namespace = 0,
FileName = 1,
FileSize = 2,
FileImportedTime = 3,
FileCreatedTime = 4,
FileChangeTime = 5,
FileType = 6,
NumTags = 7,
}
impl KeyType {
pub fn from_number(number: i32) -> Option<KeyType> {
match number {
0 => Some(Namespace),
1 => Some(FileName),
2 => Some(FileSize),
3 => Some(FileImportedTime),
4 => Some(FileCreatedTime),
5 => Some(FileChangeTime),
6 => Some(FileType),
7 => Some(NumTags),
_ => None,
}
}
pub fn to_number(&self) -> i32 {
self.clone() as i32
}
}
#[derive(Clone, Debug)]
pub struct AddSortingPresetDto {
pub keys: Vec<AddSortKeyDto>,
}
#[derive(Clone, Debug)]
pub struct AddSortKeyDto {
pub key_type: KeyType,
pub ascending: bool,
pub value: Option<String>,
}

@ -7,10 +7,10 @@ workspace = ".."
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde = "^1.0.130"
tracing = "^0.1.29"
compare = "^0.1.0"
port_check = "^0.1.5"
serde = "1.0.136"
tracing = "0.1.30"
compare = "0.1.0"
port_check = "0.1.5"
rayon = "1.5.1"
[dependencies.mediarepo-core]
@ -23,13 +23,13 @@ path = "../mediarepo-database"
path = "../mediarepo-logic"
[dependencies.tokio]
version = "^1.15.0"
version = "1.16.1"
features = ["net"]
[dependencies.chrono]
version = "^0.4.19"
version = "0.4.19"
features = ["serde"]
[dependencies.tracing-futures]
version = "0.2.5"
features = ["tokio-executor"]
features = ["tokio-executor"]

@ -1,9 +1,13 @@
use mediarepo_core::mediarepo_api::types::files::{
FileBasicDataResponse, FileMetadataResponse, FileStatus, ThumbnailMetadataResponse,
};
use mediarepo_core::mediarepo_api::types::filtering::{
SortDirection, SortKey, SortNamespace, SortingPreset,
};
use mediarepo_core::mediarepo_api::types::tags::{NamespaceResponse, TagResponse};
use mediarepo_logic::dto::{
FileDto, FileMetadataDto, FileStatus as FileStatusModel, NamespaceDto, TagDto, ThumbnailDto,
FileDto, FileMetadataDto, FileStatus as FileStatusModel, KeyType, NamespaceDto, SortKeyDto,
SortingPresetDto, TagDto, ThumbnailDto,
};
pub trait FromModel<M> {
@ -73,3 +77,42 @@ impl FromModel<NamespaceDto> for NamespaceResponse {
}
}
}
impl FromModel<SortingPresetDto> for SortingPreset {
fn from_model(model: SortingPresetDto) -> Self {
SortingPreset {
id: model.id(),
keys: model
.into_keys()
.into_iter()
.filter_map(map_sort_dto_to_key)
.collect(),
}
}
}
fn map_sort_dto_to_key(dto: SortKeyDto) -> Option<SortKey> {
let direction = map_direction(dto.ascending());
match dto.key_type()? {
KeyType::Namespace => Some(SortKey::Namespace(SortNamespace {
name: dto.value()?.to_owned(),
direction,
})),
KeyType::FileName => Some(SortKey::FileName(direction)),
KeyType::FileSize => Some(SortKey::FileSize(direction)),
KeyType::FileImportedTime => Some(SortKey::FileImportedTime(direction)),
KeyType::FileCreatedTime => Some(SortKey::FileCreatedTime(direction)),
KeyType::FileChangeTime => Some(SortKey::FileChangeTime(direction)),
KeyType::FileType => Some(SortKey::FileType(direction)),
KeyType::NumTags => Some(SortKey::NumTags(direction)),
}
}
fn map_direction(ascending: bool) -> SortDirection {
if ascending {
SortDirection::Ascending
} else {
SortDirection::Descending
}
}

@ -291,10 +291,8 @@ impl FilesNamespace {
let found_thumbnail = thumbnails.into_iter().find(|thumb| {
let Dimensions { height, width } = thumb.size();
*height >= min_size.0
&& *height <= max_size.0
&& *width >= min_size.1
&& *width <= max_size.1
(*height <= max_size.0 && *width <= max_size.1)
&& (*width >= min_size.1 || *height >= min_size.0)
});
let thumbnail = if let Some(thumbnail) = found_thumbnail {

@ -8,14 +8,11 @@ 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_logic::dao::DaoProvider;
use mediarepo_database::queries::tags::get_content_descriptors_with_tag_count;
use mediarepo_logic::dao::repo::Repo;
use mediarepo_logic::dao::DaoProvider;
use mediarepo_logic::dto::{FileDto, FileMetadataDto};
pub struct FileSortContext {
name: Option<String>,
size: u64,
@ -50,12 +47,14 @@ async fn build_sort_context(
repo: &Repo,
files: &Vec<FileDto>,
) -> RepoResult<HashMap<i64, FileSortContext>> {
let hash_ids: Vec<i64> = files.par_iter().map(|f| f.cd_id()).collect();
let cd_ids: Vec<i64> = files.par_iter().map(|f| f.cd_id()).collect();
let file_ids: Vec<i64> = files.par_iter().map(|f| f.id()).collect();
let mut cid_nsp: HashMap<i64, HashMap<String, Vec<String>>> =
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 mut cid_nsp: HashMap<i64, HashMap<String, Vec<String>>> = repo
.tag()
.cdids_with_namespaced_tags(cd_ids.clone())
.await?;
let mut cid_tag_counts = get_content_descriptors_with_tag_count(repo.db(), cd_ids).await?;
let files_metadata = repo.file().all_metadata(file_ids).await?;

@ -3,6 +3,7 @@ use mediarepo_core::bromine::{namespace, namespace::Namespace, IPCBuilder};
pub mod files;
pub mod jobs;
pub mod presets;
pub mod repo;
pub mod tags;
@ -12,4 +13,5 @@ pub fn build_namespaces<L: AsyncStreamProtocolListener>(builder: IPCBuilder<L>)
.add_namespace(namespace!(tags::TagsNamespace))
.add_namespace(namespace!(repo::RepoNamespace))
.add_namespace(namespace!(jobs::JobsNamespace))
.add_namespace(namespace!(presets::PresetsNamespace))
}

@ -0,0 +1,118 @@
use crate::from_model::FromModel;
use crate::utils::get_repo_from_context;
use mediarepo_core::bromine::prelude::*;
use mediarepo_core::mediarepo_api::types::filtering::{SortDirection, SortKey, SortingPreset};
use mediarepo_logic::dao::DaoProvider;
use mediarepo_logic::dto::{AddSortKeyDto, AddSortingPresetDto, KeyType};
pub struct PresetsNamespace;
impl NamespaceProvider for PresetsNamespace {
fn name() -> &'static str {
"presets"
}
fn register(handler: &mut EventHandler) {
events!(handler,
"all_sorting_presets" => Self::all_sorting_presets,
"add_sorting_preset" => Self::add_sorting_preset,
"delete_sorting_preset" => Self::delete_sorting_preset
);
}
}
impl PresetsNamespace {
#[tracing::instrument(skip_all)]
pub async fn all_sorting_presets(ctx: &Context, _: Event) -> IPCResult<()> {
let repo = get_repo_from_context(ctx).await;
let sorting_presets: Vec<SortingPreset> = repo
.sorting_preset()
.all()
.await?
.into_iter()
.map(SortingPreset::from_model)
.collect();
ctx.emit_to(Self::name(), "all_sorting_presets", sorting_presets)
.await?;
Ok(())
}
#[tracing::instrument(skip_all)]
pub async fn add_sorting_preset(ctx: &Context, event: Event) -> IPCResult<()> {
let keys = event
.payload::<Vec<SortKey>>()?
.into_iter()
.map(sort_key_to_add_dto)
.collect();
let repo = get_repo_from_context(ctx).await;
let preset = repo
.sorting_preset()
.add(AddSortingPresetDto { keys })
.await?;
ctx.emit_to(
Self::name(),
"add_sorting_preset",
SortingPreset::from_model(preset),
)
.await?;
Ok(())
}
#[tracing::instrument(skip_all)]
pub async fn delete_sorting_preset(ctx: &Context, event: Event) -> IPCResult<()> {
let id = event.payload::<i32>()?;
let repo = get_repo_from_context(ctx).await;
repo.sorting_preset().delete(id).await?;
ctx.emit_to(Self::name(), "delete_sorting_preset", ())
.await?;
Ok(())
}
}
fn sort_key_to_add_dto(key: SortKey) -> AddSortKeyDto {
match key {
SortKey::Namespace(namespace) => AddSortKeyDto {
ascending: namespace.direction == SortDirection::Ascending,
key_type: KeyType::Namespace,
value: Some(namespace.name),
},
SortKey::FileName(dir) => AddSortKeyDto {
ascending: dir == SortDirection::Ascending,
key_type: KeyType::FileName,
value: None,
},
SortKey::FileSize(dir) => AddSortKeyDto {
ascending: dir == SortDirection::Ascending,
key_type: KeyType::FileSize,
value: None,
},
SortKey::FileImportedTime(dir) => AddSortKeyDto {
ascending: dir == SortDirection::Ascending,
key_type: KeyType::FileImportedTime,
value: None,
},
SortKey::FileCreatedTime(dir) => AddSortKeyDto {
ascending: dir == SortDirection::Ascending,
key_type: KeyType::FileCreatedTime,
value: None,
},
SortKey::FileChangeTime(dir) => AddSortKeyDto {
ascending: dir == SortDirection::Ascending,
key_type: KeyType::FileChangeTime,
value: None,
},
SortKey::FileType(dir) => AddSortKeyDto {
ascending: dir == SortDirection::Ascending,
key_type: KeyType::FileType,
value: None,
},
SortKey::NumTags(dir) => AddSortKeyDto {
ascending: dir == SortDirection::Ascending,
key_type: KeyType::NumTags,
value: None,
},
}
}

@ -1,6 +1,6 @@
{
"name": "mediarepo-ui",
"version": "0.13.3",
"version": "0.13.4",
"scripts": {
"ng": "ng",
"start": "ng serve",

@ -40,7 +40,7 @@ checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0"
[[package]]
name = "app"
version = "0.13.3"
version = "0.13.4"
dependencies = [
"mediarepo-api",
"serde",
@ -161,9 +161,9 @@ checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
[[package]]
name = "block-buffer"
version = "0.10.0"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1d36a02058e76b040de25a4464ba1c80935655595b661505c8b39b664828b95"
checksum = "03588e54c62ae6d763e2a80090d50353b785795361b4ff5b3bf0a5097fc31c0b"
dependencies = [
"generic-array",
]
@ -506,9 +506,9 @@ dependencies = [
[[package]]
name = "crossbeam-epoch"
version = "0.9.6"
version = "0.9.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97242a70df9b89a65d0b6df3c4bf5b9ce03c5b7309019777fbde37e7537f8762"
checksum = "c00d6d2ea26e8b151d99093005cb442fb9a37aeaca582a03ec70946f49ab5ed9"
dependencies = [
"cfg-if 1.0.0",
"crossbeam-utils",
@ -519,9 +519,9 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
version = "0.8.6"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcae03edb34f947e64acdb1c33ec169824e20657e9ecb61cef6c8c74dcb8120"
checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6"
dependencies = [
"cfg-if 1.0.0",
"lazy_static",
@ -799,9 +799,9 @@ dependencies = [
[[package]]
name = "futf"
version = "0.1.4"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c9c1ce3fa9336301af935ab852c437817d14cd33690446569392e65170aac3b"
checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843"
dependencies = [
"mac",
"new_debug_unreachable",
@ -1416,9 +1416,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.116"
version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "565dbd88872dbe4cc8a46e527f26483c1d1f7afa6b884a3bd6cd893d4f98da74"
checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c"
[[package]]
name = "lock_api"
@ -1499,7 +1499,7 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]]
name = "mediarepo-api"
version = "0.28.0"
version = "0.28.1"
dependencies = [
"async-trait",
"bromine",
@ -1507,7 +1507,7 @@ dependencies = [
"directories",
"futures",
"mime_guess",
"parking_lot",
"parking_lot 0.12.0",
"pathsearch",
"serde",
"serde_json",
@ -1865,7 +1865,17 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
dependencies = [
"instant",
"lock_api",
"parking_lot_core",
"parking_lot_core 0.8.5",
]
[[package]]
name = "parking_lot"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58"
dependencies = [
"lock_api",
"parking_lot_core 0.9.0",
]
[[package]]
@ -1882,6 +1892,19 @@ dependencies = [
"winapi",
]
[[package]]
name = "parking_lot_core"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2f4f894f3865f6c0e02810fc597300f34dc2510f66400da262d8ae10e75767d"
dependencies = [
"cfg-if 1.0.0",
"libc",
"redox_syscall",
"smallvec",
"windows-sys",
]
[[package]]
name = "pathdiff"
version = "0.2.1"
@ -2377,7 +2400,7 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver 1.0.4",
"semver 1.0.5",
]
[[package]]
@ -2425,9 +2448,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "security-framework"
version = "2.6.0"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fed7948b6c68acbb6e20c334f55ad635dc0f75506963de4464289fbd3b051ac"
checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc"
dependencies = [
"bitflags",
"core-foundation 0.9.2",
@ -2438,9 +2461,9 @@ dependencies = [
[[package]]
name = "security-framework-sys"
version = "2.6.0"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a57321bf8bc2362081b2599912d2961fe899c0efadf1b4b2f8d48b3e253bb96c"
checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556"
dependencies = [
"core-foundation-sys 0.8.3",
"libc",
@ -2477,9 +2500,9 @@ dependencies = [
[[package]]
name = "semver"
version = "1.0.4"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012"
checksum = "0486718e92ec9a68fbed73bb5ef687d71103b142595b406835649bebd33f72c7"
[[package]]
name = "semver-parser"
@ -2654,14 +2677,14 @@ dependencies = [
[[package]]
name = "string_cache"
version = "0.8.2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "923f0f39b6267d37d23ce71ae7235602134b250ace715dd2c90421998ddac0c6"
checksum = "33994d0838dc2d152d17a62adf608a869b5e846b65b389af7f3dbc1de45c5b26"
dependencies = [
"lazy_static",
"new_debug_unreachable",
"parking_lot",
"phf_shared 0.8.0",
"parking_lot 0.11.2",
"phf_shared 0.10.0",
"precomputed-hash",
"serde",
]
@ -2811,7 +2834,7 @@ dependencies = [
"ndk-glue",
"ndk-sys",
"objc",
"parking_lot",
"parking_lot 0.11.2",
"raw-window-handle 0.3.4",
"scopeguard",
"serde",
@ -2857,7 +2880,7 @@ dependencies = [
"rand 0.8.4",
"raw-window-handle 0.3.4",
"rfd",
"semver 1.0.4",
"semver 1.0.5",
"serde",
"serde_json",
"serde_repr",
@ -3088,9 +3111,9 @@ dependencies = [
[[package]]
name = "tracing"
version = "0.1.29"
version = "0.1.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105"
checksum = "2d8d93354fe2a8e50d5953f5ae2e47a3fc2ef03292e7ea46e3cc38f549525fb9"
dependencies = [
"cfg-if 1.0.0",
"pin-project-lite",
@ -3100,9 +3123,9 @@ dependencies = [
[[package]]
name = "tracing-attributes"
version = "0.1.18"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e"
checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716"
dependencies = [
"proc-macro2 1.0.36",
"quote 1.0.15",
@ -3111,11 +3134,12 @@ dependencies = [
[[package]]
name = "tracing-core"
version = "0.1.21"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4"
checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23"
dependencies = [
"lazy_static",
"valuable",
]
[[package]]
@ -3131,9 +3155,9 @@ dependencies = [
[[package]]
name = "tracing-subscriber"
version = "0.3.7"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5312f325fe3588e277415f5a6cca1f4ccad0f248c4cd5a4bd33032d7286abc22"
checksum = "74786ce43333fcf51efe947aed9718fbe46d5c7328ec3f1029e818083966d9aa"
dependencies = [
"ansi_term",
"lazy_static",
@ -3235,6 +3259,12 @@ dependencies = [
"getrandom 0.2.4",
]
[[package]]
name = "valuable"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "vcpkg"
version = "0.2.15"
@ -3476,6 +3506,49 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ceb069ac8b2117d36924190469735767f0990833935ab430155e71a44bafe148"
dependencies = [
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_msvc"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d027175d00b01e0cbeb97d6ab6ebe03b12330a35786cbaca5252b1c4bf5d9b"
[[package]]
name = "windows_i686_gnu"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8793f59f7b8e8b01eda1a652b2697d87b93097198ae85f823b969ca5b89bba58"
[[package]]
name = "windows_i686_msvc"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8602f6c418b67024be2996c512f5f995de3ba417f4c75af68401ab8756796ae4"
[[package]]
name = "windows_x86_64_gnu"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3d615f419543e0bd7d2b3323af0d86ff19cbc4f816e6453f36a2c2ce889c354"
[[package]]
name = "windows_x86_64_msvc"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11d95421d9ed3672c280884da53201a5c46b7b2765ca6faf34b0d71cf34a3561"
[[package]]
name = "winres"
version = "0.1.12"

@ -1,6 +1,6 @@
[package]
name = "app"
version = "0.13.3"
version = "0.13.4"
description = "The UI for the mediarepo media management tool"
authors = ["you"]
license = ""
@ -10,20 +10,20 @@ edition = "2018"
build = "src/build.rs"
[build-dependencies]
tauri-build = { version = "^1.0.0-beta.4" }
tauri-build = "1.0.0-beta.4"
[dependencies]
serde_json = "^1.0"
serde = { version = "^1.0", features = ["derive"] }
thiserror = "^1.0.30"
typemap_rev = "^0.1.5"
serde_json = "1.0.78"
serde = { version = "1.0.136", features = ["derive"] }
thiserror = "1.0.30"
typemap_rev = "0.1.5"
[dependencies.tauri]
version = "^1.0.0-beta.8"
version = "1.0.0-beta.8"
features = ["dialog-all", "path-all", "shell-all"]
[dependencies.tracing-subscriber]
version = "^0.3.0"
version = "0.3.8"
features = ["env-filter"]
[dependencies.mediarepo-api]

@ -1,7 +1,7 @@
{
"package": {
"productName": "mediarepo-ui",
"version": "0.13.3"
"version": "0.13.4"
},
"build": {
"distDir": "../dist/mediarepo-ui",

@ -4,12 +4,14 @@ import {ApiFunction} from "./api-types/functions";
import {
AddLocalFileREquest,
AddRepositoryRequest,
AddSortingPresetRequest,
ChangeFileTagsRequest,
CheckDaemonRunningRequest,
CheckLocalRepositoryExistsRequest,
CreateTagsRequest,
DeleteFileRequest,
DeleteRepositoryRequest,
DeleteSortingPresetRequest,
DeleteThumbnailsRequest,
FindFilesRequest,
GetFileMetadataRequest,
@ -31,6 +33,7 @@ import {
import {RepositoryData, RepositoryMetadata, SizeMetadata} from "./api-types/repo";
import {CdTagMappings, NamespaceData, TagData} from "./api-types/tags";
import {ShortCache} from "./ShortCache";
import {SortingPresetData} from "./api-types/presets";
export class MediarepoApi {
@ -184,6 +187,18 @@ export class MediarepoApi {
return this.invokePlugin(ApiFunction.RunJob, request);
}
public static async getAllSortingPresets(): Promise<SortingPresetData[]> {
return ShortCache.cached("sorting-presets", () => this.invokePlugin(ApiFunction.GetAllSortingPresets), 1000);
}
public static async addSortingPreset(request: AddSortingPresetRequest): Promise<SortingPresetData> {
return this.invokePlugin(ApiFunction.AddSortingPreset, request);
}
public static async deleteSortingPreset(request: DeleteSortingPresetRequest): Promise<void> {
return this.invokePlugin(ApiFunction.DeleteSortingPreset, request);
}
private static async invokePlugin<T>(fn: ApiFunction, args?: any): Promise<T> {
return invoke<T>(`plugin:mediarepo|${fn}`, args);
}

@ -41,7 +41,7 @@ export type ValueComparator<T> =
| { Greater: T }
| { Between: T[] }
export type SortKey = { Namespace: SortNamespace }
export type SortKeyData = { Namespace: SortNamespace }
| { FileName: SortDirection }
| { FileSize: SortDirection }
| { FileImportedTime: SortDirection }

@ -40,4 +40,8 @@ export enum ApiFunction {
SetFrontendState = "set_frontend_state",
// jobs
RunJob = "run_job",
// presets
GetAllSortingPresets = "all_sorting_presets",
AddSortingPreset = "add_sorting_preset",
DeleteSortingPreset = "delete_sorting_preset",
}

@ -0,0 +1,6 @@
import {SortKeyData} from "./files";
export type SortingPresetData = {
id: number,
keys: SortKeyData[],
}

@ -1,4 +1,4 @@
import {FileOsMetadata, FileStatus, FilterExpression, SortKey} from "./files";
import {FileOsMetadata, FileStatus, FilterExpression, SortKeyData} from "./files";
import {RepositoryData, SizeType} from "./repo";
import {JobType} from "./job";
@ -40,7 +40,7 @@ export type GetSizeRequest = {
export type FindFilesRequest = {
filters: FilterExpression[],
sortBy: SortKey[]
sortBy: SortKeyData[]
};
export type UpdateFileNameRequest = {
@ -107,4 +107,12 @@ export type SetFrontendStateRequest = {
export type RunJobRequest = {
jobType: JobType,
}
};
export type AddSortingPresetRequest = {
sortKeys: SortKeyData[]
};
export type DeleteSortingPresetRequest = {
id: number
};

@ -22,11 +22,11 @@ export class File {
return this.basicData.status;
}
public get mimeType(): string {
return this.basicData.mime_type;
}
public set status(value: FileStatus) {
this.basicData.status = value;
}
public get mimeType(): string {
return this.basicData.mime_type;
}
}

@ -22,7 +22,7 @@ export class Repository {
return this.repoData.local;
}
public update(data: {name?: string, address?: string, path?: string, local?: boolean}) {
public update(data: { name?: string, address?: string, path?: string, local?: boolean }) {
this.repoData = Object.assign(this.repoData, data);
}
}

@ -0,0 +1,108 @@
import {SortDirection, SortKeyData} from "../api-types/files";
export type SortType =
"Namespace"
| "FileName"
| "FileSize"
| "FileImportedTime"
| "FileCreatedTime"
| "FileChangeTime"
| "FileType"
| "NumTags";
export class SortKey {
constructor(private data: SortKeyData) {
this.data = data;
}
public get sortType(): SortType {
return Reflect.ownKeys(this.data)[0] as SortType;
}
public set sortType(value: SortType) {
if (value == "Namespace") {
this.data = {
Namespace: {
direction: this.sortDirection,
name: ""
}
};
} else {
this.data = {
[value]: this.sortDirection
} as SortKeyData;
}
}
public get sortDirection(): SortDirection {
if ("Namespace" in this.data) {
return this.data.Namespace.direction;
} else {
// @ts-ignore
return this.data[this.sortType];
}
}
public set sortDirection(value: SortDirection) {
const sortType = this.sortType;
if ("Namespace" in this.data) {
this.data.Namespace.direction = value;
} else {
// @ts-ignore
this.data[this.sortType] = value;
}
}
public get namespaceName(): string | undefined {
if ("Namespace" in this.data) {
return this.data.Namespace.name;
}
return undefined;
}
public set namespaceName(value: string | undefined) {
if (value && "Namespace" in this.data) {
this.data.Namespace.name = value;
}
}
public get rawData(): SortKeyData {
return this.data;
}
public static fromValues(
sortType: SortType,
sortDirection: SortDirection,
namespaceName: string | undefined
) {
let data;
if (sortType === "Namespace") {
data = {
Namespace: {
name: namespaceName!,
direction: sortDirection
}
};
} else {
data = {
[sortType]: sortDirection
} as SortKeyData;
}
return new SortKey(data);
}
public toString(): string {
if (this.sortType == "Namespace") {
return `${this.sortType} '${this.namespaceName}' ${this.getDirectionString()}`;
} else {
return `${this.sortType} ${this.getDirectionString()}`;
}
}
private getDirectionString(): string {
return this.sortDirection === "Ascending" ? "▲" : "▼";
}
}

@ -0,0 +1,48 @@
import {SortKey} from "./SortKey";
import {SortingPresetData} from "../api-types/presets";
import {mapNew} from "./adaptors";
export class SortingPreset {
private keys: SortKey[];
constructor(presetData: SortingPresetData) {
this._id = presetData.id;
this.keys = presetData.keys.map(mapNew(SortKey));
}
private _id: number;
public get id(): number {
return this._id;
}
public set id(value: number) {
this._id = value;
}
public get sortKeys(): SortKey[] {
return this.keys;
}
public get rawData(): SortingPresetData {
return {
id: this._id,
keys: this.keys.map(k => k.rawData),
};
}
public static fromValues(id: number, keys: SortKey[]) {
let preset = new SortingPreset({ id, keys: [] });
preset.keys = keys;
return preset;
}
public setData(data: SortingPresetData) {
this._id = data.id;
this.keys = data.keys.map(mapNew(SortKey));
}
public toString(): string {
return this.sortKeys.join(", ");
}
}

@ -36,6 +36,7 @@ mat-tab-group {
right: 0;
top: 0;
height: 100%;
ng-icon {
font-size: 1.5em;
margin-top: calc(-50%);

@ -1,17 +1,11 @@
import {NgModule} from "@angular/core";
import {CommonModule} from "@angular/common";
import {CoreComponent} from "./core.component";
import {
RepositoriesTabComponent
} from "./repositories-tab/repositories-tab.component";
import {RepositoriesTabComponent} from "./repositories-tab/repositories-tab.component";
import {FilesTabComponent} from "./files-tab/files-tab.component";
import {
FilesTabSidebarComponent
} from "./files-tab/files-tab-sidebar/files-tab-sidebar.component";
import {FilesTabSidebarComponent} from "./files-tab/files-tab-sidebar/files-tab-sidebar.component";
import {ImportTabComponent} from "./import-tab/import-tab.component";
import {
ImportTabSidebarComponent
} from "./import-tab/import-tab-sidebar/import-tab-sidebar.component";
import {ImportTabSidebarComponent} from "./import-tab/import-tab-sidebar/import-tab-sidebar.component";
import {MatButtonModule} from "@angular/material/button";
import {MatSidenavModule} from "@angular/material/sidenav";
import {MatProgressBarModule} from "@angular/material/progress-bar";
@ -30,9 +24,7 @@ import {SidebarModule} from "../shared/sidebar/sidebar.module";
import {FileModule} from "../shared/file/file.module";
import {AppCommonModule} from "../shared/app-common/app-common.module";
import {ReactiveFormsModule} from "@angular/forms";
import {
RepositoryCardComponent
} from "./repositories-tab/repository-card/repository-card.component";
import {RepositoryCardComponent} from "./repositories-tab/repository-card/repository-card.component";
import {MatCardModule} from "@angular/material/card";
import {MatListModule} from "@angular/material/list";
import {MatDialogModule} from "@angular/material/dialog";
@ -42,9 +34,7 @@ import {TagModule} from "../shared/tag/tag.module";
import {
DownloadDaemonDialogComponent
} from "./repositories-tab/download-daemon-dialog/download-daemon-dialog.component";
import {
RepositoryModule
} from "../shared/repository/repository/repository.module";
import {RepositoryModule} from "../shared/repository/repository/repository.module";
import {MatToolbarModule} from "@angular/material/toolbar";
import {
RepositoryDetailsViewComponent
@ -76,7 +66,7 @@ import {
MatProgressBarModule,
MatCheckboxModule,
ScrollingModule,
NgIconsModule.withIcons({...materialIcons}),
NgIconsModule.withIcons({ ...materialIcons }),
FlexModule,
MatButtonModule,
MatMenuModule,

@ -1,9 +1,12 @@
<div class="sidebar-inner">
<mat-tab-group headerPosition="below">
<mat-tab label="Search">
<app-file-search [state]="this.state" [availableTags]="this.allTags" [contextTags]="this.tags"
<app-file-search (searchEndEvent)="this.onDisplayedFilesChange(); this.searchEndEvent.emit($event);"
(searchStartEvent)="this.searchStartEvent.emit($event)"
(searchEndEvent)="this.onDisplayedFilesChange(); this.searchEndEvent.emit($event);" [tagsLoading]="this.tagsLoading"></app-file-search>
[availableTags]="this.allTags"
[contextTags]="this.tags"
[state]="this.state"
[tagsLoading]="this.tagsLoading"></app-file-search>
</mat-tab>
<mat-tab *ngIf="this.selectedFiles.length > 0" label="Edit Tags">
<app-tag-edit #fileedit [files]="this.selectedFiles"></app-tag-edit>

@ -1,25 +1,10 @@
import {
Component,
EventEmitter,
Input,
OnChanges,
OnInit,
Output,
SimpleChanges,
ViewChild
} from "@angular/core";
import {Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild} from "@angular/core";
import {Tag} from "../../../../../api/models/Tag";
import {TagService} from "../../../../services/tag/tag.service";
import {File} from "../../../../../api/models/File";
import {
FileSearchComponent
} from "../../../shared/sidebar/file-search/file-search.component";
import {
RepositoryService
} from "../../../../services/repository/repository.service";
import {
TagEditComponent
} from "../../../shared/sidebar/tag-edit/tag-edit.component";
import {FileSearchComponent} from "../../../shared/sidebar/file-search/file-search.component";
import {RepositoryService} from "../../../../services/repository/repository.service";
import {TagEditComponent} from "../../../shared/sidebar/tag-edit/tag-edit.component";
import {TabState} from "../../../../models/TabState";
@Component({

@ -1,6 +1,7 @@
<mat-drawer-container (keyDownEvent)="this.onKeydown($event)" appInputReceiver autosize>
<mat-drawer disableClose mode="side" opened>
<app-files-tab-sidebar (searchEndEvent)="this.contentLoading = false;" (searchStartEvent)="this.contentLoading = true;"
<app-files-tab-sidebar (searchEndEvent)="this.contentLoading = false;"
(searchStartEvent)="this.contentLoading = true;"
[selectedFiles]="this.selectedFiles"
[state]="this.state"></app-files-tab-sidebar>
</mat-drawer>

@ -1,12 +1,13 @@
<div class="import-tab-inner">
<mat-tab-group headerPosition="below">
<mat-tab label="Import">
<app-file-import (importFinished)="importFinished.emit($event)" (fileImported)="fileImported.emit($event)"></app-file-import>
<app-file-import (fileImported)="fileImported.emit($event)"
(importFinished)="importFinished.emit($event)"></app-file-import>
</mat-tab>
<mat-tab label="Edit Tags" *ngIf="selectedFiles.length > 0">
<mat-tab *ngIf="selectedFiles.length > 0" label="Edit Tags">
<app-tag-edit [files]="selectedFiles"></app-tag-edit>
</mat-tab>
<mat-tab label="File Metadata" *ngIf="selectedFiles.length === 1">
<mat-tab *ngIf="selectedFiles.length === 1" label="File Metadata">
<app-file-metadata [file]="selectedFiles[0]"></app-file-metadata>
</mat-tab>
</mat-tab-group>

@ -1,11 +1,14 @@
<mat-drawer-container autosize>
<mat-drawer disableClose="true" mode="side" opened>
<app-import-tab-sidebar [selectedFiles]="selectedFiles" (fileImported)="this.addFileFromImport($event)"
(importFinished)="this.refreshFileView()"></app-import-tab-sidebar>
<app-import-tab-sidebar (fileImported)="this.addFileFromImport($event)"
(importFinished)="this.refreshFileView()"
[selectedFiles]="selectedFiles"></app-import-tab-sidebar>
</mat-drawer>
<mat-drawer-content>
<app-file-multiview [mode]="this.state.mode.value" (modeChangeEvent)="this.state.mode.next($event)"
[preselectedFile]="this.getSelectedFileFromState()"
(fileSelectEvent)="this.onFileSelect($event)" [files]="this.files"></app-file-multiview>
<app-file-multiview (fileSelectEvent)="this.onFileSelect($event)"
(modeChangeEvent)="this.state.mode.next($event)"
[files]="this.files"
[mode]="this.state.mode.value"
[preselectedFile]="this.getSelectedFileFromState()"></app-file-multiview>
</mat-drawer-content>
</mat-drawer-container>

@ -20,7 +20,7 @@ export class ImportTabComponent implements OnInit {
}
public ngOnInit(): void {
this.state.files.subscribe(files => files? this.files = files : undefined);
this.state.files.subscribe(files => files ? this.files = files : undefined);
}
/**

@ -4,7 +4,8 @@
<div mat-dialog-content>
No daemon could be found on the system. Please download and install a daemon executable.
<br>
<button class="download-button" mat-flat-button color="primary" (click)="this.onClickDownloadDaemon()">Download</button>
<button (click)="this.onClickDownloadDaemon()" class="download-button" color="primary" mat-flat-button>Download
</button>
</div>
<div class="download-dialog-actions" mat-dialog-actions>
<button (click)="closeDialog(false)" color="accent" mat-stroked-button>Cancel</button>

@ -1,8 +1,6 @@
import {ComponentFixture, TestBed} from "@angular/core/testing";
import {
DownloadDaemonDialogComponent
} from "./download-daemon-dialog.component";
import {DownloadDaemonDialogComponent} from "./download-daemon-dialog.component";
describe("DownloadDaemonDialogComponent", () => {
let component: DownloadDaemonDialogComponent;
@ -10,7 +8,7 @@ describe("DownloadDaemonDialogComponent", () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ DownloadDaemonDialogComponent ]
declarations: [DownloadDaemonDialogComponent]
})
.compileComponents();
});

@ -9,9 +9,11 @@ import {shell} from "@tauri-apps/api";
})
export class DownloadDaemonDialogComponent {
constructor(public dialogRef: MatDialogRef<DownloadDaemonDialogComponent>,
@Inject(
MAT_DIALOG_DATA) data: any) {
constructor(
public dialogRef: MatDialogRef<DownloadDaemonDialogComponent>,
@Inject(
MAT_DIALOG_DATA) data: any
) {
}

@ -1,13 +1,14 @@
<div class="repo-page-content" *ngIf="!selectedRepository">
<div *ngIf="!selectedRepository" class="repo-page-content">
<div class="add-repo-tools">
<button (click)="openAddRepositoryDialog()" color="primary" mat-flat-button>Add Repository</button>
</div>
<div class="repository-list">
<div *ngFor="let repository of repositories" class="repository-container">
<app-repository-card [repository]="repository" (openEvent)="this.onOpenRepository($event)"></app-repository-card>
<app-repository-card (openEvent)="this.onOpenRepository($event)"
[repository]="repository"></app-repository-card>
</div>
</div>
</div>
<div class="repo-details" *ngIf="selectedRepository">
<div *ngIf="selectedRepository" class="repo-details">
<app-repository-details-view [repository]="selectedRepository"></app-repository-details-view>
</div>

@ -1,22 +1,9 @@
import {
Component, EventEmitter,
Input,
OnDestroy,
OnInit,
Output,
ViewChild
} from "@angular/core";
import {Component, EventEmitter, Input, OnDestroy, OnInit, Output, ViewChild} from "@angular/core";
import {Repository} from "../../../../../api/models/Repository";
import {
RepositoryService
} from "../../../../services/repository/repository.service";
import {RepositoryService} from "../../../../services/repository/repository.service";
import {MatDialog} from "@angular/material/dialog";
import {
ConfirmDialogComponent
} from "../../../shared/app-common/confirm-dialog/confirm-dialog.component";
import {
BusyIndicatorComponent
} from "../../../shared/app-common/busy-indicator/busy-indicator.component";
import {ConfirmDialogComponent} from "../../../shared/app-common/confirm-dialog/confirm-dialog.component";
import {BusyIndicatorComponent} from "../../../shared/app-common/busy-indicator/busy-indicator.component";
import {
EditRepositoryDialogComponent
} from "../../../shared/repository/repository/edit-repository-dialog/edit-repository-dialog.component";
@ -39,7 +26,8 @@ export class RepositoryCardComponent implements OnInit, OnDestroy {
constructor(
public repoService: RepositoryService,
public dialog: MatDialog) {
public dialog: MatDialog
) {
}
public async ngOnInit() {
@ -81,28 +69,6 @@ export class RepositoryCardComponent implements OnInit, OnDestroy {
}
}
private async promtDeleteRepository() {
if (this.repository.local) {
const deleteContents = await this.dialog.open(
ConfirmDialogComponent, {
data: {
title: "Delete repository content",
message: "Do you want to remove the contents of the repository as well?",
confirmAction: "Delete",
confirmColor: "warn",
denyAction: "No",
}
}).afterClosed().toPromise();
if (deleteContents) {
await this.repoService.deleteRepository(this.repository.name);
} else {
await this.repoService.removeRepository(this.repository.name);
}
} else {
await this.repoService.removeRepository(this.repository.name);
}
}
public getDaemonStatusText(): string {
if (this.repository.local) {
return "Local";
@ -138,4 +104,26 @@ export class RepositoryCardComponent implements OnInit, OnDestroy {
}
});
}
private async promtDeleteRepository() {
if (this.repository.local) {
const deleteContents = await this.dialog.open(
ConfirmDialogComponent, {
data: {
title: "Delete repository content",
message: "Do you want to remove the contents of the repository as well?",
confirmAction: "Delete",
confirmColor: "warn",
denyAction: "No",
}
}).afterClosed().toPromise();
if (deleteContents) {
await this.repoService.deleteRepository(this.repository.name);
} else {
await this.repoService.removeRepository(this.repository.name);
}
} else {
await this.repoService.removeRepository(this.repository.name);
}
}
}

@ -1,43 +1,47 @@
<mat-toolbar>
<span class="repository-name">{{repository.name}}</span>
<button class="button-close-repository" mat-flat-button color="primary" (click)="this.closeRepository()">Close
<button (click)="this.closeRepository()" class="button-close-repository" color="primary" mat-flat-button>Close
</button>
</mat-toolbar>
<div class="details-content" fxLayout="row">
<div class="repository-metadata" fxFlex="100%">
<h1>Stats</h1>
<app-metadata-entry *ngIf="repository.path" attributeName="Path">{{repository.path}}</app-metadata-entry>
<app-metadata-entry *ngIf="repository.address" attributeName="Address">{{repository.address}}</app-metadata-entry>
<app-metadata-entry *ngIf="repository.address"
attributeName="Address">{{repository.address}}</app-metadata-entry>
<app-metadata-entry attributeName="File Count">
<mat-progress-bar *ngIf="!metadata"></mat-progress-bar>
{{metadata? metadata!.file_count.toString() : ''}}
{{metadata ? metadata!.file_count.toString() : ''}}
</app-metadata-entry>
<app-metadata-entry attributeName="Tag Count">
<mat-progress-bar *ngIf="!metadata"></mat-progress-bar>
{{metadata? metadata!.tag_count.toString() : ''}}
{{metadata ? metadata!.tag_count.toString() : ''}}
</app-metadata-entry>
<app-metadata-entry attributeName="Namespace Count">
<mat-progress-bar *ngIf="!metadata"></mat-progress-bar>
{{metadata? metadata!.namespace_count.toString() : ''}}
{{metadata ? metadata!.namespace_count.toString() : ''}}
</app-metadata-entry>
<app-metadata-entry attributeName="Mapping Count">
<mat-progress-bar *ngIf="!metadata"></mat-progress-bar>
{{metadata? metadata!.mapping_count.toString() : ''}}
{{metadata ? metadata!.mapping_count.toString() : ''}}
</app-metadata-entry>
<app-metadata-entry attributeName="Total Size">
<mat-progress-bar *ngIf="(this.totalSize | async) === undefined" mode="indeterminate"></mat-progress-bar>
{{this.totalSize | async}}
</app-metadata-entry>
<app-metadata-entry attributeName="File Folder Size">
<mat-progress-bar *ngIf="(this.fileFolderSize | async) === undefined" mode="indeterminate"></mat-progress-bar>
<mat-progress-bar *ngIf="(this.fileFolderSize | async) === undefined"
mode="indeterminate"></mat-progress-bar>
{{this.fileFolderSize | async}}
</app-metadata-entry>
<app-metadata-entry attributeName="Thumbnail Folder Size">
<mat-progress-bar *ngIf="(this.thumbFolderSize | async) === undefined" mode="indeterminate"></mat-progress-bar>
<mat-progress-bar *ngIf="(this.thumbFolderSize | async) === undefined"
mode="indeterminate"></mat-progress-bar>
{{this.thumbFolderSize | async}}
</app-metadata-entry>
<app-metadata-entry attributeName="Database File Size">
<mat-progress-bar *ngIf="(this.databaseFileSize | async) === undefined" mode="indeterminate"></mat-progress-bar>
<mat-progress-bar *ngIf="(this.databaseFileSize | async) === undefined"
mode="indeterminate"></mat-progress-bar>
{{this.databaseFileSize | async}}
</app-metadata-entry>
</div>

@ -1,8 +1,6 @@
import {ComponentFixture, TestBed} from "@angular/core/testing";
import {
RepositoryDetailsViewComponent
} from "./repository-details-view.component";
import {RepositoryDetailsViewComponent} from "./repository-details-view.component";
describe("RepositoryDetailsViewComponent", () => {
let component: RepositoryDetailsViewComponent;
@ -10,7 +8,7 @@ describe("RepositoryDetailsViewComponent", () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ RepositoryDetailsViewComponent ]
declarations: [RepositoryDetailsViewComponent]
})
.compileComponents();
});

@ -1,21 +1,10 @@
import {
Component,
Input,
OnChanges,
OnDestroy,
OnInit,
SimpleChanges
} from "@angular/core";
import {Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges} from "@angular/core";
import {Repository} from "../../../../../api/models/Repository";
import {
RepositoryService
} from "../../../../services/repository/repository.service";
import {RepositoryService} from "../../../../services/repository/repository.service";
import {RepositoryMetadata} from "../../../../models/RepositoryMetadata";
import {BehaviorSubject} from "rxjs";
import {MatDialog} from "@angular/material/dialog";
import {
BusyDialogComponent
} from "../../../shared/app-common/busy-dialog/busy-dialog.component";
import {BusyDialogComponent} from "../../../shared/app-common/busy-dialog/busy-dialog.component";
@Component({
selector: "app-repository-details-view",
@ -26,12 +15,11 @@ export class RepositoryDetailsViewComponent implements OnInit, OnChanges, OnDest
@Input() repository!: Repository;
public metadata?: RepositoryMetadata;
private refreshMetadataInterval?: number;
public totalSize = new BehaviorSubject<string | undefined>(undefined);
public fileFolderSize = new BehaviorSubject<string | undefined>(undefined);
public thumbFolderSize = new BehaviorSubject<string | undefined>(undefined);
public databaseFileSize = new BehaviorSubject<string | undefined>(undefined);
private refreshMetadataInterval?: number;
constructor(private repoService: RepositoryService, public dialog: MatDialog) {
}

@ -1,24 +1,17 @@
import {
Directive,
EventEmitter,
HostBinding,
HostListener,
Output
} from "@angular/core";
import {Directive, EventEmitter, HostBinding, HostListener, Output} from "@angular/core";
@Directive({
selector: "[appInputReceiver]"
})
export class InputReceiverDirective {
constructor() {
}
@Output() keyDownEvent = new EventEmitter<KeyboardEvent>();
@Output() keyUpEvent = new EventEmitter<KeyboardEvent>();
@HostBinding("tabindex") tabIndex = 1;
constructor() {
}
@HostListener("keydown", ["$event"])
onKeyDown(event: KeyboardEvent) {
this.keyDownEvent.emit(event);

@ -8,7 +8,7 @@ describe("MetadataEntryComponent", () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ MetadataEntryComponent ]
declarations: [MetadataEntryComponent]
})
.compileComponents();
});

@ -1,10 +1,4 @@
import {
Component,
HostListener,
Input,
OnChanges,
SimpleChanges
} from "@angular/core";
import {Component, HostListener, Input, OnChanges, SimpleChanges} from "@angular/core";
import {CdkDragMove} from "@angular/cdk/drag-drop";
import {SafeResourceUrl} from "@angular/platform-browser";
@ -16,7 +10,7 @@ import {SafeResourceUrl} from "@angular/platform-browser";
export class ImageViewerComponent implements OnChanges {
@Input() imageUrl!: SafeResourceUrl | string;
public imageZoom = 1;
public imagePosition = {x: 0, y: 0};
public imagePosition = { x: 0, y: 0 };
public mouseInImageView = false;
constructor() {
@ -31,7 +25,7 @@ export class ImageViewerComponent implements OnChanges {
public resetImage() {
this.imageZoom = 1;
this.imagePosition = {x: 0, y: 0};
this.imagePosition = { x: 0, y: 0 };
}
public onDragMoved($event: CdkDragMove<HTMLDivElement>): void {

@ -1,12 +1,4 @@
import {
Component,
EventEmitter,
Input,
OnChanges,
OnInit,
Output,
SimpleChanges
} from "@angular/core";
import {Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges} from "@angular/core";
import {FormControl} from "@angular/forms";
import {dialog} from "@tauri-apps/api";
import {DialogFilter} from "@tauri-apps/api/dialog";

@ -4,16 +4,24 @@
</div>
<div class="dialog-buttons" mat-dialog-actions>
<button (click)="closeDialog()" color="accent" mat-stroked-button>Cancel</button>
<button (click)="addRepository()" *ngIf="repoForm.formGroup.get('repositoryType')?.value === 'remote' || repoForm.localRepoExists"
[disabled]="!repoForm.formGroup.valid" color="primary" mat-flat-button
<button (click)="addRepository()"
*ngIf="repoForm.formGroup.get('repositoryType')?.value === 'remote' || repoForm.localRepoExists"
[disabled]="!repoForm.formGroup.valid"
color="primary"
mat-flat-button
matTooltip="Add the existing repository">Add
</button>
<button (click)="this.initLocalRepository()" *ngIf="repoForm.formGroup.get('repositoryType')?.value === 'local' && !repoForm.localRepoExists"
<button (click)="this.initLocalRepository()"
*ngIf="repoForm.formGroup.get('repositoryType')?.value === 'local' && !repoForm.localRepoExists"
[disabled]="!repoForm.formGroup.valid"
color="accent" mat-flat-button
color="accent"
mat-flat-button
matTooltip="Initialize the repository in the specified path">Init
</button>
<button (click)="repoForm.checkRepositoryStatus()" *ngIf="repoForm.formGroup.get('repositoryType')?.value === 'remote'" [disabled]="!repoForm.formGroup.valid"
class="check-connection-button" mat-stroked-button>Check Connection
<button (click)="repoForm.checkRepositoryStatus()"
*ngIf="repoForm.formGroup.get('repositoryType')?.value === 'remote'"
[disabled]="!repoForm.formGroup.valid"
class="check-connection-button"
mat-stroked-button>Check Connection
</button>
</div>

@ -1,8 +1,11 @@
<h1 mat-dialog-title>Edit {{this.selectedRepository.name}}</h1>
<div mat-dialog-content>
<app-repository-form #repoForm [name]="selectedRepository.name" [address]="selectedRepository.address ?? ''"
<app-repository-form #repoForm
[address]="selectedRepository.address ?? ''"
[name]="selectedRepository.name"
[path]="selectedRepository.path ?? ''"
[repositoryType]="selectedRepository.local? 'local' : 'remote'"
[path]="selectedRepository.path ?? ''" [validateNameDuplicate]="false"></app-repository-form>
[validateNameDuplicate]="false"></app-repository-form>
</div>
<div class="dialog-buttons" mat-dialog-actions>
<button (click)="closeDialog()" color="accent" mat-stroked-button>Cancel</button>

@ -1,8 +1,6 @@
import {ComponentFixture, TestBed} from "@angular/core/testing";
import {
EditRepositoryDialogComponent
} from "./edit-repository-dialog.component";
import {EditRepositoryDialogComponent} from "./edit-repository-dialog.component";
describe("EditRepositoryDialogComponent", () => {
let component: EditRepositoryDialogComponent;
@ -10,7 +8,7 @@ describe("EditRepositoryDialogComponent", () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ EditRepositoryDialogComponent ]
declarations: [EditRepositoryDialogComponent]
})
.compileComponents();
});

@ -8,7 +8,7 @@ describe("RepositoryFormComponent", () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ RepositoryFormComponent ]
declarations: [RepositoryFormComponent]
})
.compileComponents();
});

@ -1,15 +1,7 @@
import {Component, Input, OnInit, Output} from "@angular/core";
import {
AbstractControl,
FormControl,
FormGroup,
ValidationErrors,
Validators
} from "@angular/forms";
import {AbstractControl, FormControl, FormGroup, ValidationErrors, Validators} from "@angular/forms";
import {Repository} from "../../../../../../api/models/Repository";
import {
RepositoryService
} from "../../../../../services/repository/repository.service";
import {RepositoryService} from "../../../../../services/repository/repository.service";
import {dialog} from "@tauri-apps/api";
import {MatDialog} from "@angular/material/dialog";
@ -101,7 +93,7 @@ export class RepositoryFormComponent implements OnInit {
const value = control?.value;
if (this.validateNameDuplicate && this.repositories.find(r => r.name === value)) {
control?.setErrors({nameAlreadyExists: value});
control?.setErrors({ nameAlreadyExists: value });
}
}
@ -110,7 +102,7 @@ export class RepositoryFormComponent implements OnInit {
"repositoryType")?.value ?? "local";
if (repositoryType === "local") {
return control.value.length > 0 ? null : {valueRequired: control.value};
return control.value.length > 0 ? null : { valueRequired: control.value };
}
return null;
}
@ -121,7 +113,7 @@ export class RepositoryFormComponent implements OnInit {
if (repositoryType === "remote") {
const match = /(\d+\.){3}\d+:\d+|\S+:\d+/.test(control.value);
return match ? null : {invalidAddress: control.value};
return match ? null : { invalidAddress: control.value };
}
return null;

@ -1,14 +1,8 @@
import {NgModule} from "@angular/core";
import {CommonModule} from "@angular/common";
import {
AddRepositoryDialogComponent
} from "./add-repository-dialog/add-repository-dialog.component";
import {
RepositoryFormComponent
} from "./repository-form/repository-form.component";
import {
EditRepositoryDialogComponent
} from "./edit-repository-dialog/edit-repository-dialog.component";
import {AddRepositoryDialogComponent} from "./add-repository-dialog/add-repository-dialog.component";
import {RepositoryFormComponent} from "./repository-form/repository-form.component";
import {EditRepositoryDialogComponent} from "./edit-repository-dialog/edit-repository-dialog.component";
import {MatDialogModule} from "@angular/material/dialog";
import {MatButtonModule} from "@angular/material/button";
import {MatTooltipModule} from "@angular/material/tooltip";
@ -37,7 +31,7 @@ import {MatFolder} from "@ng-icons/material-icons";
MatSelectModule,
MatInputModule,
ReactiveFormsModule,
NgIconsModule.withIcons({MatFolder})
NgIconsModule.withIcons({ MatFolder })
]
})
export class RepositoryModule {

@ -8,7 +8,7 @@ describe("FileImportComponent", () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ FileImportComponent ]
declarations: [FileImportComponent]
})
.compileComponents();
});

@ -2,12 +2,12 @@
<app-metadata-entry *ngIf="mode === 'read'" [attributeName]="attributeName">{{value}}</app-metadata-entry>
<mat-form-field *ngIf="mode === 'write'">
<mat-label>{{attributeName}}</mat-label>
<input [formControl]="formControl" type="text" matInput [value]="value.toString()">
<input [formControl]="formControl" [value]="value.toString()" matInput type="text">
</mat-form-field>
<button *ngIf="mode === 'write'" mat-button (click)="this.onSave()">
<button (click)="this.onSave()" *ngIf="mode === 'write'" mat-button>
<ng-icon name="mat-save"></ng-icon>
</button>
<button *ngIf="mode === 'read'" mat-button (click)="mode = 'write'">
<button (click)="mode = 'write'" *ngIf="mode === 'read'" mat-button>
<ng-icon name="mat-edit"></ng-icon>
</button>
</div>

@ -1,8 +1,6 @@
import {ComponentFixture, TestBed} from "@angular/core/testing";
import {
EditableMetadataEntryComponent
} from "./editable-metadata-entry.component";
import {EditableMetadataEntryComponent} from "./editable-metadata-entry.component";
describe("EditableMetadataEntryComponent", () => {
let component: EditableMetadataEntryComponent;
@ -10,7 +8,7 @@ describe("EditableMetadataEntryComponent", () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ EditableMetadataEntryComponent ]
declarations: [EditableMetadataEntryComponent]
})
.compileComponents();
});

@ -1,12 +1,4 @@
import {
Component,
EventEmitter,
Input,
OnChanges,
OnInit,
Output,
SimpleChanges
} from "@angular/core";
import {Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges} from "@angular/core";
import {FormControl} from "@angular/forms";
@Component({

@ -20,9 +20,11 @@
width: 100%;
display: block;
}
:first-child {
margin-top: 0;
}
:last-child {
margin-bottom: 0;
}
@ -32,6 +34,7 @@
text-align: center;
height: 100px;
width: 100%;
h1 {
margin-top: 20px;
}

@ -8,7 +8,7 @@ describe("FileMetadataComponent", () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ FileMetadataComponent ]
declarations: [FileMetadataComponent]
})
.compileComponents();
});

@ -35,7 +35,9 @@
</button>
</app-filter-input>
<button (click)="openSortDialog()" id="sort-button" mat-flat-button>Sort: {{sortExpression.join(", ")}}</button>
<app-sort-button (click)="openSortDialog()"
[selectedPreset]="this.sortingPreset"
id="sort-button"></app-sort-button>
</div>
<mat-divider fxFlex="1em"></mat-divider>

@ -9,7 +9,6 @@ import {
Output,
ViewChild
} from "@angular/core";
import {SortKey} from "../../../../models/SortKey";
import {MatDialog} from "@angular/material/dialog";
import {SortDialogComponent} from "./sort-dialog/sort-dialog.component";
import {LoggingService} from "../../../../services/logging/logging.service";
@ -23,6 +22,7 @@ import {FileStatus, FilterExpression,} from "../../../../../api/api-types/files"
import {filterExpressionToString} from "../../../../utils/filter-utils";
import {MatCheckboxChange} from "@angular/material/checkbox";
import * as deepEqual from "fast-deep-equal";
import {SortingPreset} from "../../../../../api/models/SortingPreset";
@Component({
@ -32,7 +32,7 @@ import * as deepEqual from "fast-deep-equal";
changeDetection: ChangeDetectionStrategy.OnPush
})
export class FileSearchComponent implements AfterViewChecked, OnInit {
public sortExpression: SortKey[] = [];
public sortingPreset: SortingPreset = new SortingPreset({ id: -1, keys: [] });
public filters: SearchFilters = new SearchFilters([]);
@Input() availableTags: Tag[] = [];
@ -68,7 +68,7 @@ export class FileSearchComponent implements AfterViewChecked, OnInit {
this.filters = f;
this.assignDisplayedFilters();
});
this.state.sortKeys.subscribe(s => this.sortExpression = s);
this.state.sortingPreset.subscribe(s => this.sortingPreset = s);
this.applyStatusFromFilters();
this.needsScroll = true;
this.assignDisplayedFilters();
@ -127,22 +127,18 @@ export class FileSearchComponent implements AfterViewChecked, OnInit {
}
public openSortDialog() {
const sortEntries = this.sortExpression.map(
key => JSON.parse(JSON.stringify(key))).map(
key => new SortKey(key.sortType, key.sortDirection,
key.namespaceName
));
const sortingPreset = new SortingPreset(JSON.parse(JSON.stringify(this.sortingPreset.rawData)));
const openedDialog = this.dialog.open(SortDialogComponent, {
minWidth: "40vw",
data: {
sortEntries,
sortingPreset,
},
disableClose: true,
});
openedDialog.afterClosed().subscribe(async (sortExpression) => {
if (sortExpression) {
this.sortExpression = sortExpression;
this.state.setSortKeys(this.sortExpression);
openedDialog.afterClosed().subscribe(async (sortingPreset) => {
if (sortingPreset) {
this.sortingPreset = sortingPreset;
this.state.setSortingPreset(this.sortingPreset);
}
});
}

@ -0,0 +1,8 @@
<button (click)="this.appClick.emit()"
[matTooltipShowDelay]="1000"
[matTooltip]="selectedPreset.toString()"
class="sort-button"
mat-flat-button>
<i>Sort: </i>
<app-sort-preset-item [preset]="this.selectedPreset"></app-sort-preset-item>
</button>

@ -0,0 +1,9 @@
@import "src/colors";
.sort-button {
width: 100%;
align-items: center;
background-color: $background-lighter-10;
text-overflow: ellipsis;
overflow: hidden;
}

@ -0,0 +1,25 @@
import {ComponentFixture, TestBed} from "@angular/core/testing";
import {SortButtonComponent} from "./sort-button.component";
describe("SortButtonComponent", () => {
let component: SortButtonComponent;
let fixture: ComponentFixture<SortButtonComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [SortButtonComponent]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(SortButtonComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it("should create", () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,17 @@
import {ChangeDetectionStrategy, Component, EventEmitter, Input, Output} from "@angular/core";
import {SortingPreset} from "../../../../../../api/models/SortingPreset";
@Component({
selector: "app-sort-button",
templateUrl: "./sort-button.component.html",
styleUrls: ["./sort-button.component.scss"],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SortButtonComponent {
@Input() selectedPreset!: SortingPreset;
@Output() appClick = new EventEmitter<void>();
constructor() {
}
}

@ -1,7 +1,21 @@
<h1 mat-dialog-title>Sort Entries</h1>
<h1 mat-dialog-title>
Sort Entries
</h1>
<div mat-dialog-content>
<mat-form-field *ngIf="this.availablePresets.length > 0" class="preset-selection">
<mat-label>Preset</mat-label>
<mat-select (selectionChange)="this.selectPreset($event.value)" [value]="this.previousId">
<mat-option [value]="-1"></mat-option>
<mat-option *ngFor="let preset of this.availablePresets"
[matTooltipShowDelay]="1000"
[matTooltip]="preset.toString()"
[value]="preset.id">
{{preset.toString()}}
</mat-option>
</mat-select>
</mat-form-field>
<div (cdkDropListDropped)="this.onSortEntryDrop($event)" cdkDropList class="sort-input-list">
<div *ngFor="let sortKey of sortEntries" cdkDrag class="sort-input-row">
<div *ngFor="let sortKey of sortingPreset.sortKeys" cdkDrag class="sort-input-row">
<div *cdkDragPlaceholder class="drag-placeholder"></div>
<div cdkDragHandle class="drag-handle">
<ng-icon name="mat-drag-handle"></ng-icon>
@ -21,14 +35,16 @@
</mat-form-field>
<mat-form-field *ngIf="sortKey.sortType === 'Namespace'">
<mat-label>Namespace Name</mat-label>
<input #namespaceInput (change)="sortKey.namespaceName = namespaceInput.value"
[matAutocomplete]="namespaceAutocomplete"
(keyup)="this.updateAutocompleteSuggestions(namespaceInput.value)"
<input #namespaceInput
(change)="this.handlePresetChange(); sortKey.namespaceName = namespaceInput.value"
(focus)="this.updateAutocompleteSuggestions(namespaceInput.value)"
(keyup)="this.updateAutocompleteSuggestions(namespaceInput.value)"
[matAutocomplete]="namespaceAutocomplete"
[value]="sortKey.namespaceName ?? ''"
matInput
required>
<mat-autocomplete #namespaceAutocomplete (optionSelected)="sortKey.namespaceName = $event.option.value">
<mat-autocomplete #namespaceAutocomplete
(optionSelected)="this.handlePresetChange(); sortKey.namespaceName = $event.option.value">
<mat-option *ngFor="let namespace of suggestedNamespaces" [value]="namespace.name">
{{namespace.name}}
</mat-option>
@ -37,16 +53,18 @@
<div *ngIf="sortKey.sortType !== 'Namespace'" class="filler"></div>
<mat-form-field>
<mat-label>Direction</mat-label>
<mat-select [(value)]="sortKey.sortDirection" required>
<mat-select (selectionChange)="this.handlePresetChange()" [(value)]="sortKey.sortDirection" required>
<mat-option value="Ascending">Ascending</mat-option>
<mat-option value="Descending">Descending</mat-option>
</mat-select>
</mat-form-field>
<button (click)="addNewSortKey()" *ngIf="sortEntries.indexOf(sortKey) === sortEntries.length -1"
<button (click)="addNewSortKey()"
*ngIf="sortingPreset.sortKeys.indexOf(sortKey) === sortingPreset.sortKeys.length - 1"
mat-flat-button>
<ng-icon name="mat-add"></ng-icon>
</button>
<button (click)="removeSortKey(sortKey)" *ngIf="sortEntries.indexOf(sortKey) !== sortEntries.length -1"
<button (click)="removeSortKey(sortKey)"
*ngIf="sortingPreset.sortKeys.indexOf(sortKey) !== sortingPreset.sortKeys.length -1"
mat-flat-button>
<ng-icon name="mat-remove"></ng-icon>
</button>
@ -54,6 +72,22 @@
</div>
</div>
<div class="dialog-actions" mat-dialog-actions>
<button (click)="confirmSort()" color="primary" mat-flat-button>Sort</button>
<button (click)="deletePreset()" *ngIf="this.previousId >= 0" class="button-left" color="warn" mat-stroked-button>
Delete
</button>
<button (click)="saveNewPreset()"
*ngIf="this.sortingPreset.sortKeys.length > 0"
class="button-left"
color="accent"
mat-stroked-button>Save new
</button>
<button (click)="savePreset()"
*ngIf="this.sortingPreset.sortKeys.length > 0 && this.previousId >= 0"
class="button-left"
color="accent"
mat-flat-button>Save
</button>
<button (click)="cancelSort()" color="accent" mat-stroked-button>Cancel</button>
<button (click)="confirmSort()" color="primary" mat-flat-button>Sort</button>
</div>

@ -18,12 +18,17 @@ mat-form-field, .filler, .drag-handle {
}
.dialog-actions {
display: flex;
flex-direction: row-reverse;
width: 100%;
display: block;
button {
margin-left: 1em;
float: right;
}
button.button-left {
float: left;
margin-right: 1em;
}
}
@ -57,3 +62,9 @@ mat-form-field, .filler, .drag-handle {
background-color: darken(dimgrey, 20);
border-radius: 1em;
}
.preset-selection {
width: calc(100% - 2em);
display: block;
font-size: 1em;
}

@ -1,10 +1,13 @@
import {ChangeDetectionStrategy, Component, Inject} from "@angular/core";
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, OnInit} from "@angular/core";
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
import {SortKey} from "../../../../../models/SortKey";
import {SortKey} from "../../../../../../api/models/SortKey";
import {CdkDragDrop, moveItemInArray} from "@angular/cdk/drag-drop";
import {Namespace} from "../../../../../../api/models/Namespace";
import {TagService} from "../../../../../services/tag/tag.service";
import {compareSearchResults} from "../../../../../utils/compare-utils";
import {SortingPreset} from "../../../../../../api/models/SortingPreset";
import {PresetService} from "../../../../../services/preset/preset.service";
import {LoggingService} from "../../../../../services/logging/logging.service";
@Component({
selector: "app-sort-dialog",
@ -12,32 +15,52 @@ import {compareSearchResults} from "../../../../../utils/compare-utils";
styleUrls: ["./sort-dialog.component.scss"],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SortDialogComponent {
export class SortDialogComponent implements OnInit {
public sortEntries: SortKey[] = [];
public sortingPreset: SortingPreset = SortingPreset.fromValues(-1, []);
public availablePresets: SortingPreset[] = [];
public suggestedNamespaces: Namespace[] = [];
public emptyPreset = SortingPreset.fromValues(-1, [
SortKey.fromValues("FileImportedTime", "Ascending", undefined)
]);
public previousId: number = -1;
private namespaces: Namespace[] = [];
constructor(public tagService: TagService, public dialogRef: MatDialogRef<SortDialogComponent>, @Inject(
MAT_DIALOG_DATA) data: any) {
this.sortEntries = data.sortEntries;
constructor(
public logger: LoggingService,
public tagService: TagService,
public presetService: PresetService,
public changeDetector: ChangeDetectorRef,
public dialogRef: MatDialogRef<SortDialogComponent>,
@Inject(
MAT_DIALOG_DATA) data: any
) {
this.sortingPreset = data.sortingPreset;
this.previousId = this.sortingPreset.id;
console.debug(this.sortingPreset);
tagService.namespaces.subscribe(
namespaces => this.namespaces = namespaces);
}
public async ngOnInit() {
this.availablePresets = await this.presetService.getAllSortingPresets();
}
addNewSortKey() {
const sortKey = new SortKey("FileName", "Ascending", undefined);
this.sortEntries.push(sortKey);
const sortKey = SortKey.fromValues("FileName", "Ascending", undefined);
this.handlePresetChange();
this.sortingPreset.sortKeys.push(sortKey);
}
public removeSortKey(sortKey: SortKey): void {
const index = this.sortEntries.indexOf(sortKey);
this.sortEntries.splice(index, 1);
const index = this.sortingPreset.sortKeys.indexOf(sortKey);
this.handlePresetChange();
this.sortingPreset.sortKeys.splice(index, 1);
}
public confirmSort(): void {
this.dialogRef.close(this.sortEntries);
this.dialogRef.close(this.sortingPreset);
}
public cancelSort(): void {
@ -45,7 +68,8 @@ export class SortDialogComponent {
}
public onSortEntryDrop(event: CdkDragDrop<SortKey[]>): void {
moveItemInArray(this.sortEntries, event.previousIndex,
this.handlePresetChange();
moveItemInArray(this.sortingPreset.sortKeys, event.previousIndex,
event.currentIndex
);
}
@ -55,4 +79,50 @@ export class SortDialogComponent {
(a, b) => compareSearchResults(value, a.name, b.name))
.slice(0, 50);
}
public handlePresetChange() {
if (this.sortingPreset.id >= 0) {
this.previousId = this.sortingPreset.id;
this.sortingPreset.id = -1;
}
}
public async savePreset() {
await this.deletePreset();
await this.saveNewPreset();
}
public async saveNewPreset() {
let newPreset = await this.logger.try(() => this.presetService.addSortingPreset(this.sortingPreset.sortKeys));
if (newPreset) {
this.sortingPreset.setData(newPreset.rawData);
this.previousId = this.sortingPreset.id;
this.availablePresets.push(new SortingPreset(JSON.parse(JSON.stringify(newPreset.rawData))));
this.changeDetector.detectChanges();
}
}
public async deletePreset() {
if (this.previousId >= 0) {
const index = this.availablePresets.findIndex(p => p.id == this.previousId);
if (index >= 0) {
this.availablePresets.splice(index, 1);
this.changeDetector.detectChanges();
}
try {
await this.presetService.deleteSortingPreset(this.previousId);
} catch (err: any) {
this.logger.warn(`Could not delete previous preset: ${err.message}`);
}
}
}
public selectPreset(presetId: number): void {
const preset = this.availablePresets.find(p => p.id == presetId) ?? this.emptyPreset;
if (preset) {
this.sortingPreset.setData(JSON.parse(JSON.stringify(preset.rawData)));
this.previousId = preset.id;
}
}
}

@ -0,0 +1,7 @@
<span *ngFor="let key of this.preset.sortKeys" class="sort-key">
<span class="sort-key-type">{{key.sortType}}</span>
<span *ngIf="key.sortType === 'Namespace'" class="sort-key-namespace"> {{key.namespaceName}}</span>
<ng-icon *ngIf="key.sortDirection === 'Ascending'" class="sort-key-direction" name="matExpandLess"></ng-icon>
<ng-icon *ngIf="key.sortDirection === 'Descending'" class="sort-key-direction" name="matExpandMore"></ng-icon>
<span class="key-divider">| </span>
</span>

@ -0,0 +1,20 @@
@import "src/colors";
.sort-key-direction {
font-size: 1.5em;
vertical-align: bottom;
margin-bottom: 0.6em;
}
.sort-key-type {
color: $primary-lighter-50
}
.sort-key-namespace {
color: $accent-lighter-10;
}
.sort-key:last-child .key-divider {
display: none;
}

@ -0,0 +1,25 @@
import {ComponentFixture, TestBed} from "@angular/core/testing";
import {SortPresetItemComponent} from "./sort-preset-item.component";
describe("SortPresetItemComponent", () => {
let component: SortPresetItemComponent;
let fixture: ComponentFixture<SortPresetItemComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [SortPresetItemComponent]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(SortPresetItemComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it("should create", () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,16 @@
import {ChangeDetectionStrategy, Component, Input} from "@angular/core";
import {SortingPreset} from "../../../../../../api/models/SortingPreset";
@Component({
selector: "app-sort-preset-item",
templateUrl: "./sort-preset-item.component.html",
styleUrls: ["./sort-preset-item.component.scss"],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SortPresetItemComponent {
@Input() preset!: SortingPreset;
constructor() {
}
}

@ -7,9 +7,11 @@ import {
MatAddCircle,
MatChangeCircle,
MatDeleteSweep,
MatExpandLess,
MatExpandMore,
MatFilterAlt,
MatRemove,
MatRemoveCircle
MatRemoveCircle,
} from "@ng-icons/material-icons";
import {MatRippleModule} from "@angular/material/core";
import {MatButtonModule} from "@angular/material/button";
@ -48,6 +50,9 @@ import {
} from "./file-search/filter-dialog/filter-expression-list-item/filter-expression-list-item.component";
import {GetTagQueryPipe} from "./file-search/filter-pipes/get-tag-query.pipe";
import {GetPropertyQueryPipe} from "./file-search/filter-pipes/get-property-query.pipe";
import {SortButtonComponent} from "./file-search/sort-button/sort-button.component";
import {MatTooltipModule} from "@angular/material/tooltip";
import {SortPresetItemComponent} from "./file-search/sort-preset-item/sort-preset-item.component";
@NgModule({
@ -66,6 +71,8 @@ import {GetPropertyQueryPipe} from "./file-search/filter-pipes/get-property-quer
FilterExpressionListItemComponent,
GetTagQueryPipe,
GetPropertyQueryPipe,
SortButtonComponent,
SortPresetItemComponent,
],
exports: [
TagEditComponent,
@ -81,7 +88,9 @@ import {GetPropertyQueryPipe} from "./file-search/filter-pipes/get-property-quer
MatAddCircle,
MatRemoveCircle,
MatDeleteSweep,
MatFilterAlt
MatFilterAlt,
MatExpandMore,
MatExpandLess,
}),
MatRippleModule,
MatButtonModule,
@ -102,6 +111,7 @@ import {GetPropertyQueryPipe} from "./file-search/filter-pipes/get-property-quer
MatMenuModule,
ReactiveFormsModule,
MatAutocompleteModule,
MatTooltipModule,
]
})
export class SidebarModule {

@ -1,35 +0,0 @@
export class SortKey {
constructor(
public sortType: "Namespace" | "FileName" | "FileSize" | "FileImportedTime" | "FileCreatedTime" | "FileChangeTime" | "FileType" | "NumTags",
public sortDirection: "Ascending" | "Descending",
public namespaceName: string | undefined
) {
}
public toString(): string {
if (this.sortType == "Namespace") {
return `${this.sortType} '${this.namespaceName}' ${this.sortDirection}`;
} else {
return `${this.sortType} ${this.sortDirection}`;
}
}
public toBackendType(): any {
if (this.sortType == "Namespace") {
return {
"Namespace": {
direction: this.sortDirection,
name: this.namespaceName
}
};
} else {
let returnObj: any = {};
returnObj[this.sortType] = this.sortDirection;
return returnObj;
}
}
}

@ -2,10 +2,11 @@ import {BehaviorSubject} from "rxjs";
import {TabCategory} from "./TabCategory";
import {FileService} from "../services/file/file.service";
import {File} from "../../api/models/File";
import {SortKey} from "./SortKey";
import {SortKey} from "../../api/models/SortKey";
import {debounceTime} from "rxjs/operators";
import {mapNew} from "../../api/models/adaptors";
import {SearchFilters} from "../../api/models/SearchFilters";
import {SortingPreset} from "../../api/models/SortingPreset";
export class TabState {
public uuid: number;
@ -16,12 +17,14 @@ export class TabState {
public files = new BehaviorSubject<File[]>([]);
public filters = new BehaviorSubject<SearchFilters>(new SearchFilters([]));
public sortKeys = new BehaviorSubject<SortKey[]>(
[new SortKey(
public sortingPreset = new BehaviorSubject<SortingPreset>(SortingPreset.fromValues(
-1,
[SortKey.fromValues(
"FileImportedTime",
"Ascending",
undefined
)]);
)]
));
private fileService: FileService;
@ -36,7 +39,7 @@ export class TabState {
if (this.category === TabCategory.Files) {
this.filters.pipe(debounceTime(500))
.subscribe(async () => await this.findFiles());
this.sortKeys.pipe(debounceTime(100))
this.sortingPreset.pipe(debounceTime(100))
.subscribe(async () => await this.findFiles());
}
}
@ -50,16 +53,9 @@ export class TabState {
dto.category,
fileService
);
const sortKeys = dto.sortKeys.map(
(s: { sortType: any, sortDirection: any, namespaceName: any }) =>
new SortKey(
s.sortType,
s.sortDirection,
s.namespaceName
)
);
state.filters.next(new SearchFilters(dto.filters ?? []));
state.sortKeys.next(sortKeys);
state.sortingPreset.next(new SortingPreset(dto.sortingPreset));
state.mode.next(dto.mode ?? "grid");
state.selectedCD.next(dto.selectedFileHash);
state.files.next((dto.files ?? []).map(mapNew(File)));
@ -71,7 +67,7 @@ export class TabState {
this.loading.next(true);
const files = await this.fileService.findFiles(
this.filters.value,
this.sortKeys.value
this.sortingPreset.value.sortKeys
);
this.files.next(files);
this.loading.next(false);
@ -81,8 +77,8 @@ export class TabState {
this.filters.next(filters);
}
public setSortKeys(keys: SortKey[]) {
this.sortKeys.next(keys);
public setSortingPreset(preset: SortingPreset) {
this.sortingPreset.next(preset);
}
public getDTO(): any {
@ -90,7 +86,7 @@ export class TabState {
uuid: this.uuid,
category: this.category,
filters: this.filters.value.getFilters(),
sortKeys: this.sortKeys.value,
sortingPreset: this.sortingPreset.value.rawData,
mode: this.mode.value,
selectedFileHash: this.selectedCD.value,
files: this.category === TabCategory.Import ? this.files.value.map(

@ -19,7 +19,7 @@ export class FileHelper {
filters: [{
name: file.mimeType,
extensions: [extension ?? "*"]
}, {name: "All", extensions: ["*"]}]
}, { name: "All", extensions: ["*"] }]
});
}

@ -1,7 +1,7 @@
import {Inject, Injectable} from "@angular/core";
import {File} from "../../../api/models/File";
import {DomSanitizer, SafeResourceUrl} from "@angular/platform-browser";
import {SortKey} from "../../models/SortKey";
import {SortKey} from "../../../api/models/SortKey";
import {MediarepoApi} from "../../../api/Api";
import {mapMany, mapNew} from "../../../api/models/adaptors";
import {FileMetadata, FileStatus} from "../../../api/api-types/files";
@ -26,7 +26,7 @@ export class FileService {
return MediarepoApi.findFiles(
{
filters: filters.getFilters(),
sortBy: sortBy.map(k => k.toBackendType())
sortBy: sortBy.map(k => k.rawData)
})
.then(mapMany(mapNew(File)));
}

@ -19,7 +19,7 @@ export class ImportService {
* @returns {Promise<FileOsMetadata[]>}
*/
public async resolvePathsToFiles(paths: string[]): Promise<FileOsMetadata[]> {
return MediarepoApi.resolvePathsToFiles({paths});
return MediarepoApi.resolvePathsToFiles({ paths });
}
/**
@ -29,6 +29,6 @@ export class ImportService {
* @returns {Promise<File>}
*/
public async addLocalFile(metadata: FileOsMetadata, options: AddFileOptions): Promise<File> {
return MediarepoApi.addLocalFile({metadata, options}).then(mapNew(File));
return MediarepoApi.addLocalFile({ metadata, options }).then(mapNew(File));
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save