Compare commits
1
Commits
master
...
2e207c5fa0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e207c5fa0 |
No files matched your search
@@ -84,7 +84,7 @@ cargo run --bin create_user -- <username> <password>
|
|||||||
### Core Components
|
### Core Components
|
||||||
|
|
||||||
**Layered Architecture:**
|
**Layered Architecture:**
|
||||||
- **Startup wiring** (`main.rs`): only ~350 lines — env load, migrations, AppState, route registration, server bind. Background jobs are kicked off here but defined elsewhere.
|
- **Startup wiring** (`main.rs`): only ~350 lines — env load, migrations, AppState, route registration, server bind. Background jobs are kicked off here but defined elsewhere. Every module lives in the `image_api` library (`lib.rs`); `main.rs` and the `src/bin` tools only `use image_api::…`. Never add `mod` declarations to `main.rs` — that compiles the module a second time (doubling release builds and running its tests twice). Anything `main.rs` calls must be `pub`.
|
||||||
- **HTTP Layer** (`handlers/{image,video,favorites}.rs`, `files.rs`, `tags.rs`, `faces.rs`, `memories.rs`, `ai/handlers.rs`): the route handlers, grouped by domain.
|
- **HTTP Layer** (`handlers/{image,video,favorites}.rs`, `files.rs`, `tags.rs`, `faces.rs`, `memories.rs`, `ai/handlers.rs`): the route handlers, grouped by domain.
|
||||||
- **Background loops** (`watcher.rs`): the file-watcher tick (`watch_files`, `process_new_files`) and the orphaned-playlist cleanup (`cleanup_orphaned_playlists`). Per-tick drains are factored into `backfill.rs` (`backfill_unhashed_backlog`, `backfill_missing_date_taken`, `backfill_missing_content_hashes`, `process_face_backlog`, `build_face_candidates`).
|
- **Background loops** (`watcher.rs`): the file-watcher tick (`watch_files`, `process_new_files`) and the orphaned-playlist cleanup (`cleanup_orphaned_playlists`). Per-tick drains are factored into `backfill.rs` (`backfill_unhashed_backlog`, `backfill_missing_date_taken`, `backfill_missing_content_hashes`, `process_face_backlog`, `build_face_candidates`).
|
||||||
- **Thumbnails** (`thumbnails.rs`): generation pipeline + the `IMAGE_GAUGE` / `VIDEO_GAUGE` Prometheus metrics.
|
- **Thumbnails** (`thumbnails.rs`): generation pipeline + the `IMAGE_GAUGE` / `VIDEO_GAUGE` Prometheus metrics.
|
||||||
|
|||||||
@@ -4517,10 +4517,6 @@ Return ONLY the summary, nothing else."#,
|
|||||||
///
|
///
|
||||||
/// Calls `execute_tool` with empty file/image context; enabled tools
|
/// Calls `execute_tool` with empty file/image context; enabled tools
|
||||||
/// never read those fields.
|
/// never read those fields.
|
||||||
///
|
|
||||||
/// Only used by the `reels` module (compiled in `main.rs`, not `lib.rs`),
|
|
||||||
/// so the `#[allow(dead_code)]` suppresses the lib-target warning.
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub(crate) async fn run_readonly_tool_loop(
|
pub(crate) async fn run_readonly_tool_loop(
|
||||||
&self,
|
&self,
|
||||||
backend: &ResolvedBackend,
|
backend: &ResolvedBackend,
|
||||||
|
|||||||
@@ -13,9 +13,6 @@
|
|||||||
//! is request-scoped and stays in `ResolvedBackend`. This is only the
|
//! is request-scoped and stays in `ResolvedBackend`. This is only the
|
||||||
//! local stack: embeddings and offline single-shot generation.
|
//! local stack: embeddings and offline single-shot generation.
|
||||||
|
|
||||||
// Constructed by binaries, not the server — dead code from main.rs's view.
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|||||||
@@ -26,11 +26,6 @@
|
|||||||
//! The pass operates on the database alone — no filesystem access —
|
//! The pass operates on the database alone — no filesystem access —
|
||||||
//! so it doesn't need the library availability gate.
|
//! so it doesn't need the library availability gate.
|
||||||
|
|
||||||
// The lib doesn't call into this module directly — the watcher (in the
|
|
||||||
// bin) does. Dead-code analysis at the lib level can't see that, so
|
|
||||||
// suppress at the module level. Tests still exercise every function.
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use diesel::sql_query;
|
use diesel::sql_query;
|
||||||
use diesel::sqlite::SqliteConnection;
|
use diesel::sqlite::SqliteConnection;
|
||||||
|
|||||||
+1
-2
@@ -1342,8 +1342,7 @@ pub struct RealFileSystem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RealFileSystem {
|
impl RealFileSystem {
|
||||||
#[allow(dead_code)] // Used in main.rs binary and tests
|
pub fn new(base_path: String) -> RealFileSystem {
|
||||||
pub(crate) fn new(base_path: String) -> RealFileSystem {
|
|
||||||
RealFileSystem { base_path }
|
RealFileSystem { base_path }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ extern crate diesel;
|
|||||||
|
|
||||||
pub mod ai;
|
pub mod ai;
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
|
pub mod backfill;
|
||||||
pub mod bin_progress;
|
pub mod bin_progress;
|
||||||
pub mod camera_tz;
|
pub mod camera_tz;
|
||||||
pub mod cleanup;
|
pub mod cleanup;
|
||||||
@@ -25,6 +26,9 @@ pub mod file_types;
|
|||||||
pub mod filename_audit;
|
pub mod filename_audit;
|
||||||
pub mod files;
|
pub mod files;
|
||||||
pub mod geo;
|
pub mod geo;
|
||||||
|
pub mod handlers;
|
||||||
|
pub mod hls_stats;
|
||||||
|
pub mod knowledge;
|
||||||
pub mod libraries;
|
pub mod libraries;
|
||||||
pub mod library_maintenance;
|
pub mod library_maintenance;
|
||||||
pub mod memories;
|
pub mod memories;
|
||||||
@@ -33,6 +37,7 @@ pub mod parsers;
|
|||||||
pub mod perceptual_hash;
|
pub mod perceptual_hash;
|
||||||
pub mod personas;
|
pub mod personas;
|
||||||
pub mod prefs;
|
pub mod prefs;
|
||||||
|
pub mod reels;
|
||||||
pub mod service;
|
pub mod service;
|
||||||
pub mod state;
|
pub mod state;
|
||||||
pub mod tags;
|
pub mod tags;
|
||||||
@@ -42,6 +47,7 @@ pub mod thumbnails;
|
|||||||
pub mod unified_search;
|
pub mod unified_search;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
pub mod video;
|
pub mod video;
|
||||||
|
pub mod watcher;
|
||||||
|
|
||||||
// Re-export commonly used types
|
// Re-export commonly used types
|
||||||
pub use data::{Claims, ThumbnailRequest};
|
pub use data::{Claims, ThumbnailRequest};
|
||||||
|
|||||||
+14
-62
@@ -1,9 +1,7 @@
|
|||||||
|
//! The server binary. Everything it runs lives in the `image_api`
|
||||||
|
//! library so the crate compiles once, shared with the `src/bin` tools.
|
||||||
#![allow(clippy::too_many_arguments)]
|
#![allow(clippy::too_many_arguments)]
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate diesel;
|
|
||||||
extern crate rayon;
|
|
||||||
|
|
||||||
use actix_web::web::Data;
|
use actix_web::web::Data;
|
||||||
use actix_web_prom::PrometheusMetricsBuilder;
|
use actix_web_prom::PrometheusMetricsBuilder;
|
||||||
use diesel_migrations::{EmbeddedMigrations, MigrationHarness, embed_migrations};
|
use diesel_migrations::{EmbeddedMigrations, MigrationHarness, embed_migrations};
|
||||||
@@ -18,62 +16,16 @@ use actix_web::{App, HttpResponse, HttpServer, middleware, web};
|
|||||||
use diesel::sqlite::Sqlite;
|
use diesel::sqlite::Sqlite;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
||||||
use crate::ai::InsightGenerator;
|
use image_api::ai::InsightGenerator;
|
||||||
use crate::auth::login;
|
use image_api::auth::login;
|
||||||
use crate::data::*;
|
use image_api::database::*;
|
||||||
use crate::database::*;
|
use image_api::files::{RealFileSystem, move_file};
|
||||||
use crate::files::{RealFileSystem, move_file};
|
use image_api::service::ServiceBuilder;
|
||||||
use crate::service::ServiceBuilder;
|
use image_api::state::AppState;
|
||||||
use crate::state::AppState;
|
use image_api::tags::*;
|
||||||
use crate::tags::*;
|
use image_api::*;
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
|
|
||||||
mod ai;
|
|
||||||
mod auth;
|
|
||||||
mod backfill;
|
|
||||||
// Display-zone helpers land in the read-side cutover (#119/#120); the
|
|
||||||
// binary's private copy of this module doesn't use them yet.
|
|
||||||
#[allow(dead_code)]
|
|
||||||
mod camera_tz;
|
|
||||||
mod clip_search;
|
|
||||||
mod clip_watch;
|
|
||||||
mod content_hash;
|
|
||||||
mod data;
|
|
||||||
mod database;
|
|
||||||
mod date_resolver;
|
|
||||||
mod date_view;
|
|
||||||
mod duplicates;
|
|
||||||
mod error;
|
|
||||||
mod exif;
|
|
||||||
mod face_watch;
|
|
||||||
mod faces;
|
|
||||||
mod file_scan;
|
|
||||||
mod file_types;
|
|
||||||
mod files;
|
|
||||||
mod geo;
|
|
||||||
mod handlers;
|
|
||||||
mod hls_stats;
|
|
||||||
mod libraries;
|
|
||||||
mod library_maintenance;
|
|
||||||
mod perceptual_hash;
|
|
||||||
mod state;
|
|
||||||
mod tags;
|
|
||||||
mod thumbnails;
|
|
||||||
mod unified_search;
|
|
||||||
mod utils;
|
|
||||||
mod video;
|
|
||||||
mod watcher;
|
|
||||||
|
|
||||||
mod knowledge;
|
|
||||||
mod memories;
|
|
||||||
mod otel;
|
|
||||||
mod personas;
|
|
||||||
mod prefs;
|
|
||||||
mod reels;
|
|
||||||
mod service;
|
|
||||||
#[cfg(test)]
|
|
||||||
mod testhelpers;
|
|
||||||
|
|
||||||
pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!();
|
pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!();
|
||||||
|
|
||||||
fn main() -> std::io::Result<()> {
|
fn main() -> std::io::Result<()> {
|
||||||
@@ -85,7 +37,7 @@ fn main() -> std::io::Result<()> {
|
|||||||
|
|
||||||
// Recover orphaned insight generation jobs from a previous crash.
|
// Recover orphaned insight generation jobs from a previous crash.
|
||||||
{
|
{
|
||||||
use crate::database::{InsightGenerationJobDao, SqliteInsightGenerationJobDao};
|
use image_api::database::{InsightGenerationJobDao, SqliteInsightGenerationJobDao};
|
||||||
let mut dao = SqliteInsightGenerationJobDao::new();
|
let mut dao = SqliteInsightGenerationJobDao::new();
|
||||||
match dao.recover_orphaned_jobs() {
|
match dao.recover_orphaned_jobs() {
|
||||||
Ok(n) if n > 0 => {
|
Ok(n) if n > 0 => {
|
||||||
@@ -228,9 +180,9 @@ fn main() -> std::io::Result<()> {
|
|||||||
|
|
||||||
// Spawn background job to generate daily conversation summaries
|
// Spawn background job to generate daily conversation summaries
|
||||||
{
|
{
|
||||||
use crate::ai::generate_daily_summaries;
|
|
||||||
use crate::database::{DailySummaryDao, SqliteDailySummaryDao};
|
|
||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
|
use image_api::ai::generate_daily_summaries;
|
||||||
|
use image_api::database::{DailySummaryDao, SqliteDailySummaryDao};
|
||||||
|
|
||||||
// Configure date range for summary generation
|
// Configure date range for summary generation
|
||||||
// Default: August 2024 ±30 days (July 1 - September 30, 2024)
|
// Default: August 2024 ±30 days (July 1 - September 30, 2024)
|
||||||
@@ -453,7 +405,7 @@ fn main() -> std::io::Result<()> {
|
|||||||
Box::new(database::SqlitePersonaDao::new()),
|
Box::new(database::SqlitePersonaDao::new()),
|
||||||
)))
|
)))
|
||||||
.app_data::<Data<Mutex<faces::SqliteFaceDao>>>(Data::new(Mutex::new(face_dao)))
|
.app_data::<Data<Mutex<faces::SqliteFaceDao>>>(Data::new(Mutex::new(face_dao)))
|
||||||
.app_data::<Data<crate::ai::face_client::FaceClient>>(Data::new(
|
.app_data::<Data<image_api::ai::face_client::FaceClient>>(Data::new(
|
||||||
app_data.face_client.clone(),
|
app_data.face_client.clone(),
|
||||||
))
|
))
|
||||||
.app_data(mp::form::MultipartFormConfig::default().total_limit(1024 * 1024 * 1024)) // 1GB upload limit
|
.app_data(mp::form::MultipartFormConfig::default().total_limit(1024 * 1024 * 1024)) // 1GB upload limit
|
||||||
|
|||||||
+2
-2
@@ -1045,7 +1045,7 @@ fn load_pregen_params(app_state: &AppState) -> (String, Option<String>, String)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Spawn the nightly pre-generation scheduler. Runs behind `REEL_PREGEN_ENABLED`.
|
/// Spawn the nightly pre-generation scheduler. Runs behind `REEL_PREGEN_ENABLED`.
|
||||||
pub(crate) async fn spawn_pregen_scheduler(app_state: web::Data<AppState>) {
|
pub async fn spawn_pregen_scheduler(app_state: web::Data<AppState>) {
|
||||||
if std::env::var("REEL_PREGEN_ENABLED").ok() != Some("1".to_string()) {
|
if std::env::var("REEL_PREGEN_ENABLED").ok() != Some("1".to_string()) {
|
||||||
log::info!("Reel pre-generation scheduler disabled (REEL_PREGEN_ENABLED != 1)");
|
log::info!("Reel pre-generation scheduler disabled (REEL_PREGEN_ENABLED != 1)");
|
||||||
return;
|
return;
|
||||||
@@ -1270,7 +1270,7 @@ fn reel_cache_max_age_secs() -> u64 {
|
|||||||
/// Spawn the periodic on-disk reel-cache sweeper. Runs independently of the
|
/// Spawn the periodic on-disk reel-cache sweeper. Runs independently of the
|
||||||
/// pre-gen scheduler because the on-demand cache grows whether or not pre-gen
|
/// pre-gen scheduler because the on-demand cache grows whether or not pre-gen
|
||||||
/// is enabled. Disable with `REEL_CACHE_SWEEP_ENABLED=0`.
|
/// is enabled. Disable with `REEL_CACHE_SWEEP_ENABLED=0`.
|
||||||
pub(crate) async fn spawn_reel_cache_sweeper(app_state: web::Data<AppState>) {
|
pub async fn spawn_reel_cache_sweeper(app_state: web::Data<AppState>) {
|
||||||
if std::env::var("REEL_CACHE_SWEEP_ENABLED").ok().as_deref() == Some("0") {
|
if std::env::var("REEL_CACHE_SWEEP_ENABLED").ok().as_deref() == Some("0") {
|
||||||
log::info!("Reel cache sweeper disabled (REEL_CACHE_SWEEP_ENABLED=0)");
|
log::info!("Reel cache sweeper disabled (REEL_CACHE_SWEEP_ENABLED=0)");
|
||||||
return;
|
return;
|
||||||
|
|||||||
+3
-3
@@ -628,11 +628,11 @@ pub fn watch_files(
|
|||||||
// See database::reconcile and CLAUDE.md "Multi-library data
|
// See database::reconcile and CLAUDE.md "Multi-library data
|
||||||
// model" for the rules.
|
// model" for the rules.
|
||||||
{
|
{
|
||||||
let mut conn = image_api::database::connect();
|
let mut conn = crate::database::connect();
|
||||||
if is_full_scan {
|
if is_full_scan {
|
||||||
library_maintenance::refresh_planner_stats(&mut conn);
|
library_maintenance::refresh_planner_stats(&mut conn);
|
||||||
}
|
}
|
||||||
let _ = image_api::database::reconcile::run(&mut conn);
|
let _ = crate::database::reconcile::run(&mut conn);
|
||||||
|
|
||||||
// Back-ref refresh: hash-keyed rows whose
|
// Back-ref refresh: hash-keyed rows whose
|
||||||
// (library_id, rel_path) tuple no longer matches any
|
// (library_id, rel_path) tuple no longer matches any
|
||||||
@@ -721,7 +721,7 @@ pub fn process_new_files(
|
|||||||
// filters. See `file_scan` for why exclusion has to happen at WalkDir
|
// filters. See `file_scan` for why exclusion has to happen at WalkDir
|
||||||
// time (filter_entry) rather than at face-detect time.
|
// time (filter_entry) rather than at face-detect time.
|
||||||
let files: Vec<(PathBuf, String)> =
|
let files: Vec<(PathBuf, String)> =
|
||||||
image_api::file_scan::enumerate_indexable_files(base_path, excluded_dirs, modified_since);
|
crate::file_scan::enumerate_indexable_files(base_path, excluded_dirs, modified_since);
|
||||||
|
|
||||||
if files.is_empty() {
|
if files.is_empty() {
|
||||||
debug!("No files to process");
|
debug!("No files to process");
|
||||||
|
|||||||
Reference in new issue
Block a user