Skip to content

Pr/optimize sidecar#83

Merged
AlanRockefeller merged 2 commits intomainfrom
pr/optimize-sidecar
Apr 17, 2026
Merged

Pr/optimize sidecar#83
AlanRockefeller merged 2 commits intomainfrom
pr/optimize-sidecar

Conversation

@AlanRockefeller
Copy link
Copy Markdown
Owner

@AlanRockefeller AlanRockefeller commented Apr 17, 2026

Optimize bulk metadata refresh and isolate per-image sidecar failures

Summary by CodeRabbit

  • Bug Fixes

    • Improved error handling to isolate per-image metadata failures; a single image's failed metadata read no longer prevents processing other images in the same batch.
  • Performance

    • Added caching mechanism for metadata computations to accelerate bulk metadata operations.
  • Tests

    • Added test coverage validating error isolation behavior during bulk metadata operations.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 17, 2026

Walkthrough

Updated metadata keying strategy from sidecar-derived stable keys to str(img.path) across app, sidecar, and thumbnail view modules. Added per-image error isolation in metadata bulk retrieval. Introduced bounded caching in SidecarManager with base directory normalization. Added corresponding test coverage.

Changes

Cohort / File(s) Summary
Core Metadata Keying & Error Handling
faststack/app.py
Refactored _get_bulk_metadata_map() to key results by str(img.path) instead of sidecar-derived stable keys. Replaced blanket try/except around loop with per-image error handling, logging warnings for individual image metadata failures without blocking others.
Sidecar Caching & Path Normalization
faststack/io/sidecar.py
Added precomputed case-normalized base directory and two bounded in-memory caches (_stable_key_cache, _filename_key_cache) with 8192-entry limit and simple eviction. Updated key computation methods to use consistent relative path calculation and return cached results when available.
Thumbnail View Metadata Lookup
faststack/thumbnail_view/model.py
Updated refresh_from_controller() and _add_images_to_entries() to treat non-None metadata_map as authoritative. Changed metadata lookups to use str(img.path) instead of self._metadata_key_fn(img.path). Empty metadata_map now prevents fallback to sidecar lookups.
Error Isolation Test Coverage
faststack/tests/test_bulk_metadata_map.py
Added three unit tests validating per-image error isolation: one image metadata failure omits only that path; all failures return empty map; early failure does not block subsequent image processing.

Sequence Diagram(s)

sequenceDiagram
    participant App as App Controller
    participant Sidecar as SidecarManager
    participant TView as ThumbnailView

    rect rgba(100, 150, 200, 0.5)
    Note over App,TView: Bulk Metadata Retrieval with Per-Image Error Isolation
    App->>App: _get_bulk_metadata_map()
    loop For each image
        App->>Sidecar: get_metadata(img.path)
        alt Metadata retrieved
            Sidecar-->>App: metadata
            App->>App: metadata_map[str(img.path)] = metadata
        else Error on get_metadata
            Sidecar-->>App: Exception
            App->>App: Log warning, continue
        end
    end
    App->>TView: refresh_from_controller(metadata_map)
    end

    rect rgba(200, 150, 100, 0.5)
    Note over Sidecar: Cached Key Computation
    TView->>Sidecar: metadata_key_for_path(img.path)
    alt Cache hit
        Sidecar->>Sidecar: Return cached result
        Sidecar-->>TView: stable_key
    else Cache miss
        Sidecar->>Sidecar: Compute relative path, store in cache
        Sidecar-->>TView: stable_key
    end
    end

    rect rgba(150, 200, 150, 0.5)
    Note over TView: Metadata Lookup by Path
    TView->>TView: For each image, lookup str(img.path) in metadata_map
    TView->>TView: Apply flags (favorite, uploaded, stacked)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~28 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 70.59% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The PR title 'Pr/optimize sidecar' is vague and uses non-descriptive branch naming conventions rather than summarizing the actual changeset. The PR description and commit message indicate the real focus is optimizing bulk metadata refresh and isolating per-image sidecar failures, but the title does not reflect this. Revise the title to clearly describe the main change, such as 'Optimize bulk metadata refresh and isolate per-image sidecar failures' to match the actual PR objectives and commit message.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pr/optimize-sidecar

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
faststack/io/sidecar.py (1)

221-223: Eviction is FIFO, not LRU — fine for now, but note the pathological case.

del self._stable_key_cache[next(iter(self._stable_key_cache))] evicts the oldest-inserted entry (insertion order), not the least-recently-used, because cache hits don't reinsert the key. For the documented workload (bulk-map build + flag filter + grid construction over a single folder), total unique paths are typically well below _key_cache_max = 8192, so this never matters.

If a folder ever exceeds 8192 images and the same paths are re-queried across passes, you can get thrash where hot keys get evicted in favor of one-shot cold keys. Two cheap options if you want to harden this:

