feature/memories #35
248
Cargo.lock
generated
248
Cargo.lock
generated
@@ -97,7 +97,7 @@ dependencies = [
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
"sha1",
|
||||
"smallvec",
|
||||
"tokio",
|
||||
@@ -133,7 +133,7 @@ dependencies = [
|
||||
"log",
|
||||
"memchr",
|
||||
"mime",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_plain",
|
||||
@@ -449,28 +449,6 @@ version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||
|
||||
[[package]]
|
||||
name = "async-stream"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476"
|
||||
dependencies = [
|
||||
"async-stream-impl",
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-stream-impl"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.87"
|
||||
@@ -517,53 +495,6 @@ dependencies = [
|
||||
"arrayvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.7.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http 1.3.1",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"itoa",
|
||||
"matchit",
|
||||
"memchr",
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustversion",
|
||||
"serde",
|
||||
"sync_wrapper",
|
||||
"tower 0.5.2",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http 1.3.1",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"mime",
|
||||
"pin-project-lite",
|
||||
"rustversion",
|
||||
"sync_wrapper",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.74"
|
||||
@@ -1236,12 +1167,6 @@ version = "0.31.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.26"
|
||||
@@ -1254,7 +1179,7 @@ dependencies = [
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"http 0.2.12",
|
||||
"indexmap 2.6.0",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
@@ -1273,19 +1198,13 @@ dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http 1.3.1",
|
||||
"indexmap 2.6.0",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.1"
|
||||
@@ -1386,7 +1305,6 @@ dependencies = [
|
||||
"http 1.3.1",
|
||||
"http-body",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"smallvec",
|
||||
@@ -1612,7 +1530,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "image-api"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"actix",
|
||||
"actix-files",
|
||||
@@ -1640,8 +1558,9 @@ dependencies = [
|
||||
"opentelemetry_sdk",
|
||||
"path-absolutize",
|
||||
"prometheus",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
"rayon",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
@@ -1661,16 +1580,6 @@ version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aae21c3177a27788957044151cc2800043d127acaa460a47ebb9b84dfa2c6aa0"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown 0.12.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.6.0"
|
||||
@@ -1678,7 +1587,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.15.1",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1904,12 +1813,6 @@ dependencies = [
|
||||
"imgref",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
|
||||
|
||||
[[package]]
|
||||
name = "maybe-rayon"
|
||||
version = "0.1.1"
|
||||
@@ -2118,9 +2021,9 @@ checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry"
|
||||
version = "0.28.0"
|
||||
version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "236e667b670a5cdf90c258f5a55794ec5ac5027e960c224bff8367a59e1e6426"
|
||||
checksum = "aaf416e4cb72756655126f7dd7bb0af49c674f4c1b9903e80c009e0c37e552e6"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
@@ -2132,9 +2035,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-appender-log"
|
||||
version = "0.28.0"
|
||||
version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3bbd76b0dafbb54e8631ca147e7e090d09616ae7da45781d5403a83ac9af4290"
|
||||
checksum = "e688026e48f4603494f619583e0aa0b0edd9c0b9430e1c46804df2ff32bc8798"
|
||||
dependencies = [
|
||||
"log",
|
||||
"opentelemetry",
|
||||
@@ -2142,26 +2045,23 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-http"
|
||||
version = "0.28.0"
|
||||
version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8863faf2910030d139fb48715ad5ff2f35029fc5f244f6d5f689ddcf4d26253"
|
||||
checksum = "50f6639e842a97dbea8886e3439710ae463120091e2e064518ba8e716e6ac36d"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"http 1.3.1",
|
||||
"opentelemetry",
|
||||
"reqwest",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-otlp"
|
||||
version = "0.28.0"
|
||||
version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5bef114c6d41bea83d6dc60eb41720eedd0261a67af57b66dd2b84ac46c01d91"
|
||||
checksum = "dbee664a43e07615731afc539ca60c6d9f1a9425e25ca09c57bc36c87c55852b"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"futures-core",
|
||||
"http 1.3.1",
|
||||
"opentelemetry",
|
||||
"opentelemetry-http",
|
||||
@@ -2177,9 +2077,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-proto"
|
||||
version = "0.28.0"
|
||||
version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f8870d3024727e99212eb3bb1762ec16e255e3e6f58eeb3dc8db1aa226746d"
|
||||
checksum = "2e046fd7660710fe5a05e8748e70d9058dc15c94ba914e7c4faa7c728f0e8ddc"
|
||||
dependencies = [
|
||||
"opentelemetry",
|
||||
"opentelemetry_sdk",
|
||||
@@ -2189,38 +2089,31 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-stdout"
|
||||
version = "0.28.0"
|
||||
version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5eb0e5a5132e4b80bf037a78e3e12c8402535199f5de490d0c38f7eac71bc831"
|
||||
checksum = "447191061af41c3943e082ea359ab8b64ff27d6d34d30d327df309ddef1eef6f"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"chrono",
|
||||
"futures-util",
|
||||
"opentelemetry",
|
||||
"opentelemetry_sdk",
|
||||
"serde",
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry_sdk"
|
||||
version = "0.28.0"
|
||||
version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84dfad6042089c7fc1f6118b7040dc2eb4ab520abbf410b79dc481032af39570"
|
||||
checksum = "11f644aa9e5e31d11896e024305d7e3c98a88884d9f8919dbf37a9991bc47a4b"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"futures-channel",
|
||||
"futures-executor",
|
||||
"futures-util",
|
||||
"glob",
|
||||
"opentelemetry",
|
||||
"percent-encoding",
|
||||
"rand",
|
||||
"rand 0.9.2",
|
||||
"serde_json",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2458,8 +2351,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"rand_chacha 0.3.1",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||
dependencies = [
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_core 0.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2469,7 +2372,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core 0.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2481,6 +2394,15 @@ dependencies = [
|
||||
"getrandom 0.2.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||
dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rav1e"
|
||||
version = "0.7.1"
|
||||
@@ -2507,8 +2429,8 @@ dependencies = [
|
||||
"once_cell",
|
||||
"paste",
|
||||
"profiling",
|
||||
"rand",
|
||||
"rand_chacha",
|
||||
"rand 0.8.5",
|
||||
"rand_chacha 0.3.1",
|
||||
"simd_helpers",
|
||||
"system-deps",
|
||||
"thiserror 1.0.69",
|
||||
@@ -2623,7 +2545,7 @@ dependencies = [
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tower 0.5.2",
|
||||
"tower",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
@@ -2681,12 +2603,6 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.18"
|
||||
@@ -3042,21 +2958,9 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.17"
|
||||
@@ -3108,7 +3012,7 @@ version = "0.22.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
|
||||
dependencies = [
|
||||
"indexmap 2.6.0",
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
@@ -3117,16 +3021,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tonic"
|
||||
version = "0.12.3"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52"
|
||||
checksum = "7e581ba15a835f4d9ea06c55ab1bd4dce26fc53752c69a04aac00703bfb49ba9"
|
||||
dependencies = [
|
||||
"async-stream",
|
||||
"async-trait",
|
||||
"axum",
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"h2 0.4.8",
|
||||
"http 1.3.1",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
@@ -3136,30 +3037,9 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"pin-project",
|
||||
"prost",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tower 0.4.13",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"indexmap 1.9.3",
|
||||
"pin-project",
|
||||
"pin-project-lite",
|
||||
"rand",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -3173,11 +3053,15 @@ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"indexmap",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
15
Cargo.toml
15
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "image-api"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
authors = ["Cameron Cordes <cameronc.dev@gmail.com>"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -37,9 +37,10 @@ prometheus = "0.13"
|
||||
lazy_static = "1.5"
|
||||
anyhow = "1.0"
|
||||
rand = "0.8.5"
|
||||
opentelemetry = { version = "0.28.0", features = ["default", "metrics", "tracing"] }
|
||||
opentelemetry_sdk = { version = "0.28.0", features = ["default", "rt-tokio-current-thread", "tracing", "metrics"] }
|
||||
opentelemetry-otlp = { version = "0.28.0", features = ["default", "metrics", "tracing", "grpc-tonic"] }
|
||||
opentelemetry-stdout = "0.28.0"
|
||||
opentelemetry-appender-log = "0.28.0"
|
||||
tempfile = "3.20.0"
|
||||
opentelemetry = { version = "0.30.0", features = ["default", "metrics", "tracing"] }
|
||||
opentelemetry_sdk = { version = "0.30.0", features = ["default", "rt-tokio-current-thread", "metrics"] }
|
||||
opentelemetry-otlp = { version = "0.30.0", features = ["default", "metrics", "tracing", "grpc-tonic"] }
|
||||
opentelemetry-stdout = "0.30.0"
|
||||
opentelemetry-appender-log = "0.30.0"
|
||||
tempfile = "3.20.0"
|
||||
regex = "1.11.1"
|
||||
@@ -137,9 +137,9 @@ pub enum PhotoSize {
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ThumbnailRequest {
|
||||
pub(crate) path: String,
|
||||
pub(crate)size: Option<PhotoSize>,
|
||||
pub(crate) size: Option<PhotoSize>,
|
||||
#[serde(default)]
|
||||
pub(crate)format: Option<ThumbnailFormat>,
|
||||
pub(crate) format: Option<ThumbnailFormat>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq)]
|
||||
|
||||
57
src/files.rs
57
src/files.rs
@@ -505,7 +505,6 @@ mod tests {
|
||||
|
||||
mod api {
|
||||
use super::*;
|
||||
use actix::Actor;
|
||||
use actix_web::{web::Query, HttpResponse};
|
||||
|
||||
use crate::{
|
||||
@@ -516,9 +515,9 @@ mod tests {
|
||||
|
||||
use crate::database::test::in_memory_db_connection;
|
||||
use crate::tags::SqliteTagDao;
|
||||
use actix_web::web::Data;
|
||||
use std::{fs, sync::Arc};
|
||||
use actix_web::test::TestRequest;
|
||||
use actix_web::web::Data;
|
||||
use std::fs;
|
||||
|
||||
fn setup() {
|
||||
let _ = env_logger::builder().is_test(true).try_init();
|
||||
@@ -609,12 +608,22 @@ mod tests {
|
||||
|
||||
let mut tag_dao = SqliteTagDao::new(in_memory_db_connection());
|
||||
|
||||
let tag1 = tag_dao.create_tag(&opentelemetry::Context::current(), "tag1").unwrap();
|
||||
let _tag2 = tag_dao.create_tag(&opentelemetry::Context::current(), "tag2").unwrap();
|
||||
let tag3 = tag_dao.create_tag(&opentelemetry::Context::current(), "tag3").unwrap();
|
||||
let tag1 = tag_dao
|
||||
.create_tag(&opentelemetry::Context::current(), "tag1")
|
||||
.unwrap();
|
||||
let _tag2 = tag_dao
|
||||
.create_tag(&opentelemetry::Context::current(), "tag2")
|
||||
.unwrap();
|
||||
let tag3 = tag_dao
|
||||
.create_tag(&opentelemetry::Context::current(), "tag3")
|
||||
.unwrap();
|
||||
|
||||
let _ = &tag_dao.tag_file(&opentelemetry::Context::current(), "test.jpg", tag1.id).unwrap();
|
||||
let _ = &tag_dao.tag_file(&opentelemetry::Context::current(), "test.jpg", tag3.id).unwrap();
|
||||
let _ = &tag_dao
|
||||
.tag_file(&opentelemetry::Context::current(), "test.jpg", tag1.id)
|
||||
.unwrap();
|
||||
let _ = &tag_dao
|
||||
.tag_file(&opentelemetry::Context::current(), "test.jpg", tag3.id)
|
||||
.unwrap();
|
||||
|
||||
let mut files = HashMap::new();
|
||||
files.insert(
|
||||
@@ -654,15 +663,31 @@ mod tests {
|
||||
|
||||
let mut tag_dao = SqliteTagDao::new(in_memory_db_connection());
|
||||
|
||||
let tag1 = tag_dao.create_tag(&opentelemetry::Context::current(), "tag1").unwrap();
|
||||
let _tag2 = tag_dao.create_tag(&opentelemetry::Context::current(), "tag2").unwrap();
|
||||
let tag3 = tag_dao.create_tag(&opentelemetry::Context::current(), "tag3").unwrap();
|
||||
let tag1 = tag_dao
|
||||
.create_tag(&opentelemetry::Context::current(), "tag1")
|
||||
.unwrap();
|
||||
let _tag2 = tag_dao
|
||||
.create_tag(&opentelemetry::Context::current(), "tag2")
|
||||
.unwrap();
|
||||
let tag3 = tag_dao
|
||||
.create_tag(&opentelemetry::Context::current(), "tag3")
|
||||
.unwrap();
|
||||
|
||||
let _ = &tag_dao.tag_file(&opentelemetry::Context::current(), "test.jpg", tag1.id).unwrap();
|
||||
let _ = &tag_dao.tag_file(&opentelemetry::Context::current(), "test.jpg", tag3.id).unwrap();
|
||||
let _ = &tag_dao
|
||||
.tag_file(&opentelemetry::Context::current(), "test.jpg", tag1.id)
|
||||
.unwrap();
|
||||
let _ = &tag_dao
|
||||
.tag_file(&opentelemetry::Context::current(), "test.jpg", tag3.id)
|
||||
.unwrap();
|
||||
|
||||
// Should get filtered since it doesn't have tag3
|
||||
tag_dao.tag_file(&opentelemetry::Context::current(), "some-other.jpg", tag1.id).unwrap();
|
||||
tag_dao
|
||||
.tag_file(
|
||||
&opentelemetry::Context::current(),
|
||||
"some-other.jpg",
|
||||
tag1.id,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut files = HashMap::new();
|
||||
files.insert(
|
||||
@@ -755,8 +780,8 @@ mod tests {
|
||||
assert!(is_valid_full_path(&base, &test_file, false).is_some());
|
||||
|
||||
assert_eq!(
|
||||
Some(PathBuf::from(test_file.clone())),
|
||||
is_valid_full_path(&base, &PathBuf::from(test_file), false)
|
||||
Some(test_file.clone()),
|
||||
is_valid_full_path(&base, &test_file, false)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ mod state;
|
||||
mod tags;
|
||||
mod video;
|
||||
|
||||
mod memories;
|
||||
mod otel;
|
||||
mod service;
|
||||
#[cfg(test)]
|
||||
@@ -654,6 +655,7 @@ fn main() -> std::io::Result<()> {
|
||||
.service(put_add_favorite)
|
||||
.service(delete_favorite)
|
||||
.service(get_file_metadata)
|
||||
.service(memories::list_memories)
|
||||
.add_feature(add_tag_services::<_, SqliteTagDao>)
|
||||
.app_data(app_data.clone())
|
||||
.app_data::<Data<RealFileSystem>>(Data::new(RealFileSystem::new(
|
||||
|
||||
476
src/memories.rs
Normal file
476
src/memories.rs
Normal file
@@ -0,0 +1,476 @@
|
||||
use actix_web::web::Data;
|
||||
use actix_web::{get, web, HttpRequest, HttpResponse, Responder};
|
||||
use chrono::LocalResult::{Ambiguous, Single};
|
||||
use chrono::{DateTime, Datelike, FixedOffset, Local, LocalResult, NaiveDate, TimeZone, Utc};
|
||||
use log::{debug, trace, warn};
|
||||
use opentelemetry::trace::{Span, Status, Tracer};
|
||||
use opentelemetry::KeyValue;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::Path;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use crate::data::Claims;
|
||||
use crate::files::is_image_or_video;
|
||||
use crate::otel::{extract_context_from_request, global_tracer};
|
||||
use crate::state::AppState;
|
||||
|
||||
#[derive(Copy, Clone, Deserialize, PartialEq, Debug)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum MemoriesSpan {
|
||||
Day,
|
||||
Week,
|
||||
Month,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct MemoriesRequest {
|
||||
pub span: Option<MemoriesSpan>,
|
||||
/// Client timezone offset in minutes from UTC (e.g., -480 for PST, 60 for CET)
|
||||
pub timezone_offset_minutes: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Clone)]
|
||||
pub struct MemoryItem {
|
||||
pub path: String,
|
||||
pub created: Option<i64>,
|
||||
pub modified: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct MemoriesResponse {
|
||||
pub items: Vec<MemoryItem>,
|
||||
}
|
||||
|
||||
fn get_file_date_info(
|
||||
path: &Path,
|
||||
client_timezone: &Option<FixedOffset>,
|
||||
) -> Option<(NaiveDate, Option<i64>, Option<i64>)> {
|
||||
// Read file metadata once
|
||||
let meta = std::fs::metadata(path).ok()?;
|
||||
|
||||
// Extract metadata timestamps
|
||||
let metadata_created = meta.created().ok().map(|t| {
|
||||
let utc: DateTime<Utc> = t.into();
|
||||
if let Some(tz) = client_timezone {
|
||||
utc.with_timezone(tz).timestamp()
|
||||
} else {
|
||||
utc.timestamp()
|
||||
}
|
||||
});
|
||||
|
||||
let metadata_modified = meta.modified().ok().map(|t| {
|
||||
let utc: DateTime<Utc> = t.into();
|
||||
if let Some(tz) = client_timezone {
|
||||
utc.with_timezone(tz).timestamp()
|
||||
} else {
|
||||
utc.timestamp()
|
||||
}
|
||||
});
|
||||
|
||||
// Try to get date from filename
|
||||
if let Some(date_time) = path
|
||||
.file_name()
|
||||
.and_then(|filename| filename.to_str())
|
||||
.and_then(extract_date_from_filename)
|
||||
{
|
||||
// Convert to client timezone if specified
|
||||
let date_in_timezone = if let Some(tz) = client_timezone {
|
||||
date_time.with_timezone(tz)
|
||||
} else {
|
||||
date_time.with_timezone(&Local).fixed_offset()
|
||||
};
|
||||
|
||||
// Use the timestamp from the filename date
|
||||
let created_ts = date_in_timezone.timestamp();
|
||||
|
||||
debug!(
|
||||
"File date from file {:?} > {:?} = {:?}",
|
||||
path.file_name(),
|
||||
date_time,
|
||||
date_in_timezone
|
||||
);
|
||||
return Some((
|
||||
date_in_timezone.date_naive(),
|
||||
Some(created_ts),
|
||||
metadata_modified,
|
||||
));
|
||||
}
|
||||
|
||||
// Fall back to metadata if no date in filename
|
||||
let system_time = meta.created().ok().or_else(|| meta.modified().ok())?;
|
||||
let dt_utc: DateTime<Utc> = system_time.into();
|
||||
|
||||
let date_in_timezone = if let Some(tz) = client_timezone {
|
||||
dt_utc.with_timezone(tz).date_naive()
|
||||
} else {
|
||||
dt_utc.with_timezone(&Local).date_naive()
|
||||
};
|
||||
|
||||
trace!("Fallback metadata create date = {:?}", date_in_timezone);
|
||||
Some((date_in_timezone, metadata_created, metadata_modified))
|
||||
}
|
||||
|
||||
fn extract_date_from_filename(filename: &str) -> Option<DateTime<FixedOffset>> {
|
||||
let build_date_from_ymd_capture =
|
||||
|captures: ®ex::Captures| -> Option<DateTime<FixedOffset>> {
|
||||
let year = captures.get(1)?.as_str().parse::<i32>().ok()?;
|
||||
let month = captures.get(2)?.as_str().parse::<u32>().ok()?;
|
||||
let day = captures.get(3)?.as_str().parse::<u32>().ok()?;
|
||||
let hour = captures.get(4)?.as_str().parse::<u32>().ok()?;
|
||||
let min = captures.get(5)?.as_str().parse::<u32>().ok()?;
|
||||
let sec = captures.get(6)?.as_str().parse::<u32>().ok()?;
|
||||
|
||||
match Local.from_local_datetime(
|
||||
&NaiveDate::from_ymd_opt(year, month, day)?.and_hms_opt(hour, min, sec)?,
|
||||
) {
|
||||
Single(dt) => Some(dt.fixed_offset()),
|
||||
Ambiguous(early_dt, _) => Some(early_dt.fixed_offset()),
|
||||
LocalResult::None => {
|
||||
warn!("Weird local date: {:?}", filename);
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 1. Screenshot format: Screenshot_2014-06-01-20-44-50.png
|
||||
if let Some(captures) =
|
||||
regex::Regex::new(r"Screenshot_(\d{4})-(\d{2})-(\d{2})-(\d{2})-(\d{2})-(\d{2})")
|
||||
.ok()?
|
||||
.captures(filename)
|
||||
.and_then(|c| build_date_from_ymd_capture(&c))
|
||||
{
|
||||
return Some(captures);
|
||||
}
|
||||
|
||||
// 2. Dash format: 2015-01-09_02-15-15.jpg
|
||||
if let Some(captures) = regex::Regex::new(r"(\d{4})-(\d{2})-(\d{2})_(\d{2})-(\d{2})-(\d{2})")
|
||||
.ok()?
|
||||
.captures(filename)
|
||||
.and_then(|c| build_date_from_ymd_capture(&c))
|
||||
{
|
||||
return Some(captures);
|
||||
}
|
||||
|
||||
// 3. Compact format: 20140927101712.jpg
|
||||
if let Some(captures) = regex::Regex::new(r"(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})")
|
||||
.ok()?
|
||||
.captures(filename)
|
||||
.and_then(|c| build_date_from_ymd_capture(&c))
|
||||
{
|
||||
return Some(captures);
|
||||
}
|
||||
|
||||
// 4. Timestamp format: 1401638400.jpeg
|
||||
if let Some(captures) = regex::Regex::new(r"(\d{10}|\d{13})\.")
|
||||
.ok()?
|
||||
.captures(filename)
|
||||
{
|
||||
let timestamp_str = captures.get(1)?.as_str();
|
||||
|
||||
// Millisecond timestamp (13 digits)
|
||||
if timestamp_str.len() >= 13 {
|
||||
if let Ok(ts_millis) = timestamp_str[0..13].parse::<i64>() {
|
||||
if let Some(naive_dt) = DateTime::from_timestamp_millis(ts_millis) {
|
||||
return Some(naive_dt.fixed_offset());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Second timestamp (10 digits)
|
||||
if timestamp_str.len() >= 10 {
|
||||
if let Ok(ts_secs) = timestamp_str[0..10].parse::<i64>() {
|
||||
if let Some(naive_dt) = DateTime::from_timestamp(ts_secs, 0) {
|
||||
return Some(naive_dt.fixed_offset());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[get("/memories")]
|
||||
pub async fn list_memories(
|
||||
_claims: Claims,
|
||||
request: HttpRequest,
|
||||
q: web::Query<MemoriesRequest>,
|
||||
app_state: Data<AppState>,
|
||||
) -> impl Responder {
|
||||
let tracer = global_tracer();
|
||||
let context = extract_context_from_request(&request);
|
||||
let mut span = tracer.start_with_context("list_memories", &context);
|
||||
|
||||
let span_mode = q.span.unwrap_or(MemoriesSpan::Day);
|
||||
let years_back: u32 = 15;
|
||||
|
||||
// Create timezone from client offset, default to local timezone if not provided
|
||||
let client_timezone = match q.timezone_offset_minutes {
|
||||
Some(offset_mins) => {
|
||||
let offset_secs = offset_mins * 60;
|
||||
Some(
|
||||
FixedOffset::east_opt(offset_secs)
|
||||
.unwrap_or_else(|| FixedOffset::east_opt(0).unwrap()),
|
||||
)
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
let now = if let Some(tz) = client_timezone {
|
||||
debug!("Client timezone: {:?}", tz);
|
||||
Utc::now().with_timezone(&tz).date_naive()
|
||||
} else {
|
||||
Local::now().date_naive()
|
||||
};
|
||||
|
||||
debug!("Now: {:?}", now);
|
||||
|
||||
let base = Path::new(&app_state.base_path);
|
||||
|
||||
let mut memories_with_dates: Vec<(MemoryItem, NaiveDate)> = Vec::new();
|
||||
|
||||
for entry in WalkDir::new(base)
|
||||
.into_iter()
|
||||
.filter_map(|e| e.ok())
|
||||
.filter(|e| e.file_type().is_file())
|
||||
{
|
||||
let path = entry.path();
|
||||
|
||||
if !is_image_or_video(path) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get file date and timestamps in one operation
|
||||
let (file_date, created, modified) = match get_file_date_info(path, &client_timezone) {
|
||||
Some(info) => info,
|
||||
None => {
|
||||
warn!("No date info found for file: {:?}", path);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if is_memories_match(file_date, now, span_mode, years_back) {
|
||||
if let Ok(rel) = path.strip_prefix(base) {
|
||||
memories_with_dates.push((
|
||||
MemoryItem {
|
||||
path: rel.to_string_lossy().to_string(),
|
||||
created,
|
||||
modified,
|
||||
},
|
||||
file_date,
|
||||
));
|
||||
} else {
|
||||
warn!("Failed to strip prefix from path: {:?}", path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match span_mode {
|
||||
// Sort by absolute time for a more 'overview'
|
||||
MemoriesSpan::Month => memories_with_dates.sort_by(|a, b| a.1.cmp(&b.1)),
|
||||
_ => {
|
||||
memories_with_dates.sort_by(|a, b| {
|
||||
let day_comparison = a.1.day().cmp(&b.1.day());
|
||||
|
||||
if day_comparison == std::cmp::Ordering::Equal {
|
||||
match (a.0.created, b.0.created) {
|
||||
(Some(a_time), Some(b_time)) => a_time.cmp(&b_time),
|
||||
(Some(_), None) => std::cmp::Ordering::Less,
|
||||
(None, Some(_)) => std::cmp::Ordering::Greater,
|
||||
(None, None) => std::cmp::Ordering::Equal,
|
||||
}
|
||||
} else {
|
||||
day_comparison
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
// Sort by day of the month and time (using the created timestamp)
|
||||
|
||||
let items: Vec<MemoryItem> = memories_with_dates.into_iter().map(|(m, _)| m).collect();
|
||||
|
||||
span.add_event(
|
||||
"memories_scanned",
|
||||
vec![
|
||||
KeyValue::new("span", format!("{:?}", span_mode)),
|
||||
KeyValue::new("years_back", years_back.to_string()),
|
||||
KeyValue::new("result_count", items.len().to_string()),
|
||||
KeyValue::new(
|
||||
"client_timezone",
|
||||
format!(
|
||||
"{:?}",
|
||||
client_timezone.unwrap_or_else(|| FixedOffset::east_opt(0).unwrap())
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
span.set_status(Status::Ok);
|
||||
|
||||
HttpResponse::Ok().json(MemoriesResponse { items })
|
||||
}
|
||||
|
||||
fn is_memories_match(
|
||||
file_date: NaiveDate,
|
||||
today: NaiveDate,
|
||||
span: MemoriesSpan,
|
||||
years_back: u32,
|
||||
) -> bool {
|
||||
if file_date > today {
|
||||
return false;
|
||||
}
|
||||
let years_diff = (today.year() - file_date.year()).unsigned_abs();
|
||||
if years_diff > years_back {
|
||||
warn!(
|
||||
"File date is too far in the past: {:?} vs {:?}",
|
||||
file_date, today
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
match span {
|
||||
MemoriesSpan::Day => same_month_day_any_year(file_date, today),
|
||||
MemoriesSpan::Week => same_week_any_year(file_date, today),
|
||||
MemoriesSpan::Month => same_month_any_year(file_date, today),
|
||||
}
|
||||
}
|
||||
|
||||
fn same_month_day_any_year(a: NaiveDate, b: NaiveDate) -> bool {
|
||||
a.month() == b.month() && a.day() == b.day()
|
||||
}
|
||||
|
||||
// Match same ISO week number and same weekday (ignoring year)
|
||||
fn same_week_any_year(a: NaiveDate, b: NaiveDate) -> bool {
|
||||
a.iso_week().week().eq(&b.iso_week().week())
|
||||
}
|
||||
|
||||
// Match same month (ignoring day and year)
|
||||
fn same_month_any_year(a: NaiveDate, b: NaiveDate) -> bool {
|
||||
a.month() == b.month()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use chrono::Timelike;
|
||||
use std::fs::File;
|
||||
use tempfile::tempdir;
|
||||
|
||||
// Add new tests for our date extraction functionality
|
||||
|
||||
#[test]
|
||||
fn test_extract_date_from_filename_screenshot_format() {
|
||||
let filename = "Screenshot_2014-06-01-20-44-50.png";
|
||||
let date_time = extract_date_from_filename(filename).unwrap();
|
||||
|
||||
assert_eq!(date_time.year(), 2014);
|
||||
assert_eq!(date_time.month(), 6);
|
||||
assert_eq!(date_time.day(), 1);
|
||||
assert_eq!(date_time.hour(), 20);
|
||||
assert_eq!(date_time.minute(), 44);
|
||||
assert_eq!(date_time.second(), 50);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_date_from_filename_dash_format() {
|
||||
let filename = "2015-01-09_02-15-15.jpg";
|
||||
let date_time = extract_date_from_filename(filename).unwrap();
|
||||
|
||||
assert_eq!(date_time.year(), 2015);
|
||||
assert_eq!(date_time.month(), 1);
|
||||
assert_eq!(date_time.day(), 9);
|
||||
assert_eq!(date_time.hour(), 2);
|
||||
assert_eq!(date_time.minute(), 15);
|
||||
assert_eq!(date_time.second(), 15);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_date_from_filename_compact_format() {
|
||||
let filename = "20140927101712.jpg";
|
||||
let date_time = extract_date_from_filename(filename).unwrap();
|
||||
|
||||
assert_eq!(date_time.year(), 2014);
|
||||
assert_eq!(date_time.month(), 9);
|
||||
assert_eq!(date_time.day(), 27);
|
||||
assert_eq!(date_time.hour(), 10);
|
||||
assert_eq!(date_time.minute(), 17);
|
||||
assert_eq!(date_time.second(), 12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_date_from_filename_timestamp_format() {
|
||||
let filename = "xyz_1401638400.jpeg"; // Unix timestamp for 2014-06-01 16:00:00 UTC
|
||||
// Timestamps are already in UTC, so timezone doesn't matter for this test
|
||||
let date_time = extract_date_from_filename(filename).unwrap();
|
||||
|
||||
assert_eq!(date_time.year(), 2014);
|
||||
assert_eq!(date_time.month(), 6);
|
||||
assert_eq!(date_time.day(), 1);
|
||||
assert_eq!(date_time.hour(), 16);
|
||||
assert_eq!(date_time.minute(), 0);
|
||||
assert_eq!(date_time.second(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_date_from_filename_timestamp_millis_format() {
|
||||
let filename = "xyz_1401638400000.jpeg"; // Unix timestamp in milliseconds
|
||||
let date_time = extract_date_from_filename(filename).unwrap();
|
||||
|
||||
assert_eq!(date_time.year(), 2014);
|
||||
assert_eq!(date_time.month(), 6);
|
||||
assert_eq!(date_time.day(), 1);
|
||||
assert_eq!(date_time.hour(), 16);
|
||||
assert_eq!(date_time.minute(), 0);
|
||||
assert_eq!(date_time.second(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_file_date_info_from_filename() {
|
||||
let temp_dir = tempdir().unwrap();
|
||||
let temp_file = temp_dir.path().join("Screenshot_2014-06-01-20-44-50.png");
|
||||
File::create(&temp_file).unwrap();
|
||||
|
||||
let (date, created, _) =
|
||||
get_file_date_info(&temp_file, &Some(*Local::now().fixed_offset().offset())).unwrap();
|
||||
|
||||
// Check that date is from filename
|
||||
assert_eq!(date.year(), 2014);
|
||||
assert_eq!(date.month(), 6);
|
||||
assert_eq!(date.day(), 1);
|
||||
|
||||
// Check that created timestamp matches the date from filename
|
||||
assert!(created.is_some());
|
||||
let ts = created.unwrap();
|
||||
// The timestamp should be for 2014-06-01 20:44:50 in the LOCAL timezone
|
||||
let dt_from_ts = Local.timestamp_opt(ts, 0).unwrap();
|
||||
assert_eq!(dt_from_ts.year(), 2014);
|
||||
assert_eq!(dt_from_ts.month(), 6);
|
||||
assert_eq!(dt_from_ts.day(), 1);
|
||||
assert_eq!(dt_from_ts.hour(), 20);
|
||||
assert_eq!(dt_from_ts.minute(), 44);
|
||||
assert_eq!(dt_from_ts.second(), 50);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_file_date_info_from_metadata() {
|
||||
let temp_dir = tempdir().unwrap();
|
||||
let temp_file = temp_dir.path().join("regular_image.jpg");
|
||||
File::create(&temp_file).unwrap();
|
||||
|
||||
let (date, created, modified) = get_file_date_info(&temp_file, &None).unwrap();
|
||||
|
||||
// Both date and timestamps should be from metadata (recent)
|
||||
let today = Local::now().date_naive();
|
||||
assert_eq!(date.year(), today.year());
|
||||
assert_eq!(date.month(), today.month());
|
||||
|
||||
// Both timestamps should be valid
|
||||
assert!(created.is_some());
|
||||
assert!(modified.is_some());
|
||||
|
||||
// Check that timestamps are recent
|
||||
let dt_created = DateTime::<Utc>::from_timestamp(created.unwrap(), 0).unwrap();
|
||||
assert_eq!(dt_created.year(), today.year());
|
||||
|
||||
let dt_modified = DateTime::<Utc>::from_timestamp(modified.unwrap(), 0).unwrap();
|
||||
assert_eq!(dt_modified.year(), today.year());
|
||||
}
|
||||
}
|
||||
11
src/state.rs
11
src/state.rs
@@ -32,7 +32,6 @@ impl AppState {
|
||||
gif_path,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Default for AppState {
|
||||
@@ -64,10 +63,10 @@ impl AppState {
|
||||
// Create the AppState with the temporary paths
|
||||
AppState::new(
|
||||
std::sync::Arc::new(crate::video::actors::StreamActor {}.start()),
|
||||
base_path.to_string_lossy().to_string(),
|
||||
thumbnail_path.to_string_lossy().to_string(),
|
||||
video_path.to_string_lossy().to_string(),
|
||||
gif_path.to_string_lossy().to_string(),
|
||||
base_path.to_string_lossy().to_string(),
|
||||
thumbnail_path.to_string_lossy().to_string(),
|
||||
video_path.to_string_lossy().to_string(),
|
||||
gif_path.to_string_lossy().to_string(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -77,6 +76,6 @@ impl AppState {
|
||||
fn create_test_subdir(base_path: &std::path::Path, name: &str) -> std::path::PathBuf {
|
||||
let dir_path = base_path.join(name);
|
||||
std::fs::create_dir_all(&dir_path)
|
||||
.expect(&format!("Failed to create {} directory", name));
|
||||
.unwrap_or_else(|_| panic!("Failed to create {} directory", name));
|
||||
dir_path
|
||||
}
|
||||
|
||||
46
src/tags.rs
46
src/tags.rs
@@ -552,8 +552,8 @@ impl TagDao for SqliteTagDao {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use actix_web::web::{Data, Json};
|
||||
use actix_web::test::TestRequest;
|
||||
use actix_web::web::Data;
|
||||
use std::{cell::RefCell, collections::HashMap};
|
||||
|
||||
use diesel::result::Error::NotFound;
|
||||
@@ -724,7 +724,9 @@ mod tests {
|
||||
add_tag(claims, request, web::Json(body), tag_data.clone()).await;
|
||||
|
||||
let mut tag_dao = tag_data.lock().unwrap();
|
||||
let tags = tag_dao.get_all_tags(&opentelemetry::Context::current(), None).unwrap();
|
||||
let tags = tag_dao
|
||||
.get_all_tags(&opentelemetry::Context::current(), None)
|
||||
.unwrap();
|
||||
assert_eq!(tags.len(), 1);
|
||||
assert_eq!(tags.first().unwrap().1.name, "test-tag");
|
||||
let tagged_photos = tag_dao.tagged_photos.borrow();
|
||||
@@ -747,11 +749,19 @@ mod tests {
|
||||
|
||||
let tag_data = Data::new(Mutex::new(tag_dao));
|
||||
let request = TestRequest::default().to_http_request();
|
||||
add_tag(claims.clone(), request.clone(), web::Json(add_request), tag_data.clone()).await;
|
||||
add_tag(
|
||||
claims.clone(),
|
||||
request.clone(),
|
||||
web::Json(add_request),
|
||||
tag_data.clone(),
|
||||
)
|
||||
.await;
|
||||
remove_tagged_photo(claims, request, web::Json(remove_request), tag_data.clone()).await;
|
||||
|
||||
let mut tag_dao = tag_data.lock().unwrap();
|
||||
let tags = tag_dao.get_all_tags(&opentelemetry::Context::current(), None).unwrap();
|
||||
let tags = tag_dao
|
||||
.get_all_tags(&opentelemetry::Context::current(), None)
|
||||
.unwrap();
|
||||
assert!(tags.is_empty());
|
||||
let tagged_photos = tag_dao.tagged_photos.borrow();
|
||||
let previously_added_tagged_photo = tagged_photos.get("test.png").unwrap();
|
||||
@@ -761,12 +771,22 @@ mod tests {
|
||||
#[actix_rt::test]
|
||||
async fn replace_tags_keeps_existing_tags_removes_extras_adds_missing_test() {
|
||||
let mut tag_dao = TestTagDao::new();
|
||||
let new_tag = tag_dao.create_tag(&opentelemetry::Context::current(), "Test").unwrap();
|
||||
let new_tag2 = tag_dao.create_tag(&opentelemetry::Context::current(), "Test2").unwrap();
|
||||
let _ = tag_dao.create_tag(&opentelemetry::Context::current(), "Test3").unwrap();
|
||||
let new_tag = tag_dao
|
||||
.create_tag(&opentelemetry::Context::current(), "Test")
|
||||
.unwrap();
|
||||
let new_tag2 = tag_dao
|
||||
.create_tag(&opentelemetry::Context::current(), "Test2")
|
||||
.unwrap();
|
||||
let _ = tag_dao
|
||||
.create_tag(&opentelemetry::Context::current(), "Test3")
|
||||
.unwrap();
|
||||
|
||||
tag_dao.tag_file(&opentelemetry::Context::current(), "test.jpg", new_tag.id).unwrap();
|
||||
tag_dao.tag_file(&opentelemetry::Context::current(), "test.jpg", new_tag2.id).unwrap();
|
||||
tag_dao
|
||||
.tag_file(&opentelemetry::Context::current(), "test.jpg", new_tag.id)
|
||||
.unwrap();
|
||||
tag_dao
|
||||
.tag_file(&opentelemetry::Context::current(), "test.jpg", new_tag2.id)
|
||||
.unwrap();
|
||||
|
||||
let claims = Claims::valid_user(String::from("1"));
|
||||
let tag_data = Data::new(Mutex::new(tag_dao));
|
||||
@@ -777,7 +797,13 @@ mod tests {
|
||||
};
|
||||
|
||||
let request = TestRequest::default().to_http_request();
|
||||
update_tags(claims, tag_data.clone(), request, web::Json(add_tags_request)).await;
|
||||
update_tags(
|
||||
claims,
|
||||
tag_data.clone(),
|
||||
request,
|
||||
web::Json(add_tags_request),
|
||||
)
|
||||
.await;
|
||||
|
||||
let tag_dao = tag_data.lock().unwrap();
|
||||
let tags_for_test_photo = &tag_dao.tagged_photos.borrow()["test.jpg"];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use futures::TryFutureExt;
|
||||
use log::{debug, error, info, warn};
|
||||
use std::io::{Result};
|
||||
use std::io::Result;
|
||||
use std::process::{Output, Stdio};
|
||||
use std::time::Instant;
|
||||
use tokio::process::Command;
|
||||
@@ -182,4 +182,4 @@ impl Ffmpeg {
|
||||
|
||||
Ok(output.status.code().unwrap_or(-1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use crate::otel::global_tracer;
|
||||
use crate::video::ffmpeg::{Ffmpeg, GifType};
|
||||
use crate::{is_video, update_media_counts};
|
||||
use log::{info};
|
||||
use opentelemetry::trace::{Tracer};
|
||||
use log::info;
|
||||
use opentelemetry::trace::Tracer;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{fs};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
pub mod actors;
|
||||
@@ -43,8 +43,9 @@ pub async fn generate_video_gifs() {
|
||||
let gif_path = Path::new(gif_directory).join(relative_path);
|
||||
let gif_path = gif_path.with_extension("gif");
|
||||
if let Some(parent_dir) = gif_path.parent() {
|
||||
fs::create_dir_all(parent_dir).unwrap_or_else(|_| panic!("There was an issue creating gif directory {:?}",
|
||||
gif_path));
|
||||
fs::create_dir_all(parent_dir).unwrap_or_else(|_| {
|
||||
panic!("There was an issue creating gif directory {:?}", gif_path)
|
||||
});
|
||||
}
|
||||
info!("Generating gif for {:?}", path);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user