4 Commits

Author SHA1 Message Date
85ef89fa4b undo drone changes [ci skip]
All checks were successful
continuous-integration/drone/tag Build is passing
2026-02-02 11:06:38 +11:00
e5f5934eb6 try auto tagging again
All checks were successful
continuous-integration/drone/push Build is passing
2026-02-02 10:59:11 +11:00
80286da166 drone fix
Some checks failed
continuous-integration/drone/push Build is failing
2026-02-02 10:52:21 +11:00
6f2b8fe90c immich fixes
Some checks failed
continuous-integration/drone/push Build is failing
2026-02-02 10:47:08 +11:00
2 changed files with 111 additions and 18 deletions

8
CHANGELOG.md Normal file
View File

@@ -0,0 +1,8 @@
# Changelog
# [Unreleased]
- Nothing yet.
# [0.0.9] - 2026-02-01
- Fix sidecar handling

View File

@@ -148,7 +148,7 @@ QVector<ImmichAsset> ImmichClient::fetchAssets()
}
if (!config.userId.empty() && config.albumIds.empty() && config.personIds.empty())
return fetchAssetsByUser();
return fetchAssetsBySearch();
if (!config.userId.empty() && (!config.albumIds.empty() || !config.personIds.empty()))
{
@@ -166,20 +166,24 @@ QVector<ImmichAsset> ImmichClient::fetchAssetsBySearch()
int maxAssets = config.maxAssets;
bool triedZero = false;
int page = 1;
QString userFilterKey;
QByteArray firstResponse;
QJsonArray items;
int total = 0;
if (ShouldLog())
{
Log("Immich search: size=", config.size, ", order=", config.order,
", pageSize=", pageSize, ", maxAssets=", maxAssets,
", userId=", config.userId,
", albumIds=", config.albumIds.size(),
", personIds=", config.personIds.size(),
", allowedExtensions=", config.allowedExtensions.size());
}
while (true)
{
auto fetchPage = [&](int pageIndex, const QString &userKey) -> QByteArray {
QJsonObject body;
body["page"] = page;
body["page"] = pageIndex;
body["size"] = pageSize;
body["type"] = "IMAGE";
body["order"] = QString::fromStdString(config.order);
@@ -199,19 +203,69 @@ QVector<ImmichAsset> ImmichClient::fetchAssetsBySearch()
ids.append(QString::fromStdString(id));
body["personIds"] = ids;
}
if (!config.userId.empty() && !userKey.isEmpty())
{
body[userKey] = QString::fromStdString(config.userId);
}
return postJson(apiUrl("/search/metadata"), body, nullptr, kMetadataTimeoutMs);
};
QByteArray response = postJson(apiUrl("/search/metadata"), body, nullptr, kMetadataTimeoutMs);
if (response.isEmpty())
break;
auto parseSearch = [&](const QByteArray &response, QJsonArray &outItems, int &outTotal) -> bool {
QJsonDocument doc = QJsonDocument::fromJson(response);
if (!doc.isObject())
break;
return false;
QJsonObject root = doc.object();
if (root.contains("error") || root.contains("statusCode"))
return false;
if (!root.contains("assets"))
return false;
QJsonObject assetsObj = root["assets"].toObject();
QJsonArray items = assetsObj["items"].toArray();
int total = assetsObj["total"].toInt();
outItems = assetsObj["items"].toArray();
outTotal = assetsObj["total"].toInt();
return true;
};
QStringList userKeyCandidates;
if (!config.userId.empty())
userKeyCandidates << "ownerId" << "userId";
userKeyCandidates << "";
bool firstResponseReady = false;
for (const auto &candidate : userKeyCandidates)
{
QByteArray response = fetchPage(page, candidate);
if (response.isEmpty())
continue;
if (!parseSearch(response, items, total))
continue;
userFilterKey = candidate;
firstResponse = response;
firstResponseReady = true;
if (!config.userId.empty() && userFilterKey.isEmpty())
{
Log("Immich search user filter not accepted by server; falling back to search without userId.");
}
break;
}
if (!firstResponseReady)
return assets;
while (true)
{
if (firstResponseReady)
{
firstResponseReady = false;
}
else
{
QByteArray response = fetchPage(page, userFilterKey);
if (response.isEmpty())
break;
if (!parseSearch(response, items, total))
break;
}
Log("Immich page ", page, ": ", items.size(), " assets (total ", total, ")");
if (items.isEmpty())
{
@@ -259,6 +313,9 @@ QVector<ImmichAsset> ImmichClient::fetchAssetsByUser()
int pageSize = config.pageSize > 0 ? config.pageSize : 200;
int maxAssets = config.maxAssets;
int skip = 0;
QString endpointPath = "/assets";
QByteArray initialResponse;
bool endpointResolved = false;
if (ShouldLog())
{
@@ -268,24 +325,52 @@ QVector<ImmichAsset> ImmichClient::fetchAssetsByUser()
", includeArchived=", config.includeArchived);
}
while (true)
{
QUrl url = apiUrl("/assets");
auto fetchPage = [&](const QString &path, int offset) -> QByteArray {
QUrl url = apiUrl(path);
QUrlQuery query;
query.addQueryItem("take", QString::number(pageSize));
query.addQueryItem("skip", QString::number(skip));
query.addQueryItem("skip", QString::number(offset));
query.addQueryItem("userId", QString::fromStdString(config.userId));
if (!config.includeArchived)
query.addQueryItem("isArchived", "false");
url.setQuery(query);
return getBytes(url, nullptr, kMetadataTimeoutMs);
};
QByteArray response = getBytes(url, nullptr, kMetadataTimeoutMs);
initialResponse = fetchPage(endpointPath, skip);
if (initialResponse.isEmpty())
{
endpointPath = "/asset";
initialResponse = fetchPage(endpointPath, skip);
}
if (initialResponse.isEmpty())
{
Log("Immich user assets endpoint not available; falling back to metadata search (userId filter may be ignored).");
return fetchAssetsBySearch();
}
endpointResolved = true;
while (true)
{
QByteArray response;
if (endpointResolved)
{
response = initialResponse;
endpointResolved = false;
}
else
{
response = fetchPage(endpointPath, skip);
}
if (response.isEmpty())
break;
QJsonDocument doc = QJsonDocument::fromJson(response);
if (!doc.isArray())
break;
{
Log("Immich user assets response was not an array; falling back to metadata search.");
return fetchAssetsBySearch();
}
QJsonArray items = doc.array();
Log("Immich user assets skip ", skip, ": ", items.size(), " assets");