♻️ Optional: promote on hit to get LRU semantics
-        cached = self._stable_key_cache.get(cache_key)
-        if cached is not None:
-            return cached
+        cached = self._stable_key_cache.get(cache_key)
+        if cached is not None:
+            # Move to end for LRU eviction
+            self._stable_key_cache[cache_key] = self._stable_key_cache.pop(cache_key)
+            return cached

Or simply swap both caches for functools.lru_cache-backed helpers / a small OrderedDict wrapper.

Not blocking — flagging as a nice-to-have.

Also applies to: 271-273

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@faststack/io/sidecar.py` around lines 221 - 223, The cache eviction currently
deletes the first inserted key via del
self._stable_key_cache[next(iter(self._stable_key_cache))], producing FIFO
instead of LRU behavior; to fix, make the cache LRU by promoting a hit before
reinserting (or use an OrderedDict/ functools.lru_cache-backed helper): when
accessing self._stable_key_cache for cache_key, move that entry to the
"most-recent" end (e.g., via move_to_end) so subsequent evictions remove true
least-recently-used items, and keep the existing eviction logic using
self._key_cache_max; apply the same change to the other symmetric cache usage
referenced (the similar block at the other location).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@faststack/app.py`:
- Around line 2611-2615: In AppController._get_bulk_metadata_map, the current
mapping keys use the raw image path (e.g., str(img.path) /
normalize_path_key(img.path)) which breaks the sidecar key contract; update the
code so the dictionary keys are the canonical sidecar keys by calling
SidecarManager.metadata_key_for_path(path) (use the same path variable you
already have) when inserting into bulk_map so downstream consumers (thumbnail
model, folder stats) can reliably look up metadata.

---

Nitpick comments:
In `@faststack/io/sidecar.py`:
- Around line 221-223: The cache eviction currently deletes the first inserted
key via del self._stable_key_cache[next(iter(self._stable_key_cache))],
producing FIFO instead of LRU behavior; to fix, make the cache LRU by promoting
a hit before reinserting (or use an OrderedDict/ functools.lru_cache-backed
helper): when accessing self._stable_key_cache for cache_key, move that entry to
the "most-recent" end (e.g., via move_to_end) so subsequent evictions remove
true least-recently-used items, and keep the existing eviction logic using
self._key_cache_max; apply the same change to the other symmetric cache usage
referenced (the similar block at the other location).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d18af2ac-6484-4e43-b059-4d7226be6a07

📥 Commits

Reviewing files that changed from the base of the PR and between 2e930c1 and 2a2eb22.

📒 Files selected for processing (4)
  • faststack/app.py
  • faststack/io/sidecar.py
  • faststack/tests/test_bulk_metadata_map.py
  • faststack/thumbnail_view/model.py

Comment thread faststack/app.py
Comment on lines +2611 to +2615
"""Get flattened metadata map for the given images, keyed by str(img.path).

Used to avoid per-image sidecar lookups on the UI thread during grid refresh.
Used to avoid per-image sidecar lookups on the UI thread during grid
refresh. Keying by the raw path string lets downstream consumers
look up entries directly without re-resolving the stable sidecar key.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use canonical sidecar keys here to avoid cross-consumer metadata misses.

On Line 2628, keying bulk_map by str(img.path) breaks the established contract for this method and can cause lookup mismatches where consumers rely on canonical sidecar keys.

Suggested fix
-        """Get flattened metadata map for the given images, keyed by str(img.path).
+        """Get flattened metadata map for the given images, keyed by sidecar canonical key.
@@
-                bulk_map[str(img.path)] = {
+                key = self.sidecar.metadata_key_for_path(img.path)
+                bulk_map[key] = {
                     "stacked": getattr(meta, "stacked", False),
                     "uploaded": getattr(meta, "uploaded", False),
                     "edited": getattr(meta, "edited", False),
                     "restacked": getattr(meta, "restacked", False),
                     "favorite": getattr(meta, "favorite", False),
                     "todo": getattr(meta, "todo", False),
                 }

Based on learnings: In faststack.app.AppController._get_bulk_metadata_map, the dictionary must be keyed by SidecarManager.metadata_key_for_path(path) (the sidecar canonical key), not by normalize_path_key(img.path), to avoid metadata lookup misses across consumers like the thumbnail model and folder stats.

Also applies to: 2628-2628

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@faststack/app.py` around lines 2611 - 2615, In
AppController._get_bulk_metadata_map, the current mapping keys use the raw image
path (e.g., str(img.path) / normalize_path_key(img.path)) which breaks the
sidecar key contract; update the code so the dictionary keys are the canonical
sidecar keys by calling SidecarManager.metadata_key_for_path(path) (use the same
path variable you already have) when inserting into bulk_map so downstream
consumers (thumbnail model, folder stats) can reliably look up metadata.

@AlanRockefeller AlanRockefeller merged commit e435710 into main Apr 17, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant