This commit is contained in:
87
.drone.yml
87
.drone.yml
@@ -3,18 +3,48 @@ type: docker
|
|||||||
name: build
|
name: build
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: compute-release-version
|
||||||
|
image: alpine:3.19
|
||||||
|
environment:
|
||||||
|
GITEA_TOKEN:
|
||||||
|
from_secret: GITEA_TOKEN
|
||||||
|
GITEA_BASE_URL: https://git.coadcorp.com
|
||||||
|
commands:
|
||||||
|
- apk add --no-cache curl jq
|
||||||
|
- |
|
||||||
|
set -euo pipefail
|
||||||
|
repo_owner="${DRONE_REPO_OWNER}"
|
||||||
|
repo_name="${DRONE_REPO_NAME}"
|
||||||
|
releases_url="${GITEA_BASE_URL}/api/v1/repos/${repo_owner}/${repo_name}/releases?limit=1"
|
||||||
|
latest_tag="$(curl -sf -H "Authorization: token ${GITEA_TOKEN}" "${releases_url}" | jq -r '.[0].tag_name // empty')"
|
||||||
|
if [ -z "${latest_tag}" ] || [ "${latest_tag}" = "null" ]; then
|
||||||
|
latest_tag="v0.0.0"
|
||||||
|
fi
|
||||||
|
version="${latest_tag#v}"
|
||||||
|
IFS='.' read -r major minor patch <<< "${version}"
|
||||||
|
if ! [[ "${major:-}" =~ ^[0-9]+$ ]]; then major=0; fi
|
||||||
|
if ! [[ "${minor:-}" =~ ^[0-9]+$ ]]; then minor=0; fi
|
||||||
|
if ! [[ "${patch:-}" =~ ^[0-9]+$ ]]; then patch=0; fi
|
||||||
|
patch=$((patch + 1))
|
||||||
|
next_tag="v${major}.${minor}.${patch}"
|
||||||
|
echo "${next_tag}" > .release_version
|
||||||
|
echo "Next release: ${next_tag}"
|
||||||
- name: build-deb-amd64
|
- name: build-deb-amd64
|
||||||
image: cache.coadcorp.com/library/buildpack-deps:jammy
|
image: cache.coadcorp.com/library/buildpack-deps:jammy
|
||||||
|
depends_on:
|
||||||
|
- compute-release-version
|
||||||
environment:
|
environment:
|
||||||
DEBIAN_FRONTEND: noninteractive
|
DEBIAN_FRONTEND: noninteractive
|
||||||
commands:
|
commands:
|
||||||
- apt-get update
|
- apt-get update
|
||||||
- apt-get install -y --no-install-recommends build-essential qt5-qmake qtbase5-dev qtbase5-dev-tools libexif-dev qt5-image-formats-plugins libmosquitto-dev dpkg-dev fakeroot ca-certificates
|
- apt-get install -y --no-install-recommends build-essential qt5-qmake qtbase5-dev qtbase5-dev-tools libexif-dev qt5-image-formats-plugins libmosquitto-dev dpkg-dev fakeroot ca-certificates
|
||||||
- ARCH=amd64 BUILD_DIR=build-amd64 bash sbin/build_deb.sh "${DRONE_TAG:-${DRONE_COMMIT_SHA:0:8}}"
|
- ARCH=amd64 BUILD_DIR=build-amd64 bash sbin/build_deb.sh "$(cat .release_version)"
|
||||||
- ls -la dist
|
- ls -la dist
|
||||||
|
|
||||||
- name: build-deb-armhf
|
- name: build-deb-armhf
|
||||||
image: cache.coadcorp.com/library/buildpack-deps:bullseye
|
image: cache.coadcorp.com/library/buildpack-deps:bullseye
|
||||||
|
depends_on:
|
||||||
|
- compute-release-version
|
||||||
environment:
|
environment:
|
||||||
DEBIAN_FRONTEND: noninteractive
|
DEBIAN_FRONTEND: noninteractive
|
||||||
ARM_CFLAGS: -march=armv6 -mfpu=vfp -mfloat-abi=hard -marm
|
ARM_CFLAGS: -march=armv6 -mfpu=vfp -mfloat-abi=hard -marm
|
||||||
@@ -31,11 +61,13 @@ steps:
|
|||||||
EOF
|
EOF
|
||||||
- apt-get update
|
- apt-get update
|
||||||
- apt-get install -y --no-install-recommends build-essential qt5-qmake qtbase5-dev qtbase5-dev-tools libexif-dev qt5-image-formats-plugins libmosquitto-dev dpkg-dev fakeroot ca-certificates gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf qtbase5-dev:armhf libqt5core5a:armhf libqt5gui5:armhf libqt5widgets5:armhf libqt5network5:armhf libexif-dev:armhf libmosquitto-dev:armhf qt5-image-formats-plugins:armhf
|
- apt-get install -y --no-install-recommends build-essential qt5-qmake qtbase5-dev qtbase5-dev-tools libexif-dev qt5-image-formats-plugins libmosquitto-dev dpkg-dev fakeroot ca-certificates gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf qtbase5-dev:armhf libqt5core5a:armhf libqt5gui5:armhf libqt5widgets5:armhf libqt5network5:armhf libexif-dev:armhf libmosquitto-dev:armhf qt5-image-formats-plugins:armhf
|
||||||
- ARCH=armhf BUILD_DIR=build-armhf QMAKESPEC=$PWD/mkspecs/linux-armhf-g++ QMAKE_QTCONF=$PWD/mkspecs/qt-armhf.conf QMAKE_CFLAGS="$ARM_CFLAGS" QMAKE_CXXFLAGS="$ARM_CFLAGS" bash sbin/build_deb.sh "${DRONE_TAG:-${DRONE_COMMIT_SHA:0:8}}"
|
- ARCH=armhf BUILD_DIR=build-armhf QMAKESPEC=$PWD/mkspecs/linux-armhf-g++ QMAKE_QTCONF=$PWD/mkspecs/qt-armhf.conf QMAKE_CFLAGS="$ARM_CFLAGS" QMAKE_CXXFLAGS="$ARM_CFLAGS" bash sbin/build_deb.sh "$(cat .release_version)"
|
||||||
- ls -la dist
|
- ls -la dist
|
||||||
|
|
||||||
- name: build-deb-arm64
|
- name: build-deb-arm64
|
||||||
image: cache.coadcorp.com/library/buildpack-deps:bullseye
|
image: cache.coadcorp.com/library/buildpack-deps:bullseye
|
||||||
|
depends_on:
|
||||||
|
- compute-release-version
|
||||||
environment:
|
environment:
|
||||||
DEBIAN_FRONTEND: noninteractive
|
DEBIAN_FRONTEND: noninteractive
|
||||||
ARM64_CFLAGS: -march=armv8-a
|
ARM64_CFLAGS: -march=armv8-a
|
||||||
@@ -52,7 +84,7 @@ steps:
|
|||||||
EOF
|
EOF
|
||||||
- apt-get update
|
- apt-get update
|
||||||
- apt-get install -y --no-install-recommends build-essential qt5-qmake qtbase5-dev qtbase5-dev-tools libexif-dev qt5-image-formats-plugins libmosquitto-dev dpkg-dev fakeroot ca-certificates gcc-aarch64-linux-gnu g++-aarch64-linux-gnu qtbase5-dev:arm64 libqt5core5a:arm64 libqt5gui5:arm64 libqt5widgets5:arm64 libqt5network5:arm64 libexif-dev:arm64 libmosquitto-dev:arm64 qt5-image-formats-plugins:arm64
|
- apt-get install -y --no-install-recommends build-essential qt5-qmake qtbase5-dev qtbase5-dev-tools libexif-dev qt5-image-formats-plugins libmosquitto-dev dpkg-dev fakeroot ca-certificates gcc-aarch64-linux-gnu g++-aarch64-linux-gnu qtbase5-dev:arm64 libqt5core5a:arm64 libqt5gui5:arm64 libqt5widgets5:arm64 libqt5network5:arm64 libexif-dev:arm64 libmosquitto-dev:arm64 qt5-image-formats-plugins:arm64
|
||||||
- ARCH=arm64 BUILD_DIR=build-arm64 QMAKESPEC=$PWD/mkspecs/linux-arm64-g++ QMAKE_QTCONF=$PWD/mkspecs/qt-arm64.conf QMAKE_CFLAGS="$ARM64_CFLAGS" QMAKE_CXXFLAGS="$ARM64_CFLAGS" bash sbin/build_deb.sh "${DRONE_TAG:-${DRONE_COMMIT_SHA:0:8}}"
|
- ARCH=arm64 BUILD_DIR=build-arm64 QMAKESPEC=$PWD/mkspecs/linux-arm64-g++ QMAKE_QTCONF=$PWD/mkspecs/qt-arm64.conf QMAKE_CFLAGS="$ARM64_CFLAGS" QMAKE_CXXFLAGS="$ARM64_CFLAGS" bash sbin/build_deb.sh "$(cat .release_version)"
|
||||||
- ls -la dist
|
- ls -la dist
|
||||||
|
|
||||||
- name: build-deps-image
|
- name: build-deps-image
|
||||||
@@ -75,21 +107,44 @@ steps:
|
|||||||
- build-deps
|
- build-deps
|
||||||
|
|
||||||
- name: gitea-release
|
- name: gitea-release
|
||||||
image: cache.coadcorp.com/plugins/gitea-release
|
image: alpine:3.19
|
||||||
depends_on:
|
depends_on:
|
||||||
- build-deb-amd64
|
- build-deb-amd64
|
||||||
- build-deb-armhf
|
- build-deb-armhf
|
||||||
- build-deb-arm64
|
- build-deb-arm64
|
||||||
settings:
|
environment:
|
||||||
api_key:
|
GITEA_TOKEN:
|
||||||
from_secret: GITEA_TOKEN
|
from_secret: GITEA_TOKEN
|
||||||
base_url: https://git.coadcorp.com
|
GITEA_BASE_URL: https://git.coadcorp.com
|
||||||
files:
|
commands:
|
||||||
- dist/*.deb
|
- apk add --no-cache curl jq
|
||||||
draft: false
|
- |
|
||||||
prerelease: false
|
set -euo pipefail
|
||||||
title: ${DRONE_TAG}
|
release_tag="$(cat .release_version)"
|
||||||
note: Automated release for ${DRONE_TAG}
|
repo_owner="${DRONE_REPO_OWNER}"
|
||||||
when:
|
repo_name="${DRONE_REPO_NAME}"
|
||||||
event:
|
api_base="${GITEA_BASE_URL}/api/v1/repos/${repo_owner}/${repo_name}"
|
||||||
- tag
|
payload="$(jq -n \
|
||||||
|
--arg tag "${release_tag}" \
|
||||||
|
--arg name "${release_tag}" \
|
||||||
|
--arg body "Automated release for ${release_tag}" \
|
||||||
|
--arg target "${DRONE_COMMIT_SHA}" \
|
||||||
|
'{tag_name:$tag, name:$name, body:$body, draft:false, prerelease:false, target_commitish:$target}')"
|
||||||
|
response="$(curl -sS -w '\n%{http_code}' -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" -d "${payload}" "${api_base}/releases")"
|
||||||
|
status="$(echo "${response}" | tail -n1)"
|
||||||
|
body="$(echo "${response}" | sed '$d')"
|
||||||
|
if [ "${status}" != "201" ] && [ "${status}" != "200" ]; then
|
||||||
|
body="$(curl -sS -H "Authorization: token ${GITEA_TOKEN}" "${api_base}/releases/tags/${release_tag}")"
|
||||||
|
fi
|
||||||
|
release_id="$(echo "${body}" | jq -r '.id // empty')"
|
||||||
|
if [ -z "${release_id}" ] || [ "${release_id}" = "null" ]; then
|
||||||
|
echo "Failed to create or fetch release for ${release_tag}"
|
||||||
|
echo "${body}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
for file in dist/*.deb; do
|
||||||
|
name="$(basename "${file}")"
|
||||||
|
curl -sS -H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
-F "attachment=@${file}" \
|
||||||
|
"${api_base}/releases/${release_id}/assets?name=${name}" >/dev/null
|
||||||
|
done
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ QVector<ImmichAsset> ImmichClient::fetchAssets()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!config.userId.empty() && config.albumIds.empty() && config.personIds.empty())
|
if (!config.userId.empty() && config.albumIds.empty() && config.personIds.empty())
|
||||||
return fetchAssetsByUser();
|
return fetchAssetsBySearch();
|
||||||
|
|
||||||
if (!config.userId.empty() && (!config.albumIds.empty() || !config.personIds.empty()))
|
if (!config.userId.empty() && (!config.albumIds.empty() || !config.personIds.empty()))
|
||||||
{
|
{
|
||||||
@@ -166,20 +166,24 @@ QVector<ImmichAsset> ImmichClient::fetchAssetsBySearch()
|
|||||||
int maxAssets = config.maxAssets;
|
int maxAssets = config.maxAssets;
|
||||||
bool triedZero = false;
|
bool triedZero = false;
|
||||||
int page = 1;
|
int page = 1;
|
||||||
|
QString userFilterKey;
|
||||||
|
QByteArray firstResponse;
|
||||||
|
QJsonArray items;
|
||||||
|
int total = 0;
|
||||||
|
|
||||||
if (ShouldLog())
|
if (ShouldLog())
|
||||||
{
|
{
|
||||||
Log("Immich search: size=", config.size, ", order=", config.order,
|
Log("Immich search: size=", config.size, ", order=", config.order,
|
||||||
", pageSize=", pageSize, ", maxAssets=", maxAssets,
|
", pageSize=", pageSize, ", maxAssets=", maxAssets,
|
||||||
|
", userId=", config.userId,
|
||||||
", albumIds=", config.albumIds.size(),
|
", albumIds=", config.albumIds.size(),
|
||||||
", personIds=", config.personIds.size(),
|
", personIds=", config.personIds.size(),
|
||||||
", allowedExtensions=", config.allowedExtensions.size());
|
", allowedExtensions=", config.allowedExtensions.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
while (true)
|
auto fetchPage = [&](int pageIndex, const QString &userKey) -> QByteArray {
|
||||||
{
|
|
||||||
QJsonObject body;
|
QJsonObject body;
|
||||||
body["page"] = page;
|
body["page"] = pageIndex;
|
||||||
body["size"] = pageSize;
|
body["size"] = pageSize;
|
||||||
body["type"] = "IMAGE";
|
body["type"] = "IMAGE";
|
||||||
body["order"] = QString::fromStdString(config.order);
|
body["order"] = QString::fromStdString(config.order);
|
||||||
@@ -199,19 +203,69 @@ QVector<ImmichAsset> ImmichClient::fetchAssetsBySearch()
|
|||||||
ids.append(QString::fromStdString(id));
|
ids.append(QString::fromStdString(id));
|
||||||
body["personIds"] = ids;
|
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);
|
auto parseSearch = [&](const QByteArray &response, QJsonArray &outItems, int &outTotal) -> bool {
|
||||||
if (response.isEmpty())
|
|
||||||
break;
|
|
||||||
|
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(response);
|
QJsonDocument doc = QJsonDocument::fromJson(response);
|
||||||
if (!doc.isObject())
|
if (!doc.isObject())
|
||||||
break;
|
return false;
|
||||||
|
|
||||||
QJsonObject root = doc.object();
|
QJsonObject root = doc.object();
|
||||||
|
if (root.contains("error") || root.contains("statusCode"))
|
||||||
|
return false;
|
||||||
|
if (!root.contains("assets"))
|
||||||
|
return false;
|
||||||
QJsonObject assetsObj = root["assets"].toObject();
|
QJsonObject assetsObj = root["assets"].toObject();
|
||||||
QJsonArray items = assetsObj["items"].toArray();
|
outItems = assetsObj["items"].toArray();
|
||||||
int total = assetsObj["total"].toInt();
|
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, ")");
|
Log("Immich page ", page, ": ", items.size(), " assets (total ", total, ")");
|
||||||
if (items.isEmpty())
|
if (items.isEmpty())
|
||||||
{
|
{
|
||||||
@@ -259,6 +313,9 @@ QVector<ImmichAsset> ImmichClient::fetchAssetsByUser()
|
|||||||
int pageSize = config.pageSize > 0 ? config.pageSize : 200;
|
int pageSize = config.pageSize > 0 ? config.pageSize : 200;
|
||||||
int maxAssets = config.maxAssets;
|
int maxAssets = config.maxAssets;
|
||||||
int skip = 0;
|
int skip = 0;
|
||||||
|
QString endpointPath = "/assets";
|
||||||
|
QByteArray initialResponse;
|
||||||
|
bool endpointResolved = false;
|
||||||
|
|
||||||
if (ShouldLog())
|
if (ShouldLog())
|
||||||
{
|
{
|
||||||
@@ -268,24 +325,52 @@ QVector<ImmichAsset> ImmichClient::fetchAssetsByUser()
|
|||||||
", includeArchived=", config.includeArchived);
|
", includeArchived=", config.includeArchived);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (true)
|
auto fetchPage = [&](const QString &path, int offset) -> QByteArray {
|
||||||
{
|
QUrl url = apiUrl(path);
|
||||||
QUrl url = apiUrl("/assets");
|
|
||||||
QUrlQuery query;
|
QUrlQuery query;
|
||||||
query.addQueryItem("take", QString::number(pageSize));
|
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));
|
query.addQueryItem("userId", QString::fromStdString(config.userId));
|
||||||
if (!config.includeArchived)
|
if (!config.includeArchived)
|
||||||
query.addQueryItem("isArchived", "false");
|
query.addQueryItem("isArchived", "false");
|
||||||
url.setQuery(query);
|
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())
|
if (response.isEmpty())
|
||||||
break;
|
break;
|
||||||
|
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(response);
|
QJsonDocument doc = QJsonDocument::fromJson(response);
|
||||||
if (!doc.isArray())
|
if (!doc.isArray())
|
||||||
break;
|
{
|
||||||
|
Log("Immich user assets response was not an array; falling back to metadata search.");
|
||||||
|
return fetchAssetsBySearch();
|
||||||
|
}
|
||||||
|
|
||||||
QJsonArray items = doc.array();
|
QJsonArray items = doc.array();
|
||||||
Log("Immich user assets skip ", skip, ": ", items.size(), " assets");
|
Log("Immich user assets skip ", skip, ": ", items.size(), " assets");
|
||||||
|
|||||||
Reference in New Issue
Block a user