Skip to content

Conversation

@jpg0
Copy link
Contributor

@jpg0 jpg0 commented Jul 15, 2025

Ensures that memories are returned chronologically. Tested by called the API with a mock immich server. Includes a few changes to make the implementation testable.

Also ensures that repeated calls iterate through correctly.

fixes #163 (again)

Summary by CodeRabbit

  • New Features

    • Deterministic sequential asset selection and chronological Memory ordering.
  • Improvements

    • Centralized pool configuration for consistent pool composition.
    • Faster, more reliable asset loading and counting.
  • Bug Fixes

    • Broader HTTP mocking in tests to reduce intermittent failures.
  • Refactor

    • Shared adapter and centralized pool wiring for cleaner architecture.
  • Tests

    • Expanded memory-ordering tests and updated test integrations.

@jpg0 jpg0 force-pushed the feat/sort-memories-chronologically branch 2 times, most recently from a1ce2a5 to 33e2f5d Compare July 15, 2025 23:06
@JW-CH JW-CH added the enhancement New feature or request label Jul 16, 2025
@3rob3
Copy link
Collaborator

3rob3 commented Sep 23, 2025

Anyone know why this was never merged? Now it has conflicts.

@jpg0
Copy link
Contributor Author

jpg0 commented Sep 24, 2025

No; if you want to merge I'm happy to resolve the conflicts though.

@3rob3
Copy link
Collaborator

3rob3 commented Sep 24, 2025

No; if you want to merge I'm happy to resolve the conflicts though.

Sure, if you don't mind. Seems like a nice addition, and sorry for the hassle.

@jpg0 jpg0 force-pushed the feat/sort-memories-chronologically branch from 33e2f5d to d927278 Compare October 10, 2025 03:38
@coderabbitai
Copy link

coderabbitai bot commented Oct 10, 2025

Walkthrough

Refactors pool APIs to return IList, makes CachingApiAssetsPool deterministic and list-based, adds LogicPoolAdapter and PoolConfiguration to compose pools and ImmichApi, updates PooledImmichFrameLogic to inherit LogicPoolAdapter and change its constructor, adjusts DI and tests, and ensures MemoryAssetsPool returns memories in chronological order.

Changes

Cohort / File(s) Summary
Core pools refactor (signatures & behavior)
ImmichFrame.Core/Logic/Pool/CachingApiAssetsPool.cs, ImmichFrame.Core/Logic/Pool/AlbumAssetsPool.cs, ImmichFrame.Core/Logic/Pool/FavoriteAssetsPool.cs, ImmichFrame.Core/Logic/Pool/MemoryAssetsPool.cs, ImmichFrame.Core/Logic/Pool/PeopleAssetsPool.cs
Change pool APIs to return IList<AssetResponseDto> instead of IEnumerable<>; remove ImmichApi parameter from CachingApiAssetsPool ctor; implement deterministic sequential GetAssets using an internal _next index with wrap-around; materialize filtered results as lists; MemoryAssetsPool orders memories chronologically.
Logic composition & adapter
ImmichFrame.Core/Logic/LogicPoolAdapter.cs, ImmichFrame.Core/Logic/PoolConfiguration.cs
Add LogicPoolAdapter exposing GetNextAsset/GetAssets/GetImage/GetAssetInfo/GetAlbumInfo/GetTotalAssets/SendWebhookNotification and delegating to a pool and ImmichApi; add PoolConfiguration to build ImmichApi and ApiCache and expose them for pool construction.
Pooled logic changes
ImmichFrame.Core/Logic/PooledImmichFrameLogic.cs
Make PooledImmichFrameLogic inherit LogicPoolAdapter; change constructor to accept PoolConfiguration, call base with built pool and ImmichApi, remove internal image/webhook methods and several private fields, and expose AccountSettings property.
Tests updated to lists & ctor changes
ImmichFrame.Core.Tests/Logic/Pool/AlbumAssetsPoolTests.cs, .../FavoriteAssetsPoolTests.cs, .../PersonAssetsPoolTests.cs, .../CachingApiAssetsPoolTests.cs
Update test helpers, delegates, mocks, and default returns from IEnumerable<AssetResponseDto> to IList<AssetResponseDto>; adjust testable pool constructors to match new CachingApiAssetsPool ctor and list-based delegates.
Web API tests & project refs
ImmichFrame.WebApi.Tests/Logic/MemoryAssetsPoolTests.cs, ImmichFrame.WebApi.Tests/Controllers/AssetControllerTests.cs, ImmichFrame.WebApi.Tests/ImmichFrame.WebApi.Tests.csproj
Add adapter usage in memory tests, assert chronological memory order (including over multiple calls), adjust sample-asset creation to pass DateTime, expand HTTP mocks to include /assets/ and a catch-all, and add ProjectReference to ImmichFrame.Core.Tests.
DI wiring
ImmichFrame.WebApi/Program.cs
Update DI factory registration to construct PooledImmichFrameLogic with a PoolConfiguration instance.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client
  participant Logic as LogicPoolAdapter / PooledImmichFrameLogic
  participant Pool as IAssetPool
  participant Api as ImmichApi
  participant Webhook as WebhookHelper

  rect rgba(227,242,253,0.6)
    note over Client,Logic: Retrieve next asset
    Client->>Logic: GetNextAsset()
    Logic->>Pool: GetAssets(1)
    Pool-->>Logic: IList<Asset>
    Logic-->>Client: Asset? (first or null)
  end

  rect rgba(232,245,233,0.6)
    note over Client,Logic: Retrieve multiple assets
    Client->>Logic: GetAssets()
    Logic->>Pool: GetAssets(25)
    Pool-->>Logic: IList<Asset>
    Logic-->>Client: collection
  end

  rect rgba(255,243,224,0.6)
    note over Client,Logic: Fetch image bytes
    Client->>Logic: GetImage(assetId)
    Logic->>Api: ViewAssetAsync(id, "", Preview)
    Api-->>Logic: HttpResponse (stream + headers)
    alt Found
      Logic-->>Client: (fileName, contentType, stream)
    else Not found
      Logic-->>Client: throws AssetNotFoundException
    end
  end

  rect rgba(252,228,236,0.6)
    note over Client,Logic: Send webhook (adapter)
    Client->>Logic: SendWebhookNotification(notification)
    Logic->>Webhook: Send(webhookUrl, notification)
    Webhook-->>Logic: Ack
    Logic-->>Client: Completed
  end
Loading
sequenceDiagram
  autonumber
  participant Caller as Logic (GetAssets)
  participant Pool as CachingApiAssetsPool

  Caller->>Pool: GetAssets(requested)
  alt requested == 0 or none available
    Pool-->>Caller: []
  else deterministic sequence
    Pool->>Pool: compute slice using _next (tail/head)
    Pool-->>Caller: IList<Asset> (slice), advance/reset _next
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

A hop, a skip, through memory’s lane,
I sort the years like drops of rain.
Lists in paw, I queue the show—
One year, two years, on we go!
Webhooks squeak, the cache says “stay,”
I nibble bugs, then bound away. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning This PR includes broad refactoring and feature additions that extend well beyond sorting memories, including changing return types from IEnumerable to IList across all asset pools and tests, introducing a new LogicPoolAdapter and PoolConfiguration, overhauling PooledImmichFrameLogic, updating DI in WebApi Program, and modifying numerous test project references. These changes are unrelated to the linked issue’s focus on memory sorting and therefore are out of scope. Consider isolating the memory-sorting changes into a dedicated PR and deferring the return-type adjustments, adapter introduction, and DI refactoring to separate feature or cleanup PRs.
Docstring Coverage ⚠️ Warning Docstring coverage is 4.26% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly describes the primary feature introduced—sorting memories chronologically—and directly aligns with the PR’s stated objective without extraneous detail. It’s concise, clear, and specific enough for reviewers to understand the main change at a glance.
Linked Issues Check ✅ Passed The changes to MemoryAssetsPool now explicitly sort memories by MemoryAt before returning and the removal of randomness and introduction of deterministic sequencing in CachingApiAssetsPool ensure that memories are returned in chronological order and that repeated calls iterate correctly, directly addressing issue #163’s requirement for ordered memory display.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
ImmichFrame.Core/Logic/Pool/PeopleAssetsPool.cs (1)

14-36: Pagination bug: loop will prematurely stop after the first page

You compare total (overall matches) with batchSize; if total > batchSize, the condition total == batchSize is false and you stop after page 1. Use Items.Count == batchSize or the API’s NextPage indicator instead.

Apply one of these fixes:

Option A — continue while page returns a full batch:

-            int total;
-            do
+            int returned;
+            do
             {
                 var metadataBody = new MetadataSearchDto
                 {
                     Page = page,
                     Size = batchSize,
                     PersonIds = [personId],
                     Type = AssetTypeEnum.IMAGE,
                     WithExif = true,
                     WithPeople = true
                 };
 
                 var personInfo = await immichApi.SearchAssetsAsync(metadataBody, ct);
-
-                total = personInfo.Assets.Total;
-
-                personAssets.AddRange(personInfo.Assets.Items);
+                var items = personInfo.Assets.Items;
+                returned = items.Count;
+                personAssets.AddRange(items);
                 page++;
-            } while (total == batchSize);
+            } while (returned == batchSize && !ct.IsCancellationRequested);

Option B — use NextPage if available:

-            int total;
-            do
+            int? nextPage;
+            do
             {
                 var metadataBody = new MetadataSearchDto
                 {
                     Page = page,
                     Size = batchSize,
                     PersonIds = [personId],
                     Type = AssetTypeEnum.IMAGE,
                     WithExif = true,
                     WithPeople = true
                 };
 
                 var personInfo = await immichApi.SearchAssetsAsync(metadataBody, ct);
-
-                total = personInfo.Assets.Total;
-
+                nextPage = personInfo.Assets.NextPage;
                 personAssets.AddRange(personInfo.Assets.Items);
-                page++;
-            } while (total == batchSize);
+                page = nextPage ?? (page + 1);
+            } while (nextPage.HasValue && !ct.IsCancellationRequested);

Also consider de-duplicating across people to avoid the same asset appearing multiple times:

-        return personAssets;
+        return personAssets
+            .GroupBy(a => a.Id)
+            .Select(g => g.First())
+            .ToList();
ImmichFrame.Core/Logic/PooledImmichFrameLogic.cs (3)

16-30: Remove redundant initialization logic.

The constructor duplicates initialization that already happens in PoolConfiguration:

  • Line 20 assigns _immichApi from poolConfiguration.ImmichApi, but line 26 overwrites it with a new instance
  • Lines 28-29 create new _apiCache and _pool instances, but the base class already uses poolConfiguration.AssetPool and poolConfiguration.ApiCache

This defeats the purpose of introducing PoolConfiguration and causes unnecessary object creation.

