feat(versions): link at ingest, hide in listings, version endpoints
XMP (PNG iTXt DerivedFrom, native chunk reader) then file-name convention links at ingest; unlinked name candidates are retried each tick. Versions are hidden from /photos (SQL for the recursive listing, hidden-row set for the walk and tag branches), /memories, reels, /photos/exif, the GPS summary, unified search and the media gauges while their original is reachable in the view; include_versions=true lists them. /photos gains the parallel arrays photo_display_paths / photo_version_counts, memories / search hits / /photos/exif rows gain display_path / version_count, all skipped when empty. GET /image/versions and PUT /image/versions/display (library optional). Tags and favorites on a version path bind to the original; faces, CLIP and duplicate grouping skip version hashes; insight generation redirects to the original. Orphan GC and the back-ref refresh cover the new tables. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
044240037a
commit
080a75c336
24 files changed
+1614
-47
No files matched your search
@@ -170,6 +170,10 @@ pub trait VersionDao: Sync + Send {
|
||||
/// The link row for a version hash, if any.
|
||||
fn get_link(&mut self, version_hash: &str) -> Result<Option<PhotoVersionRow>, DbError>;
|
||||
|
||||
/// If `rel_path` is a linked version whose original is still indexed,
|
||||
/// the original's rel_path (same library when possible).
|
||||
fn original_path_of(&mut self, rel_path: &str) -> Result<Option<String>, DbError>;
|
||||
|
||||
/// Which of `hashes` are versions of something.
|
||||
fn version_hashes_among(&mut self, hashes: &[String]) -> Result<HashSet<String>, DbError>;
|
||||
|
||||
@@ -191,8 +195,11 @@ pub trait VersionDao: Sync + Send {
|
||||
fn get_display(&mut self, original_hash: &str) -> Result<Option<String>, DbError>;
|
||||
|
||||
/// Set (`Some`) or clear (`None`) the global display choice.
|
||||
fn set_display(&mut self, original_hash: &str, display_hash: Option<&str>)
|
||||
-> Result<(), DbError>;
|
||||
fn set_display(
|
||||
&mut self,
|
||||
original_hash: &str,
|
||||
display_hash: Option<&str>,
|
||||
) -> Result<(), DbError>;
|
||||
|
||||
/// Per original `(library_id, rel_path)` that has at least one live
|
||||
/// version: the version count and display file. Empty when no photo has
|
||||
@@ -406,6 +413,15 @@ impl VersionDao for SqliteVersionDao {
|
||||
.map_err(|e| DbError::log(DbErrorKind::QueryError, e))
|
||||
}
|
||||
|
||||
fn original_path_of(&mut self, rel_path: &str) -> Result<Option<String>, DbError> {
|
||||
trace_db_call("query", "original_path_of", |_span| {
|
||||
let mut conn = self.connection.lock().expect("Unable to get VersionDao");
|
||||
canonical_rel_path(&mut conn, rel_path)
|
||||
.map_err(|e| anyhow::anyhow!("Query error: {}", e))
|
||||
})
|
||||
.map_err(|e| DbError::log(DbErrorKind::QueryError, e))
|
||||
}
|
||||
|
||||
fn version_hashes_among(&mut self, hashes: &[String]) -> Result<HashSet<String>, DbError> {
|
||||
trace_db_call("query", "version_hashes_among", |_span| {
|
||||
#[derive(QueryableByName)]
|
||||
@@ -912,10 +928,7 @@ mod tests {
|
||||
);
|
||||
// Original deleted: the version shows up everywhere.
|
||||
exec(&c, "DELETE FROM image_exif WHERE rel_path = 'a/orig.jpg'");
|
||||
assert_eq!(
|
||||
visible(&[]),
|
||||
vec!["a/orig.jpg.restored.png", "b/other.jpg"]
|
||||
);
|
||||
assert_eq!(visible(&[]), vec!["a/orig.jpg.restored.png", "b/other.jpg"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user