feat: wire up album filtering to photo queue
Album selection now controls which photos are displayed.
Uses POST /api/search/random with albumIds filter (Immich v3
compatible) instead of the removed GET /api/albums/{id} assets.
- Add albumIds parameter to fetchRandomAssets/fetchRandomAssetIds
- syncFromAlbums: fetch random assets filtered by selected albums
- syncFromAllPhotos: preserve existing global random behavior
- Album mode reshuffles on exhaustion instead of re-fetching
- 24h resync checks album assetCounts for changes before re-fetch
- requestSync + albums_changed action triggers queue rebuild on save
- timerWakeCycle respects album selection
- Discard stale pre-renders on album change
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -87,14 +87,18 @@ std::vector<String> ImmichClient::fetchAlbumAssetIds(const String& albumId) {
|
|||||||
return ids;
|
return ids;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<String> ImmichClient::fetchRandomAssetIds(int count) {
|
std::vector<String> ImmichClient::fetchRandomAssetIds(int count,
|
||||||
|
const std::vector<String>& albumIds) {
|
||||||
std::vector<String> ids;
|
std::vector<String> ids;
|
||||||
String url = buildUrl("/api/search/random");
|
String url = buildUrl("/api/search/random");
|
||||||
|
|
||||||
// Build POST body — filter to images only (excludes videos, audio)
|
|
||||||
JsonDocument reqDoc;
|
JsonDocument reqDoc;
|
||||||
reqDoc["size"] = count;
|
reqDoc["size"] = count;
|
||||||
reqDoc["type"] = "IMAGE";
|
reqDoc["type"] = "IMAGE";
|
||||||
|
if (!albumIds.empty()) {
|
||||||
|
JsonArray arr = reqDoc["albumIds"].to<JsonArray>();
|
||||||
|
for (auto& id : albumIds) arr.add(id);
|
||||||
|
}
|
||||||
String body;
|
String body;
|
||||||
serializeJson(reqDoc, body);
|
serializeJson(reqDoc, body);
|
||||||
|
|
||||||
@@ -118,7 +122,6 @@ std::vector<String> ImmichClient::fetchRandomAssetIds(int count) {
|
|||||||
String id = asset["id"].as<String>();
|
String id = asset["id"].as<String>();
|
||||||
if (id.length() == 0) continue;
|
if (id.length() == 0) continue;
|
||||||
|
|
||||||
// Filter out RAW/DNG files — their previews work but we prefer actual photos
|
|
||||||
String filename = asset["originalFileName"] | "";
|
String filename = asset["originalFileName"] | "";
|
||||||
filename.toLowerCase();
|
filename.toLowerCase();
|
||||||
if (filename.endsWith(".dng") || filename.endsWith(".raw") ||
|
if (filename.endsWith(".dng") || filename.endsWith(".raw") ||
|
||||||
@@ -133,18 +136,23 @@ std::vector<String> ImmichClient::fetchRandomAssetIds(int count) {
|
|||||||
ids.push_back(id);
|
ids.push_back(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[immich] Fetched %d random assets (skipped %d RAW)\n",
|
Serial.printf("[immich] Fetched %d random assets%s (skipped %d RAW)\n",
|
||||||
ids.size(), skippedRaw);
|
ids.size(), albumIds.empty() ? "" : " from albums", skippedRaw);
|
||||||
return ids;
|
return ids;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<RandomAsset> ImmichClient::fetchRandomAssets(int count) {
|
std::vector<RandomAsset> ImmichClient::fetchRandomAssets(int count,
|
||||||
|
const std::vector<String>& albumIds) {
|
||||||
std::vector<RandomAsset> assets;
|
std::vector<RandomAsset> assets;
|
||||||
String url = buildUrl("/api/search/random");
|
String url = buildUrl("/api/search/random");
|
||||||
|
|
||||||
JsonDocument reqDoc;
|
JsonDocument reqDoc;
|
||||||
reqDoc["size"] = count;
|
reqDoc["size"] = count;
|
||||||
reqDoc["type"] = "IMAGE";
|
reqDoc["type"] = "IMAGE";
|
||||||
|
if (!albumIds.empty()) {
|
||||||
|
JsonArray albumArr = reqDoc["albumIds"].to<JsonArray>();
|
||||||
|
for (auto& aid : albumIds) albumArr.add(aid);
|
||||||
|
}
|
||||||
String body;
|
String body;
|
||||||
serializeJson(reqDoc, body);
|
serializeJson(reqDoc, body);
|
||||||
|
|
||||||
@@ -178,8 +186,8 @@ std::vector<RandomAsset> ImmichClient::fetchRandomAssets(int count) {
|
|||||||
assets.push_back(ra);
|
assets.push_back(ra);
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[immich] Fetched %d random assets with dates (skipped %d RAW)\n",
|
Serial.printf("[immich] Fetched %d random assets with dates%s (skipped %d RAW)\n",
|
||||||
assets.size(), skippedRaw);
|
assets.size(), albumIds.empty() ? "" : " from albums", skippedRaw);
|
||||||
return assets;
|
return assets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,8 +31,10 @@ public:
|
|||||||
void begin(const String& baseUrl, const String& apiKey);
|
void begin(const String& baseUrl, const String& apiKey);
|
||||||
std::vector<AlbumInfo> fetchAlbums();
|
std::vector<AlbumInfo> fetchAlbums();
|
||||||
std::vector<String> fetchAlbumAssetIds(const String& albumId);
|
std::vector<String> fetchAlbumAssetIds(const String& albumId);
|
||||||
std::vector<String> fetchRandomAssetIds(int count = 50);
|
std::vector<String> fetchRandomAssetIds(int count = 50,
|
||||||
std::vector<RandomAsset> fetchRandomAssets(int count = 50);
|
const std::vector<String>& albumIds = {});
|
||||||
|
std::vector<RandomAsset> fetchRandomAssets(int count = 50,
|
||||||
|
const std::vector<String>& albumIds = {});
|
||||||
std::vector<String> fetchFavoriteAssetIds();
|
std::vector<String> fetchFavoriteAssetIds();
|
||||||
AssetInfo fetchAssetInfo(const String& assetId);
|
AssetInfo fetchAssetInfo(const String& assetId);
|
||||||
bool downloadAsset(const String& assetId, ImageQuality quality,
|
bool downloadAsset(const String& assetId, ImageQuality quality,
|
||||||
|
|||||||
25
src/main.cpp
25
src/main.cpp
@@ -1,5 +1,6 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <M5Unified.h>
|
#include <M5Unified.h>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
#include <esp_heap_caps.h>
|
#include <esp_heap_caps.h>
|
||||||
#include <freertos/FreeRTOS.h>
|
#include <freertos/FreeRTOS.h>
|
||||||
#include <freertos/task.h>
|
#include <freertos/task.h>
|
||||||
@@ -238,9 +239,10 @@ void displayTask(void* param) {
|
|||||||
xSemaphoreGive(stateMutex);
|
xSemaphoreGive(stateMutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Periodic re-sync
|
// Periodic re-sync (includes album change requests)
|
||||||
if (photoQueue.needsResync() && wifiManager.isConnected()) {
|
if (photoQueue.needsResync() && wifiManager.isConnected()) {
|
||||||
photoQueue.sync();
|
photoQueue.sync();
|
||||||
|
discardPending(pending);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Phase 1: Display ---
|
// --- Phase 1: Display ---
|
||||||
@@ -343,6 +345,9 @@ void webActionCallback(const String& action) {
|
|||||||
slideshowPlaying = false;
|
slideshowPlaying = false;
|
||||||
} else if (action == "play") {
|
} else if (action == "play") {
|
||||||
slideshowPlaying = true;
|
slideshowPlaying = true;
|
||||||
|
} else if (action == "albums_changed") {
|
||||||
|
photoQueue.requestSync();
|
||||||
|
refreshRequested = true;
|
||||||
}
|
}
|
||||||
xSemaphoreGive(stateMutex);
|
xSemaphoreGive(stateMutex);
|
||||||
}
|
}
|
||||||
@@ -368,11 +373,25 @@ void timerWakeCycle() {
|
|||||||
imagePipeline.setBlueNoiseEnabled(s.dither_noise != 0);
|
imagePipeline.setBlueNoiseEnabled(s.dither_noise != 0);
|
||||||
displayManager.setTranslucent(s.meta_translucent != 0);
|
displayManager.setTranslucent(s.meta_translucent != 0);
|
||||||
|
|
||||||
// Fetch a small batch of random assets
|
// Fetch assets — respect album selection
|
||||||
static constexpr int TIMER_WAKE_RETRIES = 5;
|
static constexpr int TIMER_WAKE_RETRIES = 5;
|
||||||
bool displayed = false;
|
bool displayed = false;
|
||||||
|
|
||||||
std::vector<String> ids = immichClient.fetchRandomAssetIds(10);
|
// Parse selected album IDs from settings
|
||||||
|
std::vector<String> albumIds;
|
||||||
|
{
|
||||||
|
JsonDocument doc;
|
||||||
|
DeserializationError err = deserializeJson(doc, s.albums_json);
|
||||||
|
if (!err) {
|
||||||
|
JsonArray arr = doc.as<JsonArray>();
|
||||||
|
for (JsonVariant v : arr) {
|
||||||
|
albumIds.push_back(v.as<String>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<String> ids = immichClient.fetchRandomAssetIds(10, albumIds);
|
||||||
|
|
||||||
for (int i = 0; i < (int)ids.size() && i < TIMER_WAKE_RETRIES && !displayed; i++) {
|
for (int i = 0; i < (int)ids.size() && i < TIMER_WAKE_RETRIES && !displayed; i++) {
|
||||||
AssetInfo info = immichClient.fetchAssetInfo(ids[i]);
|
AssetInfo info = immichClient.fetchAssetInfo(ids[i]);
|
||||||
|
|
||||||
|
|||||||
@@ -16,11 +16,79 @@ bool PhotoQueue::sync() {
|
|||||||
if (_client == nullptr || _settings == nullptr) return false;
|
if (_client == nullptr || _settings == nullptr) return false;
|
||||||
|
|
||||||
_lastSyncAttempt = millis();
|
_lastSyncAttempt = millis();
|
||||||
|
_syncRequested = false;
|
||||||
|
|
||||||
Settings s = _settings->get();
|
Settings s = _settings->get();
|
||||||
|
auto albumIds = getSelectedAlbumIds();
|
||||||
|
|
||||||
|
bool success;
|
||||||
|
if (!albumIds.empty()) {
|
||||||
|
success = syncFromAlbums(albumIds, s);
|
||||||
|
} else {
|
||||||
|
success = syncFromAllPhotos(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
_syncRetryDelay = min(_syncRetryDelay * 2, SYNC_RETRY_MAX);
|
||||||
|
Serial.printf("[queue] Sync failed, retry in %lus\n", _syncRetryDelay / 1000);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_cursor = 0;
|
||||||
|
_lastSyncTime = millis();
|
||||||
|
_syncRetryDelay = SYNC_RETRY_MIN;
|
||||||
|
Serial.printf("[queue] Synced: %d assets, album_mode=%d, cursor at %d\n",
|
||||||
|
_queue.size(), _albumMode, _cursor);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PhotoQueue::syncFromAlbums(const std::vector<String>& albumIds, const Settings& s) {
|
||||||
|
Serial.printf("[queue] Syncing from %d selected album(s)\n", albumIds.size());
|
||||||
|
|
||||||
|
// Get current album counts for 24h resync change detection
|
||||||
|
auto albums = _client->fetchAlbums();
|
||||||
|
std::map<String, int> albumCounts;
|
||||||
|
for (auto& selId : albumIds) {
|
||||||
|
for (auto& album : albums) {
|
||||||
|
if (album.id == selId) {
|
||||||
|
albumCounts[selId] = album.assetCount;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use POST /api/search/random with albumIds filter (Immich v3 compatible).
|
||||||
|
// The server returns up to `size` random assets from the specified albums.
|
||||||
|
// For small albums (< TARGET_QUEUE_SIZE), all assets are returned.
|
||||||
|
auto batch = _client->fetchRandomAssets(
|
||||||
|
static_cast<int>(TARGET_QUEUE_SIZE), albumIds);
|
||||||
|
|
||||||
|
if (batch.empty()) {
|
||||||
|
Serial.println("[queue] No assets found in selected albums");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<String> newIds;
|
||||||
|
std::vector<String> newDates;
|
||||||
|
for (auto& asset : batch) {
|
||||||
|
if (std::find(newIds.begin(), newIds.end(), asset.id) != newIds.end()) continue;
|
||||||
|
newIds.push_back(asset.id);
|
||||||
|
newDates.push_back(asset.dateTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.printf("[queue] Album sync: %d assets from server\n", newIds.size());
|
||||||
|
|
||||||
|
_queue = newIds;
|
||||||
|
_albumMode = true;
|
||||||
|
_lastAlbumCounts = albumCounts;
|
||||||
|
|
||||||
|
applyCycleMode(s, newDates);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PhotoQueue::syncFromAllPhotos(const Settings& s) {
|
||||||
|
Serial.printf("[queue] Syncing from all photos (global random)\n");
|
||||||
|
|
||||||
// Fetch in batches until we have TARGET_QUEUE_SIZE usable, non-duplicate IDs.
|
|
||||||
// Use fetchRandomAssets() to capture dates alongside IDs.
|
|
||||||
std::vector<String> newIds;
|
std::vector<String> newIds;
|
||||||
std::vector<String> newDates;
|
std::vector<String> newDates;
|
||||||
int rounds = 0;
|
int rounds = 0;
|
||||||
@@ -37,10 +105,8 @@ bool PhotoQueue::sync() {
|
|||||||
|
|
||||||
for (auto& asset : batch) {
|
for (auto& asset : batch) {
|
||||||
if (newIds.size() >= TARGET_QUEUE_SIZE) break;
|
if (newIds.size() >= TARGET_QUEUE_SIZE) break;
|
||||||
|
|
||||||
if (std::find(newIds.begin(), newIds.end(), asset.id) != newIds.end()) continue;
|
if (std::find(newIds.begin(), newIds.end(), asset.id) != newIds.end()) continue;
|
||||||
if (std::find(_shown.begin(), _shown.end(), asset.id) != _shown.end()) continue;
|
if (std::find(_shown.begin(), _shown.end(), asset.id) != _shown.end()) continue;
|
||||||
|
|
||||||
newIds.push_back(asset.id);
|
newIds.push_back(asset.id);
|
||||||
newDates.push_back(asset.dateTime);
|
newDates.push_back(asset.dateTime);
|
||||||
}
|
}
|
||||||
@@ -50,59 +116,68 @@ bool PhotoQueue::sync() {
|
|||||||
rounds, newIds.size(), TARGET_QUEUE_SIZE);
|
rounds, newIds.size(), TARGET_QUEUE_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newIds.empty()) {
|
if (newIds.empty()) return false;
|
||||||
_syncRetryDelay = min(_syncRetryDelay * 2, SYNC_RETRY_MAX);
|
|
||||||
Serial.printf("[queue] Sync failed after %d rounds, retry in %lus\n",
|
|
||||||
rounds, _syncRetryDelay / 1000);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_queue = newIds;
|
_queue = newIds;
|
||||||
|
_albumMode = false;
|
||||||
|
_lastAlbumCounts.clear();
|
||||||
|
|
||||||
// Apply cycling mode
|
applyCycleMode(s, newDates);
|
||||||
|
Serial.printf("[queue] All-photos sync: %d assets in %d rounds\n",
|
||||||
|
_queue.size(), rounds);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhotoQueue::applyCycleMode(const Settings& s, const std::vector<String>& dates) {
|
||||||
switch (s.cycle_mode) {
|
switch (s.cycle_mode) {
|
||||||
case CycleMode::Random:
|
case CycleMode::Random:
|
||||||
applyFavoritesWeighting();
|
applyFavoritesWeighting();
|
||||||
shuffle();
|
shuffle();
|
||||||
break;
|
break;
|
||||||
case CycleMode::Chronological:
|
case CycleMode::Chronological:
|
||||||
sortChronological(newDates, false);
|
sortChronological(dates, false);
|
||||||
break;
|
break;
|
||||||
case CycleMode::ReverseChronological:
|
case CycleMode::ReverseChronological:
|
||||||
sortChronological(newDates, true);
|
sortChronological(dates, true);
|
||||||
break;
|
break;
|
||||||
case CycleMode::FavoritesWeighted:
|
case CycleMode::FavoritesWeighted:
|
||||||
applyFavoritesWeighting();
|
applyFavoritesWeighting();
|
||||||
shuffle();
|
shuffle();
|
||||||
break;
|
break;
|
||||||
case CycleMode::WeightedChronological:
|
case CycleMode::WeightedChronological:
|
||||||
applyRecencyWeighting(newDates, true);
|
applyRecencyWeighting(dates, true);
|
||||||
shuffle();
|
shuffle();
|
||||||
break;
|
break;
|
||||||
case CycleMode::WeightedReverseChronological:
|
case CycleMode::WeightedReverseChronological:
|
||||||
applyRecencyWeighting(newDates, false);
|
applyRecencyWeighting(dates, false);
|
||||||
shuffle();
|
shuffle();
|
||||||
break;
|
break;
|
||||||
// No default — compiler warns on unhandled CycleMode via -Wswitch
|
// No default — compiler warns on unhandled CycleMode via -Wswitch
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Reset cursor for fresh queue
|
void PhotoQueue::requestSync() {
|
||||||
_cursor = 0;
|
_syncRequested = true;
|
||||||
|
|
||||||
_lastSyncTime = millis();
|
|
||||||
_syncRetryDelay = SYNC_RETRY_MIN;
|
|
||||||
Serial.printf("[queue] Synced: %d assets in %d rounds, cursor at %d\n",
|
|
||||||
_queue.size(), rounds, _cursor);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String PhotoQueue::next() {
|
String PhotoQueue::next() {
|
||||||
// Auto-refill when queue is exhausted
|
// Auto-refill when queue is exhausted
|
||||||
if (_queue.empty() || _cursor >= _queue.size()) {
|
if (_queue.empty() || _cursor >= _queue.size()) {
|
||||||
|
if (_albumMode && !_queue.empty()) {
|
||||||
|
// Album mode: reshuffle existing queue instead of re-fetching
|
||||||
|
Serial.println("[queue] Album queue exhausted, reshuffling in place");
|
||||||
|
Settings s = _settings->get();
|
||||||
|
// Clear shown history since we're replaying the same set
|
||||||
|
_shown.clear();
|
||||||
|
applyCycleMode(s, {});
|
||||||
|
_cursor = 0;
|
||||||
|
} else {
|
||||||
|
// All-photos mode: fetch a new batch
|
||||||
Serial.println("[queue] Queue exhausted, fetching new batch");
|
Serial.println("[queue] Queue exhausted, fetching new batch");
|
||||||
sync();
|
sync();
|
||||||
_cursor = 0;
|
_cursor = 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (_queue.empty()) return "";
|
if (_queue.empty()) return "";
|
||||||
|
|
||||||
@@ -137,6 +212,8 @@ size_t PhotoQueue::size() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool PhotoQueue::needsResync() {
|
bool PhotoQueue::needsResync() {
|
||||||
|
if (_syncRequested) return true;
|
||||||
|
|
||||||
unsigned long now = millis();
|
unsigned long now = millis();
|
||||||
|
|
||||||
// If never synced successfully, use retry backoff
|
// If never synced successfully, use retry backoff
|
||||||
@@ -147,7 +224,44 @@ bool PhotoQueue::needsResync() {
|
|||||||
|
|
||||||
// Normal resync interval
|
// Normal resync interval
|
||||||
unsigned long elapsed = now - _lastSyncTime;
|
unsigned long elapsed = now - _lastSyncTime;
|
||||||
return elapsed >= (QUEUE_RESYNC_HOURS * 3600000UL);
|
if (elapsed < (QUEUE_RESYNC_HOURS * 3600000UL)) return false;
|
||||||
|
|
||||||
|
// In album mode, check if album sizes actually changed before re-fetching
|
||||||
|
if (_albumMode && !_lastAlbumCounts.empty()) {
|
||||||
|
auto albums = _client->fetchAlbums();
|
||||||
|
auto selectedIds = getSelectedAlbumIds();
|
||||||
|
bool changed = false;
|
||||||
|
|
||||||
|
for (auto& selId : selectedIds) {
|
||||||
|
int currentCount = 0;
|
||||||
|
for (auto& album : albums) {
|
||||||
|
if (album.id == selId) {
|
||||||
|
currentCount = album.assetCount;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto it = _lastAlbumCounts.find(selId);
|
||||||
|
int lastCount = (it != _lastAlbumCounts.end()) ? it->second : -1;
|
||||||
|
if (currentCount != lastCount) {
|
||||||
|
Serial.printf("[queue] Album %s count changed: %d -> %d\n",
|
||||||
|
selId.c_str(), lastCount, currentCount);
|
||||||
|
changed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!changed) {
|
||||||
|
Serial.println("[queue] Album counts unchanged, reshuffling instead of re-fetching");
|
||||||
|
Settings s = _settings->get();
|
||||||
|
_shown.clear();
|
||||||
|
applyCycleMode(s, {});
|
||||||
|
_cursor = 0;
|
||||||
|
_lastSyncTime = millis();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
String PhotoQueue::findNextPortrait() {
|
String PhotoQueue::findNextPortrait() {
|
||||||
@@ -177,6 +291,11 @@ void PhotoQueue::shuffle() {
|
|||||||
|
|
||||||
void PhotoQueue::sortChronological(const std::vector<String>& dates, bool reverse) {
|
void PhotoQueue::sortChronological(const std::vector<String>& dates, bool reverse) {
|
||||||
if (dates.size() != _queue.size()) {
|
if (dates.size() != _queue.size()) {
|
||||||
|
// Dates unavailable (e.g. reshuffle after exhaustion) — fall back to shuffle
|
||||||
|
if (dates.empty()) {
|
||||||
|
shuffle();
|
||||||
|
return;
|
||||||
|
}
|
||||||
Serial.println("[queue] sortChronological: date/queue size mismatch, skipping");
|
Serial.println("[queue] sortChronological: date/queue size mismatch, skipping");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -200,7 +319,11 @@ void PhotoQueue::sortChronological(const std::vector<String>& dates, bool revers
|
|||||||
}
|
}
|
||||||
|
|
||||||
void PhotoQueue::applyRecencyWeighting(const std::vector<String>& dates, bool favorRecent) {
|
void PhotoQueue::applyRecencyWeighting(const std::vector<String>& dates, bool favorRecent) {
|
||||||
if (dates.size() != _queue.size() || _queue.size() < 3) return;
|
if (dates.size() != _queue.size() || _queue.size() < 3) {
|
||||||
|
// Dates unavailable — fall back to shuffle
|
||||||
|
if (dates.empty()) return;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Sort queue by date (oldest first)
|
// Sort queue by date (oldest first)
|
||||||
std::vector<size_t> indices(dates.size());
|
std::vector<size_t> indices(dates.size());
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
#include <map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
|
|
||||||
@@ -10,6 +11,7 @@ class PhotoQueue {
|
|||||||
public:
|
public:
|
||||||
void begin(ImmichClient& client, SettingsManager& settings);
|
void begin(ImmichClient& client, SettingsManager& settings);
|
||||||
bool sync();
|
bool sync();
|
||||||
|
void requestSync();
|
||||||
String next();
|
String next();
|
||||||
String random();
|
String random();
|
||||||
String current();
|
String current();
|
||||||
@@ -27,6 +29,9 @@ private:
|
|||||||
unsigned long _lastSyncTime = 0;
|
unsigned long _lastSyncTime = 0;
|
||||||
unsigned long _lastSyncAttempt = 0;
|
unsigned long _lastSyncAttempt = 0;
|
||||||
unsigned long _syncRetryDelay = 10000;
|
unsigned long _syncRetryDelay = 10000;
|
||||||
|
volatile bool _syncRequested = false;
|
||||||
|
bool _albumMode = false;
|
||||||
|
std::map<String, int> _lastAlbumCounts;
|
||||||
|
|
||||||
static constexpr unsigned long SYNC_RETRY_MIN = 10000; // 10 seconds
|
static constexpr unsigned long SYNC_RETRY_MIN = 10000; // 10 seconds
|
||||||
static constexpr unsigned long SYNC_RETRY_MAX = 300000; // 5 minutes
|
static constexpr unsigned long SYNC_RETRY_MAX = 300000; // 5 minutes
|
||||||
@@ -34,6 +39,9 @@ private:
|
|||||||
static constexpr int MAX_FETCH_ROUNDS = 5;
|
static constexpr int MAX_FETCH_ROUNDS = 5;
|
||||||
static constexpr size_t MAX_SHOWN_HISTORY = 200;
|
static constexpr size_t MAX_SHOWN_HISTORY = 200;
|
||||||
|
|
||||||
|
bool syncFromAlbums(const std::vector<String>& albumIds, const Settings& s);
|
||||||
|
bool syncFromAllPhotos(const Settings& s);
|
||||||
|
void applyCycleMode(const Settings& s, const std::vector<String>& dates);
|
||||||
void shuffle();
|
void shuffle();
|
||||||
void sortChronological(const std::vector<String>& dates, bool reverse);
|
void sortChronological(const std::vector<String>& dates, bool reverse);
|
||||||
void applyRecencyWeighting(const std::vector<String>& dates, bool favorRecent);
|
void applyRecencyWeighting(const std::vector<String>& dates, bool favorRecent);
|
||||||
|
|||||||
@@ -272,6 +272,7 @@ void AppWebServer::handlePostAlbumsSelect(AsyncWebServerRequest* request,
|
|||||||
String albumsJson;
|
String albumsJson;
|
||||||
serializeJson(doc["album_ids"], albumsJson);
|
serializeJson(doc["album_ids"], albumsJson);
|
||||||
_settings->saveField("albums_json", albumsJson.c_str());
|
_settings->saveField("albums_json", albumsJson.c_str());
|
||||||
|
if (_actionCb) _actionCb("albums_changed");
|
||||||
request->send(200, "application/json", "{\"ok\":true}");
|
request->send(200, "application/json", "{\"ok\":true}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user