Apply this diff to remove redundant initialization:

     public PooledImmichFrameLogic(IGeneralSettings generalSettings, IAccountSettings accountSettings, PoolConfiguration poolConfiguration, IHttpClientFactory httpClientFactory)
         : base(poolConfiguration.AssetPool, poolConfiguration.ImmichApi, generalSettings.Webhook)
   {
         _generalSettings = generalSettings;
-        _immichApi = poolConfiguration.ImmichApi;
-
-        var httpClient = httpClientFactory.CreateClient("ImmichApiAccountClient");
+        _immichApi = poolConfiguration.ImmichApi;
+        _apiCache = poolConfiguration.ApiCache;
+        _pool = poolConfiguration.AssetPool;
         AccountSettings = accountSettings;
-
-        httpClient.UseApiKey(accountSettings.ApiKey);
-        _immichApi = new ImmichApi(accountSettings.ImmichServerUrl, httpClient);
-
-        _apiCache = new ApiCache(RefreshInterval(generalSettings.RefreshAlbumPeopleInterval));
-        _pool = BuildPool(accountSettings);
     }

33-60: Remove obsolete methods.

Both RefreshInterval and BuildPool are no longer needed:

  • RefreshInterval was only used in the removed cache initialization (line 28)
  • BuildPool duplicates PoolConfiguration.BuildPool (lines 24-46 in PoolConfiguration.cs)

Since PoolConfiguration now handles pool building, these methods should be removed.

Apply this diff:

-    private static TimeSpan RefreshInterval(int hours)
-        => hours > 0 ? TimeSpan.FromHours(hours) : TimeSpan.FromMilliseconds(1);
     public IAccountSettings AccountSettings { get; }
-    
-
-    private IAssetPool BuildPool(IAccountSettings accountSettings)
-    {
-        if (!accountSettings.ShowFavorites && !accountSettings.ShowMemories && !accountSettings.Albums.Any() && !accountSettings.People.Any())
-        {
-            return new AllAssetsPool(_apiCache, _immichApi, accountSettings);
-        }
-
-        var pools = new List<IAssetPool>();
-
-        if (accountSettings.ShowFavorites)
-            pools.Add(new FavoriteAssetsPool(_apiCache, _immichApi, accountSettings));
-
-        if (accountSettings.ShowMemories)
-            pools.Add(new MemoryAssetsPool(_immichApi, accountSettings));
-
-        if (accountSettings.Albums.Any())
-            pools.Add(new AlbumAssetsPool(_apiCache, _immichApi, accountSettings));
-
-        if (accountSettings.People.Any())
-            pools.Add(new PersonAssetsPool(_apiCache, _immichApi, accountSettings));
-
-        return new MultiAssetPool(pools);
-    }

62-76: Remove methods that duplicate base class implementation.

These methods (GetNextAsset, GetAssets, GetAssetInfoById, GetAlbumInfoById, GetTotalAssets) are already implemented identically in the LogicPoolAdapter base class (lines 10-22 in LogicPoolAdapter.cs). After fixing the constructor to use poolConfiguration.AssetPool instead of creating a new _pool, these overrides become redundant.

Apply this diff:

     public IAccountSettings AccountSettings { get; }
-
-    public async Task<AssetResponseDto?> GetNextAsset()
-    {
-        return (await _pool.GetAssets(1)).FirstOrDefault();
-    }
-
-    public Task<IEnumerable<AssetResponseDto>> GetAssets()
-    {
-        return _pool.GetAssets(25);
-    }
-
-    public Task<AssetResponseDto> GetAssetInfoById(Guid assetId) => _immichApi.GetAssetInfoAsync(assetId, null);
-
-    public async Task<IEnumerable<AlbumResponseDto>> GetAlbumInfoById(Guid assetId) => await _immichApi.GetAllAlbumsAsync(assetId, null);
-
-    public Task<long> GetTotalAssets() => _pool.GetAssetCount();

     public async Task<(string fileName, string ContentType, Stream fileStream)> GetImage(Guid id)
🧹 Nitpick comments (8)
ImmichFrame.WebApi.Tests/ImmichFrame.WebApi.Tests.csproj (1)

29-31: Avoid referencing a test project from another test project

Referencing ImmichFrame.Core.Tests from ImmichFrame.WebApi.Tests can cause:

  • Duplicate test discovery and analyzer conflicts
  • Tight coupling and circular dependency risks
  • Longer builds

Prefer:

  • Referencing the production project(s) only; and/or
  • Extracting shared fixtures/mocks into a TestCommon library referenced by both test projects.

If you need specific helpers from Core.Tests, I can sketch a small ImmichFrame.TestCommon project to host them and update references accordingly.

ImmichFrame.Core/Logic/Pool/PeopleAssetsPool.cs (1)

1-1: Minor: file/class naming mismatch

File is PeopleAssetsPool.cs but class is PersonAssetsPool. Consider aligning names for clarity.

ImmichFrame.Core/Logic/Pool/MemoryAssetsPool.cs (2)

15-16: Confirm desired order: ascending vs descending by MemoryAt

Ordering with OrderBy(m => m.MemoryAt) yields oldest memories first (e.g., 3y ago → 2y → 1y). If the intent is “one year ago, two years ago, …”, sort descending:

-foreach (var memory in memories.OrderBy(m => m.MemoryAt))
+foreach (var memory in memories.OrderByDescending(m => m.MemoryAt))

8-8: Daily cache: consider UTC to avoid TZ edge cases

Using local time for both the API date and cache expiration can drift around DST/offsets. Consider DateTime.UtcNow and AbsoluteExpiration at UTC midnight to reduce boundary surprises.

ImmichFrame.Core/Logic/Pool/CachingApiAssetsPool.cs (3)

8-9: Potential race on _next with concurrent GetAssets

GetAssets mutates _next without synchronization; concurrent calls can skip/duplicate assets. Guard updates and wrap-around with a lock.

Apply this diff:

-    private int _next; //next asset to return
+    private int _next; // next asset to return
+    private readonly object _sync = new();

And update GetAssets as below.


13-39: Make GetAssets thread-safe, handle negatives, and simplify slicing

  • Protect _next updates
  • Treat non-positive requested as empty
  • Use Skip instead of TakeLast
-    public async Task<IEnumerable<AssetResponseDto>> GetAssets(int requested, CancellationToken ct = default)
+    public async Task<IEnumerable<AssetResponseDto>> GetAssets(int requested, CancellationToken ct = default)
     {
-        if (requested == 0)
+        if (requested <= 0)
         {
             return new List<AssetResponseDto>();
         }
         
         var all = await AllAssets(ct);
 
         if (all.Count < requested)
         {
             requested = all.Count; //limit request to what we have
         }
-        
-        var tail = all.TakeLast(all.Count - _next).ToList();
-        
-        if (tail.Count >= requested)
-        {
-            _next += requested;
-            return tail.Take(requested);
-        }
-
-        // not enough left in tail; need to read head too
-        var overrun = requested - tail.Count;
-        _next = overrun;
-        return tail.Concat(all.Take(overrun));
+
+        if (requested == 0)
+            return Array.Empty<AssetResponseDto>();
+
+        int start, firstCount, secondCount;
+        lock (_sync)
+        {
+            if (_next >= all.Count)
+                _next %= Math.Max(1, all.Count);
+
+            start = _next;
+            firstCount = Math.Min(requested, all.Count - start);
+            secondCount = requested - firstCount;
+            _next = (start + requested) % (all.Count == 0 ? 1 : all.Count);
+        }
+
+        var head = all.Skip(start).Take(firstCount);
+        return secondCount > 0
+            ? head.Concat(all.Take(secondCount))
+            : head;
     }

47-73: Null-safety in account filters (optional)

Some asset sources may not include ExifInfo. Guard against nulls to avoid NREs.

-        if (takenBefore.HasValue)
-        {
-            assets = assets.Where(x => x.ExifInfo.DateTimeOriginal <= takenBefore);
-        }
+        if (takenBefore.HasValue)
+            assets = assets.Where(x => x.ExifInfo != null && x.ExifInfo.DateTimeOriginal <= takenBefore);
...
-        if (takenAfter.HasValue)
-        {
-            assets = assets.Where(x => x.ExifInfo.DateTimeOriginal >= takenAfter);
-        }
+        if (takenAfter.HasValue)
+            assets = assets.Where(x => x.ExifInfo != null && x.ExifInfo.DateTimeOriginal >= takenAfter);
...
-        if (accountSettings.Rating is int rating)
-        {
-            assets = assets.Where(x => x.ExifInfo.Rating == rating);
-        }
+        if (accountSettings.Rating is int rating)
+            assets = assets.Where(x => x.ExifInfo != null && x.ExifInfo.Rating == rating);
ImmichFrame.WebApi.Tests/Controllers/AssetControllerTests.cs (1)

177-193: Catch‑all 200 may mask unintended HTTP calls

Returning 200 {} for any other host requests can hide regressions. Prefer failing fast with 404 for unknown paths so tests surface unexpected traffic.

-                .ReturnsAsync(() => new HttpResponseMessage
-                {
-                    StatusCode = HttpStatusCode.OK,
-                    Content = new StringContent("{}", System.Text.Encoding.UTF8, "application/json")
-                });
+                .ReturnsAsync(() => new HttpResponseMessage
+                {
+                    StatusCode = HttpStatusCode.NotFound,
+                    Content = new StringContent("{\"error\":\"unhandled mock path\"}", System.Text.Encoding.UTF8, "application/json")
+                });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e7d88b5 and d927278.

📒 Files selected for processing (16)
  • ImmichFrame.Core.Tests/Logic/Pool/AlbumAssetsPoolTests.cs (1 hunks)
  • ImmichFrame.Core.Tests/Logic/Pool/CachingApiAssetsPoolTests.cs (12 hunks)
  • ImmichFrame.Core.Tests/Logic/Pool/FavoriteAssetsPoolTests.cs (1 hunks)
  • ImmichFrame.Core.Tests/Logic/Pool/PersonAssetsPoolTests.cs (1 hunks)
  • ImmichFrame.Core/Logic/LogicPoolAdapter.cs (1 hunks)
  • ImmichFrame.Core/Logic/Pool/AlbumAssetsPool.cs (2 hunks)
  • ImmichFrame.Core/Logic/Pool/CachingApiAssetsPool.cs (2 hunks)
  • ImmichFrame.Core/Logic/Pool/FavoriteAssetsPool.cs (1 hunks)
  • ImmichFrame.Core/Logic/Pool/MemoryAssetsPool.cs (1 hunks)
  • ImmichFrame.Core/Logic/Pool/PeopleAssetsPool.cs (1 hunks)
  • ImmichFrame.Core/Logic/PoolConfiguration.cs (1 hunks)
  • ImmichFrame.Core/Logic/PooledImmichFrameLogic.cs (3 hunks)
  • ImmichFrame.WebApi.Tests/Controllers/AssetControllerTests.cs (2 hunks)
  • ImmichFrame.WebApi.Tests/ImmichFrame.WebApi.Tests.csproj (1 hunks)
  • ImmichFrame.WebApi.Tests/Logic/MemoryAssetsPoolTests.cs (3 hunks)
  • ImmichFrame.WebApi/Program.cs (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: JoeRu
PR: immichFrame/ImmichFrame#481
File: ImmichFrame.Core.Tests/Logic/Pool/ChronologicalAssetsPoolWrapperTests.cs:306-306
Timestamp: 2025-10-09T16:12:49.467Z
Learning: When testing the ChronologicalAssetsPoolWrapper in ImmichFrame.Core.Tests, use `Is.SupersetOf` rather than `Is.EquivalentTo` or `Is.EqualTo` assertions because the wrapper uses Fisher-Yates shuffle to randomize set order, making output non-deterministic between runs. The wrapper also uses a 10x fetch multiplier (capped at 1000) that may return more assets than requested, which is legitimate behavior.
📚 Learning: 2025-10-09T16:12:49.467Z
Learnt from: JoeRu
PR: immichFrame/ImmichFrame#481
File: ImmichFrame.Core.Tests/Logic/Pool/ChronologicalAssetsPoolWrapperTests.cs:306-306
Timestamp: 2025-10-09T16:12:49.467Z
Learning: When testing the ChronologicalAssetsPoolWrapper in ImmichFrame.Core.Tests, use `Is.SupersetOf` rather than `Is.EquivalentTo` or `Is.EqualTo` assertions because the wrapper uses Fisher-Yates shuffle to randomize set order, making output non-deterministic between runs. The wrapper also uses a 10x fetch multiplier (capped at 1000) that may return more assets than requested, which is legitimate behavior.

Applied to files:

  • ImmichFrame.Core.Tests/Logic/Pool/FavoriteAssetsPoolTests.cs
  • ImmichFrame.Core.Tests/Logic/Pool/PersonAssetsPoolTests.cs
  • ImmichFrame.Core.Tests/Logic/Pool/AlbumAssetsPoolTests.cs
  • ImmichFrame.WebApi.Tests/Logic/MemoryAssetsPoolTests.cs
  • ImmichFrame.Core.Tests/Logic/Pool/CachingApiAssetsPoolTests.cs
  • ImmichFrame.Core/Logic/Pool/CachingApiAssetsPool.cs
🧬 Code graph analysis (15)
ImmichFrame.Core/Logic/Pool/PeopleAssetsPool.cs (2)
ImmichFrame.Core/Logic/Pool/CachingApiAssetsPool.cs (6)
  • CachingApiAssetsPool (6-76)
  • Task (10-11)
  • Task (13-39)
  • Task (41-44)
  • Task (47-73)
  • Task (75-75)
ImmichFrame.Core.Tests/Logic/Pool/PersonAssetsPoolTests.cs (2)
  • Task (27-30)
  • AssetResponseDto (44-44)
ImmichFrame.Core.Tests/Logic/Pool/FavoriteAssetsPoolTests.cs (5)
ImmichFrame.Core.Tests/Logic/Pool/AlbumAssetsPoolTests.cs (2)
  • Task (26-26)
  • AssetResponseDto (41-41)
ImmichFrame.Core.Tests/Logic/Pool/CachingApiAssetsPoolTests.cs (1)
  • Task (32-35)
ImmichFrame.Core.Tests/Logic/Pool/PersonAssetsPoolTests.cs (2)
  • Task (27-30)
  • AssetResponseDto (44-44)
ImmichFrame.Core/Logic/Pool/CachingApiAssetsPool.cs (5)
  • Task (10-11)
  • Task (13-39)
  • Task (41-44)
  • Task (47-73)
  • Task (75-75)
ImmichFrame.Core/Logic/Pool/FavoriteAssetsPool.cs (1)
  • Task (8-36)
ImmichFrame.Core.Tests/Logic/Pool/PersonAssetsPoolTests.cs (4)
ImmichFrame.Core.Tests/Logic/Pool/AlbumAssetsPoolTests.cs (2)
  • Task (26-26)
  • AssetResponseDto (41-41)
ImmichFrame.Core.Tests/Logic/Pool/FavoriteAssetsPoolTests.cs (2)
  • Task (27-30)
  • AssetResponseDto (42-42)
ImmichFrame.Core/Logic/Pool/CachingApiAssetsPool.cs (5)
  • Task (10-11)
  • Task (13-39)
  • Task (41-44)
  • Task (47-73)
  • Task (75-75)
ImmichFrame.Core/Logic/Pool/PeopleAssetsPool.cs (1)
  • Task (8-39)
ImmichFrame.Core/Logic/Pool/AlbumAssetsPool.cs (3)
ImmichFrame.Core/Logic/Pool/CachingApiAssetsPool.cs (6)
  • CachingApiAssetsPool (6-76)
  • Task (10-11)
  • Task (13-39)
  • Task (41-44)
  • Task (47-73)
  • Task (75-75)
ImmichFrame.Core/Logic/Pool/FavoriteAssetsPool.cs (1)
  • Task (8-36)
ImmichFrame.Core/Logic/Pool/MemoryAssetsPool.cs (1)
  • Task (10-35)
ImmichFrame.Core.Tests/Logic/Pool/AlbumAssetsPoolTests.cs (4)
ImmichFrame.Core.Tests/Logic/Pool/CachingApiAssetsPoolTests.cs (1)
  • Task (32-35)
ImmichFrame.Core.Tests/Logic/Pool/FavoriteAssetsPoolTests.cs (2)
  • Task (27-30)
  • AssetResponseDto (42-42)
ImmichFrame.Core.Tests/Logic/Pool/PersonAssetsPoolTests.cs (2)
  • Task (27-30)
  • AssetResponseDto (44-44)
ImmichFrame.Core/Logic/Pool/AlbumAssetsPool.cs (1)
  • Task (9-28)
ImmichFrame.WebApi/Program.cs (2)
ImmichFrame.Core/Logic/PooledImmichFrameLogic.cs (2)
  • PooledImmichFrameLogic (8-123)
  • PooledImmichFrameLogic (16-30)
ImmichFrame.Core/Logic/PoolConfiguration.cs (2)
  • PoolConfiguration (8-47)
  • PoolConfiguration (14-22)
ImmichFrame.Core/Logic/Pool/MemoryAssetsPool.cs (3)
ImmichFrame.Core/Logic/Pool/CachingApiAssetsPool.cs (5)
  • CachingApiAssetsPool (6-76)
  • Task (10-11)
  • Task (13-39)
  • Task (41-44)
  • Task (47-73)
ImmichFrame.Core/Logic/Pool/AlbumAssetsPool.cs (1)
  • Task (9-28)
ImmichFrame.WebApi.Tests/Logic/MemoryAssetsPoolTests.cs (2)
  • List (32-49)
  • List (51-70)
ImmichFrame.WebApi.Tests/Logic/MemoryAssetsPoolTests.cs (3)
ImmichFrame.Core/Logic/Pool/MemoryAssetsPool.cs (1)
  • MemoryAssetsPool (8-36)
ImmichFrame.Core/Logic/LogicPoolAdapter.cs (8)
  • LogicPoolAdapter (8-45)
  • Task (10-11)
  • Task (13-14)
  • Task (16-17)
  • Task (19-20)
  • Task (22-22)
  • Task (24-25)
  • Task (27-44)
ImmichFrame.Core/Logic/Pool/CachingApiAssetsPool.cs (4)
  • Task (10-11)
  • Task (13-39)
  • Task (41-44)
  • Task (47-73)
ImmichFrame.Core/Logic/Pool/FavoriteAssetsPool.cs (4)
ImmichFrame.Core/Logic/Pool/CachingApiAssetsPool.cs (6)
  • CachingApiAssetsPool (6-76)
  • Task (10-11)
  • Task (13-39)
  • Task (41-44)
  • Task (47-73)
  • Task (75-75)
ImmichFrame.Core.Tests/Logic/Pool/AlbumAssetsPoolTests.cs (2)
  • Task (26-26)
  • AssetResponseDto (41-41)
ImmichFrame.Core.Tests/Logic/Pool/FavoriteAssetsPoolTests.cs (2)
  • Task (27-30)
  • AssetResponseDto (42-42)
ImmichFrame.Core.Tests/Logic/Pool/PersonAssetsPoolTests.cs (2)
  • Task (27-30)
  • AssetResponseDto (44-44)
ImmichFrame.Core/Logic/PoolConfiguration.cs (7)
ImmichFrame.Core/Logic/PooledImmichFrameLogic.cs (2)
  • IAssetPool (38-60)
  • TimeSpan (33-34)
ImmichFrame.Core/Logic/Pool/AllAssetsPool.cs (1)
  • AllAssetsPool (6-78)
ImmichFrame.Core/Logic/Pool/FavoriteAssetsPool.cs (1)
  • FavoriteAssetsPool (6-37)
ImmichFrame.Core/Logic/Pool/MemoryAssetsPool.cs (1)
  • MemoryAssetsPool (8-36)
ImmichFrame.Core/Logic/Pool/AlbumAssetsPool.cs (1)
  • AlbumAssetsPool (7-29)
ImmichFrame.Core/Logic/Pool/PeopleAssetsPool.cs (1)
  • PersonAssetsPool (6-40)
ImmichFrame.Core/Logic/Pool/MultiAssetPool.cs (1)
  • MultiAssetPool (6-22)
ImmichFrame.Core.Tests/Logic/Pool/CachingApiAssetsPoolTests.cs (4)
ImmichFrame.Core.Tests/Logic/Pool/AlbumAssetsPoolTests.cs (2)
  • Task (26-26)
  • AssetResponseDto (41-41)
ImmichFrame.Core.Tests/Logic/Pool/FavoriteAssetsPoolTests.cs (2)
  • Task (27-30)
  • AssetResponseDto (42-42)
ImmichFrame.Core.Tests/Logic/Pool/PersonAssetsPoolTests.cs (2)
  • Task (27-30)
  • AssetResponseDto (44-44)
ImmichFrame.Core/Logic/Pool/CachingApiAssetsPool.cs (5)
  • Task (10-11)
  • Task (13-39)
  • Task (41-44)
  • Task (47-73)
  • Task (75-75)
ImmichFrame.Core/Logic/Pool/CachingApiAssetsPool.cs (4)
ImmichFrame.Core/Logic/PoolConfiguration.cs (1)
  • IAssetPool (24-46)
ImmichFrame.Core/Logic/PooledImmichFrameLogic.cs (7)
  • IAssetPool (38-60)
  • Task (62-65)
  • Task (67-70)
  • Task (72-72)
  • Task (74-74)
  • Task (76-76)
  • Task (78-120)
ImmichFrame.Core.Tests/Logic/Pool/CachingApiAssetsPoolTests.cs (2)
  • Task (32-35)
  • List (62-72)
ImmichFrame.Core/Logic/Pool/AlbumAssetsPool.cs (1)
  • Task (9-28)
ImmichFrame.Core/Logic/LogicPoolAdapter.cs (5)
ImmichFrame.Core/Logic/PoolConfiguration.cs (1)
  • IAssetPool (24-46)
ImmichFrame.Core/Logic/PooledImmichFrameLogic.cs (4)
  • IAssetPool (38-60)
  • Task (62-65)
  • Task (67-70)
  • Task (72-72)
ImmichFrame.Core/Logic/Pool/AlbumAssetsPool.cs (1)
  • Task (9-28)
ImmichFrame.Core/Logic/Pool/CachingApiAssetsPool.cs (5)
  • Task (10-11)
  • Task (13-39)
  • Task (41-44)
  • Task (47-73)
  • Task (75-75)
ImmichFrame.Core/Helpers/WebhookHelper.cs (1)
  • WebhookHelper (5-33)
ImmichFrame.WebApi.Tests/Controllers/AssetControllerTests.cs (1)
ImmichFrame.WebApi.Tests/Mocks/MockHttpMessageHandler.cs (1)
  • Task (19-27)
ImmichFrame.Core/Logic/PooledImmichFrameLogic.cs (3)
ImmichFrame.Core/Logic/LogicPoolAdapter.cs (1)
  • LogicPoolAdapter (8-45)
ImmichFrame.Core/Helpers/ApiCache.cs (3)
  • ApiCache (5-27)
  • ApiCache (10-15)
  • ApiCache (17-20)
ImmichFrame.Core/Logic/PoolConfiguration.cs (3)
  • IAssetPool (24-46)
  • PoolConfiguration (8-47)
  • PoolConfiguration (14-22)
🪛 GitHub Check: test
ImmichFrame.Core/Logic/Pool/PeopleAssetsPool.cs

[warning] 6-6:
Parameter 'IAccountSettings accountSettings' is captured into the state of the enclosing type and its value is also passed to the base constructor. The value might be captured by the base class as well.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (csharp)
🔇 Additional comments (16)
ImmichFrame.Core/Logic/Pool/PeopleAssetsPool.cs (1)

6-6: Constructor alignment with base change looks good

Dropping ImmichApi from the base ctor and keeping it as a primary-ctor capture here is consistent with the refactor across pools. LGTM.

ImmichFrame.WebApi.Tests/Controllers/AssetControllerTests.cs (1)

161-176: Broader asset/thumbnails mock looks good

Covers both /assets/ and /thumbnail endpoints, returning minimal JPEG bytes. This stabilizes the test path.

If the API may hit additional asset-related endpoints (e.g., /api/assets/), consider including those patterns explicitly.

ImmichFrame.WebApi/Program.cs (1)

63-65: LGTM!

The DI setup correctly constructs PoolConfiguration and passes it to PooledImmichFrameLogic. ActivatorUtilities will resolve the missing dependencies (IGeneralSettings and IHttpClientFactory) from the service provider.

ImmichFrame.Core.Tests/Logic/Pool/FavoriteAssetsPoolTests.cs (1)

27-29: LGTM!

The return type update from IEnumerable<AssetResponseDto> to IList<AssetResponseDto> aligns with the base class signature changes. The test logic remains unchanged.

ImmichFrame.Core.Tests/Logic/Pool/PersonAssetsPoolTests.cs (1)

27-29: LGTM!

Consistent with the refactor to return IList<AssetResponseDto> instead of IEnumerable<AssetResponseDto>.

ImmichFrame.Core.Tests/Logic/Pool/AlbumAssetsPoolTests.cs (1)

26-26: LGTM!

The return type update is consistent with the broader refactor.

ImmichFrame.Core/Logic/Pool/FavoriteAssetsPool.cs (1)

6-8: LGTM!

The constructor base call and return type updates align with the refactored CachingApiAssetsPool base class, which no longer accepts ImmichApi in its constructor and expects IList<AssetResponseDto> return types.

ImmichFrame.Core/Logic/Pool/AlbumAssetsPool.cs (2)

7-9: LGTM!

The constructor and return type updates are consistent with the base class refactor.


27-27: LGTM!

The .ToList() call is necessary to convert the IEnumerable result from WhereExcludes to IList<AssetResponseDto>.

ImmichFrame.WebApi.Tests/Logic/MemoryAssetsPoolTests.cs (3)

191-216: LGTM!

This test effectively verifies that memories are sorted chronologically by reversing the input order and asserting that assets are still returned oldest-first ("2 years ago" before "1 year ago").


218-244: LGTM!

Excellent use of [Repeat(10)] and shuffling to ensure the chronological sorting is deterministic and not accidentally passing due to input order.


246-274: LGTM!

This test verifies that chronological ordering is maintained across multiple calls to GetNextAsset, confirming that the pool's internal iteration state respects the sorted order.

ImmichFrame.Core.Tests/Logic/Pool/CachingApiAssetsPoolTests.cs (2)

25-34: LGTM!

The test harness updates from IEnumerable<AssetResponseDto> to IList<AssetResponseDto> are consistent with the base class refactor.


48-52: LGTM!

The mock setup correctly uses the updated IList<AssetResponseDto> type for the cache delegate.

ImmichFrame.Core/Logic/PooledImmichFrameLogic.cs (1)

78-120: LGTM!

The GetImage override correctly adds local caching on top of the base implementation. The delegation to base.GetImage(id) at line 106 and the surrounding cache logic are appropriate.

ImmichFrame.Core/Logic/LogicPoolAdapter.cs (1)

27-44: Verify and extend image content-type mapping
The GetImage logic assumes only “image/webp” and defaults all other types to JPEG. Confirm whether the Immich API can return formats like PNG or GIF and, if so, map those content-types to their correct extensions.

@jpg0 jpg0 force-pushed the feat/sort-memories-chronologically branch from d927278 to 62596c5 Compare October 10, 2025 04:06
Copy link

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
ImmichFrame.Core/Logic/PooledImmichFrameLogic.cs (1)

34-56: Code duplication: BuildPool logic exists in both classes.

The BuildPool method (lines 34-56) duplicates pool-building logic that already exists in PoolConfiguration.BuildPool (lines 17-46 per context). This violates the DRY principle and creates maintenance burden - any changes to pool construction must be made in two places.

Given that PoolConfiguration is already responsible for building the pool, consider removing this method and relying on the pool from poolConfiguration.AssetPool.

Apply this approach to eliminate duplication:

-    private IAssetPool BuildPool(IAccountSettings accountSettings)
-    {
-        if (!accountSettings.ShowFavorites && !accountSettings.ShowMemories && !accountSettings.Albums.Any() && !accountSettings.People.Any())
-        {
-            return new AllAssetsPool(_apiCache, _immichApi, accountSettings);
-        }
-
-        var pools = new List<IAssetPool>();
-
-        if (accountSettings.ShowFavorites)
-            pools.Add(new FavoriteAssetsPool(_apiCache, _immichApi, accountSettings));
-
-        if (accountSettings.ShowMemories)
-            pools.Add(new MemoryAssetsPool(_immichApi, accountSettings));
-
-        if (accountSettings.Albums.Any())
-            pools.Add(new AlbumAssetsPool(_apiCache, _immichApi, accountSettings));
-
-        if (accountSettings.People.Any())
-            pools.Add(new PersonAssetsPool(_apiCache, _immichApi, accountSettings));
-
-        return new MultiAssetPool(pools);
-    }
🧹 Nitpick comments (3)
ImmichFrame.WebApi.Tests/Logic/MemoryAssetsPoolTests.cs (2)

25-25: Consider using MockBehavior.Strict for better test safety.

Passing null! for required constructor parameters works but provides minimal mock setup. Consider using MockBehavior.Strict instead to catch unexpected calls during test development.

Apply this diff:

-        _mockImmichApi = new Mock<ImmichApi>(null!, null!); // Base constructor requires ILogger, IHttpClientFactory, IOptions, pass null
+        _mockImmichApi = new Mock<ImmichApi>(MockBehavior.Strict, null!, null!);

218-244: Consider removing [Repeat(10)] for deterministic chronological ordering.

Since MemoryAssetsPool now explicitly sorts by MemoryAt (line 8 in MemoryAssetsPool.cs: OrderBy(m => m.MemoryAt)), the ordering is deterministic. The [Repeat(10)] attribute is unnecessary overhead for a test that will produce the same result every time.

Based on learnings: The retrieved learning about ChronologicalAssetsPoolWrapper using Fisher-Yates shuffle doesn't apply here, as MemoryAssetsPool uses explicit ordering, not shuffling.

Apply this diff:

     [Test]
-    [Repeat(10)] // In case it accidentally passes due to random order
     public async Task LoadAssetsFromApi_ReturnsAssetsInChronologicalMemoryOrder()
ImmichFrame.Core/Logic/PoolConfiguration.cs (1)

10-12: Consider using init-only setters for immutability.

The properties already use auto-property syntax (addressing the past review comment), but consider using { get; init; } instead of { get; } to enable initialization syntax while maintaining immutability after construction.

Apply this diff:

-    public ImmichApi ImmichApi { get; }
-    public IAssetPool AssetPool { get; }
-    public ApiCache ApiCache { get; }
+    public ImmichApi ImmichApi { get; init; }
+    public IAssetPool AssetPool { get; init; }
+    public ApiCache ApiCache { get; init; }

Then update the constructor:

-        ImmichApi = new ImmichApi(accountSettings.ImmichServerUrl, httpClient);
-        ApiCache = new ApiCache(TimeSpan.FromHours(generalSettings.RefreshAlbumPeopleInterval));
-        AssetPool = BuildPool(accountSettings, ApiCache, ImmichApi);
+        ImmichApi = new ImmichApi(accountSettings.ImmichServerUrl, httpClient),
+        ApiCache = new ApiCache(TimeSpan.FromHours(generalSettings.RefreshAlbumPeopleInterval)),
+        AssetPool = BuildPool(accountSettings, ApiCache, ImmichApi)

Wait, that's not correct syntax. Actually with { get; init; }, the assignment in the constructor body would remain the same. The benefit is that you can also use object initializer syntax if needed. Keep the constructor body as is.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d927278 and 62596c5.

📒 Files selected for processing (16)
  • ImmichFrame.Core.Tests/Logic/Pool/AlbumAssetsPoolTests.cs (1 hunks)
  • ImmichFrame.Core.Tests/Logic/Pool/CachingApiAssetsPoolTests.cs (12 hunks)
  • ImmichFrame.Core.Tests/Logic/Pool/FavoriteAssetsPoolTests.cs (1 hunks)
  • ImmichFrame.Core.Tests/Logic/Pool/PersonAssetsPoolTests.cs (1 hunks)
  • ImmichFrame.Core/Logic/LogicPoolAdapter.cs (1 hunks)
  • ImmichFrame.Core/Logic/Pool/AlbumAssetsPool.cs (2 hunks)
  • ImmichFrame.Core/Logic/Pool/CachingApiAssetsPool.cs (2 hunks)
  • ImmichFrame.Core/Logic/Pool/FavoriteAssetsPool.cs (1 hunks)
  • ImmichFrame.Core/Logic/Pool/MemoryAssetsPool.cs (1 hunks)
  • ImmichFrame.Core/Logic/Pool/PeopleAssetsPool.cs (2 hunks)
  • ImmichFrame.Core/Logic/PoolConfiguration.cs (1 hunks)
  • ImmichFrame.Core/Logic/PooledImmichFrameLogic.cs (2 hunks)
  • ImmichFrame.WebApi.Tests/Controllers/AssetControllerTests.cs (2 hunks)
  • ImmichFrame.WebApi.Tests/ImmichFrame.WebApi.Tests.csproj (1 hunks)
  • ImmichFrame.WebApi.Tests/Logic/MemoryAssetsPoolTests.cs (3 hunks)
  • ImmichFrame.WebApi/Program.cs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • ImmichFrame.Core.Tests/Logic/Pool/AlbumAssetsPoolTests.cs
  • ImmichFrame.Core/Logic/Pool/FavoriteAssetsPool.cs
  • ImmichFrame.Core/Logic/LogicPoolAdapter.cs
  • ImmichFrame.Core/Logic/Pool/CachingApiAssetsPool.cs
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: JoeRu
PR: immichFrame/ImmichFrame#481
File: ImmichFrame.Core.Tests/Logic/Pool/ChronologicalAssetsPoolWrapperTests.cs:306-306
Timestamp: 2025-10-09T16:12:49.467Z
Learning: When testing the ChronologicalAssetsPoolWrapper in ImmichFrame.Core.Tests, use `Is.SupersetOf` rather than `Is.EquivalentTo` or `Is.EqualTo` assertions because the wrapper uses Fisher-Yates shuffle to randomize set order, making output non-deterministic between runs. The wrapper also uses a 10x fetch multiplier (capped at 1000) that may return more assets than requested, which is legitimate behavior.
📚 Learning: 2025-10-09T16:12:49.467Z
Learnt from: JoeRu
PR: immichFrame/ImmichFrame#481
File: ImmichFrame.Core.Tests/Logic/Pool/ChronologicalAssetsPoolWrapperTests.cs:306-306
Timestamp: 2025-10-09T16:12:49.467Z
Learning: When testing the ChronologicalAssetsPoolWrapper in ImmichFrame.Core.Tests, use `Is.SupersetOf` rather than `Is.EquivalentTo` or `Is.EqualTo` assertions because the wrapper uses Fisher-Yates shuffle to randomize set order, making output non-deterministic between runs. The wrapper also uses a 10x fetch multiplier (capped at 1000) that may return more assets than requested, which is legitimate behavior.

Applied to files:

  • ImmichFrame.Core.Tests/Logic/Pool/PersonAssetsPoolTests.cs
  • ImmichFrame.WebApi.Tests/Logic/MemoryAssetsPoolTests.cs
  • ImmichFrame.Core.Tests/Logic/Pool/FavoriteAssetsPoolTests.cs
  • ImmichFrame.Core.Tests/Logic/Pool/CachingApiAssetsPoolTests.cs
🧬 Code graph analysis (12)
ImmichFrame.Core.Tests/Logic/Pool/PersonAssetsPoolTests.cs (4)
ImmichFrame.Core.Tests/Logic/Pool/AlbumAssetsPoolTests.cs (2)
  • Task (26-26)
  • AssetResponseDto (41-41)
ImmichFrame.Core.Tests/Logic/Pool/FavoriteAssetsPoolTests.cs (2)
  • Task (27-30)
  • AssetResponseDto (42-42)
ImmichFrame.Core/Logic/Pool/CachingApiAssetsPool.cs (5)
  • Task (10-11)
  • Task (13-39)
  • Task (41-44)
  • Task (47-73)
  • Task (75-75)
ImmichFrame.Core/Logic/Pool/PeopleAssetsPool.cs (1)
  • Task (8-39)
ImmichFrame.WebApi.Tests/ImmichFrame.WebApi.Tests.csproj (2)
ImmichFrame.Core.Tests/Logic/Pool/MultiAssetPoolTests.cs (1)
  • ImmichFrame (12-303)
ImmichFrame.Core.Tests/Logic/Pool/AggregatingAssetPoolTests.cs (1)
  • ImmichFrame (6-208)
ImmichFrame.WebApi.Tests/Controllers/AssetControllerTests.cs (1)
ImmichFrame.WebApi.Tests/Mocks/MockHttpMessageHandler.cs (1)
  • Task (19-27)
ImmichFrame.WebApi/Program.cs (3)
ImmichFrame.Core/Logic/PooledImmichFrameLogic.cs (2)
  • PooledImmichFrameLogic (8-119)
  • PooledImmichFrameLogic (16-26)
ImmichFrame.Core/Logic/PoolConfiguration.cs (2)
  • PoolConfiguration (8-47)
  • PoolConfiguration (14-22)
ImmichFrame.Core/Logic/MultiImmichFrameLogicDelegate.cs (1)
  • MultiImmichFrameLogicDelegate (16-28)
ImmichFrame.Core/Logic/Pool/PeopleAssetsPool.cs (4)
ImmichFrame.Core/Logic/Pool/CachingApiAssetsPool.cs (6)
  • CachingApiAssetsPool (6-76)
  • Task (10-11)
  • Task (13-39)
  • Task (41-44)
  • Task (47-73)
  • Task (75-75)
ImmichFrame.Core.Tests/Logic/Pool/PersonAssetsPoolTests.cs (2)
  • Task (27-30)
  • AssetResponseDto (44-44)
ImmichFrame.Core/Logic/Pool/AlbumAssetsPool.cs (1)
  • Task (9-28)
ImmichFrame.Core/Logic/Pool/FavoriteAssetsPool.cs (1)
  • Task (8-36)
ImmichFrame.Core/Logic/Pool/MemoryAssetsPool.cs (1)
ImmichFrame.Core/Logic/Pool/CachingApiAssetsPool.cs (5)
  • CachingApiAssetsPool (6-76)
  • Task (10-11)
  • Task (13-39)
  • Task (41-44)
  • Task (47-73)
ImmichFrame.Core/Logic/Pool/AlbumAssetsPool.cs (2)
ImmichFrame.Core/Logic/Pool/CachingApiAssetsPool.cs (6)
  • CachingApiAssetsPool (6-76)
  • Task (10-11)
  • Task (13-39)
  • Task (41-44)
  • Task (47-73)
  • Task (75-75)
ImmichFrame.Core.Tests/Logic/Pool/AlbumAssetsPoolTests.cs (2)
  • Task (26-26)
  • AssetResponseDto (41-41)
ImmichFrame.Core/Logic/PooledImmichFrameLogic.cs (3)
ImmichFrame.Core/Logic/LogicPoolAdapter.cs (1)
  • LogicPoolAdapter (8-45)
ImmichFrame.Core/Helpers/ApiCache.cs (3)
  • ApiCache (5-27)
  • ApiCache (10-15)
  • ApiCache (17-20)
ImmichFrame.Core/Logic/PoolConfiguration.cs (3)
  • IAssetPool (24-46)
  • PoolConfiguration (8-47)
  • PoolConfiguration (14-22)
ImmichFrame.WebApi.Tests/Logic/MemoryAssetsPoolTests.cs (3)
ImmichFrame.Core/Logic/Pool/MemoryAssetsPool.cs (1)
  • MemoryAssetsPool (8-36)
ImmichFrame.Core/Logic/LogicPoolAdapter.cs (8)
  • LogicPoolAdapter (8-45)
  • Task (10-11)
  • Task (13-14)
  • Task (16-17)
  • Task (19-20)
  • Task (22-22)
  • Task (24-25)
  • Task (27-44)
ImmichFrame.Core/Logic/Pool/CachingApiAssetsPool.cs (4)
  • Task (10-11)
  • Task (13-39)
  • Task (41-44)
  • Task (47-73)
ImmichFrame.Core.Tests/Logic/Pool/FavoriteAssetsPoolTests.cs (4)
ImmichFrame.Core.Tests/Logic/Pool/AlbumAssetsPoolTests.cs (2)
  • Task (26-26)
  • AssetResponseDto (41-41)
ImmichFrame.Core.Tests/Logic/Pool/PersonAssetsPoolTests.cs (2)
  • Task (27-30)
  • AssetResponseDto (44-44)
ImmichFrame.Core/Logic/Pool/CachingApiAssetsPool.cs (5)
  • Task (10-11)
  • Task (13-39)
  • Task (41-44)
  • Task (47-73)
  • Task (75-75)
ImmichFrame.Core/Logic/Pool/FavoriteAssetsPool.cs (1)
  • Task (8-36)
ImmichFrame.Core/Logic/PoolConfiguration.cs (3)
ImmichFrame.Core/Logic/PooledImmichFrameLogic.cs (1)
  • IAssetPool (34-56)
ImmichFrame.Core/Helpers/HttpClientExtensionMethods.cs (1)
  • UseApiKey (5-8)
ImmichFrame.Core/Logic/Pool/MultiAssetPool.cs (1)
  • MultiAssetPool (6-22)
ImmichFrame.Core.Tests/Logic/Pool/CachingApiAssetsPoolTests.cs (3)
ImmichFrame.Core.Tests/Logic/Pool/AlbumAssetsPoolTests.cs (2)
  • Task (26-26)
  • AssetResponseDto (41-41)
ImmichFrame.Core/Logic/Pool/AlbumAssetsPool.cs (1)
  • Task (9-28)
ImmichFrame.Core/Logic/Pool/CachingApiAssetsPool.cs (5)
  • Task (10-11)
  • Task (13-39)
  • Task (41-44)
  • Task (47-73)
  • Task (75-75)
🪛 GitHub Check: test
ImmichFrame.Core/Logic/Pool/PeopleAssetsPool.cs

[warning] 6-6:
Parameter 'IAccountSettings accountSettings' is captured into the state of the enclosing type and its value is also passed to the base constructor. The value might be captured by the base class as well.

ImmichFrame.Core/Logic/Pool/AlbumAssetsPool.cs

[warning] 7-7:
Parameter 'IAccountSettings accountSettings' is captured into the state of the enclosing type and its value is also passed to the base constructor. The value might be captured by the base class as well.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (csharp)
🔇 Additional comments (22)
ImmichFrame.Core/Logic/Pool/MemoryAssetsPool.cs (3)

15-15: LGTM! Chronological ordering successfully implemented.

The memories are now correctly sorted by MemoryAt date, ensuring they're displayed in chronological order as specified in the PR objectives.


15-15: Excellent! Memories now sorted chronologically.

This .OrderBy(m => m.MemoryAt) directly addresses the PR objective and issue #163 by ensuring memories are processed in chronological order (e.g., one year ago, two years ago) rather than in random order.


10-10: Return type change aligns with broader refactor.

The change from IEnumerable<AssetResponseDto> to IList<AssetResponseDto> is consistent with the codebase-wide migration to concrete list types across all pool implementations.

ImmichFrame.Core/Logic/Pool/AlbumAssetsPool.cs (3)

7-7: The static analysis warning is a false positive.

The warning about accountSettings being captured in the primary constructor state and passed to base is expected C# 12 primary constructor behavior. This pattern is intentional and does not indicate an issue.


7-7: Static analysis warning can be ignored.

The analyzer flags accountSettings being passed to both the base constructor and captured by this class. This is intentional and safe—the base class (CachingApiAssetsPool) requires it for applying account filters, while this class uses it for album-specific logic. No actual issue exists.


9-27: Return type and ToList() addition look good.

The return type change from IEnumerable<AssetResponseDto> to IList<AssetResponseDto> with the corresponding .ToList() call aligns with the broader refactor across all pool implementations.

ImmichFrame.WebApi.Tests/ImmichFrame.WebApi.Tests.csproj (2)

29-29: LGTM! Test project reference enables test utility sharing.

Adding a reference to ImmichFrame.Core.Tests allows this test project to reuse test helpers and utilities, which is a reasonable pattern for reducing test code duplication.


29-29: Project reference addition is appropriate.

Adding a reference to ImmichFrame.Core.Tests enables the Web API test project to leverage shared test utilities and helpers from the core test suite, which is a standard and beneficial practice.

ImmichFrame.WebApi/Program.cs (2)

64-65: LGTM! DI factory correctly wired for new architecture.

The factory now properly creates PoolConfiguration and passes it to PooledImmichFrameLogic, supporting the new pool-based orchestration pattern.


64-65: DI wiring correctly updated for new architecture.

The factory now creates PoolConfiguration via ActivatorUtilities and passes it to PooledImmichFrameLogic, correctly aligning with the updated constructor signature and the new pool-based orchestration architecture.

ImmichFrame.WebApi.Tests/Controllers/AssetControllerTests.cs (2)

161-192: LGTM! Enhanced mock coverage prevents unintended network calls.

The extended mock setup now handles both /assets/ and /thumbnail endpoints, with a catch-all for any other requests. This defensive approach ensures all HTTP interactions are mocked during tests.


161-192: Enhanced mock coverage improves test robustness.

The expanded mocking now handles both /assets/ and /thumbnail endpoints (lines 166-168), plus a catch-all for other endpoints (lines 177-192). This prevents unintended network calls and ensures the tests remain isolated and deterministic, especially important given the new pool orchestration and LogicPoolAdapter image retrieval paths.

ImmichFrame.WebApi.Tests/Logic/MemoryAssetsPoolTests.cs (2)

51-69: LGTM! Memory creation logic supports chronological ordering.

The implementation correctly computes memoryAt relative to memoryYear and passes the year as DateTimeOriginal, enabling the chronological ordering tests.


191-216: LGTM! Test validates chronological ordering.

This test correctly verifies that memories are returned oldest-first regardless of input order. The use of Reverse() ensures the test isn't passing accidentally.

ImmichFrame.Core/Logic/Pool/PeopleAssetsPool.cs (2)

6-6: Static analysis warning can be safely ignored.

The warning about accountSettings being both captured and passed to base is a common pattern in C# when derived classes need to use constructor parameters both locally and in the base class. In this case, accountSettings.People is accessed at line 12, which is the intended design.


8-39: LGTM! Return type change and pagination improvements are correct.

The changes correctly:

  • Update return type to IList<AssetResponseDto> for consistency
  • Replace total with returned for clearer pagination semantics
  • Add cancellation token check in the loop condition to enable early termination
ImmichFrame.Core.Tests/Logic/Pool/CachingApiAssetsPoolTests.cs (1)

25-272: LGTM! Test harness consistently updated for IList return type.

All changes correctly update the test infrastructure and assertions to work with IList<AssetResponseDto> instead of IEnumerable<AssetResponseDto>. The test logic remains sound and comprehensive.

ImmichFrame.Core/Logic/PoolConfiguration.cs (2)

14-22: LGTM! Constructor properly initializes all components.

The constructor correctly:

  • Creates HTTP client from factory (supporting proper disposal and configuration)
  • Applies API key to the client
  • Initializes ImmichApi, ApiCache, and AssetPool in the correct order

24-46: LGTM! Pool configuration logic correctly consolidated.

The BuildPool method appropriately:

  • Returns AllAssetsPool when no specific filters are enabled
  • Assembles a MultiAssetPool from enabled pool types in a consistent order
  • Correctly passes apiCache to pools that need caching (Favorites, Albums, People) while MemoryAssetsPool uses its own daily cache

This consolidation improves maintainability by centralizing pool configuration logic.

ImmichFrame.Core/Logic/PooledImmichFrameLogic.cs (1)

74-116: GetImage refactor looks good.

The delegation to base.GetImage(id) appropriately leverages the base class implementation from LogicPoolAdapter, eliminating duplication and maintaining consistent image retrieval behavior.

ImmichFrame.Core.Tests/Logic/Pool/FavoriteAssetsPoolTests.cs (1)

27-27: Test helper return type correctly updated.

The return type change from Task<IEnumerable<AssetResponseDto>> to Task<IList<AssetResponseDto>> aligns with the implementation change in FavoriteAssetsPool.LoadAssets and the broader migration to list-based return types across all pool implementations.

ImmichFrame.Core.Tests/Logic/Pool/PersonAssetsPoolTests.cs (1)

27-27: Test helper return type correctly updated.

The return type change from Task<IEnumerable<AssetResponseDto>> to Task<IList<AssetResponseDto>> aligns with the implementation change in PersonAssetsPool.LoadAssets and maintains consistency with the broader refactor to list-based return types.

@JoeRu
Copy link

JoeRu commented Oct 10, 2025

I guess you will run into the same Problem as i did - there are two random|shuffles after a sorting:
one is in
ImmichFrame.Core/Logic/AccountSelection/TotalAccountImagesSelectionStrategy.cs
And the other in
ImmichFrame.Core/Logic/MultiImmichFrameLogicDelegate.cs
Both randomize any sorting you do in any Pool-Class.
And as such i guess this will not work;

@jpg0 jpg0 force-pushed the feat/sort-memories-chronologically branch from 62596c5 to 5569f97 Compare October 13, 2025 21:55
Copy link

@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: 3

🧹 Nitpick comments (4)
ImmichFrame.WebApi.Tests/Controllers/AssetControllerTests.cs (1)

177-192: Recommend adding a clarifying comment.

The catch-all mock improves test stability by handling unmocked endpoints, but could mask unexpected API calls. Consider adding a comment explaining its purpose to aid future maintenance.

Example comment:

+// Catch-all for any other API calls during test execution that aren't explicitly mocked above.
+// This prevents spurious test failures from additional endpoints introduced by PoolConfiguration/LogicPoolAdapter changes.
+// Returns minimal valid JSON to satisfy most GET endpoints.
 _mockHttpMessageHandler.Protected()
ImmichFrame.WebApi.Tests/Logic/MemoryAssetsPoolTests.cs (1)

25-25: Consider avoiding null-forgiving operators.

The use of null! to suppress null warnings for ImmichApi constructor parameters may hide actual nullability issues. If the constructor truly accepts nullable parameters, consider using explicit null values or creating a test-specific mock setup.

Consider this alternative:

-        _mockImmichApi = new Mock<ImmichApi>(null!, null!);
+        _mockImmichApi = new Mock<ImmichApi>(MockBehavior.Strict);
+        // or create a test helper that provides default parameters
ImmichFrame.Core/Logic/Pool/PeopleAssetsPool.cs (1)

16-35: Use Assets.Total for pagination to avoid an extra API call
SearchAssetsAsync returns Assets.Total; update the loop in PeopleAssetsPool.cs to include a check like page*batchSize < total instead of relying solely on items.Count == batchSize so you don’t fetch an empty final page when the total is an exact multiple of batchSize.

ImmichFrame.Core/Logic/LogicPoolAdapter.cs (1)

27-44: Simplistic extension inference may cause incorrect file extensions.

The content-type detection defaults to "jpeg" for any non-webp image, which could be incorrect for PNG, GIF, SVG, BMP, TIFF, or other image formats. While this may work for most cases, it could cause issues with file caching or client-side rendering expectations.

Consider a more comprehensive mapping:

-        var ext = contentType.ToLower() == "image/webp" ? "webp" : "jpeg";
+        var ext = contentType.ToLower() switch
+        {
+            "image/webp" => "webp",
+            "image/png" => "png",
+            "image/gif" => "gif",
+            "image/svg+xml" => "svg",
+            "image/bmp" => "bmp",
+            "image/tiff" => "tiff",
+            _ => "jpeg"
+        };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 62596c5 and 5569f97.

📒 Files selected for processing (16)
  • ImmichFrame.Core.Tests/Logic/Pool/AlbumAssetsPoolTests.cs (1 hunks)
  • ImmichFrame.Core.Tests/Logic/Pool/CachingApiAssetsPoolTests.cs (12 hunks)
  • ImmichFrame.Core.Tests/Logic/Pool/FavoriteAssetsPoolTests.cs (1 hunks)
  • ImmichFrame.Core.Tests/Logic/Pool/PersonAssetsPoolTests.cs (1 hunks)
  • ImmichFrame.Core/Logic/LogicPoolAdapter.cs (1 hunks)
  • ImmichFrame.Core/Logic/Pool/AlbumAssetsPool.cs (2 hunks)
  • ImmichFrame.Core/Logic/Pool/CachingApiAssetsPool.cs (2 hunks)
  • ImmichFrame.Core/Logic/Pool/FavoriteAssetsPool.cs (1 hunks)
  • ImmichFrame.Core/Logic/Pool/MemoryAssetsPool.cs (1 hunks)
  • ImmichFrame.Core/Logic/Pool/PeopleAssetsPool.cs (2 hunks)
  • ImmichFrame.Core/Logic/PoolConfiguration.cs (1 hunks)
  • ImmichFrame.Core/Logic/PooledImmichFrameLogic.cs (1 hunks)
  • ImmichFrame.WebApi.Tests/Controllers/AssetControllerTests.cs (2 hunks)
  • ImmichFrame.WebApi.Tests/ImmichFrame.WebApi.Tests.csproj (1 hunks)
  • ImmichFrame.WebApi.Tests/Logic/MemoryAssetsPoolTests.cs (3 hunks)
  • ImmichFrame.WebApi/Program.cs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • ImmichFrame.WebApi/Program.cs
  • ImmichFrame.WebApi.Tests/ImmichFrame.WebApi.Tests.csproj
  • ImmichFrame.Core.Tests/Logic/Pool/CachingApiAssetsPoolTests.cs
  • ImmichFrame.Core/Logic/Pool/FavoriteAssetsPool.cs
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: JoeRu
PR: immichFrame/ImmichFrame#481
File: ImmichFrame.Core.Tests/Logic/Pool/ChronologicalAssetsPoolWrapperTests.cs:306-306
Timestamp: 2025-10-09T16:12:49.488Z
Learning: When testing the ChronologicalAssetsPoolWrapper in ImmichFrame.Core.Tests, use `Is.SupersetOf` rather than `Is.EquivalentTo` or `Is.EqualTo` assertions because the wrapper uses Fisher-Yates shuffle to randomize set order, making output non-deterministic between runs. The wrapper also uses a 10x fetch multiplier (capped at 1000) that may return more assets than requested, which is legitimate behavior.
📚 Learning: 2025-10-09T16:12:49.488Z
Learnt from: JoeRu
PR: immichFrame/ImmichFrame#481
File: ImmichFrame.Core.Tests/Logic/Pool/ChronologicalAssetsPoolWrapperTests.cs:306-306
Timestamp: 2025-10-09T16:12:49.488Z
Learning: When testing the ChronologicalAssetsPoolWrapper in ImmichFrame.Core.Tests, use `Is.SupersetOf` rather than `Is.EquivalentTo` or `Is.EqualTo` assertions because the wrapper uses Fisher-Yates shuffle to randomize set order, making output non-deterministic between runs. The wrapper also uses a 10x fetch multiplier (capped at 1000) that may return more assets than requested, which is legitimate behavior.

Applied to files:

  • ImmichFrame.Core.Tests/Logic/Pool/FavoriteAssetsPoolTests.cs
  • ImmichFrame.Core.Tests/Logic/Pool/PersonAssetsPoolTests.cs
  • ImmichFrame.Core.Tests/Logic/Pool/AlbumAssetsPoolTests.cs
  • ImmichFrame.Core/Logic/Pool/CachingApiAssetsPool.cs
  • ImmichFrame.WebApi.Tests/Controllers/AssetControllerTests.cs
  • ImmichFrame.WebApi.Tests/Logic/MemoryAssetsPoolTests.cs
🧬 Code graph analysis (12)
ImmichFrame.Core.Tests/Logic/Pool/FavoriteAssetsPoolTests.cs (5)
ImmichFrame.Core.Tests/Logic/Pool/AlbumAssetsPoolTests.cs (2)
  • Task (26-26)
  • AssetResponseDto (41-41)
ImmichFrame.Core.Tests/Logic/Pool/CachingApiAssetsPoolTests.cs (1)
  • Task (32-35)
ImmichFrame.Core.Tests/Logic/Pool/PersonAssetsPoolTests.cs (2)
  • Task (27-30)
  • AssetResponseDto (44-44)
ImmichFrame.Core/Logic/Pool/CachingApiAssetsPool.cs (5)
  • Task (10-11)
  • Task (13-39)
  • Task (41-44)
  • Task (47-73)
  • Task (75-75)
ImmichFrame.Core/Logic/Pool/FavoriteAssetsPool.cs (1)
  • Task (8-36)
ImmichFrame.Core/Logic/LogicPoolAdapter.cs (3)
ImmichFrame.Core/Helpers/WebhookHelper.cs (1)
  • WebhookHelper (5-33)
ImmichFrame.Core/Interfaces/IImmichFrameLogic.cs (1)
  • IImmichFrameLogic (6-15)
ImmichFrame.Core/Logic/Pool/IAssetPool.cs (1)
  • IAssetPool (5-36)
ImmichFrame.Core.Tests/Logic/Pool/PersonAssetsPoolTests.cs (5)
ImmichFrame.Core.Tests/Logic/Pool/AlbumAssetsPoolTests.cs (2)
  • Task (26-26)
  • AssetResponseDto (41-41)
ImmichFrame.Core.Tests/Logic/Pool/CachingApiAssetsPoolTests.cs (1)
  • Task (32-35)
ImmichFrame.Core.Tests/Logic/Pool/FavoriteAssetsPoolTests.cs (2)
  • Task (27-30)
  • AssetResponseDto (42-42)
ImmichFrame.Core/Logic/Pool/CachingApiAssetsPool.cs (5)
  • Task (10-11)
  • Task (13-39)
  • Task (41-44)
  • Task (47-73)
  • Task (75-75)
ImmichFrame.Core/Logic/Pool/PeopleAssetsPool.cs (1)
  • Task (8-39)
ImmichFrame.Core/Logic/PooledImmichFrameLogic.cs (8)
ImmichFrame.Core/Logic/LogicPoolAdapter.cs (1)
  • LogicPoolAdapter (8-45)
ImmichFrame.Core/Logic/PoolConfiguration.cs (2)
  • PoolConfiguration (8-21)
  • PoolConfiguration (13-20)
ImmichFrame.Core/Logic/Pool/AllAssetsPool.cs (1)
  • AllAssetsPool (6-78)
ImmichFrame.Core/Helpers/ApiCache.cs (3)
  • ApiCache (5-27)
  • ApiCache (10-15)
  • ApiCache (17-20)
ImmichFrame.Core/Logic/Pool/FavoriteAssetsPool.cs (1)
  • FavoriteAssetsPool (6-37)
ImmichFrame.Core/Logic/Pool/MemoryAssetsPool.cs (1)
  • MemoryAssetsPool (8-36)
ImmichFrame.Core/Logic/Pool/AlbumAssetsPool.cs (1)
  • AlbumAssetsPool (7-29)
ImmichFrame.Core/Logic/Pool/PeopleAssetsPool.cs (1)
  • PersonAssetsPool (6-40)
ImmichFrame.Core.Tests/Logic/Pool/AlbumAssetsPoolTests.cs (5)
ImmichFrame.Core.Tests/Logic/Pool/CachingApiAssetsPoolTests.cs (1)
  • Task (32-35)
ImmichFrame.Core.Tests/Logic/Pool/FavoriteAssetsPoolTests.cs (2)
  • Task (27-30)
  • AssetResponseDto (42-42)
ImmichFrame.Core.Tests/Logic/Pool/PersonAssetsPoolTests.cs (2)
  • Task (27-30)
  • AssetResponseDto (44-44)
ImmichFrame.Core/Logic/Pool/AlbumAssetsPool.cs (1)
  • Task (9-28)
ImmichFrame.Core/Logic/Pool/CachingApiAssetsPool.cs (5)
  • Task (10-11)
  • Task (13-39)
  • Task (41-44)
  • Task (47-73)
  • Task (75-75)
ImmichFrame.Core/Logic/Pool/PeopleAssetsPool.cs (2)
ImmichFrame.Core/Logic/Pool/CachingApiAssetsPool.cs (6)
  • CachingApiAssetsPool (6-76)
  • Task (10-11)
  • Task (13-39)
  • Task (41-44)
  • Task (47-73)
  • Task (75-75)
ImmichFrame.Core/Logic/Pool/FavoriteAssetsPool.cs (1)
  • Task (8-36)
ImmichFrame.Core/Logic/Pool/CachingApiAssetsPool.cs (6)
ImmichFrame.Core/Logic/PooledImmichFrameLogic.cs (1)
  • IAssetPool (20-42)
ImmichFrame.Core.Tests/Logic/Pool/CachingApiAssetsPoolTests.cs (2)
  • Task (32-35)
  • List (62-72)
ImmichFrame.Core/Logic/Pool/AlbumAssetsPool.cs (1)
  • Task (9-28)
ImmichFrame.Core/Logic/Pool/FavoriteAssetsPool.cs (1)
  • Task (8-36)
ImmichFrame.Core/Logic/Pool/MemoryAssetsPool.cs (1)
  • Task (10-35)
ImmichFrame.Core/Logic/Pool/PeopleAssetsPool.cs (1)
  • Task (8-39)
ImmichFrame.Core/Logic/PoolConfiguration.cs (1)
ImmichFrame.Core/Helpers/HttpClientExtensionMethods.cs (1)
  • UseApiKey (5-8)
ImmichFrame.Core/Logic/Pool/MemoryAssetsPool.cs (1)
ImmichFrame.Core/Logic/Pool/CachingApiAssetsPool.cs (6)
  • CachingApiAssetsPool (6-76)
  • Task (10-11)
  • Task (13-39)
  • Task (41-44)
  • Task (47-73)
  • Task (75-75)
ImmichFrame.WebApi.Tests/Controllers/AssetControllerTests.cs (2)
ImmichFrame.Core/Logic/LogicPoolAdapter.cs (7)
  • Task (10-11)
  • Task (13-14)
  • Task (16-17)
  • Task (19-20)
  • Task (22-22)
  • Task (24-25)
  • Task (27-44)
ImmichFrame.WebApi.Tests/Mocks/MockHttpMessageHandler.cs (1)
  • Task (19-27)
ImmichFrame.WebApi.Tests/Logic/MemoryAssetsPoolTests.cs (3)
ImmichFrame.Core/Logic/Pool/MemoryAssetsPool.cs (1)
  • MemoryAssetsPool (8-36)
ImmichFrame.Core/Logic/LogicPoolAdapter.cs (8)
  • LogicPoolAdapter (8-45)
  • Task (10-11)
  • Task (13-14)
  • Task (16-17)
  • Task (19-20)
  • Task (22-22)
  • Task (24-25)
  • Task (27-44)
ImmichFrame.Core/Logic/Pool/CachingApiAssetsPool.cs (4)
  • Task (10-11)
  • Task (13-39)
  • Task (41-44)
  • Task (47-73)
ImmichFrame.Core/Logic/Pool/AlbumAssetsPool.cs (4)
ImmichFrame.Core/Logic/Pool/CachingApiAssetsPool.cs (6)
  • CachingApiAssetsPool (6-76)
  • Task (10-11)
  • Task (13-39)
  • Task (41-44)
  • Task (47-73)
  • Task (75-75)
ImmichFrame.Core.Tests/Logic/Pool/AlbumAssetsPoolTests.cs (2)
  • Task (26-26)
  • AssetResponseDto (41-41)
ImmichFrame.Core.Tests/Logic/Pool/CachingApiAssetsPoolTests.cs (1)
  • Task (32-35)
ImmichFrame.Core/Logic/Pool/AllAssetsPool.cs (3)
  • Task (8-13)
  • Task (15-61)
  • Task (64-76)
🪛 GitHub Check: test
ImmichFrame.Core/Logic/Pool/PeopleAssetsPool.cs

[warning] 6-6:
Parameter 'IAccountSettings accountSettings' is captured into the state of the enclosing type and its value is also passed to the base constructor. The value might be captured by the base class as well.

ImmichFrame.Core/Logic/Pool/AlbumAssetsPool.cs

[warning] 7-7:
Parameter 'IAccountSettings accountSettings' is captured into the state of the enclosing type and its value is also passed to the base constructor. The value might be captured by the base class as well.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (csharp)
🔇 Additional comments (16)
ImmichFrame.WebApi.Tests/Controllers/AssetControllerTests.cs (2)

7-7: LGTM! Required for test infrastructure.

The new using directives support HttpClientFactoryOptions configuration (line 36) and interface registrations (lines 83-84).

Also applies to: 12-12


161-175: LGTM! Expanded coverage for asset view requests.

Correctly mocks both /assets/ and /thumbnail endpoints to cover different asset retrieval paths used by LogicPoolAdapter.

ImmichFrame.Core/Logic/Pool/PeopleAssetsPool.cs (1)

6-6: Static analysis warning about parameter capture.

The static analysis tool flags that accountSettings is captured both by the class and passed to the base constructor, which may lead to redundant state storage. This is a design choice but could indicate unnecessary duplication.

However, examining the base class CachingApiAssetsPool, it appears the parameter is needed by the base class for filtering logic, so this warning can be safely ignored as a false positive.

ImmichFrame.Core.Tests/Logic/Pool/FavoriteAssetsPoolTests.cs (1)

27-27: LGTM!

The return type change from IEnumerable<AssetResponseDto> to IList<AssetResponseDto> is consistent with the broader pool API refactoring. Tests correctly validate the new interface.

ImmichFrame.Core.Tests/Logic/Pool/PersonAssetsPoolTests.cs (1)

27-27: LGTM!

The return type change aligns with the pool API refactoring. The test coverage is comprehensive and correctly validates pagination behavior.

ImmichFrame.Core.Tests/Logic/Pool/AlbumAssetsPoolTests.cs (1)

26-26: LGTM!

The return type change is consistent with the pool API refactoring. Tests properly validate the album inclusion/exclusion logic.

ImmichFrame.Core/Logic/PoolConfiguration.cs (1)

8-21: LGTM!

The PoolConfiguration class provides a clean abstraction for wiring API access and caching. The use of read-only properties ensures immutability after construction, which is a good design choice.

ImmichFrame.Core/Logic/Pool/AlbumAssetsPool.cs (1)

7-28: LGTM!

The changes align with the pool API refactoring:

  • Base constructor simplified by removing immichApi parameter
  • Return type changed to IList<AssetResponseDto> for consistency
  • Explicit ToList() materializes the filtered result

The static analysis warning about accountSettings parameter capture is a false positive, as the parameter is correctly used by both the derived class and the base class.

ImmichFrame.Core/Logic/Pool/MemoryAssetsPool.cs (1)

8-36: Verify DailyApiCache expiration behavior.

The MemoryAssetsPool uses a DailyApiCache that expires at midnight. Ensure this behavior aligns with the expectation that memories should refresh daily (when "Today's memories" change at midnight).

If memories should update more frequently (e.g., hourly), the cache expiration may need adjustment.

ImmichFrame.WebApi.Tests/Logic/MemoryAssetsPoolTests.cs (2)

191-244: Excellent test coverage for chronological sorting!

The new tests comprehensively verify that memories are returned in chronological order:

  • LoadAssets_ReturnsAssetsInChronologicalMemoryOrder verifies sorting with reversed input
  • LoadAssetsFromApi_ReturnsAssetsInChronologicalMemoryOrder uses [Repeat(10)] to guard against flakiness from random ordering
  • LoadAssetsFromApi_ReturnsAssetsInChronologicalMemoryOrderOverMultipleCalls verifies sorting is preserved across multiple GetNextAsset calls

These tests directly validate the fix for issue #163.


32-49: Improved test helper signature.

The change from CreateSampleAssets(int count, bool withExif, int yearCreated) to CreateSampleAssets(int count, bool withExif, DateTime created) is more precise and makes the test setup clearer. Using DateTime directly eliminates the need to derive dates from year integers.

ImmichFrame.Core/Logic/LogicPoolAdapter.cs (2)

8-9: LGTM! Clean adapter implementation.

The primary constructor syntax and delegation pattern effectively compose the pool and API layers.


10-14: LGTM! Asset retrieval methods delegate correctly.

Both methods properly delegate to the pool, with GetNextAsset correctly using FirstOrDefault() to handle empty results.

ImmichFrame.Core/Logic/Pool/CachingApiAssetsPool.cs (2)

6-11: LGTM! Constructor signature correctly updated.

The removal of the ImmichApi parameter aligns with the refactoring to use PoolConfiguration, and the deterministic iteration via _next supports the PR's objective to ensure consistent sequential access.


15-39: Ensure wrap-around and empty-list cases are tested

  • Add or confirm a test where _next >= all.Count (e.g. exhaust the pool with sequential GetAssets calls, then request again to force wrap-around)
  • Add or confirm a test where AllAssets returns an empty list for a non-zero requested (should return an empty result)
ImmichFrame.Core/Logic/PooledImmichFrameLogic.cs (1)

13-18: LGTM! Constructor properly uses PoolConfiguration.

The past review comments flagged duplicate pool/cache initialization, but the current implementation correctly uses PoolConfiguration to supply both the pool (via BuildPool) and API instances, eliminating the redundancy. The base class initialization properly delegates to LogicPoolAdapter.

{
private readonly Random _random = new();
private int _next; //next asset to return
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Thread safety: Concurrent GetAssets calls will corrupt _next.

The _next field is mutated without synchronization (lines 31, 37), so concurrent calls to GetAssets will race and produce incorrect results or skip assets. If this pool is accessed from multiple threads (e.g., concurrent requests in a web app), the iteration state will be corrupted.

Apply thread-local state or locking:

+    private readonly object _lock = new();
     private int _next; //next asset to return
 
     public async Task<IEnumerable<AssetResponseDto>> GetAssets(int requested, CancellationToken ct = default)
     {
+        lock (_lock)
+        {
             if (requested == 0)
             {
                 return new List<AssetResponseDto>();
             }
             
             var all = await AllAssets(ct);
 
             if (all.Count < requested)
             {
                 requested = all.Count; //limit request to what we have
             }
             
             var tail = all.TakeLast(all.Count - _next).ToList();
             
             if (tail.Count >= requested)
             {
                 _next += requested;
                 return tail.Take(requested);
             }
 
             // not enough left in tail; need to read head too
             var overrun = requested - tail.Count;
             _next = overrun;
             return tail.Concat(all.Take(overrun));
+        }
     }

Note: If AllAssets performs async I/O, consider using SemaphoreSlim instead of lock to avoid blocking threads.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In ImmichFrame.Core/Logic/Pool/CachingApiAssetsPool.cs around line 8 (field
_next) and the mutations at lines ~31 and ~37, the mutable field _next is
modified without synchronization causing races when GetAssets is called
concurrently; fix by protecting access to _next with a thread-safe strategy:
either make _next thread-local (e.g., use ThreadLocal<int> or maintain a
per-call index) or serialize updates with a lock (create a private readonly
object _lock and read/update _next inside lock blocks), and if AllAssets or
surrounding logic is asynchronous avoid blocking locks and use an async-aware
semaphore (SemaphoreSlim.WaitAsync/Release) to guard the critical section;
ensure all reads and writes of _next use the chosen synchronization so
concurrent callers cannot corrupt iteration state.


var memoryAssets = new List<AssetResponseDto>();
foreach (var memory in memories)
foreach (var memory in memories.OrderBy(m => m.MemoryAt))
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Chronological sorting implemented correctly.

The addition of .OrderBy(m => m.MemoryAt) ensures memories are processed in chronological order (oldest to newest), which addresses the core requirement of issue #163. This will display memories as "1 year ago", "2 years ago", etc., in the correct sequence.

However, there's a critical concern raised in the PR comments: JoeRu noted that sorting may be undone by random/shuffle operations in TotalAccountImagesSelectionStrategy.cs and MultiImmichFrameLogicDelegate.cs. These randomizations could override the sorting done here, making this fix ineffective.

Run the following script to identify potential shuffle operations that might override this sorting:


🏁 Script executed:

#!/bin/bash
# Search for random/shuffle operations in the codebase that might affect asset order

echo "=== Checking TotalAccountImagesSelectionStrategy.cs ==="
rg -nC3 -g 'TotalAccountImagesSelectionStrategy.cs' '\b(Random|Shuffle|OrderBy.*Guid\.NewGuid)\b'

echo "=== Checking MultiImmichFrameLogicDelegate.cs ==="
rg -nC3 -g 'MultiImmichFrameLogicDelegate.cs' '\b(Random|Shuffle|OrderBy.*Guid\.NewGuid)\b'

echo "=== Checking for any shuffle operations in Logic/AccountSelection ==="
rg -nC3 -g '*.cs' --type-add 'cs:*.cs' 'Logic/AccountSelection' -e '\b(Random|Shuffle|OrderBy.*Guid\.NewGuid)\b'

echo "=== Checking for any shuffle operations in PooledImmichFrameLogic ==="
rg -nC3 -g 'PooledImmichFrameLogic.cs' '\b(Random|Shuffle|OrderBy.*Guid\.NewGuid)\b'

Length of output: 1953


Sorting is negated by downstream shuffle operations.

  • TotalAccountImagesSelectionStrategy.cs (line 63): assets.Shuffle() overrides the chronological sort.
  • MultiImmichFrameLogicDelegate.cs (line 34): .Shuffle() on the assembled assets also discards prior ordering.

Reapply OrderBy(m => m.MemoryAt) after the final shuffle or relocate/remove these shuffles to ensure memories remain in chronological order.

🤖 Prompt for AI Agents
In ImmichFrame.Core/Logic/Pool/MemoryAssetsPool.cs around line 15, the foreach
over memories.OrderBy(m => m.MemoryAt) is being negated by downstream Shuffle()
calls (TotalAccountImagesSelectionStrategy.cs line 63 and
MultiImmichFrameLogicDelegate.cs line 34); to fix, ensure chronological ordering
is preserved by either removing those downstream Shuffle() calls or reapplying
OrderBy(m => m.MemoryAt) after the final Shuffle (i.e., in the final asset
assembly/return path), so the resulting collection is explicitly sorted by
MemoryAt before being consumed or returned.

Comment on lines +20 to 42
private static IAssetPool BuildPool(IAccountSettings accountSettings, PoolConfiguration poolConfiguration)
{
if (!accountSettings.ShowFavorites && !accountSettings.ShowMemories && !accountSettings.Albums.Any() && !accountSettings.People.Any())
{
return new AllAssetsPool(_apiCache, _immichApi, accountSettings);
return new AllAssetsPool(poolConfiguration.ApiCache, poolConfiguration.ImmichApi, accountSettings);
}

var pools = new List<IAssetPool>();

if (accountSettings.ShowFavorites)
pools.Add(new FavoriteAssetsPool(_apiCache, _immichApi, accountSettings));
pools.Add(new FavoriteAssetsPool(poolConfiguration.ApiCache, poolConfiguration.ImmichApi, accountSettings));

if (accountSettings.ShowMemories)
pools.Add(new MemoryAssetsPool(_immichApi, accountSettings));
pools.Add(new MemoryAssetsPool(poolConfiguration.ImmichApi, accountSettings));

if (accountSettings.Albums.Any())
pools.Add(new AlbumAssetsPool(_apiCache, _immichApi, accountSettings));
pools.Add(new AlbumAssetsPool(poolConfiguration.ApiCache, poolConfiguration.ImmichApi, accountSettings));

if (accountSettings.People.Any())
pools.Add(new PersonAssetsPool(_apiCache, _immichApi, accountSettings));
pools.Add(new PersonAssetsPool(poolConfiguration.ApiCache, poolConfiguration.ImmichApi, accountSettings));

return new MultiAssetPool(pools);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Verify that downstream randomization doesn't undo chronological sorting.

The BuildPool logic correctly uses PoolConfiguration to create pools, including MemoryAssetsPool for chronological memories. However, as noted in the PR comments, JoeRu warned that sorting may be undone by random/shuffle operations in TotalAccountImagesSelectionStrategy.cs and MultiImmichFrameLogicDelegate.cs. These randomizations could override the chronological order established by MemoryAssetsPool.

Run the following script to check for randomization operations that might affect the order:


🏁 Script executed:

#!/bin/bash
# Description: Find shuffle/randomization logic that might override chronological order

# Search for Random usage in selection strategies
rg -n -A10 -B5 'Random|Shuffle|OrderBy.*Guid\.NewGuid' \
  --glob='**/TotalAccountImagesSelectionStrategy.cs' \
  --glob='**/MultiImmichFrameLogicDelegate.cs'

# Look for places where assets are reordered after pool creation
ast-grep --pattern $'$ASSETS.OrderBy($$$)'
ast-grep --pattern $'$ASSETS.Shuffle($$$)'

Length of output: 4581


Preserve chronological order after pooling
MemoryAssetsPool already sorts by MemoryAt, but its output is later randomized by:

  • TotalAccountImagesSelectionStrategy.cs:63 (assets.Shuffle())
  • MultiImmichFrameLogicDelegate.cs:34 (GetAssets().Shuffle())
    Move shuffles before sorting or reapply the chronological sort (or skip shuffle for memories) to maintain the intended order.
🤖 Prompt for AI Agents
In ImmichFrame.Core/Logic/PooledImmichFrameLogic.cs around lines 20 to 42, the
logic builds a MultiAssetPool which ends up having assets shuffled elsewhere and
thereby breaks MemoryAssetsPool's chronological sorting by MemoryAt; update the
pooling behavior so the chronological order for memories is preserved by either
(A) preventing shuffles from being applied after pooling for memory-origin
assets (e.g., tag or flag memory assets and skip Shuffle in
TotalAccountImagesSelectionStrategy.cs:63 and
MultiImmichFrameLogicDelegate.cs:34 when assets include memory-origin items), or
(B) reapply sorting by MemoryAt immediately after any shuffle calls (or before
returning from MultiAssetPool) so memories are restored to chronological order;
implement the chosen approach consistently across the shuffle sites so
MemoryAssetsPool output remains chronologically ordered.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Display Memories in correct order

4 participants