mirror of
https://github.com/immich-app/immich.git
synced 2026-05-24 08:32:28 -04:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cc66ad8a74 |
@@ -288,6 +288,7 @@ jobs:
|
|||||||
APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
|
APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
|
||||||
APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }}
|
APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }}
|
||||||
ENVIRONMENT: ${{ inputs.environment || 'development' }}
|
ENVIRONMENT: ${{ inputs.environment || 'development' }}
|
||||||
|
BUNDLE_ID_SUFFIX: ${{ inputs.environment == 'production' && '' || 'development' }}
|
||||||
GITHUB_REF: ${{ github.ref }}
|
GITHUB_REF: ${{ github.ref }}
|
||||||
FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT: 120
|
FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT: 120
|
||||||
FASTLANE_XCODEBUILD_SETTINGS_RETRIES: 6
|
FASTLANE_XCODEBUILD_SETTINGS_RETRIES: 6
|
||||||
|
|||||||
@@ -536,7 +536,7 @@ test.describe('Timeline', () => {
|
|||||||
force: false,
|
force: false,
|
||||||
ids: [assetToTrash.id],
|
ids: [assetToTrash.id],
|
||||||
});
|
});
|
||||||
await page.keyboard.press('Escape');
|
await page.locator('#asset-selection-app-bar').getByLabel('Close').click();
|
||||||
await page.getByText('Trash', { exact: true }).click();
|
await page.getByText('Trash', { exact: true }).click();
|
||||||
await timelineUtils.waitForTimelineLoad(page);
|
await timelineUtils.waitForTimelineLoad(page);
|
||||||
await thumbnailUtils.expectInViewport(page, assetToTrash.id);
|
await thumbnailUtils.expectInViewport(page, assetToTrash.id);
|
||||||
@@ -676,7 +676,7 @@ test.describe('Timeline', () => {
|
|||||||
ids: [assetToArchive.id],
|
ids: [assetToArchive.id],
|
||||||
});
|
});
|
||||||
await thumbnailUtils.expectThumbnailIsArchive(page, assetToArchive.id);
|
await thumbnailUtils.expectThumbnailIsArchive(page, assetToArchive.id);
|
||||||
await page.keyboard.press('Escape');
|
await page.locator('#asset-selection-app-bar').getByLabel('Close').click();
|
||||||
await page.getByRole('link').getByText('Archive').click();
|
await page.getByRole('link').getByText('Archive').click();
|
||||||
await timelineUtils.waitForTimelineLoad(page);
|
await timelineUtils.waitForTimelineLoad(page);
|
||||||
await thumbnailUtils.expectInViewport(page, assetToArchive.id);
|
await thumbnailUtils.expectInViewport(page, assetToArchive.id);
|
||||||
@@ -823,7 +823,7 @@ test.describe('Timeline', () => {
|
|||||||
});
|
});
|
||||||
// ensure thumbnail still exists and has favorite icon
|
// ensure thumbnail still exists and has favorite icon
|
||||||
await thumbnailUtils.expectThumbnailIsFavorite(page, assetToFavorite.id);
|
await thumbnailUtils.expectThumbnailIsFavorite(page, assetToFavorite.id);
|
||||||
await page.keyboard.press('Escape');
|
await page.locator('#asset-selection-app-bar').getByLabel('Close').click();
|
||||||
await page.getByRole('link').getByText('Favorites').click();
|
await page.getByRole('link').getByText('Favorites').click();
|
||||||
await timelineUtils.waitForTimelineLoad(page);
|
await timelineUtils.waitForTimelineLoad(page);
|
||||||
await pageUtils.goToAsset(page, assetToFavorite.fileCreatedAt);
|
await pageUtils.goToAsset(page, assetToFavorite.fileCreatedAt);
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
# @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html
|
# @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html
|
||||||
|
|
||||||
[[tools."aqua:flutter/flutter"]]
|
|
||||||
version = "3.41.9"
|
|
||||||
backend = "aqua:flutter/flutter"
|
|
||||||
|
|
||||||
[[tools.flutter]]
|
[[tools.flutter]]
|
||||||
version = "3.41.9-stable"
|
version = "3.41.9-stable"
|
||||||
backend = "asdf:flutter"
|
backend = "asdf:flutter"
|
||||||
@@ -16,36 +12,43 @@ backend = "github:CQLabs/homebrew-dcm"
|
|||||||
checksum = "sha256:253da2512b149913dfe345bf9a62a79acb2d730f66e71162ba4a92dfc4224b82"
|
checksum = "sha256:253da2512b149913dfe345bf9a62a79acb2d730f66e71162ba4a92dfc4224b82"
|
||||||
url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-linux-arm-release.zip"
|
url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-linux-arm-release.zip"
|
||||||
url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543838"
|
url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543838"
|
||||||
|
github_attestations = "unavailable"
|
||||||
|
|
||||||
[tools."github:CQLabs/homebrew-dcm"."platforms.linux-arm64-musl"]
|
[tools."github:CQLabs/homebrew-dcm"."platforms.linux-arm64-musl"]
|
||||||
checksum = "sha256:253da2512b149913dfe345bf9a62a79acb2d730f66e71162ba4a92dfc4224b82"
|
checksum = "sha256:253da2512b149913dfe345bf9a62a79acb2d730f66e71162ba4a92dfc4224b82"
|
||||||
url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-linux-arm-release.zip"
|
url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-linux-arm-release.zip"
|
||||||
url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543838"
|
url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543838"
|
||||||
|
github_attestations = "unavailable"
|
||||||
|
|
||||||
[tools."github:CQLabs/homebrew-dcm"."platforms.linux-x64"]
|
[tools."github:CQLabs/homebrew-dcm"."platforms.linux-x64"]
|
||||||
checksum = "sha256:477e086d4099c12f21e5ccd83b005d5fb945dd4cac4fd127fd9a08d7649af1cf"
|
checksum = "sha256:477e086d4099c12f21e5ccd83b005d5fb945dd4cac4fd127fd9a08d7649af1cf"
|
||||||
url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-linux-x64-release.zip"
|
url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-linux-x64-release.zip"
|
||||||
url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543797"
|
url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543797"
|
||||||
|
github_attestations = "unavailable"
|
||||||
|
|
||||||
[tools."github:CQLabs/homebrew-dcm"."platforms.linux-x64-musl"]
|
[tools."github:CQLabs/homebrew-dcm"."platforms.linux-x64-musl"]
|
||||||
checksum = "sha256:477e086d4099c12f21e5ccd83b005d5fb945dd4cac4fd127fd9a08d7649af1cf"
|
checksum = "sha256:477e086d4099c12f21e5ccd83b005d5fb945dd4cac4fd127fd9a08d7649af1cf"
|
||||||
url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-linux-x64-release.zip"
|
url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-linux-x64-release.zip"
|
||||||
url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543797"
|
url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543797"
|
||||||
|
github_attestations = "unavailable"
|
||||||
|
|
||||||
[tools."github:CQLabs/homebrew-dcm"."platforms.macos-arm64"]
|
[tools."github:CQLabs/homebrew-dcm"."platforms.macos-arm64"]
|
||||||
checksum = "sha256:30bede64367d09067093cc57af6ec9496d7717898138ded5cb98a16ac8dd9d93"
|
checksum = "sha256:30bede64367d09067093cc57af6ec9496d7717898138ded5cb98a16ac8dd9d93"
|
||||||
url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-macos-arm-release.zip"
|
url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-macos-arm-release.zip"
|
||||||
url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543757"
|
url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543757"
|
||||||
|
github_attestations = "unavailable"
|
||||||
|
|
||||||
[tools."github:CQLabs/homebrew-dcm"."platforms.macos-x64"]
|
[tools."github:CQLabs/homebrew-dcm"."platforms.macos-x64"]
|
||||||
checksum = "sha256:e56cb99872be7445a4de1d37e5438ca70e3bcd83be7a2b9b385e3538881f8068"
|
checksum = "sha256:e56cb99872be7445a4de1d37e5438ca70e3bcd83be7a2b9b385e3538881f8068"
|
||||||
url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-macos-x64-release.zip"
|
url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-macos-x64-release.zip"
|
||||||
url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543727"
|
url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543727"
|
||||||
|
github_attestations = "unavailable"
|
||||||
|
|
||||||
[tools."github:CQLabs/homebrew-dcm"."platforms.windows-x64"]
|
[tools."github:CQLabs/homebrew-dcm"."platforms.windows-x64"]
|
||||||
checksum = "sha256:f133470daa3fb0427f039b424392af7e917d7e7db6b556aa2a968ab0e31587da"
|
checksum = "sha256:f133470daa3fb0427f039b424392af7e917d7e7db6b556aa2a968ab0e31587da"
|
||||||
url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-windows-release.zip"
|
url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-windows-release.zip"
|
||||||
url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543660"
|
url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543660"
|
||||||
|
github_attestations = "unavailable"
|
||||||
|
|
||||||
[[tools."github:extism/cli"]]
|
[[tools."github:extism/cli"]]
|
||||||
version = "1.6.3"
|
version = "1.6.3"
|
||||||
@@ -55,36 +58,43 @@ backend = "github:extism/cli"
|
|||||||
checksum = "sha256:d92f830c9be39637569feacb04e9750c28848df6d9a219db94152a9b4eb9452b"
|
checksum = "sha256:d92f830c9be39637569feacb04e9750c28848df6d9a219db94152a9b4eb9452b"
|
||||||
url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-linux-arm64.tar.gz"
|
url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-linux-arm64.tar.gz"
|
||||||
url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694030"
|
url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694030"
|
||||||
|
github_attestations = "unavailable"
|
||||||
|
|
||||||
[tools."github:extism/cli"."platforms.linux-arm64-musl"]
|
[tools."github:extism/cli"."platforms.linux-arm64-musl"]
|
||||||
checksum = "sha256:d92f830c9be39637569feacb04e9750c28848df6d9a219db94152a9b4eb9452b"
|
checksum = "sha256:d92f830c9be39637569feacb04e9750c28848df6d9a219db94152a9b4eb9452b"
|
||||||
url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-linux-arm64.tar.gz"
|
url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-linux-arm64.tar.gz"
|
||||||
url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694030"
|
url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694030"
|
||||||
|
github_attestations = "unavailable"
|
||||||
|
|
||||||
[tools."github:extism/cli"."platforms.linux-x64"]
|
[tools."github:extism/cli"."platforms.linux-x64"]
|
||||||
checksum = "sha256:34e7ae9bfded6e2c32dee83f70a4e50d34f9d3e80d1762b09625fe82e214d02d"
|
checksum = "sha256:34e7ae9bfded6e2c32dee83f70a4e50d34f9d3e80d1762b09625fe82e214d02d"
|
||||||
url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-linux-amd64.tar.gz"
|
url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-linux-amd64.tar.gz"
|
||||||
url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694025"
|
url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694025"
|
||||||
|
github_attestations = "unavailable"
|
||||||
|
|
||||||
[tools."github:extism/cli"."platforms.linux-x64-musl"]
|
[tools."github:extism/cli"."platforms.linux-x64-musl"]
|
||||||
checksum = "sha256:34e7ae9bfded6e2c32dee83f70a4e50d34f9d3e80d1762b09625fe82e214d02d"
|
checksum = "sha256:34e7ae9bfded6e2c32dee83f70a4e50d34f9d3e80d1762b09625fe82e214d02d"
|
||||||
url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-linux-amd64.tar.gz"
|
url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-linux-amd64.tar.gz"
|
||||||
url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694025"
|
url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694025"
|
||||||
|
github_attestations = "unavailable"
|
||||||
|
|
||||||
[tools."github:extism/cli"."platforms.macos-arm64"]
|
[tools."github:extism/cli"."platforms.macos-arm64"]
|
||||||
checksum = "sha256:b4ddbc575b5ac000115247f781723f9b9f284ed87b29c600539d72161b5b29fc"
|
checksum = "sha256:b4ddbc575b5ac000115247f781723f9b9f284ed87b29c600539d72161b5b29fc"
|
||||||
url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-darwin-arm64.tar.gz"
|
url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-darwin-arm64.tar.gz"
|
||||||
url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694029"
|
url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694029"
|
||||||
|
github_attestations = "unavailable"
|
||||||
|
|
||||||
[tools."github:extism/cli"."platforms.macos-x64"]
|
[tools."github:extism/cli"."platforms.macos-x64"]
|
||||||
checksum = "sha256:9a2f71b6e6009685a622cc3084e52d2a1a8e23c98d29ffa72e666e9dc699855f"
|
checksum = "sha256:9a2f71b6e6009685a622cc3084e52d2a1a8e23c98d29ffa72e666e9dc699855f"
|
||||||
url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-darwin-amd64.tar.gz"
|
url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-darwin-amd64.tar.gz"
|
||||||
url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694026"
|
url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694026"
|
||||||
|
github_attestations = "unavailable"
|
||||||
|
|
||||||
[tools."github:extism/cli"."platforms.windows-x64"]
|
[tools."github:extism/cli"."platforms.windows-x64"]
|
||||||
checksum = "sha256:47e4ed2782445b2b08a4d1ac127211588f8b4d1fc25fd6481d4cb65151b5213c"
|
checksum = "sha256:47e4ed2782445b2b08a4d1ac127211588f8b4d1fc25fd6481d4cb65151b5213c"
|
||||||
url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-windows-amd64.zip"
|
url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-windows-amd64.zip"
|
||||||
url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694035"
|
url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694035"
|
||||||
|
github_attestations = "unavailable"
|
||||||
|
|
||||||
[[tools."github:extism/js-pdk"]]
|
[[tools."github:extism/js-pdk"]]
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
@@ -94,36 +104,43 @@ backend = "github:extism/js-pdk"
|
|||||||
checksum = "sha256:15a186250e68d6bff4ec839fff275d45a90e383a69209dcc1239eb9e3aee6e1b"
|
checksum = "sha256:15a186250e68d6bff4ec839fff275d45a90e383a69209dcc1239eb9e3aee6e1b"
|
||||||
url = "https://github.com/extism/js-pdk/releases/download/v1.6.0/extism-js-aarch64-linux-v1.6.0.gz"
|
url = "https://github.com/extism/js-pdk/releases/download/v1.6.0/extism-js-aarch64-linux-v1.6.0.gz"
|
||||||
url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353223214"
|
url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353223214"
|
||||||
|
github_attestations = "unavailable"
|
||||||
|
|
||||||
[tools."github:extism/js-pdk"."platforms.linux-arm64-musl"]
|
[tools."github:extism/js-pdk"."platforms.linux-arm64-musl"]
|
||||||
checksum = "sha256:15a186250e68d6bff4ec839fff275d45a90e383a69209dcc1239eb9e3aee6e1b"
|
checksum = "sha256:15a186250e68d6bff4ec839fff275d45a90e383a69209dcc1239eb9e3aee6e1b"
|
||||||
url = "https://github.com/extism/js-pdk/releases/download/v1.6.0/extism-js-aarch64-linux-v1.6.0.gz"
|
url = "https://github.com/extism/js-pdk/releases/download/v1.6.0/extism-js-aarch64-linux-v1.6.0.gz"
|
||||||
url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353223214"
|
url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353223214"
|
||||||
|
github_attestations = "unavailable"
|
||||||
|
|
||||||
[tools."github:extism/js-pdk"."platforms.linux-x64"]
|
[tools."github:extism/js-pdk"."platforms.linux-x64"]
|
||||||
checksum = "sha256:4ded271ccf465031ccd0dc35e7a140e134d7f30721671cc4a8e1ff805d4aad68"
|
checksum = "sha256:4ded271ccf465031ccd0dc35e7a140e134d7f30721671cc4a8e1ff805d4aad68"
|
||||||
url = "https://github.com/extism/js-pdk/releases/download/v1.6.0/extism-js-x86_64-linux-v1.6.0.gz"
|
url = "https://github.com/extism/js-pdk/releases/download/v1.6.0/extism-js-x86_64-linux-v1.6.0.gz"
|
||||||
url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353223119"
|
url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353223119"
|
||||||
|
github_attestations = "unavailable"
|
||||||
|
|
||||||
[tools."github:extism/js-pdk"."platforms.linux-x64-musl"]
|
[tools."github:extism/js-pdk"."platforms.linux-x64-musl"]
|
||||||
checksum = "sha256:4ded271ccf465031ccd0dc35e7a140e134d7f30721671cc4a8e1ff805d4aad68"
|
checksum = "sha256:4ded271ccf465031ccd0dc35e7a140e134d7f30721671cc4a8e1ff805d4aad68"
|
||||||
url = "https://github.com/extism/js-pdk/releases/download/v1.6.0/extism-js-x86_64-linux-v1.6.0.gz"
|
url = "https://github.com/extism/js-pdk/releases/download/v1.6.0/extism-js-x86_64-linux-v1.6.0.gz"
|
||||||
url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353223119"
|
url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353223119"
|
||||||
|
github_attestations = "unavailable"
|
||||||
|
|
||||||
[tools."github:extism/js-pdk"."platforms.macos-arm64"]
|
[tools."github:extism/js-pdk"."platforms.macos-arm64"]
|
||||||
checksum = "sha256:548e25bda3971a07c32d78a249135cf8cb7b3eede101e878e06e53e01ac2e0ce"
|
checksum = "sha256:548e25bda3971a07c32d78a249135cf8cb7b3eede101e878e06e53e01ac2e0ce"
|
||||||
url = "https://github.com/extism/js-pdk/releases/download/v1.6.0/extism-js-aarch64-macos-v1.6.0.gz"
|
url = "https://github.com/extism/js-pdk/releases/download/v1.6.0/extism-js-aarch64-macos-v1.6.0.gz"
|
||||||
url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353223215"
|
url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353223215"
|
||||||
|
github_attestations = "unavailable"
|
||||||
|
|
||||||
[tools."github:extism/js-pdk"."platforms.macos-x64"]
|
[tools."github:extism/js-pdk"."platforms.macos-x64"]
|
||||||
checksum = "sha256:d85a875c2a071f0c29fe572764c52c3a499f157ab7f9efac8939a4364390e29b"
|
checksum = "sha256:d85a875c2a071f0c29fe572764c52c3a499f157ab7f9efac8939a4364390e29b"
|
||||||
url = "https://github.com/extism/js-pdk/releases/download/v1.6.0/extism-js-x86_64-macos-v1.6.0.gz"
|
url = "https://github.com/extism/js-pdk/releases/download/v1.6.0/extism-js-x86_64-macos-v1.6.0.gz"
|
||||||
url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353223239"
|
url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353223239"
|
||||||
|
github_attestations = "unavailable"
|
||||||
|
|
||||||
[tools."github:extism/js-pdk"."platforms.windows-x64"]
|
[tools."github:extism/js-pdk"."platforms.windows-x64"]
|
||||||
checksum = "sha256:97b7b746141e4777e1ca2b76febdeb16dc9d314ff6a4257df05a476b67228acc"
|
checksum = "sha256:97b7b746141e4777e1ca2b76febdeb16dc9d314ff6a4257df05a476b67228acc"
|
||||||
url = "https://github.com/extism/js-pdk/releases/download/v1.6.0/extism-js-x86_64-windows-v1.6.0.gz"
|
url = "https://github.com/extism/js-pdk/releases/download/v1.6.0/extism-js-x86_64-windows-v1.6.0.gz"
|
||||||
url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353224133"
|
url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353224133"
|
||||||
|
github_attestations = "unavailable"
|
||||||
|
|
||||||
[[tools."github:jellyfin/jellyfin-ffmpeg"]]
|
[[tools."github:jellyfin/jellyfin-ffmpeg"]]
|
||||||
version = "7.1.3-6"
|
version = "7.1.3-6"
|
||||||
@@ -133,36 +150,43 @@ backend = "github:jellyfin/jellyfin-ffmpeg"
|
|||||||
checksum = "sha256:bea03c670e8cc5bfe9edc0c5d624d4735421610cef5e808db93e7d8596952886"
|
checksum = "sha256:bea03c670e8cc5bfe9edc0c5d624d4735421610cef5e808db93e7d8596952886"
|
||||||
url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linuxarm64-gpl.tar.xz"
|
url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linuxarm64-gpl.tar.xz"
|
||||||
url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048876"
|
url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048876"
|
||||||
|
github_attestations = "unavailable"
|
||||||
|
|
||||||
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-arm64-musl"]
|
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-arm64-musl"]
|
||||||
checksum = "sha256:bea03c670e8cc5bfe9edc0c5d624d4735421610cef5e808db93e7d8596952886"
|
checksum = "sha256:bea03c670e8cc5bfe9edc0c5d624d4735421610cef5e808db93e7d8596952886"
|
||||||
url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linuxarm64-gpl.tar.xz"
|
url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linuxarm64-gpl.tar.xz"
|
||||||
url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048876"
|
url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048876"
|
||||||
|
github_attestations = "unavailable"
|
||||||
|
|
||||||
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-x64"]
|
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-x64"]
|
||||||
checksum = "sha256:39e99a7927468a6abec5f65d00f55010e8ff2ae3c2605294f179c94f6ae21af2"
|
checksum = "sha256:39e99a7927468a6abec5f65d00f55010e8ff2ae3c2605294f179c94f6ae21af2"
|
||||||
url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linux64-gpl.tar.xz"
|
url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linux64-gpl.tar.xz"
|
||||||
url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048879"
|
url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048879"
|
||||||
|
github_attestations = "unavailable"
|
||||||
|
|
||||||
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-x64-musl"]
|
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-x64-musl"]
|
||||||
checksum = "sha256:39e99a7927468a6abec5f65d00f55010e8ff2ae3c2605294f179c94f6ae21af2"
|
checksum = "sha256:39e99a7927468a6abec5f65d00f55010e8ff2ae3c2605294f179c94f6ae21af2"
|
||||||
url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linux64-gpl.tar.xz"
|
url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linux64-gpl.tar.xz"
|
||||||
url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048879"
|
url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048879"
|
||||||
|
github_attestations = "unavailable"
|
||||||
|
|
||||||
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.macos-arm64"]
|
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.macos-arm64"]
|
||||||
checksum = "sha256:e024d5e78d5414e75f0181036cd21373fafb9270c72894dfd7dbda2572439820"
|
checksum = "sha256:e024d5e78d5414e75f0181036cd21373fafb9270c72894dfd7dbda2572439820"
|
||||||
url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_macarm64-gpl.tar.xz"
|
url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_macarm64-gpl.tar.xz"
|
||||||
url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/408995838"
|
url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/408995838"
|
||||||
|
github_attestations = "unavailable"
|
||||||
|
|
||||||
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.macos-x64"]
|
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.macos-x64"]
|
||||||
checksum = "sha256:066ede9774aaae97a18098aaeea8b7e0d286653eb8618f640476e99c59a536c2"
|
checksum = "sha256:066ede9774aaae97a18098aaeea8b7e0d286653eb8618f640476e99c59a536c2"
|
||||||
url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_mac64-gpl.tar.xz"
|
url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_mac64-gpl.tar.xz"
|
||||||
url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/408995889"
|
url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/408995889"
|
||||||
|
github_attestations = "unavailable"
|
||||||
|
|
||||||
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.windows-x64"]
|
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.windows-x64"]
|
||||||
checksum = "sha256:7b7168149689610296f3a187c717056ce0786cc125a31caf28056737e9ba1cc1"
|
checksum = "sha256:7b7168149689610296f3a187c717056ce0786cc125a31caf28056737e9ba1cc1"
|
||||||
url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_win64-clang-gpl.zip"
|
url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_win64-clang-gpl.zip"
|
||||||
url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409036094"
|
url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409036094"
|
||||||
|
github_attestations = "unavailable"
|
||||||
|
|
||||||
[[tools."github:webassembly/binaryen"]]
|
[[tools."github:webassembly/binaryen"]]
|
||||||
version = "version_124"
|
version = "version_124"
|
||||||
@@ -172,36 +196,43 @@ backend = "github:webassembly/binaryen"
|
|||||||
checksum = "sha256:6291bd9a57d8e046f3bc099a4db386c147433a87f71c783a901c5b1792e38de3"
|
checksum = "sha256:6291bd9a57d8e046f3bc099a4db386c147433a87f71c783a901c5b1792e38de3"
|
||||||
url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-aarch64-linux.tar.gz"
|
url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-aarch64-linux.tar.gz"
|
||||||
url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288927659"
|
url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288927659"
|
||||||
|
github_attestations = "unavailable"
|
||||||
|
|
||||||
[tools."github:webassembly/binaryen"."platforms.linux-arm64-musl"]
|
[tools."github:webassembly/binaryen"."platforms.linux-arm64-musl"]
|
||||||
checksum = "sha256:6291bd9a57d8e046f3bc099a4db386c147433a87f71c783a901c5b1792e38de3"
|
checksum = "sha256:6291bd9a57d8e046f3bc099a4db386c147433a87f71c783a901c5b1792e38de3"
|
||||||
url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-aarch64-linux.tar.gz"
|
url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-aarch64-linux.tar.gz"
|
||||||
url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288927659"
|
url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288927659"
|
||||||
|
github_attestations = "unavailable"
|
||||||
|
|
||||||
[tools."github:webassembly/binaryen"."platforms.linux-x64"]
|
[tools."github:webassembly/binaryen"."platforms.linux-x64"]
|
||||||
checksum = "sha256:0290c3779fedf592b8da0ded3032ff55c41a2b7bfa2d6bf7b7bac6f0e6e28963"
|
checksum = "sha256:0290c3779fedf592b8da0ded3032ff55c41a2b7bfa2d6bf7b7bac6f0e6e28963"
|
||||||
url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-x86_64-linux.tar.gz"
|
url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-x86_64-linux.tar.gz"
|
||||||
url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288926769"
|
url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288926769"
|
||||||
|
github_attestations = "unavailable"
|
||||||
|
|
||||||
[tools."github:webassembly/binaryen"."platforms.linux-x64-musl"]
|
[tools."github:webassembly/binaryen"."platforms.linux-x64-musl"]
|
||||||
checksum = "sha256:0290c3779fedf592b8da0ded3032ff55c41a2b7bfa2d6bf7b7bac6f0e6e28963"
|
checksum = "sha256:0290c3779fedf592b8da0ded3032ff55c41a2b7bfa2d6bf7b7bac6f0e6e28963"
|
||||||
url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-x86_64-linux.tar.gz"
|
url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-x86_64-linux.tar.gz"
|
||||||
url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288926769"
|
url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288926769"
|
||||||
|
github_attestations = "unavailable"
|
||||||
|
|
||||||
[tools."github:webassembly/binaryen"."platforms.macos-arm64"]
|
[tools."github:webassembly/binaryen"."platforms.macos-arm64"]
|
||||||
checksum = "sha256:86a2c960ff62c6d2ea6009d1f89745c22c70100d394a095eab45eb941bdaa24c"
|
checksum = "sha256:86a2c960ff62c6d2ea6009d1f89745c22c70100d394a095eab45eb941bdaa24c"
|
||||||
url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-arm64-macos.tar.gz"
|
url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-arm64-macos.tar.gz"
|
||||||
url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288926134"
|
url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288926134"
|
||||||
|
github_attestations = "unavailable"
|
||||||
|
|
||||||
[tools."github:webassembly/binaryen"."platforms.macos-x64"]
|
[tools."github:webassembly/binaryen"."platforms.macos-x64"]
|
||||||
checksum = "sha256:b389bb0731758d86c3cb266d01d28a12725c23bd3cabc3df34faa162af0887e9"
|
checksum = "sha256:b389bb0731758d86c3cb266d01d28a12725c23bd3cabc3df34faa162af0887e9"
|
||||||
url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-x86_64-macos.tar.gz"
|
url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-x86_64-macos.tar.gz"
|
||||||
url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288926135"
|
url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288926135"
|
||||||
|
github_attestations = "unavailable"
|
||||||
|
|
||||||
[tools."github:webassembly/binaryen"."platforms.windows-x64"]
|
[tools."github:webassembly/binaryen"."platforms.windows-x64"]
|
||||||
checksum = "sha256:b5e1d2a1ad3c03229ddc89823848f4a1c11f9c6402a51fa26f0aaa5f1d7a2203"
|
checksum = "sha256:b5e1d2a1ad3c03229ddc89823848f4a1c11f9c6402a51fa26f0aaa5f1d7a2203"
|
||||||
url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-x86_64-windows.tar.gz"
|
url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-x86_64-windows.tar.gz"
|
||||||
url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288925833"
|
url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288925833"
|
||||||
|
github_attestations = "unavailable"
|
||||||
|
|
||||||
[[tools.java]]
|
[[tools.java]]
|
||||||
version = "21.0.2"
|
version = "21.0.2"
|
||||||
@@ -296,9 +327,37 @@ checksum = "sha256:27323f70c875b8251bfd7e61a4cffc3ebff4e56ed1e611b955016f0c70773
|
|||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_windows_amd64.tar.gz"
|
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_windows_amd64.tar.gz"
|
||||||
|
|
||||||
[[tools.pnpm]]
|
[[tools.pnpm]]
|
||||||
version = "10.33.4"
|
version = "10.33.1"
|
||||||
backend = "aqua:pnpm/pnpm"
|
backend = "aqua:pnpm/pnpm"
|
||||||
|
|
||||||
|
[tools.pnpm."platforms.linux-arm64"]
|
||||||
|
checksum = "sha256:ed8aa7901cf325f4cf5019405bdd6bf988426e4b23d08fe9b12ea4df7046f23e"
|
||||||
|
url = "https://github.com/pnpm/pnpm/releases/download/v10.33.1/pnpm-linux-arm64"
|
||||||
|
|
||||||
|
[tools.pnpm."platforms.linux-arm64-musl"]
|
||||||
|
checksum = "sha256:ed8aa7901cf325f4cf5019405bdd6bf988426e4b23d08fe9b12ea4df7046f23e"
|
||||||
|
url = "https://github.com/pnpm/pnpm/releases/download/v10.33.1/pnpm-linux-arm64"
|
||||||
|
|
||||||
|
[tools.pnpm."platforms.linux-x64"]
|
||||||
|
checksum = "sha256:fba950842532edd365e949b74643b64e6311089a45532dbe1e8f909a247fe3e9"
|
||||||
|
url = "https://github.com/pnpm/pnpm/releases/download/v10.33.1/pnpm-linux-x64"
|
||||||
|
|
||||||
|
[tools.pnpm."platforms.linux-x64-musl"]
|
||||||
|
checksum = "sha256:fba950842532edd365e949b74643b64e6311089a45532dbe1e8f909a247fe3e9"
|
||||||
|
url = "https://github.com/pnpm/pnpm/releases/download/v10.33.1/pnpm-linux-x64"
|
||||||
|
|
||||||
|
[tools.pnpm."platforms.macos-arm64"]
|
||||||
|
checksum = "sha256:909ced0038b00881d4d620ba2018c5d9691de373deea8e3c84b722b44324e47c"
|
||||||
|
url = "https://github.com/pnpm/pnpm/releases/download/v10.33.1/pnpm-macos-arm64"
|
||||||
|
|
||||||
|
[tools.pnpm."platforms.macos-x64"]
|
||||||
|
checksum = "sha256:afdad60b83f4f482f4c95cc79325f29aef776d0922a324f023a312f40e0cc7d3"
|
||||||
|
url = "https://github.com/pnpm/pnpm/releases/download/v10.33.1/pnpm-macos-x64"
|
||||||
|
|
||||||
|
[tools.pnpm."platforms.windows-x64"]
|
||||||
|
checksum = "sha256:67b23fd8c6800566b1cc04c446b170ff6e7977250084e4d8df9bfdbd8e6f4d02"
|
||||||
|
url = "https://github.com/pnpm/pnpm/releases/download/v10.33.1/pnpm-win-x64.exe"
|
||||||
|
|
||||||
[[tools.terragrunt]]
|
[[tools.terragrunt]]
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
backend = "aqua:gruntwork-io/terragrunt"
|
backend = "aqua:gruntwork-io/terragrunt"
|
||||||
|
|||||||
@@ -17,8 +17,6 @@ import app.alextran.immich.images.LocalImageApi
|
|||||||
import app.alextran.immich.images.LocalImagesImpl
|
import app.alextran.immich.images.LocalImagesImpl
|
||||||
import app.alextran.immich.images.RemoteImageApi
|
import app.alextran.immich.images.RemoteImageApi
|
||||||
import app.alextran.immich.images.RemoteImagesImpl
|
import app.alextran.immich.images.RemoteImagesImpl
|
||||||
import app.alextran.immich.permission.PermissionApi
|
|
||||||
import app.alextran.immich.permission.PermissionApiImpl
|
|
||||||
import app.alextran.immich.sync.NativeSyncApi
|
import app.alextran.immich.sync.NativeSyncApi
|
||||||
import app.alextran.immich.sync.NativeSyncApiImpl26
|
import app.alextran.immich.sync.NativeSyncApiImpl26
|
||||||
import app.alextran.immich.sync.NativeSyncApiImpl30
|
import app.alextran.immich.sync.NativeSyncApiImpl30
|
||||||
@@ -46,9 +44,7 @@ class MainActivity : FlutterFragmentActivity() {
|
|||||||
} else {
|
} else {
|
||||||
NativeSyncApiImpl30(ctx)
|
NativeSyncApiImpl30(ctx)
|
||||||
}
|
}
|
||||||
val permissionApiImpl = PermissionApiImpl(ctx)
|
|
||||||
NativeSyncApi.setUp(messenger, nativeSyncApiImpl)
|
NativeSyncApi.setUp(messenger, nativeSyncApiImpl)
|
||||||
PermissionApi.setUp(messenger, permissionApiImpl)
|
|
||||||
LocalImageApi.setUp(messenger, LocalImagesImpl(ctx))
|
LocalImageApi.setUp(messenger, LocalImagesImpl(ctx))
|
||||||
RemoteImageApi.setUp(messenger, RemoteImagesImpl(ctx))
|
RemoteImageApi.setUp(messenger, RemoteImagesImpl(ctx))
|
||||||
|
|
||||||
@@ -57,7 +53,6 @@ class MainActivity : FlutterFragmentActivity() {
|
|||||||
|
|
||||||
flutterEngine.plugins.add(backgroundEngineLockImpl)
|
flutterEngine.plugins.add(backgroundEngineLockImpl)
|
||||||
flutterEngine.plugins.add(nativeSyncApiImpl)
|
flutterEngine.plugins.add(nativeSyncApiImpl)
|
||||||
flutterEngine.plugins.add(permissionApiImpl)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cancelPlugins(flutterEngine: FlutterEngine) {
|
fun cancelPlugins(flutterEngine: FlutterEngine) {
|
||||||
@@ -65,8 +60,6 @@ class MainActivity : FlutterFragmentActivity() {
|
|||||||
flutterEngine.plugins.get(NativeSyncApiImpl26::class.java) as ImmichPlugin?
|
flutterEngine.plugins.get(NativeSyncApiImpl26::class.java) as ImmichPlugin?
|
||||||
?: flutterEngine.plugins.get(NativeSyncApiImpl30::class.java) as ImmichPlugin?
|
?: flutterEngine.plugins.get(NativeSyncApiImpl30::class.java) as ImmichPlugin?
|
||||||
nativeApi?.detachFromEngine()
|
nativeApi?.detachFromEngine()
|
||||||
val permissionApi = flutterEngine.plugins.get(PermissionApiImpl::class.java) as ImmichPlugin?
|
|
||||||
permissionApi?.detachFromEngine()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -315,7 +315,6 @@ interface NetworkApi {
|
|||||||
fun hasCertificate(): Boolean
|
fun hasCertificate(): Boolean
|
||||||
fun getClientPointer(): Long
|
fun getClientPointer(): Long
|
||||||
fun setRequestHeaders(headers: Map<String, String>, serverUrls: List<String>, token: String?)
|
fun setRequestHeaders(headers: Map<String, String>, serverUrls: List<String>, token: String?)
|
||||||
fun getAppGroupId(): String
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/** The codec used by NetworkApi. */
|
/** The codec used by NetworkApi. */
|
||||||
@@ -431,21 +430,6 @@ interface NetworkApi {
|
|||||||
channel.setMessageHandler(null)
|
channel.setMessageHandler(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
run {
|
|
||||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NetworkApi.getAppGroupId$separatedMessageChannelSuffix", codec)
|
|
||||||
if (api != null) {
|
|
||||||
channel.setMessageHandler { _, reply ->
|
|
||||||
val wrapped: List<Any?> = try {
|
|
||||||
listOf(api.getAppGroupId())
|
|
||||||
} catch (exception: Throwable) {
|
|
||||||
NetworkPigeonUtils.wrapError(exception)
|
|
||||||
}
|
|
||||||
reply.reply(wrapped)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
channel.setMessageHandler(null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class NetworkApiPlugin : FlutterPlugin, ActivityAware {
|
|||||||
private var networkApi: NetworkApiImpl? = null
|
private var networkApi: NetworkApiImpl? = null
|
||||||
|
|
||||||
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
||||||
networkApi = NetworkApiImpl(binding.applicationContext)
|
networkApi = NetworkApiImpl()
|
||||||
NetworkApi.setUp(binding.binaryMessenger, networkApi)
|
NetworkApi.setUp(binding.binaryMessenger, networkApi)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,11 +39,9 @@ class NetworkApiPlugin : FlutterPlugin, ActivityAware {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class NetworkApiImpl(private val context: Context) : NetworkApi {
|
private class NetworkApiImpl : NetworkApi {
|
||||||
var activity: Activity? = null
|
var activity: Activity? = null
|
||||||
|
|
||||||
override fun getAppGroupId(): String = context.packageName
|
|
||||||
|
|
||||||
override fun addCertificate(clientData: ClientCertData, callback: (Result<Unit>) -> Unit) {
|
override fun addCertificate(clientData: ClientCertData, callback: (Result<Unit>) -> Unit) {
|
||||||
try {
|
try {
|
||||||
HttpClientManager.setKeyEntry(clientData.data, clientData.password.toCharArray())
|
HttpClientManager.setKeyEntry(clientData.data, clientData.password.toCharArray())
|
||||||
|
|||||||
-96
@@ -1,96 +0,0 @@
|
|||||||
package app.alextran.immich.permission
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Build
|
|
||||||
import android.provider.MediaStore
|
|
||||||
import android.provider.Settings
|
|
||||||
import androidx.core.net.toUri
|
|
||||||
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
|
|
||||||
import io.flutter.plugin.common.PluginRegistry
|
|
||||||
|
|
||||||
class ManageMediaPermissionDelegate(
|
|
||||||
context: Context,
|
|
||||||
private val requestCode: Int = 1003,
|
|
||||||
) : PluginRegistry.ActivityResultListener {
|
|
||||||
private val ctx = context.applicationContext
|
|
||||||
private var activityBinding: ActivityPluginBinding? = null
|
|
||||||
private var pendingResult: ((Result<Boolean>) -> Unit)? = null
|
|
||||||
|
|
||||||
fun hasManageMediaPermission(): Boolean {
|
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
||||||
MediaStore.canManageMedia(ctx)
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun requestManageMediaPermission(callback: (Result<Boolean>) -> Unit) {
|
|
||||||
if (hasManageMediaPermission()) {
|
|
||||||
callback(Result.success(true))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
openManageMediaPermissionSettings(callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun manageMediaPermission(callback: (Result<Boolean>) -> Unit) {
|
|
||||||
openManageMediaPermissionSettings(callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun openManageMediaPermissionSettings(callback: (Result<Boolean>) -> Unit) {
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
|
||||||
callback(Result.success(false))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val activity = activityBinding?.activity
|
|
||||||
if (activity == null) {
|
|
||||||
callback(Result.failure(FlutterError("NO_ACTIVITY", "Activity not available", null)))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
pendingResult = callback
|
|
||||||
val intent = Intent(Settings.ACTION_REQUEST_MANAGE_MEDIA).apply {
|
|
||||||
data = "package:${activity.packageName}".toUri()
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
activity.startActivityForResult(intent, requestCode)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
pendingResult = null
|
|
||||||
callback(
|
|
||||||
Result.failure(
|
|
||||||
FlutterError("ACTIVITY_LAUNCH_FAILED", "Failed to launch MANAGE_MEDIA settings", e.toString())
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onAttachedToActivity(binding: ActivityPluginBinding) {
|
|
||||||
activityBinding = binding
|
|
||||||
binding.addActivityResultListener(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onDetachedFromActivity() {
|
|
||||||
failPending("ACTIVITY_DETACHED", "Activity detached before MANAGE_MEDIA result")
|
|
||||||
activityBinding?.removeActivityResultListener(this)
|
|
||||||
activityBinding = null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
|
|
||||||
if (requestCode == this.requestCode) {
|
|
||||||
val callback = pendingResult
|
|
||||||
pendingResult = null
|
|
||||||
callback?.invoke(Result.success(hasManageMediaPermission()))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun failPending(code: String, message: String) {
|
|
||||||
val callback = pendingResult ?: return
|
|
||||||
pendingResult = null
|
|
||||||
callback(Result.failure(FlutterError(code, message, null)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-128
@@ -1,128 +0,0 @@
|
|||||||
// Autogenerated from Pigeon (v26.3.4), do not edit directly.
|
|
||||||
// See also: https://pub.dev/packages/pigeon
|
|
||||||
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
|
|
||||||
|
|
||||||
package app.alextran.immich.permission
|
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import io.flutter.plugin.common.BasicMessageChannel
|
|
||||||
import io.flutter.plugin.common.BinaryMessenger
|
|
||||||
import io.flutter.plugin.common.EventChannel
|
|
||||||
import io.flutter.plugin.common.MessageCodec
|
|
||||||
import io.flutter.plugin.common.StandardMethodCodec
|
|
||||||
import io.flutter.plugin.common.StandardMessageCodec
|
|
||||||
import java.io.ByteArrayOutputStream
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
private object PermissionApiPigeonUtils {
|
|
||||||
|
|
||||||
fun wrapResult(result: Any?): List<Any?> {
|
|
||||||
return listOf(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun wrapError(exception: Throwable): List<Any?> {
|
|
||||||
return if (exception is FlutterError) {
|
|
||||||
listOf(
|
|
||||||
exception.code,
|
|
||||||
exception.message,
|
|
||||||
exception.details
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
listOf(
|
|
||||||
exception.javaClass.simpleName,
|
|
||||||
exception.toString(),
|
|
||||||
"Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error class for passing custom error details to Flutter via a thrown PlatformException.
|
|
||||||
* @property code The error code.
|
|
||||||
* @property message The error message.
|
|
||||||
* @property details The error details. Must be a datatype supported by the api codec.
|
|
||||||
*/
|
|
||||||
class FlutterError (
|
|
||||||
val code: String,
|
|
||||||
override val message: String? = null,
|
|
||||||
val details: Any? = null
|
|
||||||
) : RuntimeException()
|
|
||||||
private open class PermissionApiPigeonCodec : StandardMessageCodec() {
|
|
||||||
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
|
|
||||||
return super.readValueOfType(type, buffer)
|
|
||||||
}
|
|
||||||
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
|
|
||||||
super.writeValue(stream, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
|
||||||
interface PermissionApi {
|
|
||||||
fun hasManageMediaPermission(): Boolean
|
|
||||||
fun requestManageMediaPermission(callback: (Result<Boolean>) -> Unit)
|
|
||||||
fun manageMediaPermission(callback: (Result<Boolean>) -> Unit)
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
/** The codec used by PermissionApi. */
|
|
||||||
val codec: MessageCodec<Any?> by lazy {
|
|
||||||
PermissionApiPigeonCodec()
|
|
||||||
}
|
|
||||||
/** Sets up an instance of `PermissionApi` to handle messages through the `binaryMessenger`. */
|
|
||||||
@JvmOverloads
|
|
||||||
fun setUp(binaryMessenger: BinaryMessenger, api: PermissionApi?, messageChannelSuffix: String = "") {
|
|
||||||
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
|
|
||||||
run {
|
|
||||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.PermissionApi.hasManageMediaPermission$separatedMessageChannelSuffix", codec)
|
|
||||||
if (api != null) {
|
|
||||||
channel.setMessageHandler { _, reply ->
|
|
||||||
val wrapped: List<Any?> = try {
|
|
||||||
listOf(api.hasManageMediaPermission())
|
|
||||||
} catch (exception: Throwable) {
|
|
||||||
PermissionApiPigeonUtils.wrapError(exception)
|
|
||||||
}
|
|
||||||
reply.reply(wrapped)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
channel.setMessageHandler(null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
run {
|
|
||||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.PermissionApi.requestManageMediaPermission$separatedMessageChannelSuffix", codec)
|
|
||||||
if (api != null) {
|
|
||||||
channel.setMessageHandler { _, reply ->
|
|
||||||
api.requestManageMediaPermission{ result: Result<Boolean> ->
|
|
||||||
val error = result.exceptionOrNull()
|
|
||||||
if (error != null) {
|
|
||||||
reply.reply(PermissionApiPigeonUtils.wrapError(error))
|
|
||||||
} else {
|
|
||||||
val data = result.getOrNull()
|
|
||||||
reply.reply(PermissionApiPigeonUtils.wrapResult(data))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
channel.setMessageHandler(null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
run {
|
|
||||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.PermissionApi.manageMediaPermission$separatedMessageChannelSuffix", codec)
|
|
||||||
if (api != null) {
|
|
||||||
channel.setMessageHandler { _, reply ->
|
|
||||||
api.manageMediaPermission{ result: Result<Boolean> ->
|
|
||||||
val error = result.exceptionOrNull()
|
|
||||||
if (error != null) {
|
|
||||||
reply.reply(PermissionApiPigeonUtils.wrapError(error))
|
|
||||||
} else {
|
|
||||||
val data = result.getOrNull()
|
|
||||||
reply.reply(PermissionApiPigeonUtils.wrapResult(data))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
channel.setMessageHandler(null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-37
@@ -1,37 +0,0 @@
|
|||||||
package app.alextran.immich.permission
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import app.alextran.immich.core.ImmichPlugin
|
|
||||||
import io.flutter.embedding.engine.plugins.activity.ActivityAware
|
|
||||||
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
|
|
||||||
|
|
||||||
class PermissionApiImpl(context: Context) : ImmichPlugin(), PermissionApi, ActivityAware {
|
|
||||||
private val manageMediaPermissionDelegate = ManageMediaPermissionDelegate(context)
|
|
||||||
|
|
||||||
override fun hasManageMediaPermission(): Boolean =
|
|
||||||
manageMediaPermissionDelegate.hasManageMediaPermission()
|
|
||||||
|
|
||||||
override fun requestManageMediaPermission(callback: (Result<Boolean>) -> Unit) {
|
|
||||||
manageMediaPermissionDelegate.requestManageMediaPermission { completeWhenActive(callback, it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun manageMediaPermission(callback: (Result<Boolean>) -> Unit) {
|
|
||||||
manageMediaPermissionDelegate.manageMediaPermission { completeWhenActive(callback, it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
|
|
||||||
manageMediaPermissionDelegate.onAttachedToActivity(binding)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDetachedFromActivityForConfigChanges() {
|
|
||||||
manageMediaPermissionDelegate.onDetachedFromActivity()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
|
|
||||||
manageMediaPermissionDelegate.onAttachedToActivity(binding)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDetachedFromActivity() {
|
|
||||||
manageMediaPermissionDelegate.onDetachedFromActivity()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
package app.alextran.immich.sync
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.ContentResolver
|
|
||||||
import android.content.ContentUris
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.provider.MediaStore
|
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
|
|
||||||
import io.flutter.plugin.common.PluginRegistry
|
|
||||||
|
|
||||||
class MediaTrashDelegate(
|
|
||||||
context: Context,
|
|
||||||
private val trashRequestCode: Int = 1002,
|
|
||||||
) : PluginRegistry.ActivityResultListener {
|
|
||||||
private val ctx = context.applicationContext
|
|
||||||
private var activityBinding: ActivityPluginBinding? = null
|
|
||||||
private var pendingResult: ((Result<Boolean>) -> Unit)? = null
|
|
||||||
|
|
||||||
private fun hasManageMediaPermission(): Boolean {
|
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
||||||
MediaStore.canManageMedia(ctx)
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun restoreFromTrashById(mediaId: String, type: Long, callback: (Result<Boolean>) -> Unit) {
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R || !hasManageMediaPermission()) {
|
|
||||||
callback(Result.failure(FlutterError("PERMISSION_DENIED", "Media permission required", null)))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val id = mediaId.toLongOrNull()
|
|
||||||
if (id == null) {
|
|
||||||
callback(Result.failure(FlutterError("INVALID_ID", "The file id is not a valid number: $mediaId", null)))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isInTrash(id)) {
|
|
||||||
callback(Result.failure(FlutterError("TRASH_NOT_FOUND", "Item with id=$id not found in trash", null)))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
restoreUri(ContentUris.withAppendedId(contentUriForType(type.toInt()), id), callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.R)
|
|
||||||
private fun restoreUri(
|
|
||||||
contentUri: Uri,
|
|
||||||
callback: (Result<Boolean>) -> Unit,
|
|
||||||
) {
|
|
||||||
val activity = activityBinding?.activity
|
|
||||||
if (activity == null) {
|
|
||||||
callback(Result.failure(FlutterError("NO_ACTIVITY", "Activity not available", null)))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
val pendingIntent = MediaStore.createTrashRequest(ctx.contentResolver, listOf(contentUri), false)
|
|
||||||
pendingResult = callback
|
|
||||||
activity.startIntentSenderForResult(
|
|
||||||
pendingIntent.intentSender,
|
|
||||||
trashRequestCode,
|
|
||||||
null,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
pendingResult = null
|
|
||||||
callback(
|
|
||||||
Result.failure(
|
|
||||||
FlutterError("TRASH_ERROR", "Error creating or starting trash request", e.toString())
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.R)
|
|
||||||
private fun isInTrash(id: Long): Boolean {
|
|
||||||
val filesUri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL)
|
|
||||||
val args = Bundle().apply {
|
|
||||||
putString(ContentResolver.QUERY_ARG_SQL_SELECTION, "${MediaStore.Files.FileColumns._ID}=?")
|
|
||||||
putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, arrayOf(id.toString()))
|
|
||||||
putInt(MediaStore.QUERY_ARG_MATCH_TRASHED, MediaStore.MATCH_ONLY)
|
|
||||||
putInt(ContentResolver.QUERY_ARG_LIMIT, 1)
|
|
||||||
}
|
|
||||||
return ctx.contentResolver.query(filesUri, arrayOf(MediaStore.Files.FileColumns._ID), args, null)
|
|
||||||
?.use { it.moveToFirst() } == true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun contentUriForType(type: Int): Uri =
|
|
||||||
when (type) {
|
|
||||||
// Same order as AssetType from Dart.
|
|
||||||
1 -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
|
|
||||||
2 -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
|
|
||||||
3 -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
|
|
||||||
else -> MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onAttachedToActivity(binding: ActivityPluginBinding) {
|
|
||||||
activityBinding = binding
|
|
||||||
binding.addActivityResultListener(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onDetachedFromActivity() {
|
|
||||||
failPending("ACTIVITY_DETACHED", "Activity detached before trash result")
|
|
||||||
activityBinding?.removeActivityResultListener(this)
|
|
||||||
activityBinding = null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
|
|
||||||
if (requestCode == trashRequestCode) {
|
|
||||||
val callback = pendingResult
|
|
||||||
pendingResult = null
|
|
||||||
callback?.invoke(Result.success(resultCode == Activity.RESULT_OK))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun failPending(code: String, message: String) {
|
|
||||||
val callback = pendingResult ?: return
|
|
||||||
pendingResult = null
|
|
||||||
callback(Result.failure(FlutterError(code, message, null)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -553,7 +553,6 @@ interface NativeSyncApi {
|
|||||||
fun hashAssets(assetIds: List<String>, allowNetworkAccess: Boolean, callback: (Result<List<HashResult>>) -> Unit)
|
fun hashAssets(assetIds: List<String>, allowNetworkAccess: Boolean, callback: (Result<List<HashResult>>) -> Unit)
|
||||||
fun cancelHashing()
|
fun cancelHashing()
|
||||||
fun getTrashedAssets(): Map<String, List<PlatformAsset>>
|
fun getTrashedAssets(): Map<String, List<PlatformAsset>>
|
||||||
fun restoreFromTrashById(mediaId: String, type: Long, callback: (Result<Boolean>) -> Unit)
|
|
||||||
fun getCloudIdForAssetIds(assetIds: List<String>): List<CloudIdResult>
|
fun getCloudIdForAssetIds(assetIds: List<String>): List<CloudIdResult>
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -748,27 +747,6 @@ interface NativeSyncApi {
|
|||||||
channel.setMessageHandler(null)
|
channel.setMessageHandler(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
run {
|
|
||||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.restoreFromTrashById$separatedMessageChannelSuffix", codec)
|
|
||||||
if (api != null) {
|
|
||||||
channel.setMessageHandler { message, reply ->
|
|
||||||
val args = message as List<Any?>
|
|
||||||
val mediaIdArg = args[0] as String
|
|
||||||
val typeArg = args[1] as Long
|
|
||||||
api.restoreFromTrashById(mediaIdArg, typeArg) { result: Result<Boolean> ->
|
|
||||||
val error = result.exceptionOrNull()
|
|
||||||
if (error != null) {
|
|
||||||
reply.reply(MessagesPigeonUtils.wrapError(error))
|
|
||||||
} else {
|
|
||||||
val data = result.getOrNull()
|
|
||||||
reply.reply(MessagesPigeonUtils.wrapResult(data))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
channel.setMessageHandler(null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
run {
|
run {
|
||||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getCloudIdForAssetIds$separatedMessageChannelSuffix", codec, taskQueue)
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getCloudIdForAssetIds$separatedMessageChannelSuffix", codec, taskQueue)
|
||||||
if (api != null) {
|
if (api != null) {
|
||||||
|
|||||||
@@ -17,8 +17,6 @@ import com.bumptech.glide.Glide
|
|||||||
import com.bumptech.glide.load.ImageHeaderParser
|
import com.bumptech.glide.load.ImageHeaderParser
|
||||||
import com.bumptech.glide.load.ImageHeaderParserUtils
|
import com.bumptech.glide.load.ImageHeaderParserUtils
|
||||||
import com.bumptech.glide.load.resource.bitmap.DefaultImageHeaderParser
|
import com.bumptech.glide.load.resource.bitmap.DefaultImageHeaderParser
|
||||||
import io.flutter.embedding.engine.plugins.activity.ActivityAware
|
|
||||||
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@@ -41,11 +39,10 @@ sealed class AssetResult {
|
|||||||
private const val TAG = "NativeSyncApiImplBase"
|
private const val TAG = "NativeSyncApiImplBase"
|
||||||
|
|
||||||
@SuppressLint("InlinedApi")
|
@SuppressLint("InlinedApi")
|
||||||
open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAware {
|
open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() {
|
||||||
private val ctx: Context = context.applicationContext
|
private val ctx: Context = context.applicationContext
|
||||||
|
|
||||||
private var hashTask: Job? = null
|
private var hashTask: Job? = null
|
||||||
private val mediaTrashDelegate = MediaTrashDelegate(ctx)
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val MAX_CONCURRENT_HASH_OPERATIONS = 16
|
private const val MAX_CONCURRENT_HASH_OPERATIONS = 16
|
||||||
@@ -451,26 +448,6 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa
|
|||||||
hashTask = null
|
hashTask = null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun restoreFromTrashById(mediaId: String, type: Long, callback: (Result<Boolean>) -> Unit) {
|
|
||||||
mediaTrashDelegate.restoreFromTrashById(mediaId, type) { completeWhenActive(callback, it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
|
|
||||||
mediaTrashDelegate.onAttachedToActivity(binding)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDetachedFromActivityForConfigChanges() {
|
|
||||||
mediaTrashDelegate.onDetachedFromActivity()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
|
|
||||||
mediaTrashDelegate.onAttachedToActivity(binding)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDetachedFromActivity() {
|
|
||||||
mediaTrashDelegate.onDetachedFromActivity()
|
|
||||||
}
|
|
||||||
|
|
||||||
// This method is only implemented on iOS; on Android, we do not have a concept of cloud IDs
|
// This method is only implemented on iOS; on Android, we do not have a concept of cloud IDs
|
||||||
@Suppress("unused", "UNUSED_PARAMETER")
|
@Suppress("unused", "UNUSED_PARAMETER")
|
||||||
fun getCloudIdForAssetIds(assetIds: List<String>): List<CloudIdResult> {
|
fun getCloudIdForAssetIds(assetIds: List<String>): List<CloudIdResult> {
|
||||||
|
|||||||
@@ -19,8 +19,6 @@
|
|||||||
B21E34AC2E5B09190031FDB9 /* BackgroundWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B21E34AB2E5B09100031FDB9 /* BackgroundWorker.swift */; };
|
B21E34AC2E5B09190031FDB9 /* BackgroundWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B21E34AB2E5B09100031FDB9 /* BackgroundWorker.swift */; };
|
||||||
B25D377A2E72CA15008B6CA7 /* Connectivity.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = B25D37782E72CA15008B6CA7 /* Connectivity.g.swift */; };
|
B25D377A2E72CA15008B6CA7 /* Connectivity.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = B25D37782E72CA15008B6CA7 /* Connectivity.g.swift */; };
|
||||||
B25D377C2E72CA26008B6CA7 /* ConnectivityApiImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = B25D377B2E72CA20008B6CA7 /* ConnectivityApiImpl.swift */; };
|
B25D377C2E72CA26008B6CA7 /* ConnectivityApiImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = B25D377B2E72CA20008B6CA7 /* ConnectivityApiImpl.swift */; };
|
||||||
B2EE00022E72CA15008B6CA7 /* PermissionApi.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2EE00012E72CA15008B6CA7 /* PermissionApi.g.swift */; };
|
|
||||||
B2EE00042E72CA15008B6CA7 /* PermissionApiImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2EE00032E72CA15008B6CA7 /* PermissionApiImpl.swift */; };
|
|
||||||
B2BE315F2E5E5229006EEF88 /* BackgroundWorker.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2BE315E2E5E5229006EEF88 /* BackgroundWorker.g.swift */; };
|
B2BE315F2E5E5229006EEF88 /* BackgroundWorker.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2BE315E2E5E5229006EEF88 /* BackgroundWorker.g.swift */; };
|
||||||
D218389C4A4C4693F141F7D1 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 886774DBDDE6B35BF2B4F2CD /* Pods_Runner.framework */; };
|
D218389C4A4C4693F141F7D1 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 886774DBDDE6B35BF2B4F2CD /* Pods_Runner.framework */; };
|
||||||
F02538E92DFBCBDD008C3FA3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
F02538E92DFBCBDD008C3FA3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||||
@@ -107,8 +105,6 @@
|
|||||||
B21E34AB2E5B09100031FDB9 /* BackgroundWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundWorker.swift; sourceTree = "<group>"; };
|
B21E34AB2E5B09100031FDB9 /* BackgroundWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundWorker.swift; sourceTree = "<group>"; };
|
||||||
B25D37782E72CA15008B6CA7 /* Connectivity.g.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Connectivity.g.swift; sourceTree = "<group>"; };
|
B25D37782E72CA15008B6CA7 /* Connectivity.g.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Connectivity.g.swift; sourceTree = "<group>"; };
|
||||||
B25D377B2E72CA20008B6CA7 /* ConnectivityApiImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectivityApiImpl.swift; sourceTree = "<group>"; };
|
B25D377B2E72CA20008B6CA7 /* ConnectivityApiImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectivityApiImpl.swift; sourceTree = "<group>"; };
|
||||||
B2EE00012E72CA15008B6CA7 /* PermissionApi.g.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionApi.g.swift; sourceTree = "<group>"; };
|
|
||||||
B2EE00032E72CA15008B6CA7 /* PermissionApiImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionApiImpl.swift; sourceTree = "<group>"; };
|
|
||||||
B2BE315E2E5E5229006EEF88 /* BackgroundWorker.g.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundWorker.g.swift; sourceTree = "<group>"; };
|
B2BE315E2E5E5229006EEF88 /* BackgroundWorker.g.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundWorker.g.swift; sourceTree = "<group>"; };
|
||||||
E0E99CDC17B3EB7FA8BA2332 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
E0E99CDC17B3EB7FA8BA2332 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
F0B57D382DF764BD00DC5BCC /* WidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
F0B57D382DF764BD00DC5BCC /* WidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
@@ -287,7 +283,6 @@
|
|||||||
B25D37792E72CA15008B6CA7 /* Connectivity */,
|
B25D37792E72CA15008B6CA7 /* Connectivity */,
|
||||||
B21E34A62E5AF9760031FDB9 /* Background */,
|
B21E34A62E5AF9760031FDB9 /* Background */,
|
||||||
B2CF7F8C2DDE4EBB00744BF6 /* Sync */,
|
B2CF7F8C2DDE4EBB00744BF6 /* Sync */,
|
||||||
B2EE00052E72CA15008B6CA7 /* Permission */,
|
|
||||||
FA9973382CF6DF4B000EF859 /* Runner.entitlements */,
|
FA9973382CF6DF4B000EF859 /* Runner.entitlements */,
|
||||||
FAC7416727DB9F5500C668D8 /* RunnerProfile.entitlements */,
|
FAC7416727DB9F5500C668D8 /* RunnerProfile.entitlements */,
|
||||||
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||||
@@ -322,15 +317,6 @@
|
|||||||
path = Connectivity;
|
path = Connectivity;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
B2EE00052E72CA15008B6CA7 /* Permission */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
B2EE00032E72CA15008B6CA7 /* PermissionApiImpl.swift */,
|
|
||||||
B2EE00012E72CA15008B6CA7 /* PermissionApi.g.swift */,
|
|
||||||
);
|
|
||||||
path = Permission;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
FAC6F8B62D287F120078CB2F /* ShareExtension */ = {
|
FAC6F8B62D287F120078CB2F /* ShareExtension */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -633,8 +619,6 @@
|
|||||||
FE5499F42F1197D8006016CB /* RemoteImages.g.swift in Sources */,
|
FE5499F42F1197D8006016CB /* RemoteImages.g.swift in Sources */,
|
||||||
FE5FE4AE2F30FBC000A71243 /* ImageProcessing.swift in Sources */,
|
FE5FE4AE2F30FBC000A71243 /* ImageProcessing.swift in Sources */,
|
||||||
B25D377A2E72CA15008B6CA7 /* Connectivity.g.swift in Sources */,
|
B25D377A2E72CA15008B6CA7 /* Connectivity.g.swift in Sources */,
|
||||||
B2EE00022E72CA15008B6CA7 /* PermissionApi.g.swift in Sources */,
|
|
||||||
B2EE00042E72CA15008B6CA7 /* PermissionApiImpl.swift in Sources */,
|
|
||||||
FE5499F82F1198E2006016CB /* RemoteImagesImpl.swift in Sources */,
|
FE5499F82F1198E2006016CB /* RemoteImagesImpl.swift in Sources */,
|
||||||
FEAFA8732E4D42F4001E47FE /* Thumbhash.swift in Sources */,
|
FEAFA8732E4D42F4001E47FE /* Thumbhash.swift in Sources */,
|
||||||
B25D377C2E72CA26008B6CA7 /* ConnectivityApiImpl.swift in Sources */,
|
B25D377C2E72CA26008B6CA7 /* ConnectivityApiImpl.swift in Sources */,
|
||||||
@@ -734,7 +718,6 @@
|
|||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CUSTOM_GROUP_ID = group.app.immich.share.profile;
|
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
@@ -767,6 +750,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 240;
|
CURRENT_PROJECT_VERSION = 240;
|
||||||
|
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||||
DEVELOPMENT_TEAM = 2W7AC6T8T5;
|
DEVELOPMENT_TEAM = 2W7AC6T8T5;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
@@ -817,7 +801,6 @@
|
|||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CUSTOM_GROUP_ID = group.app.immich.share.debug;
|
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
ENABLE_TESTABILITY = YES;
|
ENABLE_TESTABILITY = YES;
|
||||||
@@ -877,7 +860,6 @@
|
|||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
@@ -912,6 +894,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 240;
|
CURRENT_PROJECT_VERSION = 240;
|
||||||
|
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||||
DEVELOPMENT_TEAM = 2W7AC6T8T5;
|
DEVELOPMENT_TEAM = 2W7AC6T8T5;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
@@ -941,6 +924,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 240;
|
CURRENT_PROJECT_VERSION = 240;
|
||||||
|
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||||
DEVELOPMENT_TEAM = 2W7AC6T8T5;
|
DEVELOPMENT_TEAM = 2W7AC6T8T5;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
@@ -1096,6 +1080,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 240;
|
CURRENT_PROJECT_VERSION = 240;
|
||||||
|
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||||
DEVELOPMENT_TEAM = 2W7AC6T8T5;
|
DEVELOPMENT_TEAM = 2W7AC6T8T5;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
@@ -1139,6 +1124,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 240;
|
CURRENT_PROJECT_VERSION = 240;
|
||||||
|
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||||
DEVELOPMENT_TEAM = 2W7AC6T8T5;
|
DEVELOPMENT_TEAM = 2W7AC6T8T5;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
@@ -1179,6 +1165,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 240;
|
CURRENT_PROJECT_VERSION = 240;
|
||||||
|
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||||
DEVELOPMENT_TEAM = 2W7AC6T8T5;
|
DEVELOPMENT_TEAM = 2W7AC6T8T5;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import native_video_player
|
|||||||
|
|
||||||
public static func registerPlugins(with registry: FlutterPluginRegistry, messenger: FlutterBinaryMessenger) {
|
public static func registerPlugins(with registry: FlutterPluginRegistry, messenger: FlutterBinaryMessenger) {
|
||||||
NativeSyncApiImpl.register(with: registry.registrar(forPlugin: NativeSyncApiImpl.name)!)
|
NativeSyncApiImpl.register(with: registry.registrar(forPlugin: NativeSyncApiImpl.name)!)
|
||||||
PermissionApiSetup.setUp(binaryMessenger: messenger, api: PermissionApiImpl())
|
|
||||||
LocalImageApiSetup.setUp(binaryMessenger: messenger, api: LocalImageApiImpl())
|
LocalImageApiSetup.setUp(binaryMessenger: messenger, api: LocalImageApiImpl())
|
||||||
RemoteImageApiSetup.setUp(binaryMessenger: messenger, api: RemoteImageApiImpl())
|
RemoteImageApiSetup.setUp(binaryMessenger: messenger, api: RemoteImageApiImpl())
|
||||||
BackgroundWorkerFgHostApiSetup.setUp(binaryMessenger: messenger, api: BackgroundWorkerApiImpl())
|
BackgroundWorkerFgHostApiSetup.setUp(binaryMessenger: messenger, api: BackgroundWorkerApiImpl())
|
||||||
|
|||||||
Generated
-14
@@ -288,7 +288,6 @@ protocol NetworkApi {
|
|||||||
func hasCertificate() throws -> Bool
|
func hasCertificate() throws -> Bool
|
||||||
func getClientPointer() throws -> Int64
|
func getClientPointer() throws -> Int64
|
||||||
func setRequestHeaders(headers: [String: String], serverUrls: [String], token: String?) throws
|
func setRequestHeaders(headers: [String: String], serverUrls: [String], token: String?) throws
|
||||||
func getAppGroupId() throws -> String
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
|
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
|
||||||
@@ -389,18 +388,5 @@ class NetworkApiSetup {
|
|||||||
} else {
|
} else {
|
||||||
setRequestHeadersChannel.setMessageHandler(nil)
|
setRequestHeadersChannel.setMessageHandler(nil)
|
||||||
}
|
}
|
||||||
let getAppGroupIdChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NetworkApi.getAppGroupId\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
|
||||||
if let api = api {
|
|
||||||
getAppGroupIdChannel.setMessageHandler { _, reply in
|
|
||||||
do {
|
|
||||||
let result = try api.getAppGroupId()
|
|
||||||
reply(wrapResult(result))
|
|
||||||
} catch {
|
|
||||||
reply(wrapError(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
getAppGroupIdChannel.setMessageHandler(nil)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,10 +61,6 @@ class NetworkApiImpl: NetworkApi {
|
|||||||
return Int64(Int(bitPattern: pointer))
|
return Int64(Int(bitPattern: pointer))
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAppGroupId() throws -> String {
|
|
||||||
return Bundle.main.object(forInfoDictionaryKey: "AppGroupId") as! String
|
|
||||||
}
|
|
||||||
|
|
||||||
func setRequestHeaders(headers: [String : String], serverUrls: [String], token: String?) throws {
|
func setRequestHeaders(headers: [String : String], serverUrls: [String], token: String?) throws {
|
||||||
URLSessionManager.setServerUrls(serverUrls)
|
URLSessionManager.setServerUrls(serverUrls)
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import native_video_player
|
|||||||
let CLIENT_CERT_LABEL = "app.alextran.immich.client_identity"
|
let CLIENT_CERT_LABEL = "app.alextran.immich.client_identity"
|
||||||
let HEADERS_KEY = "immich.request_headers"
|
let HEADERS_KEY = "immich.request_headers"
|
||||||
let SERVER_URLS_KEY = "immich.server_urls"
|
let SERVER_URLS_KEY = "immich.server_urls"
|
||||||
let APP_GROUP = Bundle.main.object(forInfoDictionaryKey: "AppGroupId") as! String
|
let APP_GROUP = "group.app.immich.share"
|
||||||
let COOKIE_EXPIRY_DAYS: TimeInterval = 400
|
let COOKIE_EXPIRY_DAYS: TimeInterval = 400
|
||||||
|
|
||||||
enum AuthCookie: CaseIterable {
|
enum AuthCookie: CaseIterable {
|
||||||
|
|||||||
-106
@@ -1,106 +0,0 @@
|
|||||||
// Autogenerated from Pigeon (v26.3.4), do not edit directly.
|
|
||||||
// See also: https://pub.dev/packages/pigeon
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
#if os(iOS)
|
|
||||||
import Flutter
|
|
||||||
#elseif os(macOS)
|
|
||||||
import FlutterMacOS
|
|
||||||
#else
|
|
||||||
#error("Unsupported platform.")
|
|
||||||
#endif
|
|
||||||
|
|
||||||
private func wrapResult(_ result: Any?) -> [Any?] {
|
|
||||||
return [result]
|
|
||||||
}
|
|
||||||
|
|
||||||
private func wrapError(_ error: Any) -> [Any?] {
|
|
||||||
if let pigeonError = error as? PigeonError {
|
|
||||||
return [
|
|
||||||
pigeonError.code,
|
|
||||||
pigeonError.message,
|
|
||||||
pigeonError.details,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
if let flutterError = error as? FlutterError {
|
|
||||||
return [
|
|
||||||
flutterError.code,
|
|
||||||
flutterError.message,
|
|
||||||
flutterError.details,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
return [
|
|
||||||
"\(error)",
|
|
||||||
"\(Swift.type(of: error))",
|
|
||||||
"Stacktrace: \(Thread.callStackSymbols)",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
private func isNullish(_ value: Any?) -> Bool {
|
|
||||||
return value is NSNull || value == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
private func nilOrValue<T>(_ value: Any?) -> T? {
|
|
||||||
if value is NSNull { return nil }
|
|
||||||
return value as! T?
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
|
|
||||||
protocol PermissionApi {
|
|
||||||
func hasManageMediaPermission() throws -> Bool
|
|
||||||
func requestManageMediaPermission(completion: @escaping (Result<Bool, Error>) -> Void)
|
|
||||||
func manageMediaPermission(completion: @escaping (Result<Bool, Error>) -> Void)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
|
|
||||||
class PermissionApiSetup {
|
|
||||||
static var codec: FlutterStandardMessageCodec { FlutterStandardMessageCodec.sharedInstance() }
|
|
||||||
/// Sets up an instance of `PermissionApi` to handle messages through the `binaryMessenger`.
|
|
||||||
static func setUp(binaryMessenger: FlutterBinaryMessenger, api: PermissionApi?, messageChannelSuffix: String = "") {
|
|
||||||
let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""
|
|
||||||
let hasManageMediaPermissionChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.PermissionApi.hasManageMediaPermission\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
|
||||||
if let api = api {
|
|
||||||
hasManageMediaPermissionChannel.setMessageHandler { _, reply in
|
|
||||||
do {
|
|
||||||
let result = try api.hasManageMediaPermission()
|
|
||||||
reply(wrapResult(result))
|
|
||||||
} catch {
|
|
||||||
reply(wrapError(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
hasManageMediaPermissionChannel.setMessageHandler(nil)
|
|
||||||
}
|
|
||||||
let requestManageMediaPermissionChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.PermissionApi.requestManageMediaPermission\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
|
||||||
if let api = api {
|
|
||||||
requestManageMediaPermissionChannel.setMessageHandler { _, reply in
|
|
||||||
api.requestManageMediaPermission { result in
|
|
||||||
switch result {
|
|
||||||
case .success(let res):
|
|
||||||
reply(wrapResult(res))
|
|
||||||
case .failure(let error):
|
|
||||||
reply(wrapError(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
requestManageMediaPermissionChannel.setMessageHandler(nil)
|
|
||||||
}
|
|
||||||
let manageMediaPermissionChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.PermissionApi.manageMediaPermission\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
|
||||||
if let api = api {
|
|
||||||
manageMediaPermissionChannel.setMessageHandler { _, reply in
|
|
||||||
api.manageMediaPermission { result in
|
|
||||||
switch result {
|
|
||||||
case .success(let res):
|
|
||||||
reply(wrapResult(res))
|
|
||||||
case .failure(let error):
|
|
||||||
reply(wrapError(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
manageMediaPermissionChannel.setMessageHandler(nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
class PermissionApiImpl: PermissionApi {
|
|
||||||
func hasManageMediaPermission() throws -> Bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func requestManageMediaPermission(completion: @escaping (Result<Bool, Error>) -> Void) {
|
|
||||||
completion(.success(false))
|
|
||||||
}
|
|
||||||
|
|
||||||
func manageMediaPermission(completion: @escaping (Result<Bool, Error>) -> Void) {
|
|
||||||
completion(.success(false))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.application-groups</key>
|
<key>com.apple.security.application-groups</key>
|
||||||
<array>
|
<array>
|
||||||
<string>$(CUSTOM_GROUP_ID)</string>
|
<string>group.app.immich.share</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.application-groups</key>
|
<key>com.apple.security.application-groups</key>
|
||||||
<array>
|
<array>
|
||||||
<string>$(CUSTOM_GROUP_ID)</string>
|
<string>group.app.immich.share</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
Generated
-19
@@ -537,7 +537,6 @@ protocol NativeSyncApi {
|
|||||||
func hashAssets(assetIds: [String], allowNetworkAccess: Bool, completion: @escaping (Result<[HashResult], Error>) -> Void)
|
func hashAssets(assetIds: [String], allowNetworkAccess: Bool, completion: @escaping (Result<[HashResult], Error>) -> Void)
|
||||||
func cancelHashing() throws
|
func cancelHashing() throws
|
||||||
func getTrashedAssets() throws -> [String: [PlatformAsset]]
|
func getTrashedAssets() throws -> [String: [PlatformAsset]]
|
||||||
func restoreFromTrashById(mediaId: String, type: Int64, completion: @escaping (Result<Bool, Error>) -> Void)
|
|
||||||
func getCloudIdForAssetIds(assetIds: [String]) throws -> [CloudIdResult]
|
func getCloudIdForAssetIds(assetIds: [String]) throws -> [CloudIdResult]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -722,24 +721,6 @@ class NativeSyncApiSetup {
|
|||||||
} else {
|
} else {
|
||||||
getTrashedAssetsChannel.setMessageHandler(nil)
|
getTrashedAssetsChannel.setMessageHandler(nil)
|
||||||
}
|
}
|
||||||
let restoreFromTrashByIdChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.restoreFromTrashById\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
|
||||||
if let api = api {
|
|
||||||
restoreFromTrashByIdChannel.setMessageHandler { message, reply in
|
|
||||||
let args = message as! [Any?]
|
|
||||||
let mediaIdArg = args[0] as! String
|
|
||||||
let typeArg = args[1] as! Int64
|
|
||||||
api.restoreFromTrashById(mediaId: mediaIdArg, type: typeArg) { result in
|
|
||||||
switch result {
|
|
||||||
case .success(let res):
|
|
||||||
reply(wrapResult(res))
|
|
||||||
case .failure(let error):
|
|
||||||
reply(wrapError(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
restoreFromTrashByIdChannel.setMessageHandler(nil)
|
|
||||||
}
|
|
||||||
let getCloudIdForAssetIdsChannel = taskQueue == nil
|
let getCloudIdForAssetIdsChannel = taskQueue == nil
|
||||||
? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getCloudIdForAssetIds\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getCloudIdForAssetIds\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
||||||
: FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getCloudIdForAssetIds\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue)
|
: FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getCloudIdForAssetIds\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue)
|
||||||
|
|||||||
@@ -383,10 +383,6 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
|
|||||||
throw PigeonError(code: "UNSUPPORTED_OS", message: "This feature not supported on iOS.", details: nil)
|
throw PigeonError(code: "UNSUPPORTED_OS", message: "This feature not supported on iOS.", details: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func restoreFromTrashById(mediaId: String, type: Int64, completion: @escaping (Result<Bool, Error>) -> Void) {
|
|
||||||
completion(.success(false))
|
|
||||||
}
|
|
||||||
|
|
||||||
private func getAssetsFromAlbum(in album: PHAssetCollection, options: PHFetchOptions) -> PHFetchResult<PHAsset> {
|
private func getAssetsFromAlbum(in album: PHAssetCollection, options: PHFetchOptions) -> PHFetchResult<PHAsset> {
|
||||||
// Ensure to actually getting all assets for the Recents album
|
// Ensure to actually getting all assets for the Recents album
|
||||||
if (album.assetCollectionSubtype == .smartAlbumUserLibrary) {
|
if (album.assetCollectionSubtype == .smartAlbumUserLibrary) {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<dict>
|
<dict>
|
||||||
<key>com.apple.security.application-groups</key>
|
<key>com.apple.security.application-groups</key>
|
||||||
<array>
|
<array>
|
||||||
<string>$(CUSTOM_GROUP_ID)</string>
|
<string>group.app.immich.share</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
@@ -2,7 +2,7 @@ import Foundation
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import WidgetKit
|
import WidgetKit
|
||||||
|
|
||||||
let IMMICH_SHARE_GROUP = Bundle.main.object(forInfoDictionaryKey: "AppGroupId") as! String
|
let IMMICH_SHARE_GROUP = "group.app.immich.share"
|
||||||
|
|
||||||
enum WidgetError: Error, Codable {
|
enum WidgetError: Error, Codable {
|
||||||
case noLogin
|
case noLogin
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>AppGroupId</key>
|
|
||||||
<string>$(CUSTOM_GROUP_ID)</string>
|
|
||||||
<key>NSAppTransportSecurity</key>
|
<key>NSAppTransportSecurity</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSAllowsArbitraryLoads</key>
|
<key>NSAllowsArbitraryLoads</key>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<dict>
|
<dict>
|
||||||
<key>com.apple.security.application-groups</key>
|
<key>com.apple.security.application-groups</key>
|
||||||
<array>
|
<array>
|
||||||
<string>$(CUSTOM_GROUP_ID)</string>
|
<string>group.app.immich.share</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
@@ -21,7 +21,6 @@ platform :ios do
|
|||||||
CODE_SIGN_IDENTITY = "Apple Distribution: FUTO Holdings, Inc. (#{TEAM_ID})"
|
CODE_SIGN_IDENTITY = "Apple Distribution: FUTO Holdings, Inc. (#{TEAM_ID})"
|
||||||
BASE_BUNDLE_ID = "app.alextran.immich"
|
BASE_BUNDLE_ID = "app.alextran.immich"
|
||||||
DEV_BUNDLE_ID = "tech.futo.immich.testflight"
|
DEV_BUNDLE_ID = "tech.futo.immich.testflight"
|
||||||
DEV_GROUP_ID = "group.app.immich.share.testflight"
|
|
||||||
|
|
||||||
# Helper method to get App Store Connect API key
|
# Helper method to get App Store Connect API key
|
||||||
def get_api_key
|
def get_api_key
|
||||||
@@ -34,13 +33,6 @@ platform :ios do
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Helper method to assemble xcargs with optional CUSTOM_GROUP_ID override
|
|
||||||
def build_xcargs(group_id: nil)
|
|
||||||
args = "-skipMacroValidation CODE_SIGN_IDENTITY='#{CODE_SIGN_IDENTITY}' CODE_SIGN_STYLE=Manual"
|
|
||||||
args += " CUSTOM_GROUP_ID='#{group_id}'" if group_id
|
|
||||||
args
|
|
||||||
end
|
|
||||||
|
|
||||||
# Helper method to get version from pubspec.yaml
|
# Helper method to get version from pubspec.yaml
|
||||||
def get_version_from_pubspec
|
def get_version_from_pubspec
|
||||||
require 'yaml'
|
require 'yaml'
|
||||||
@@ -97,8 +89,7 @@ end
|
|||||||
version_number: nil,
|
version_number: nil,
|
||||||
profile_name_main:,
|
profile_name_main:,
|
||||||
profile_name_share:,
|
profile_name_share:,
|
||||||
profile_name_widget:,
|
profile_name_widget:
|
||||||
group_id: nil
|
|
||||||
)
|
)
|
||||||
app_identifier = base_bundle_id
|
app_identifier = base_bundle_id
|
||||||
|
|
||||||
@@ -122,7 +113,7 @@ end
|
|||||||
workspace: "Runner.xcworkspace",
|
workspace: "Runner.xcworkspace",
|
||||||
configuration: configuration,
|
configuration: configuration,
|
||||||
export_method: "app-store",
|
export_method: "app-store",
|
||||||
xcargs: build_xcargs(group_id: group_id),
|
xcargs: "-skipMacroValidation CODE_SIGN_IDENTITY='#{CODE_SIGN_IDENTITY}' CODE_SIGN_STYLE=Manual",
|
||||||
export_options: {
|
export_options: {
|
||||||
provisioningProfiles: {
|
provisioningProfiles: {
|
||||||
"#{app_identifier}" => profile_name_main,
|
"#{app_identifier}" => profile_name_main,
|
||||||
@@ -174,8 +165,7 @@ end
|
|||||||
distribute_external: false,
|
distribute_external: false,
|
||||||
profile_name_main: main_profile_name,
|
profile_name_main: main_profile_name,
|
||||||
profile_name_share: share_profile_name,
|
profile_name_share: share_profile_name,
|
||||||
profile_name_widget: widget_profile_name,
|
profile_name_widget: widget_profile_name
|
||||||
group_id: DEV_GROUP_ID
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -284,7 +274,7 @@ end
|
|||||||
configuration: "Release",
|
configuration: "Release",
|
||||||
export_method: "app-store",
|
export_method: "app-store",
|
||||||
skip_package_ipa: true,
|
skip_package_ipa: true,
|
||||||
xcargs: build_xcargs(group_id: DEV_GROUP_ID),
|
xcargs: "-skipMacroValidation CODE_SIGN_IDENTITY='#{CODE_SIGN_IDENTITY}' CODE_SIGN_STYLE=Manual",
|
||||||
export_options: {
|
export_options: {
|
||||||
provisioningProfiles: {
|
provisioningProfiles: {
|
||||||
DEV_BUNDLE_ID => main_profile_name,
|
DEV_BUNDLE_ID => main_profile_name,
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ const int kTimelineAssetLoadBatchSize = 1024;
|
|||||||
const int kTimelineAssetLoadOppositeSize = 64;
|
const int kTimelineAssetLoadOppositeSize = 64;
|
||||||
|
|
||||||
// Widget keys
|
// Widget keys
|
||||||
|
const String appShareGroupId = "group.app.immich.share";
|
||||||
const String kWidgetAuthToken = "widget_auth_token";
|
const String kWidgetAuthToken = "widget_auth_token";
|
||||||
const String kWidgetServerEndpoint = "widget_server_url";
|
const String kWidgetServerEndpoint = "widget_server_url";
|
||||||
const String kWidgetCustomHeaders = "widget_custom_headers";
|
const String kWidgetCustomHeaders = "widget_custom_headers";
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ import 'package:immich_mobile/entities/store.entity.dart';
|
|||||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart';
|
||||||
import 'package:immich_mobile/platform/native_sync_api.g.dart';
|
import 'package:immich_mobile/platform/native_sync_api.g.dart';
|
||||||
import 'package:immich_mobile/repositories/asset_media.repository.dart';
|
import 'package:immich_mobile/repositories/local_files_manager.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/permission.repository.dart';
|
|
||||||
import 'package:immich_mobile/utils/datetime_helpers.dart';
|
import 'package:immich_mobile/utils/datetime_helpers.dart';
|
||||||
import 'package:immich_mobile/utils/diff.dart';
|
import 'package:immich_mobile/utils/diff.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
@@ -23,29 +23,29 @@ class LocalSyncService {
|
|||||||
final DriftLocalAssetRepository _localAssetRepository;
|
final DriftLocalAssetRepository _localAssetRepository;
|
||||||
final NativeSyncApi _nativeSyncApi;
|
final NativeSyncApi _nativeSyncApi;
|
||||||
final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository;
|
final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository;
|
||||||
final AssetMediaRepository _assetMediaRepository;
|
final LocalFilesManagerRepository _localFilesManager;
|
||||||
final IPermissionRepository _permissionRepository;
|
final StorageRepository _storageRepository;
|
||||||
final Logger _log = Logger("DeviceSyncService");
|
final Logger _log = Logger("DeviceSyncService");
|
||||||
|
|
||||||
LocalSyncService({
|
LocalSyncService({
|
||||||
required DriftLocalAlbumRepository localAlbumRepository,
|
required DriftLocalAlbumRepository localAlbumRepository,
|
||||||
required DriftLocalAssetRepository localAssetRepository,
|
required DriftLocalAssetRepository localAssetRepository,
|
||||||
required DriftTrashedLocalAssetRepository trashedLocalAssetRepository,
|
required DriftTrashedLocalAssetRepository trashedLocalAssetRepository,
|
||||||
required AssetMediaRepository assetMediaRepository,
|
required LocalFilesManagerRepository localFilesManager,
|
||||||
required IPermissionRepository permissionRepository,
|
required StorageRepository storageRepository,
|
||||||
required NativeSyncApi nativeSyncApi,
|
required NativeSyncApi nativeSyncApi,
|
||||||
}) : _localAlbumRepository = localAlbumRepository,
|
}) : _localAlbumRepository = localAlbumRepository,
|
||||||
_localAssetRepository = localAssetRepository,
|
_localAssetRepository = localAssetRepository,
|
||||||
_trashedLocalAssetRepository = trashedLocalAssetRepository,
|
_trashedLocalAssetRepository = trashedLocalAssetRepository,
|
||||||
_assetMediaRepository = assetMediaRepository,
|
_localFilesManager = localFilesManager,
|
||||||
_permissionRepository = permissionRepository,
|
_storageRepository = storageRepository,
|
||||||
_nativeSyncApi = nativeSyncApi;
|
_nativeSyncApi = nativeSyncApi;
|
||||||
|
|
||||||
Future<void> sync({bool full = false}) async {
|
Future<void> sync({bool full = false}) async {
|
||||||
final Stopwatch stopwatch = Stopwatch()..start();
|
final Stopwatch stopwatch = Stopwatch()..start();
|
||||||
try {
|
try {
|
||||||
if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) {
|
if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) {
|
||||||
final hasPermission = await _permissionRepository.hasManageMediaPermission();
|
final hasPermission = await _localFilesManager.hasManageMediaPermission();
|
||||||
if (hasPermission) {
|
if (hasPermission) {
|
||||||
await _syncTrashedAssets();
|
await _syncTrashedAssets();
|
||||||
} else {
|
} else {
|
||||||
@@ -373,7 +373,7 @@ class LocalSyncService {
|
|||||||
|
|
||||||
final assetsToRestore = await _trashedLocalAssetRepository.getToRestore();
|
final assetsToRestore = await _trashedLocalAssetRepository.getToRestore();
|
||||||
if (assetsToRestore.isNotEmpty) {
|
if (assetsToRestore.isNotEmpty) {
|
||||||
final restoredIds = await _assetMediaRepository.restoreAssetsFromTrash(assetsToRestore);
|
final restoredIds = await _localFilesManager.restoreAssetsFromTrash(assetsToRestore);
|
||||||
await _trashedLocalAssetRepository.applyRestoredAssets(restoredIds);
|
await _trashedLocalAssetRepository.applyRestoredAssets(restoredIds);
|
||||||
} else {
|
} else {
|
||||||
_log.info("syncTrashedAssets, No remote assets found for restoration");
|
_log.info("syncTrashedAssets, No remote assets found for restoration");
|
||||||
@@ -381,15 +381,15 @@ class LocalSyncService {
|
|||||||
|
|
||||||
final localAssetsToTrash = await _trashedLocalAssetRepository.getToTrash();
|
final localAssetsToTrash = await _trashedLocalAssetRepository.getToTrash();
|
||||||
if (localAssetsToTrash.isNotEmpty) {
|
if (localAssetsToTrash.isNotEmpty) {
|
||||||
final localIds = localAssetsToTrash.values.expand((assets) => assets).map((asset) => asset.id).toList();
|
final mediaUrls = await Future.wait(
|
||||||
_log.info("Moving to trash ${localIds.join(", ")} assets");
|
localAssetsToTrash.values
|
||||||
final movedIds = await _assetMediaRepository.deleteAll(localIds);
|
.expand((e) => e)
|
||||||
if (movedIds.isNotEmpty) {
|
.map((localAsset) => _storageRepository.getAssetEntityForAsset(localAsset).then((e) => e?.getMediaUrl())),
|
||||||
final movedAssetsByAlbum = localAssetsToTrash.map(
|
);
|
||||||
(albumId, assets) => MapEntry(albumId, assets.where((asset) => movedIds.contains(asset.id)).toList()),
|
_log.info("Moving to trash ${mediaUrls.join(", ")} assets");
|
||||||
)..removeWhere((_, assets) => assets.isEmpty);
|
final result = await _localFilesManager.moveToTrash(mediaUrls.nonNulls.toList());
|
||||||
|
if (result) {
|
||||||
await _trashedLocalAssetRepository.trashLocalAsset(movedAssetsByAlbum);
|
await _trashedLocalAssetRepository.trashLocalAsset(localAssetsToTrash);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_log.info("syncTrashedAssets, No assets found in backup-enabled albums for move to trash");
|
_log.info("syncTrashedAssets, No assets found in backup-enabled albums for move to trash");
|
||||||
|
|||||||
@@ -9,12 +9,12 @@ import 'package:immich_mobile/domain/models/sync_event.model.dart';
|
|||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/sync_migration.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/sync_migration.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/asset_media.repository.dart';
|
import 'package:immich_mobile/repositories/local_files_manager.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/permission.repository.dart';
|
|
||||||
import 'package:immich_mobile/services/api.service.dart';
|
import 'package:immich_mobile/services/api.service.dart';
|
||||||
import 'package:immich_mobile/utils/semver.dart';
|
import 'package:immich_mobile/utils/semver.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
@@ -34,8 +34,8 @@ class SyncStreamService {
|
|||||||
final SyncStreamRepository _syncStreamRepository;
|
final SyncStreamRepository _syncStreamRepository;
|
||||||
final DriftLocalAssetRepository _localAssetRepository;
|
final DriftLocalAssetRepository _localAssetRepository;
|
||||||
final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository;
|
final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository;
|
||||||
final AssetMediaRepository _assetMediaRepository;
|
final LocalFilesManagerRepository _localFilesManager;
|
||||||
final IPermissionRepository _permissionRepository;
|
final StorageRepository _storageRepository;
|
||||||
final SyncMigrationRepository _syncMigrationRepository;
|
final SyncMigrationRepository _syncMigrationRepository;
|
||||||
final ApiService _api;
|
final ApiService _api;
|
||||||
final bool Function()? _cancelChecker;
|
final bool Function()? _cancelChecker;
|
||||||
@@ -45,8 +45,8 @@ class SyncStreamService {
|
|||||||
required SyncStreamRepository syncStreamRepository,
|
required SyncStreamRepository syncStreamRepository,
|
||||||
required DriftLocalAssetRepository localAssetRepository,
|
required DriftLocalAssetRepository localAssetRepository,
|
||||||
required DriftTrashedLocalAssetRepository trashedLocalAssetRepository,
|
required DriftTrashedLocalAssetRepository trashedLocalAssetRepository,
|
||||||
required AssetMediaRepository assetMediaRepository,
|
required LocalFilesManagerRepository localFilesManager,
|
||||||
required IPermissionRepository permissionRepository,
|
required StorageRepository storageRepository,
|
||||||
required SyncMigrationRepository syncMigrationRepository,
|
required SyncMigrationRepository syncMigrationRepository,
|
||||||
required ApiService api,
|
required ApiService api,
|
||||||
bool Function()? cancelChecker,
|
bool Function()? cancelChecker,
|
||||||
@@ -54,8 +54,8 @@ class SyncStreamService {
|
|||||||
_syncStreamRepository = syncStreamRepository,
|
_syncStreamRepository = syncStreamRepository,
|
||||||
_localAssetRepository = localAssetRepository,
|
_localAssetRepository = localAssetRepository,
|
||||||
_trashedLocalAssetRepository = trashedLocalAssetRepository,
|
_trashedLocalAssetRepository = trashedLocalAssetRepository,
|
||||||
_assetMediaRepository = assetMediaRepository,
|
_localFilesManager = localFilesManager,
|
||||||
_permissionRepository = permissionRepository,
|
_storageRepository = storageRepository,
|
||||||
_syncMigrationRepository = syncMigrationRepository,
|
_syncMigrationRepository = syncMigrationRepository,
|
||||||
_api = api,
|
_api = api,
|
||||||
_cancelChecker = cancelChecker;
|
_cancelChecker = cancelChecker;
|
||||||
@@ -500,22 +500,22 @@ class SyncStreamService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _trashLocalAssets(Map<String, List<LocalAsset>> localAssetsToTrash) async {
|
Future<void> _trashLocalAssets(Map<String, List<LocalAsset>> localAssetsToTrash) async {
|
||||||
final localIds = localAssetsToTrash.values.expand((assets) => assets).map((asset) => asset.id).toList();
|
final mediaUrls = await Future.wait(
|
||||||
_logger.info("Moving to trash ${localIds.join(", ")} assets");
|
localAssetsToTrash.values
|
||||||
final movedIds = await _assetMediaRepository.deleteAll(localIds);
|
.expand((e) => e)
|
||||||
if (movedIds.isNotEmpty) {
|
.map((localAsset) => _storageRepository.getAssetEntityForAsset(localAsset).then((e) => e?.getMediaUrl())),
|
||||||
final movedAssetsByAlbum = localAssetsToTrash.map(
|
);
|
||||||
(albumId, assets) => MapEntry(albumId, assets.where((asset) => movedIds.contains(asset.id)).toList()),
|
_logger.info("Moving to trash ${mediaUrls.join(", ")} assets");
|
||||||
)..removeWhere((_, assets) => assets.isEmpty);
|
final result = await _localFilesManager.moveToTrash(mediaUrls.nonNulls.toList());
|
||||||
|
if (result) {
|
||||||
await _trashedLocalAssetRepository.trashLocalAsset(movedAssetsByAlbum);
|
await _trashedLocalAssetRepository.trashLocalAsset(localAssetsToTrash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _applyRemoteRestoreToLocal() async {
|
Future<void> _applyRemoteRestoreToLocal() async {
|
||||||
final assetsToRestore = await _trashedLocalAssetRepository.getToRestore();
|
final assetsToRestore = await _trashedLocalAssetRepository.getToRestore();
|
||||||
if (assetsToRestore.isNotEmpty) {
|
if (assetsToRestore.isNotEmpty) {
|
||||||
final restoredIds = await _assetMediaRepository.restoreAssetsFromTrash(assetsToRestore);
|
final restoredIds = await _localFilesManager.restoreAssetsFromTrash(assetsToRestore);
|
||||||
await _trashedLocalAssetRepository.applyRestoredAssets(restoredIds);
|
await _trashedLocalAssetRepository.applyRestoredAssets(restoredIds);
|
||||||
} else {
|
} else {
|
||||||
_logger.info("No remote assets found for restoration");
|
_logger.info("No remote assets found for restoration");
|
||||||
@@ -523,7 +523,7 @@ class SyncStreamService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _syncAssetTrashStatus(List<String> remoteIds) async {
|
Future<void> _syncAssetTrashStatus(List<String> remoteIds) async {
|
||||||
if (!(await _permissionRepository.hasManageMediaPermission())) {
|
if (!(await _localFilesManager.hasManageMediaPermission())) {
|
||||||
_logger.warning("Syncing asset trash status cannot proceed because MANAGE_MEDIA permission is missing");
|
_logger.warning("Syncing asset trash status cannot proceed because MANAGE_MEDIA permission is missing");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -533,7 +533,7 @@ class SyncStreamService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _syncAssetDeletion(List<String> remoteIds) async {
|
Future<void> _syncAssetDeletion(List<String> remoteIds) async {
|
||||||
if (!(await _permissionRepository.hasManageMediaPermission())) {
|
if (!(await _localFilesManager.hasManageMediaPermission())) {
|
||||||
_logger.warning("Syncing asset deletion cannot proceed because MANAGE_MEDIA permission is missing");
|
_logger.warning("Syncing asset deletion cannot proceed because MANAGE_MEDIA permission is missing");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
-19
@@ -654,25 +654,6 @@ class NativeSyncApi {
|
|||||||
return (pigeonVar_replyValue! as Map<Object?, Object?>).cast<String, List<PlatformAsset>>();
|
return (pigeonVar_replyValue! as Map<Object?, Object?>).cast<String, List<PlatformAsset>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> restoreFromTrashById(String mediaId, int type) async {
|
|
||||||
final pigeonVar_channelName =
|
|
||||||
'dev.flutter.pigeon.immich_mobile.NativeSyncApi.restoreFromTrashById$pigeonVar_messageChannelSuffix';
|
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
|
||||||
pigeonVar_channelName,
|
|
||||||
pigeonChannelCodec,
|
|
||||||
binaryMessenger: pigeonVar_binaryMessenger,
|
|
||||||
);
|
|
||||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[mediaId, type]);
|
|
||||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
|
||||||
|
|
||||||
final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
|
|
||||||
pigeonVar_replyList,
|
|
||||||
pigeonVar_channelName,
|
|
||||||
isNullValid: false,
|
|
||||||
);
|
|
||||||
return pigeonVar_replyValue! as bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<CloudIdResult>> getCloudIdForAssetIds(List<String> assetIds) async {
|
Future<List<CloudIdResult>> getCloudIdForAssetIds(List<String> assetIds) async {
|
||||||
final pigeonVar_channelName =
|
final pigeonVar_channelName =
|
||||||
'dev.flutter.pigeon.immich_mobile.NativeSyncApi.getCloudIdForAssetIds$pigeonVar_messageChannelSuffix';
|
'dev.flutter.pigeon.immich_mobile.NativeSyncApi.getCloudIdForAssetIds$pigeonVar_messageChannelSuffix';
|
||||||
|
|||||||
Generated
-19
@@ -309,23 +309,4 @@ class NetworkApi {
|
|||||||
|
|
||||||
_extractReplyValueOrThrow(pigeonVar_replyList, pigeonVar_channelName, isNullValid: true);
|
_extractReplyValueOrThrow(pigeonVar_replyList, pigeonVar_channelName, isNullValid: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> getAppGroupId() async {
|
|
||||||
final pigeonVar_channelName =
|
|
||||||
'dev.flutter.pigeon.immich_mobile.NetworkApi.getAppGroupId$pigeonVar_messageChannelSuffix';
|
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
|
||||||
pigeonVar_channelName,
|
|
||||||
pigeonChannelCodec,
|
|
||||||
binaryMessenger: pigeonVar_binaryMessenger,
|
|
||||||
);
|
|
||||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
|
|
||||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
|
||||||
|
|
||||||
final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
|
|
||||||
pigeonVar_replyList,
|
|
||||||
pigeonVar_channelName,
|
|
||||||
isNullValid: false,
|
|
||||||
);
|
|
||||||
return pigeonVar_replyValue! as String;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
-119
@@ -1,119 +0,0 @@
|
|||||||
// Autogenerated from Pigeon (v26.3.4), do not edit directly.
|
|
||||||
// See also: https://pub.dev/packages/pigeon
|
|
||||||
// ignore_for_file: unused_import, unused_shown_name
|
|
||||||
// ignore_for_file: type=lint
|
|
||||||
|
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:typed_data' show Float64List, Int32List, Int64List;
|
|
||||||
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:meta/meta.dart' show immutable, protected, visibleForTesting;
|
|
||||||
|
|
||||||
Object? _extractReplyValueOrThrow(List<Object?>? replyList, String channelName, {required bool isNullValid}) {
|
|
||||||
if (replyList == null) {
|
|
||||||
throw PlatformException(
|
|
||||||
code: 'channel-error',
|
|
||||||
message: 'Unable to establish connection on channel: "$channelName".',
|
|
||||||
);
|
|
||||||
} else if (replyList.length > 1) {
|
|
||||||
throw PlatformException(code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2]);
|
|
||||||
} else if (!isNullValid && (replyList.isNotEmpty && replyList[0] == null)) {
|
|
||||||
throw PlatformException(
|
|
||||||
code: 'null-error',
|
|
||||||
message: 'Host platform returned null value for non-null return value.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return replyList.firstOrNull;
|
|
||||||
}
|
|
||||||
|
|
||||||
class _PigeonCodec extends StandardMessageCodec {
|
|
||||||
const _PigeonCodec();
|
|
||||||
@override
|
|
||||||
void writeValue(WriteBuffer buffer, Object? value) {
|
|
||||||
if (value is int) {
|
|
||||||
buffer.putUint8(4);
|
|
||||||
buffer.putInt64(value);
|
|
||||||
} else {
|
|
||||||
super.writeValue(buffer, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Object? readValueOfType(int type, ReadBuffer buffer) {
|
|
||||||
switch (type) {
|
|
||||||
default:
|
|
||||||
return super.readValueOfType(type, buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class PermissionApi {
|
|
||||||
/// Constructor for [PermissionApi]. The [binaryMessenger] named argument is
|
|
||||||
/// available for dependency injection. If it is left null, the default
|
|
||||||
/// BinaryMessenger will be used which routes to the host platform.
|
|
||||||
PermissionApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
|
|
||||||
: pigeonVar_binaryMessenger = binaryMessenger,
|
|
||||||
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
|
||||||
final BinaryMessenger? pigeonVar_binaryMessenger;
|
|
||||||
|
|
||||||
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
|
||||||
|
|
||||||
final String pigeonVar_messageChannelSuffix;
|
|
||||||
|
|
||||||
Future<bool> hasManageMediaPermission() async {
|
|
||||||
final pigeonVar_channelName =
|
|
||||||
'dev.flutter.pigeon.immich_mobile.PermissionApi.hasManageMediaPermission$pigeonVar_messageChannelSuffix';
|
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
|
||||||
pigeonVar_channelName,
|
|
||||||
pigeonChannelCodec,
|
|
||||||
binaryMessenger: pigeonVar_binaryMessenger,
|
|
||||||
);
|
|
||||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
|
|
||||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
|
||||||
|
|
||||||
final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
|
|
||||||
pigeonVar_replyList,
|
|
||||||
pigeonVar_channelName,
|
|
||||||
isNullValid: false,
|
|
||||||
);
|
|
||||||
return pigeonVar_replyValue! as bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> requestManageMediaPermission() async {
|
|
||||||
final pigeonVar_channelName =
|
|
||||||
'dev.flutter.pigeon.immich_mobile.PermissionApi.requestManageMediaPermission$pigeonVar_messageChannelSuffix';
|
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
|
||||||
pigeonVar_channelName,
|
|
||||||
pigeonChannelCodec,
|
|
||||||
binaryMessenger: pigeonVar_binaryMessenger,
|
|
||||||
);
|
|
||||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
|
|
||||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
|
||||||
|
|
||||||
final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
|
|
||||||
pigeonVar_replyList,
|
|
||||||
pigeonVar_channelName,
|
|
||||||
isNullValid: false,
|
|
||||||
);
|
|
||||||
return pigeonVar_replyValue! as bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> manageMediaPermission() async {
|
|
||||||
final pigeonVar_channelName =
|
|
||||||
'dev.flutter.pigeon.immich_mobile.PermissionApi.manageMediaPermission$pigeonVar_messageChannelSuffix';
|
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
|
||||||
pigeonVar_channelName,
|
|
||||||
pigeonChannelCodec,
|
|
||||||
binaryMessenger: pigeonVar_binaryMessenger,
|
|
||||||
);
|
|
||||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
|
|
||||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
|
||||||
|
|
||||||
final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
|
|
||||||
pigeonVar_replyList,
|
|
||||||
pigeonVar_channelName,
|
|
||||||
isNullValid: false,
|
|
||||||
);
|
|
||||||
return pigeonVar_replyValue! as bool;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,10 +3,9 @@ import 'package:immich_mobile/domain/services/background_worker.service.dart';
|
|||||||
import 'package:immich_mobile/platform/background_worker_api.g.dart';
|
import 'package:immich_mobile/platform/background_worker_api.g.dart';
|
||||||
import 'package:immich_mobile/platform/background_worker_lock_api.g.dart';
|
import 'package:immich_mobile/platform/background_worker_lock_api.g.dart';
|
||||||
import 'package:immich_mobile/platform/connectivity_api.g.dart';
|
import 'package:immich_mobile/platform/connectivity_api.g.dart';
|
||||||
import 'package:immich_mobile/platform/local_image_api.g.dart';
|
|
||||||
import 'package:immich_mobile/platform/native_sync_api.g.dart';
|
import 'package:immich_mobile/platform/native_sync_api.g.dart';
|
||||||
|
import 'package:immich_mobile/platform/local_image_api.g.dart';
|
||||||
import 'package:immich_mobile/platform/network_api.g.dart';
|
import 'package:immich_mobile/platform/network_api.g.dart';
|
||||||
import 'package:immich_mobile/platform/permission_api.g.dart';
|
|
||||||
import 'package:immich_mobile/platform/remote_image_api.g.dart';
|
import 'package:immich_mobile/platform/remote_image_api.g.dart';
|
||||||
|
|
||||||
final backgroundWorkerFgServiceProvider = Provider((_) => BackgroundWorkerFgService(BackgroundWorkerFgHostApi()));
|
final backgroundWorkerFgServiceProvider = Provider((_) => BackgroundWorkerFgService(BackgroundWorkerFgHostApi()));
|
||||||
@@ -17,8 +16,6 @@ final backgroundWorkerLockServiceProvider = Provider<BackgroundWorkerLockService
|
|||||||
|
|
||||||
final nativeSyncApiProvider = Provider<NativeSyncApi>((_) => NativeSyncApi());
|
final nativeSyncApiProvider = Provider<NativeSyncApi>((_) => NativeSyncApi());
|
||||||
|
|
||||||
final permissionApiProvider = Provider<PermissionApi>((_) => PermissionApi());
|
|
||||||
|
|
||||||
final connectivityApiProvider = Provider<ConnectivityApi>((_) => ConnectivityApi());
|
final connectivityApiProvider = Provider<ConnectivityApi>((_) => ConnectivityApi());
|
||||||
|
|
||||||
final localImageApi = LocalImageApi();
|
final localImageApi = LocalImageApi();
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
|||||||
import 'package:immich_mobile/providers/infrastructure/cancel.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/cancel.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
||||||
import 'package:immich_mobile/repositories/asset_media.repository.dart';
|
import 'package:immich_mobile/providers/infrastructure/storage.provider.dart';
|
||||||
import 'package:immich_mobile/repositories/permission.repository.dart';
|
import 'package:immich_mobile/repositories/local_files_manager.repository.dart';
|
||||||
|
|
||||||
final syncMigrationRepositoryProvider = Provider((ref) => SyncMigrationRepository(ref.watch(driftProvider)));
|
final syncMigrationRepositoryProvider = Provider((ref) => SyncMigrationRepository(ref.watch(driftProvider)));
|
||||||
|
|
||||||
@@ -22,8 +22,8 @@ final syncStreamServiceProvider = Provider(
|
|||||||
syncStreamRepository: ref.watch(syncStreamRepositoryProvider),
|
syncStreamRepository: ref.watch(syncStreamRepositoryProvider),
|
||||||
localAssetRepository: ref.watch(localAssetRepository),
|
localAssetRepository: ref.watch(localAssetRepository),
|
||||||
trashedLocalAssetRepository: ref.watch(trashedLocalAssetRepository),
|
trashedLocalAssetRepository: ref.watch(trashedLocalAssetRepository),
|
||||||
assetMediaRepository: ref.watch(assetMediaRepositoryProvider),
|
localFilesManager: ref.watch(localFilesManagerRepositoryProvider),
|
||||||
permissionRepository: ref.watch(permissionRepositoryProvider),
|
storageRepository: ref.watch(storageRepositoryProvider),
|
||||||
syncMigrationRepository: ref.watch(syncMigrationRepositoryProvider),
|
syncMigrationRepository: ref.watch(syncMigrationRepositoryProvider),
|
||||||
api: ref.watch(apiServiceProvider),
|
api: ref.watch(apiServiceProvider),
|
||||||
cancelChecker: ref.watch(cancellationProvider),
|
cancelChecker: ref.watch(cancellationProvider),
|
||||||
@@ -39,8 +39,8 @@ final localSyncServiceProvider = Provider(
|
|||||||
localAlbumRepository: ref.watch(localAlbumRepository),
|
localAlbumRepository: ref.watch(localAlbumRepository),
|
||||||
localAssetRepository: ref.watch(localAssetRepository),
|
localAssetRepository: ref.watch(localAssetRepository),
|
||||||
trashedLocalAssetRepository: ref.watch(trashedLocalAssetRepository),
|
trashedLocalAssetRepository: ref.watch(trashedLocalAssetRepository),
|
||||||
assetMediaRepository: ref.watch(assetMediaRepositoryProvider),
|
localFilesManager: ref.watch(localFilesManagerRepositoryProvider),
|
||||||
permissionRepository: ref.watch(permissionRepositoryProvider),
|
storageRepository: ref.watch(storageRepositoryProvider),
|
||||||
nativeSyncApi: ref.watch(nativeSyncApiProvider),
|
nativeSyncApi: ref.watch(nativeSyncApiProvider),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,24 +8,19 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
|||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/response_extensions.dart';
|
import 'package:immich_mobile/extensions/response_extensions.dart';
|
||||||
import 'package:immich_mobile/platform/native_sync_api.g.dart';
|
|
||||||
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
|
||||||
import 'package:immich_mobile/repositories/asset_api.repository.dart';
|
import 'package:immich_mobile/repositories/asset_api.repository.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:photo_manager/photo_manager.dart';
|
import 'package:photo_manager/photo_manager.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
|
||||||
final assetMediaRepositoryProvider = Provider(
|
final assetMediaRepositoryProvider = Provider((ref) => AssetMediaRepository(ref.watch(assetApiRepositoryProvider)));
|
||||||
(ref) => AssetMediaRepository(ref.watch(assetApiRepositoryProvider), ref.watch(nativeSyncApiProvider)),
|
|
||||||
);
|
|
||||||
|
|
||||||
class AssetMediaRepository {
|
class AssetMediaRepository {
|
||||||
final AssetApiRepository _assetApiRepository;
|
final AssetApiRepository _assetApiRepository;
|
||||||
final NativeSyncApi _nativeSyncApi;
|
|
||||||
static final Logger _log = Logger("AssetMediaRepository");
|
static final Logger _log = Logger("AssetMediaRepository");
|
||||||
|
|
||||||
const AssetMediaRepository(this._assetApiRepository, this._nativeSyncApi);
|
const AssetMediaRepository(this._assetApiRepository);
|
||||||
|
|
||||||
Future<bool> _androidSupportsTrash() async {
|
Future<bool> _androidSupportsTrash() async {
|
||||||
if (Platform.isAndroid) {
|
if (Platform.isAndroid) {
|
||||||
@@ -50,27 +45,6 @@ class AssetMediaRepository {
|
|||||||
return PhotoManager.editor.deleteWithIds(ids);
|
return PhotoManager.editor.deleteWithIds(ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> _restoreFromTrashById(String mediaId, int type) async {
|
|
||||||
try {
|
|
||||||
return await _nativeSyncApi.restoreFromTrashById(mediaId, type);
|
|
||||||
} catch (e, s) {
|
|
||||||
_log.warning('Error restore file from trash by Id', e, s);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<String>> restoreAssetsFromTrash(Iterable<LocalAsset> assets) async {
|
|
||||||
final restoredIds = <String>[];
|
|
||||||
for (final asset in assets) {
|
|
||||||
_log.info("Restoring from trash, localId: ${asset.id}, checksum: ${asset.checksum}");
|
|
||||||
final result = await _restoreFromTrashById(asset.id, asset.type.index);
|
|
||||||
if (result) {
|
|
||||||
restoredIds.add(asset.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return restoredIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<AssetEntity?> get(String id) async {
|
Future<AssetEntity?> get(String id) async {
|
||||||
final entity = await AssetEntity.fromId(id);
|
final entity = await AssetEntity.fromId(id);
|
||||||
return entity;
|
return entity;
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
|
import 'package:immich_mobile/services/local_files_manager.service.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
|
final localFilesManagerRepositoryProvider = Provider(
|
||||||
|
(ref) => LocalFilesManagerRepository(ref.watch(localFileManagerServiceProvider)),
|
||||||
|
);
|
||||||
|
|
||||||
|
class LocalFilesManagerRepository {
|
||||||
|
LocalFilesManagerRepository(this._service);
|
||||||
|
|
||||||
|
final Logger _logger = Logger('LocalFilesManagerRepo');
|
||||||
|
final LocalFilesManagerService _service;
|
||||||
|
|
||||||
|
Future<bool> moveToTrash(List<String> mediaUrls) async {
|
||||||
|
return await _service.moveToTrash(mediaUrls);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> restoreFromTrash(String fileName, int type) async {
|
||||||
|
return await _service.restoreFromTrash(fileName, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> requestManageMediaPermission() async {
|
||||||
|
return await _service.requestManageMediaPermission();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> hasManageMediaPermission() async {
|
||||||
|
return await _service.hasManageMediaPermission();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> manageMediaPermission() async {
|
||||||
|
return await _service.manageMediaPermission();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<String>> restoreAssetsFromTrash(Iterable<LocalAsset> assets) async {
|
||||||
|
final restoredIds = <String>[];
|
||||||
|
for (final asset in assets) {
|
||||||
|
_logger.info("Restoring from trash, localId: ${asset.id}, remoteId: ${asset.checksum}");
|
||||||
|
try {
|
||||||
|
final result = await _service.restoreFromTrashById(asset.id, asset.type.index);
|
||||||
|
if (result) {
|
||||||
|
restoredIds.add(asset.id);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
_logger.warning("Restoring failure: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return restoredIds;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,12 @@
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/platform/permission_api.g.dart';
|
|
||||||
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
|
||||||
final permissionRepositoryProvider = Provider((ref) {
|
final permissionRepositoryProvider = Provider((_) {
|
||||||
return PermissionRepository(ref.watch(permissionApiProvider));
|
return const PermissionRepository();
|
||||||
});
|
});
|
||||||
|
|
||||||
class PermissionRepository implements IPermissionRepository {
|
class PermissionRepository implements IPermissionRepository {
|
||||||
final PermissionApi _permissionApi;
|
const PermissionRepository();
|
||||||
|
|
||||||
const PermissionRepository(this._permissionApi);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> hasLocationWhenInUsePermission() {
|
Future<bool> hasLocationWhenInUsePermission() {
|
||||||
@@ -38,21 +34,6 @@ class PermissionRepository implements IPermissionRepository {
|
|||||||
Future<bool> openSettings() {
|
Future<bool> openSettings() {
|
||||||
return openAppSettings();
|
return openAppSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<bool> hasManageMediaPermission() {
|
|
||||||
return _permissionApi.hasManageMediaPermission();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<bool> requestManageMediaPermission() {
|
|
||||||
return _permissionApi.requestManageMediaPermission();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<bool> manageMediaPermission() {
|
|
||||||
return _permissionApi.manageMediaPermission();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract interface class IPermissionRepository {
|
abstract interface class IPermissionRepository {
|
||||||
@@ -61,7 +42,4 @@ abstract interface class IPermissionRepository {
|
|||||||
Future<bool> hasLocationAlwaysPermission();
|
Future<bool> hasLocationAlwaysPermission();
|
||||||
Future<bool> requestLocationAlwaysPermission();
|
Future<bool> requestLocationAlwaysPermission();
|
||||||
Future<bool> openSettings();
|
Future<bool> openSettings();
|
||||||
Future<bool> hasManageMediaPermission();
|
|
||||||
Future<bool> requestManageMediaPermission();
|
|
||||||
Future<bool> manageMediaPermission();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'package:home_widget/home_widget.dart';
|
import 'package:home_widget/home_widget.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
|
||||||
|
|
||||||
final widgetRepositoryProvider = Provider((_) => const WidgetRepository());
|
final widgetRepositoryProvider = Provider((_) => const WidgetRepository());
|
||||||
|
|
||||||
@@ -15,7 +14,7 @@ class WidgetRepository {
|
|||||||
await HomeWidget.updateWidget(iOSName: iosName, qualifiedAndroidName: androidName);
|
await HomeWidget.updateWidget(iOSName: iosName, qualifiedAndroidName: androidName);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setAppGroupId() async {
|
Future<void> setAppGroupId(String appGroupId) async {
|
||||||
await HomeWidget.setAppGroupId(await networkApi.getAppGroupId());
|
await HomeWidget.setAppGroupId(appGroupId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
|
final localFileManagerServiceProvider = Provider<LocalFilesManagerService>((ref) => const LocalFilesManagerService());
|
||||||
|
|
||||||
|
class LocalFilesManagerService {
|
||||||
|
const LocalFilesManagerService();
|
||||||
|
|
||||||
|
static final Logger _logger = Logger('LocalFilesManager');
|
||||||
|
static const MethodChannel _channel = MethodChannel('file_trash');
|
||||||
|
|
||||||
|
Future<bool> moveToTrash(List<String> mediaUrls) async {
|
||||||
|
try {
|
||||||
|
return await _channel.invokeMethod('moveToTrash', {'mediaUrls': mediaUrls});
|
||||||
|
} catch (e, s) {
|
||||||
|
_logger.warning('Error moving file to trash', e, s);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> restoreFromTrash(String fileName, int type) async {
|
||||||
|
try {
|
||||||
|
return await _channel.invokeMethod('restoreFromTrash', {'fileName': fileName, 'type': type});
|
||||||
|
} catch (e, s) {
|
||||||
|
_logger.warning('Error restore file from trash', e, s);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> restoreFromTrashById(String mediaId, int type) async {
|
||||||
|
try {
|
||||||
|
return await _channel.invokeMethod('restoreFromTrash', {'mediaId': mediaId, 'type': type});
|
||||||
|
} catch (e, s) {
|
||||||
|
_logger.warning('Error restore file from trash by Id', e, s);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> requestManageMediaPermission() async {
|
||||||
|
try {
|
||||||
|
return await _channel.invokeMethod('requestManageMediaPermission');
|
||||||
|
} catch (e, s) {
|
||||||
|
_logger.warning('Error requesting manage media permission', e, s);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> hasManageMediaPermission() async {
|
||||||
|
try {
|
||||||
|
return await _channel.invokeMethod('hasManageMediaPermission');
|
||||||
|
} catch (e, s) {
|
||||||
|
_logger.warning('Error requesting manage media permission state', e, s);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> manageMediaPermission() async {
|
||||||
|
try {
|
||||||
|
return await _channel.invokeMethod('manageMediaPermission');
|
||||||
|
} catch (e, s) {
|
||||||
|
_logger.warning('Error requesting manage media permission settings', e, s);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ class WidgetService {
|
|||||||
const WidgetService(this._repository);
|
const WidgetService(this._repository);
|
||||||
|
|
||||||
Future<void> writeCredentials(String serverURL, String sessionKey, String? customHeaders) async {
|
Future<void> writeCredentials(String serverURL, String sessionKey, String? customHeaders) async {
|
||||||
await _repository.setAppGroupId();
|
await _repository.setAppGroupId(appShareGroupId);
|
||||||
await _repository.saveData(kWidgetServerEndpoint, serverURL);
|
await _repository.saveData(kWidgetServerEndpoint, serverURL);
|
||||||
await _repository.saveData(kWidgetAuthToken, sessionKey);
|
await _repository.saveData(kWidgetAuthToken, sessionKey);
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ class WidgetService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> clearCredentials() async {
|
Future<void> clearCredentials() async {
|
||||||
await _repository.setAppGroupId();
|
await _repository.setAppGroupId(appShareGroupId);
|
||||||
await _repository.saveData(kWidgetServerEndpoint, "");
|
await _repository.saveData(kWidgetServerEndpoint, "");
|
||||||
await _repository.saveData(kWidgetAuthToken, "");
|
await _repository.saveData(kWidgetAuthToken, "");
|
||||||
await _repository.saveData(kWidgetCustomHeaders, "");
|
await _repository.saveData(kWidgetCustomHeaders, "");
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
|||||||
import 'package:immich_mobile/providers/oauth.provider.dart';
|
import 'package:immich_mobile/providers/oauth.provider.dart';
|
||||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||||
import 'package:immich_mobile/providers/websocket.provider.dart';
|
import 'package:immich_mobile/providers/websocket.provider.dart';
|
||||||
import 'package:immich_mobile/repositories/permission.repository.dart';
|
import 'package:immich_mobile/repositories/local_files_manager.repository.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
import 'package:immich_mobile/utils/provider_utils.dart';
|
import 'package:immich_mobile/utils/provider_utils.dart';
|
||||||
import 'package:immich_mobile/utils/url_helper.dart';
|
import 'package:immich_mobile/utils/url_helper.dart';
|
||||||
@@ -193,7 +193,7 @@ class LoginForm extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getManageMediaPermission() async {
|
getManageMediaPermission() async {
|
||||||
final hasPermission = await ref.read(permissionRepositoryProvider).hasManageMediaPermission();
|
final hasPermission = await ref.read(localFilesManagerRepositoryProvider).hasManageMediaPermission();
|
||||||
if (!hasPermission) {
|
if (!hasPermission) {
|
||||||
await showDialog(
|
await showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -224,7 +224,7 @@ class LoginForm extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
unawaited(ref.read(permissionRepositoryProvider).requestManageMediaPermission());
|
ref.read(localFilesManagerRepositoryProvider).requestManageMediaPermission();
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
|||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart';
|
||||||
import 'package:immich_mobile/repositories/permission.repository.dart';
|
import 'package:immich_mobile/repositories/local_files_manager.repository.dart';
|
||||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
import 'package:immich_mobile/utils/bytes_units.dart';
|
import 'package:immich_mobile/utils/bytes_units.dart';
|
||||||
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
|
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
|
||||||
@@ -57,7 +57,9 @@ class AdvancedSettings extends HookConsumerWidget {
|
|||||||
() async {
|
() async {
|
||||||
isManageMediaSupported.value = await checkAndroidVersion();
|
isManageMediaSupported.value = await checkAndroidVersion();
|
||||||
if (isManageMediaSupported.value) {
|
if (isManageMediaSupported.value) {
|
||||||
manageMediaAndroidPermission.value = await ref.read(permissionRepositoryProvider).hasManageMediaPermission();
|
manageMediaAndroidPermission.value = await ref
|
||||||
|
.read(localFilesManagerRepositoryProvider)
|
||||||
|
.hasManageMediaPermission();
|
||||||
}
|
}
|
||||||
}();
|
}();
|
||||||
return null;
|
return null;
|
||||||
@@ -80,7 +82,7 @@ class AdvancedSettings extends HookConsumerWidget {
|
|||||||
subtitle: "advanced_settings_sync_remote_deletions_subtitle".tr(),
|
subtitle: "advanced_settings_sync_remote_deletions_subtitle".tr(),
|
||||||
onChanged: (value) async {
|
onChanged: (value) async {
|
||||||
if (value) {
|
if (value) {
|
||||||
final result = await ref.read(permissionRepositoryProvider).requestManageMediaPermission();
|
final result = await ref.read(localFilesManagerRepositoryProvider).requestManageMediaPermission();
|
||||||
manageLocalMediaAndroid.value = result;
|
manageLocalMediaAndroid.value = result;
|
||||||
manageMediaAndroidPermission.value = result;
|
manageMediaAndroidPermission.value = result;
|
||||||
}
|
}
|
||||||
@@ -94,7 +96,7 @@ class AdvancedSettings extends HookConsumerWidget {
|
|||||||
? const Color.fromARGB(255, 243, 188, 106)
|
? const Color.fromARGB(255, 243, 188, 106)
|
||||||
: null,
|
: null,
|
||||||
onActionTap: () async {
|
onActionTap: () async {
|
||||||
final result = await ref.read(permissionRepositoryProvider).manageMediaPermission();
|
final result = await ref.read(localFilesManagerRepositoryProvider).manageMediaPermission();
|
||||||
manageMediaAndroidPermission.value = result;
|
manageMediaAndroidPermission.value = result;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
Generated
+6
-1
@@ -92,10 +92,12 @@ Class | Method | HTTP request | Description
|
|||||||
*AlbumsApi* | [**getAlbumMapMarkers**](doc//AlbumsApi.md#getalbummapmarkers) | **GET** /albums/{id}/map-markers | Retrieve album map markers
|
*AlbumsApi* | [**getAlbumMapMarkers**](doc//AlbumsApi.md#getalbummapmarkers) | **GET** /albums/{id}/map-markers | Retrieve album map markers
|
||||||
*AlbumsApi* | [**getAlbumStatistics**](doc//AlbumsApi.md#getalbumstatistics) | **GET** /albums/statistics | Retrieve album statistics
|
*AlbumsApi* | [**getAlbumStatistics**](doc//AlbumsApi.md#getalbumstatistics) | **GET** /albums/statistics | Retrieve album statistics
|
||||||
*AlbumsApi* | [**getAllAlbums**](doc//AlbumsApi.md#getallalbums) | **GET** /albums | List all albums
|
*AlbumsApi* | [**getAllAlbums**](doc//AlbumsApi.md#getallalbums) | **GET** /albums | List all albums
|
||||||
|
*AlbumsApi* | [**getOwnAlbumUser**](doc//AlbumsApi.md#getownalbumuser) | **GET** /albums/{id}/user/self | Get own sharing permissions
|
||||||
*AlbumsApi* | [**removeAssetFromAlbum**](doc//AlbumsApi.md#removeassetfromalbum) | **DELETE** /albums/{id}/assets | Remove assets from an album
|
*AlbumsApi* | [**removeAssetFromAlbum**](doc//AlbumsApi.md#removeassetfromalbum) | **DELETE** /albums/{id}/assets | Remove assets from an album
|
||||||
*AlbumsApi* | [**removeUserFromAlbum**](doc//AlbumsApi.md#removeuserfromalbum) | **DELETE** /albums/{id}/user/{userId} | Remove user from album
|
*AlbumsApi* | [**removeUserFromAlbum**](doc//AlbumsApi.md#removeuserfromalbum) | **DELETE** /albums/{id}/user/{userId} | Remove user from album
|
||||||
*AlbumsApi* | [**updateAlbumInfo**](doc//AlbumsApi.md#updatealbuminfo) | **PATCH** /albums/{id} | Update an album
|
*AlbumsApi* | [**updateAlbumInfo**](doc//AlbumsApi.md#updatealbuminfo) | **PATCH** /albums/{id} | Update an album
|
||||||
*AlbumsApi* | [**updateAlbumUser**](doc//AlbumsApi.md#updatealbumuser) | **PUT** /albums/{id}/user/{userId} | Update user role
|
*AlbumsApi* | [**updateAlbumUser**](doc//AlbumsApi.md#updatealbumuser) | **PUT** /albums/{id}/user/{userId} | Update user role
|
||||||
|
*AlbumsApi* | [**updateOwnAlbumUser**](doc//AlbumsApi.md#updateownalbumuser) | **PUT** /albums/{id}/user/self | Update own sharing permissions
|
||||||
*AssetsApi* | [**checkBulkUpload**](doc//AssetsApi.md#checkbulkupload) | **POST** /assets/bulk-upload-check | Check bulk upload
|
*AssetsApi* | [**checkBulkUpload**](doc//AssetsApi.md#checkbulkupload) | **POST** /assets/bulk-upload-check | Check bulk upload
|
||||||
*AssetsApi* | [**copyAsset**](doc//AssetsApi.md#copyasset) | **PUT** /assets/copy | Copy asset
|
*AssetsApi* | [**copyAsset**](doc//AssetsApi.md#copyasset) | **PUT** /assets/copy | Copy asset
|
||||||
*AssetsApi* | [**deleteAssetMetadata**](doc//AssetsApi.md#deleteassetmetadata) | **DELETE** /assets/{id}/metadata/{key} | Delete asset metadata by key
|
*AssetsApi* | [**deleteAssetMetadata**](doc//AssetsApi.md#deleteassetmetadata) | **DELETE** /assets/{id}/metadata/{key} | Delete asset metadata by key
|
||||||
@@ -452,7 +454,7 @@ Class | Method | HTTP request | Description
|
|||||||
- [MemoryStatisticsResponseDto](doc//MemoryStatisticsResponseDto.md)
|
- [MemoryStatisticsResponseDto](doc//MemoryStatisticsResponseDto.md)
|
||||||
- [MemoryType](doc//MemoryType.md)
|
- [MemoryType](doc//MemoryType.md)
|
||||||
- [MemoryUpdateDto](doc//MemoryUpdateDto.md)
|
- [MemoryUpdateDto](doc//MemoryUpdateDto.md)
|
||||||
- [MergePersonDto](doc//MergePersonDto.md)
|
- [MergeFaceClusterDto](doc//MergeFaceClusterDto.md)
|
||||||
- [MetadataSearchDto](doc//MetadataSearchDto.md)
|
- [MetadataSearchDto](doc//MetadataSearchDto.md)
|
||||||
- [MirrorAxis](doc//MirrorAxis.md)
|
- [MirrorAxis](doc//MirrorAxis.md)
|
||||||
- [MirrorParameters](doc//MirrorParameters.md)
|
- [MirrorParameters](doc//MirrorParameters.md)
|
||||||
@@ -544,6 +546,8 @@ Class | Method | HTTP request | Description
|
|||||||
- [SharedLinkType](doc//SharedLinkType.md)
|
- [SharedLinkType](doc//SharedLinkType.md)
|
||||||
- [SharedLinksResponse](doc//SharedLinksResponse.md)
|
- [SharedLinksResponse](doc//SharedLinksResponse.md)
|
||||||
- [SharedLinksUpdate](doc//SharedLinksUpdate.md)
|
- [SharedLinksUpdate](doc//SharedLinksUpdate.md)
|
||||||
|
- [SharingOptionsResponseDto](doc//SharingOptionsResponseDto.md)
|
||||||
|
- [SharingPermission](doc//SharingPermission.md)
|
||||||
- [SignUpDto](doc//SignUpDto.md)
|
- [SignUpDto](doc//SignUpDto.md)
|
||||||
- [SmartSearchDto](doc//SmartSearchDto.md)
|
- [SmartSearchDto](doc//SmartSearchDto.md)
|
||||||
- [SourceType](doc//SourceType.md)
|
- [SourceType](doc//SourceType.md)
|
||||||
@@ -643,6 +647,7 @@ Class | Method | HTTP request | Description
|
|||||||
- [UpdateAlbumUserDto](doc//UpdateAlbumUserDto.md)
|
- [UpdateAlbumUserDto](doc//UpdateAlbumUserDto.md)
|
||||||
- [UpdateAssetDto](doc//UpdateAssetDto.md)
|
- [UpdateAssetDto](doc//UpdateAssetDto.md)
|
||||||
- [UpdateLibraryDto](doc//UpdateLibraryDto.md)
|
- [UpdateLibraryDto](doc//UpdateLibraryDto.md)
|
||||||
|
- [UpdateSharingOptionsDto](doc//UpdateSharingOptionsDto.md)
|
||||||
- [UsageByUserDto](doc//UsageByUserDto.md)
|
- [UsageByUserDto](doc//UsageByUserDto.md)
|
||||||
- [UserAdminCreateDto](doc//UserAdminCreateDto.md)
|
- [UserAdminCreateDto](doc//UserAdminCreateDto.md)
|
||||||
- [UserAdminDeleteDto](doc//UserAdminDeleteDto.md)
|
- [UserAdminDeleteDto](doc//UserAdminDeleteDto.md)
|
||||||
|
|||||||
Generated
+4
-1
@@ -198,7 +198,7 @@ part 'model/memory_search_order.dart';
|
|||||||
part 'model/memory_statistics_response_dto.dart';
|
part 'model/memory_statistics_response_dto.dart';
|
||||||
part 'model/memory_type.dart';
|
part 'model/memory_type.dart';
|
||||||
part 'model/memory_update_dto.dart';
|
part 'model/memory_update_dto.dart';
|
||||||
part 'model/merge_person_dto.dart';
|
part 'model/merge_face_cluster_dto.dart';
|
||||||
part 'model/metadata_search_dto.dart';
|
part 'model/metadata_search_dto.dart';
|
||||||
part 'model/mirror_axis.dart';
|
part 'model/mirror_axis.dart';
|
||||||
part 'model/mirror_parameters.dart';
|
part 'model/mirror_parameters.dart';
|
||||||
@@ -290,6 +290,8 @@ part 'model/shared_link_response_dto.dart';
|
|||||||
part 'model/shared_link_type.dart';
|
part 'model/shared_link_type.dart';
|
||||||
part 'model/shared_links_response.dart';
|
part 'model/shared_links_response.dart';
|
||||||
part 'model/shared_links_update.dart';
|
part 'model/shared_links_update.dart';
|
||||||
|
part 'model/sharing_options_response_dto.dart';
|
||||||
|
part 'model/sharing_permission.dart';
|
||||||
part 'model/sign_up_dto.dart';
|
part 'model/sign_up_dto.dart';
|
||||||
part 'model/smart_search_dto.dart';
|
part 'model/smart_search_dto.dart';
|
||||||
part 'model/source_type.dart';
|
part 'model/source_type.dart';
|
||||||
@@ -389,6 +391,7 @@ part 'model/update_album_dto.dart';
|
|||||||
part 'model/update_album_user_dto.dart';
|
part 'model/update_album_user_dto.dart';
|
||||||
part 'model/update_asset_dto.dart';
|
part 'model/update_asset_dto.dart';
|
||||||
part 'model/update_library_dto.dart';
|
part 'model/update_library_dto.dart';
|
||||||
|
part 'model/update_sharing_options_dto.dart';
|
||||||
part 'model/usage_by_user_dto.dart';
|
part 'model/usage_by_user_dto.dart';
|
||||||
part 'model/user_admin_create_dto.dart';
|
part 'model/user_admin_create_dto.dart';
|
||||||
part 'model/user_admin_delete_dto.dart';
|
part 'model/user_admin_delete_dto.dart';
|
||||||
|
|||||||
Generated
+110
@@ -580,6 +580,63 @@ class AlbumsApi {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get own sharing permissions
|
||||||
|
///
|
||||||
|
/// Get the own sharing permissions in a specific album.
|
||||||
|
///
|
||||||
|
/// Note: This method returns the HTTP [Response].
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
///
|
||||||
|
/// * [String] id (required):
|
||||||
|
Future<Response> getOwnAlbumUserWithHttpInfo(String id,) async {
|
||||||
|
// ignore: prefer_const_declarations
|
||||||
|
final apiPath = r'/albums/{id}/user/self'
|
||||||
|
.replaceAll('{id}', id);
|
||||||
|
|
||||||
|
// ignore: prefer_final_locals
|
||||||
|
Object? postBody;
|
||||||
|
|
||||||
|
final queryParams = <QueryParam>[];
|
||||||
|
final headerParams = <String, String>{};
|
||||||
|
final formParams = <String, String>{};
|
||||||
|
|
||||||
|
const contentTypes = <String>[];
|
||||||
|
|
||||||
|
|
||||||
|
return apiClient.invokeAPI(
|
||||||
|
apiPath,
|
||||||
|
'GET',
|
||||||
|
queryParams,
|
||||||
|
postBody,
|
||||||
|
headerParams,
|
||||||
|
formParams,
|
||||||
|
contentTypes.isEmpty ? null : contentTypes.first,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get own sharing permissions
|
||||||
|
///
|
||||||
|
/// Get the own sharing permissions in a specific album.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
///
|
||||||
|
/// * [String] id (required):
|
||||||
|
Future<SharingOptionsResponseDto?> getOwnAlbumUser(String id,) async {
|
||||||
|
final response = await getOwnAlbumUserWithHttpInfo(id,);
|
||||||
|
if (response.statusCode >= HttpStatus.badRequest) {
|
||||||
|
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||||
|
}
|
||||||
|
// When a remote server returns no body with a status of 204, we shall not decode it.
|
||||||
|
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
|
||||||
|
// FormatException when trying to decode an empty string.
|
||||||
|
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
|
||||||
|
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'SharingOptionsResponseDto',) as SharingOptionsResponseDto;
|
||||||
|
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/// Remove assets from an album
|
/// Remove assets from an album
|
||||||
///
|
///
|
||||||
/// Remove multiple assets from a specific album by its ID.
|
/// Remove multiple assets from a specific album by its ID.
|
||||||
@@ -816,4 +873,57 @@ class AlbumsApi {
|
|||||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Update own sharing permissions
|
||||||
|
///
|
||||||
|
/// Change the own sharing permissions in a specific album.
|
||||||
|
///
|
||||||
|
/// Note: This method returns the HTTP [Response].
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
///
|
||||||
|
/// * [String] id (required):
|
||||||
|
///
|
||||||
|
/// * [UpdateSharingOptionsDto] updateSharingOptionsDto (required):
|
||||||
|
Future<Response> updateOwnAlbumUserWithHttpInfo(String id, UpdateSharingOptionsDto updateSharingOptionsDto,) async {
|
||||||
|
// ignore: prefer_const_declarations
|
||||||
|
final apiPath = r'/albums/{id}/user/self'
|
||||||
|
.replaceAll('{id}', id);
|
||||||
|
|
||||||
|
// ignore: prefer_final_locals
|
||||||
|
Object? postBody = updateSharingOptionsDto;
|
||||||
|
|
||||||
|
final queryParams = <QueryParam>[];
|
||||||
|
final headerParams = <String, String>{};
|
||||||
|
final formParams = <String, String>{};
|
||||||
|
|
||||||
|
const contentTypes = <String>['application/json'];
|
||||||
|
|
||||||
|
|
||||||
|
return apiClient.invokeAPI(
|
||||||
|
apiPath,
|
||||||
|
'PUT',
|
||||||
|
queryParams,
|
||||||
|
postBody,
|
||||||
|
headerParams,
|
||||||
|
formParams,
|
||||||
|
contentTypes.isEmpty ? null : contentTypes.first,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update own sharing permissions
|
||||||
|
///
|
||||||
|
/// Change the own sharing permissions in a specific album.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
///
|
||||||
|
/// * [String] id (required):
|
||||||
|
///
|
||||||
|
/// * [UpdateSharingOptionsDto] updateSharingOptionsDto (required):
|
||||||
|
Future<void> updateOwnAlbumUser(String id, UpdateSharingOptionsDto updateSharingOptionsDto,) async {
|
||||||
|
final response = await updateOwnAlbumUserWithHttpInfo(id, updateSharingOptionsDto,);
|
||||||
|
if (response.statusCode >= HttpStatus.badRequest) {
|
||||||
|
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+6
-6
@@ -448,14 +448,14 @@ class PeopleApi {
|
|||||||
///
|
///
|
||||||
/// * [String] id (required):
|
/// * [String] id (required):
|
||||||
///
|
///
|
||||||
/// * [MergePersonDto] mergePersonDto (required):
|
/// * [MergeFaceClusterDto] mergeFaceClusterDto (required):
|
||||||
Future<Response> mergePersonWithHttpInfo(String id, MergePersonDto mergePersonDto,) async {
|
Future<Response> mergePersonWithHttpInfo(String id, MergeFaceClusterDto mergeFaceClusterDto,) async {
|
||||||
// ignore: prefer_const_declarations
|
// ignore: prefer_const_declarations
|
||||||
final apiPath = r'/people/{id}/merge'
|
final apiPath = r'/people/{id}/merge'
|
||||||
.replaceAll('{id}', id);
|
.replaceAll('{id}', id);
|
||||||
|
|
||||||
// ignore: prefer_final_locals
|
// ignore: prefer_final_locals
|
||||||
Object? postBody = mergePersonDto;
|
Object? postBody = mergeFaceClusterDto;
|
||||||
|
|
||||||
final queryParams = <QueryParam>[];
|
final queryParams = <QueryParam>[];
|
||||||
final headerParams = <String, String>{};
|
final headerParams = <String, String>{};
|
||||||
@@ -483,9 +483,9 @@ class PeopleApi {
|
|||||||
///
|
///
|
||||||
/// * [String] id (required):
|
/// * [String] id (required):
|
||||||
///
|
///
|
||||||
/// * [MergePersonDto] mergePersonDto (required):
|
/// * [MergeFaceClusterDto] mergeFaceClusterDto (required):
|
||||||
Future<List<BulkIdResponseDto>?> mergePerson(String id, MergePersonDto mergePersonDto,) async {
|
Future<List<BulkIdResponseDto>?> mergePerson(String id, MergeFaceClusterDto mergeFaceClusterDto,) async {
|
||||||
final response = await mergePersonWithHttpInfo(id, mergePersonDto,);
|
final response = await mergePersonWithHttpInfo(id, mergeFaceClusterDto,);
|
||||||
if (response.statusCode >= HttpStatus.badRequest) {
|
if (response.statusCode >= HttpStatus.badRequest) {
|
||||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+8
-2
@@ -442,8 +442,8 @@ class ApiClient {
|
|||||||
return MemoryTypeTypeTransformer().decode(value);
|
return MemoryTypeTypeTransformer().decode(value);
|
||||||
case 'MemoryUpdateDto':
|
case 'MemoryUpdateDto':
|
||||||
return MemoryUpdateDto.fromJson(value);
|
return MemoryUpdateDto.fromJson(value);
|
||||||
case 'MergePersonDto':
|
case 'MergeFaceClusterDto':
|
||||||
return MergePersonDto.fromJson(value);
|
return MergeFaceClusterDto.fromJson(value);
|
||||||
case 'MetadataSearchDto':
|
case 'MetadataSearchDto':
|
||||||
return MetadataSearchDto.fromJson(value);
|
return MetadataSearchDto.fromJson(value);
|
||||||
case 'MirrorAxis':
|
case 'MirrorAxis':
|
||||||
@@ -626,6 +626,10 @@ class ApiClient {
|
|||||||
return SharedLinksResponse.fromJson(value);
|
return SharedLinksResponse.fromJson(value);
|
||||||
case 'SharedLinksUpdate':
|
case 'SharedLinksUpdate':
|
||||||
return SharedLinksUpdate.fromJson(value);
|
return SharedLinksUpdate.fromJson(value);
|
||||||
|
case 'SharingOptionsResponseDto':
|
||||||
|
return SharingOptionsResponseDto.fromJson(value);
|
||||||
|
case 'SharingPermission':
|
||||||
|
return SharingPermissionTypeTransformer().decode(value);
|
||||||
case 'SignUpDto':
|
case 'SignUpDto':
|
||||||
return SignUpDto.fromJson(value);
|
return SignUpDto.fromJson(value);
|
||||||
case 'SmartSearchDto':
|
case 'SmartSearchDto':
|
||||||
@@ -824,6 +828,8 @@ class ApiClient {
|
|||||||
return UpdateAssetDto.fromJson(value);
|
return UpdateAssetDto.fromJson(value);
|
||||||
case 'UpdateLibraryDto':
|
case 'UpdateLibraryDto':
|
||||||
return UpdateLibraryDto.fromJson(value);
|
return UpdateLibraryDto.fromJson(value);
|
||||||
|
case 'UpdateSharingOptionsDto':
|
||||||
|
return UpdateSharingOptionsDto.fromJson(value);
|
||||||
case 'UsageByUserDto':
|
case 'UsageByUserDto':
|
||||||
return UsageByUserDto.fromJson(value);
|
return UsageByUserDto.fromJson(value);
|
||||||
case 'UserAdminCreateDto':
|
case 'UserAdminCreateDto':
|
||||||
|
|||||||
Generated
+3
@@ -163,6 +163,9 @@ String parameterToString(dynamic value) {
|
|||||||
if (value is SharedLinkType) {
|
if (value is SharedLinkType) {
|
||||||
return SharedLinkTypeTypeTransformer().encode(value).toString();
|
return SharedLinkTypeTypeTransformer().encode(value).toString();
|
||||||
}
|
}
|
||||||
|
if (value is SharingPermission) {
|
||||||
|
return SharingPermissionTypeTransformer().encode(value).toString();
|
||||||
|
}
|
||||||
if (value is SourceType) {
|
if (value is SourceType) {
|
||||||
return SourceTypeTypeTransformer().encode(value).toString();
|
return SourceTypeTypeTransformer().encode(value).toString();
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-1
@@ -37,6 +37,7 @@ class AssetResponseDto {
|
|||||||
this.owner,
|
this.owner,
|
||||||
required this.ownerId,
|
required this.ownerId,
|
||||||
this.people = const [],
|
this.people = const [],
|
||||||
|
this.permissions = const [],
|
||||||
this.resized,
|
this.resized,
|
||||||
this.stack,
|
this.stack,
|
||||||
this.tags = const [],
|
this.tags = const [],
|
||||||
@@ -140,6 +141,8 @@ class AssetResponseDto {
|
|||||||
|
|
||||||
List<PersonResponseDto> people;
|
List<PersonResponseDto> people;
|
||||||
|
|
||||||
|
List<SharingPermission> permissions;
|
||||||
|
|
||||||
/// Is resized
|
/// Is resized
|
||||||
///
|
///
|
||||||
/// Please note: This property should have been non-nullable! Since the specification file
|
/// Please note: This property should have been non-nullable! Since the specification file
|
||||||
@@ -195,6 +198,7 @@ class AssetResponseDto {
|
|||||||
other.owner == owner &&
|
other.owner == owner &&
|
||||||
other.ownerId == ownerId &&
|
other.ownerId == ownerId &&
|
||||||
_deepEquality.equals(other.people, people) &&
|
_deepEquality.equals(other.people, people) &&
|
||||||
|
_deepEquality.equals(other.permissions, permissions) &&
|
||||||
other.resized == resized &&
|
other.resized == resized &&
|
||||||
other.stack == stack &&
|
other.stack == stack &&
|
||||||
_deepEquality.equals(other.tags, tags) &&
|
_deepEquality.equals(other.tags, tags) &&
|
||||||
@@ -231,6 +235,7 @@ class AssetResponseDto {
|
|||||||
(owner == null ? 0 : owner!.hashCode) +
|
(owner == null ? 0 : owner!.hashCode) +
|
||||||
(ownerId.hashCode) +
|
(ownerId.hashCode) +
|
||||||
(people.hashCode) +
|
(people.hashCode) +
|
||||||
|
(permissions.hashCode) +
|
||||||
(resized == null ? 0 : resized!.hashCode) +
|
(resized == null ? 0 : resized!.hashCode) +
|
||||||
(stack == null ? 0 : stack!.hashCode) +
|
(stack == null ? 0 : stack!.hashCode) +
|
||||||
(tags.hashCode) +
|
(tags.hashCode) +
|
||||||
@@ -241,7 +246,7 @@ class AssetResponseDto {
|
|||||||
(width == null ? 0 : width!.hashCode);
|
(width == null ? 0 : width!.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'AssetResponseDto[checksum=$checksum, createdAt=$createdAt, duplicateId=$duplicateId, duration=$duration, exifInfo=$exifInfo, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, hasMetadata=$hasMetadata, height=$height, id=$id, isArchived=$isArchived, isEdited=$isEdited, isFavorite=$isFavorite, isOffline=$isOffline, isTrashed=$isTrashed, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, originalFileName=$originalFileName, originalMimeType=$originalMimeType, originalPath=$originalPath, owner=$owner, ownerId=$ownerId, people=$people, resized=$resized, stack=$stack, tags=$tags, thumbhash=$thumbhash, type=$type, updatedAt=$updatedAt, visibility=$visibility, width=$width]';
|
String toString() => 'AssetResponseDto[checksum=$checksum, createdAt=$createdAt, duplicateId=$duplicateId, duration=$duration, exifInfo=$exifInfo, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, hasMetadata=$hasMetadata, height=$height, id=$id, isArchived=$isArchived, isEdited=$isEdited, isFavorite=$isFavorite, isOffline=$isOffline, isTrashed=$isTrashed, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, originalFileName=$originalFileName, originalMimeType=$originalMimeType, originalPath=$originalPath, owner=$owner, ownerId=$ownerId, people=$people, permissions=$permissions, resized=$resized, stack=$stack, tags=$tags, thumbhash=$thumbhash, type=$type, updatedAt=$updatedAt, visibility=$visibility, width=$width]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
@@ -301,6 +306,7 @@ class AssetResponseDto {
|
|||||||
}
|
}
|
||||||
json[r'ownerId'] = this.ownerId;
|
json[r'ownerId'] = this.ownerId;
|
||||||
json[r'people'] = this.people;
|
json[r'people'] = this.people;
|
||||||
|
json[r'permissions'] = this.permissions;
|
||||||
if (this.resized != null) {
|
if (this.resized != null) {
|
||||||
json[r'resized'] = this.resized;
|
json[r'resized'] = this.resized;
|
||||||
} else {
|
} else {
|
||||||
@@ -361,6 +367,7 @@ class AssetResponseDto {
|
|||||||
owner: UserResponseDto.fromJson(json[r'owner']),
|
owner: UserResponseDto.fromJson(json[r'owner']),
|
||||||
ownerId: mapValueOfType<String>(json, r'ownerId')!,
|
ownerId: mapValueOfType<String>(json, r'ownerId')!,
|
||||||
people: PersonResponseDto.listFromJson(json[r'people']),
|
people: PersonResponseDto.listFromJson(json[r'people']),
|
||||||
|
permissions: SharingPermission.listFromJson(json[r'permissions']),
|
||||||
resized: mapValueOfType<bool>(json, r'resized'),
|
resized: mapValueOfType<bool>(json, r'resized'),
|
||||||
stack: AssetStackResponseDto.fromJson(json[r'stack']),
|
stack: AssetStackResponseDto.fromJson(json[r'stack']),
|
||||||
tags: TagResponseDto.listFromJson(json[r'tags']),
|
tags: TagResponseDto.listFromJson(json[r'tags']),
|
||||||
@@ -433,6 +440,7 @@ class AssetResponseDto {
|
|||||||
'originalFileName',
|
'originalFileName',
|
||||||
'originalPath',
|
'originalPath',
|
||||||
'ownerId',
|
'ownerId',
|
||||||
|
'permissions',
|
||||||
'thumbhash',
|
'thumbhash',
|
||||||
'type',
|
'type',
|
||||||
'updatedAt',
|
'updatedAt',
|
||||||
|
|||||||
Generated
+3
@@ -42,6 +42,7 @@ class JobName {
|
|||||||
static const databaseBackup = JobName._(r'DatabaseBackup');
|
static const databaseBackup = JobName._(r'DatabaseBackup');
|
||||||
static const facialRecognitionQueueAll = JobName._(r'FacialRecognitionQueueAll');
|
static const facialRecognitionQueueAll = JobName._(r'FacialRecognitionQueueAll');
|
||||||
static const facialRecognition = JobName._(r'FacialRecognition');
|
static const facialRecognition = JobName._(r'FacialRecognition');
|
||||||
|
static const facialRecognitionMerge = JobName._(r'FacialRecognitionMerge');
|
||||||
static const fileDelete = JobName._(r'FileDelete');
|
static const fileDelete = JobName._(r'FileDelete');
|
||||||
static const fileMigrationQueueAll = JobName._(r'FileMigrationQueueAll');
|
static const fileMigrationQueueAll = JobName._(r'FileMigrationQueueAll');
|
||||||
static const libraryDeleteCheck = JobName._(r'LibraryDeleteCheck');
|
static const libraryDeleteCheck = JobName._(r'LibraryDeleteCheck');
|
||||||
@@ -100,6 +101,7 @@ class JobName {
|
|||||||
databaseBackup,
|
databaseBackup,
|
||||||
facialRecognitionQueueAll,
|
facialRecognitionQueueAll,
|
||||||
facialRecognition,
|
facialRecognition,
|
||||||
|
facialRecognitionMerge,
|
||||||
fileDelete,
|
fileDelete,
|
||||||
fileMigrationQueueAll,
|
fileMigrationQueueAll,
|
||||||
libraryDeleteCheck,
|
libraryDeleteCheck,
|
||||||
@@ -193,6 +195,7 @@ class JobNameTypeTransformer {
|
|||||||
case r'DatabaseBackup': return JobName.databaseBackup;
|
case r'DatabaseBackup': return JobName.databaseBackup;
|
||||||
case r'FacialRecognitionQueueAll': return JobName.facialRecognitionQueueAll;
|
case r'FacialRecognitionQueueAll': return JobName.facialRecognitionQueueAll;
|
||||||
case r'FacialRecognition': return JobName.facialRecognition;
|
case r'FacialRecognition': return JobName.facialRecognition;
|
||||||
|
case r'FacialRecognitionMerge': return JobName.facialRecognitionMerge;
|
||||||
case r'FileDelete': return JobName.fileDelete;
|
case r'FileDelete': return JobName.fileDelete;
|
||||||
case r'FileMigrationQueueAll': return JobName.fileMigrationQueueAll;
|
case r'FileMigrationQueueAll': return JobName.fileMigrationQueueAll;
|
||||||
case r'LibraryDeleteCheck': return JobName.libraryDeleteCheck;
|
case r'LibraryDeleteCheck': return JobName.libraryDeleteCheck;
|
||||||
|
|||||||
+3
@@ -29,6 +29,7 @@ class ManualJobName {
|
|||||||
static const memoryCleanup = ManualJobName._(r'memory-cleanup');
|
static const memoryCleanup = ManualJobName._(r'memory-cleanup');
|
||||||
static const memoryCreate = ManualJobName._(r'memory-create');
|
static const memoryCreate = ManualJobName._(r'memory-create');
|
||||||
static const backupDatabase = ManualJobName._(r'backup-database');
|
static const backupDatabase = ManualJobName._(r'backup-database');
|
||||||
|
static const personGroupMerge = ManualJobName._(r'person-group-merge');
|
||||||
|
|
||||||
/// List of all possible values in this [enum][ManualJobName].
|
/// List of all possible values in this [enum][ManualJobName].
|
||||||
static const values = <ManualJobName>[
|
static const values = <ManualJobName>[
|
||||||
@@ -38,6 +39,7 @@ class ManualJobName {
|
|||||||
memoryCleanup,
|
memoryCleanup,
|
||||||
memoryCreate,
|
memoryCreate,
|
||||||
backupDatabase,
|
backupDatabase,
|
||||||
|
personGroupMerge,
|
||||||
];
|
];
|
||||||
|
|
||||||
static ManualJobName? fromJson(dynamic value) => ManualJobNameTypeTransformer().decode(value);
|
static ManualJobName? fromJson(dynamic value) => ManualJobNameTypeTransformer().decode(value);
|
||||||
@@ -82,6 +84,7 @@ class ManualJobNameTypeTransformer {
|
|||||||
case r'memory-cleanup': return ManualJobName.memoryCleanup;
|
case r'memory-cleanup': return ManualJobName.memoryCleanup;
|
||||||
case r'memory-create': return ManualJobName.memoryCreate;
|
case r'memory-create': return ManualJobName.memoryCreate;
|
||||||
case r'backup-database': return ManualJobName.backupDatabase;
|
case r'backup-database': return ManualJobName.backupDatabase;
|
||||||
|
case r'person-group-merge': return ManualJobName.personGroupMerge;
|
||||||
default:
|
default:
|
||||||
if (!allowNull) {
|
if (!allowNull) {
|
||||||
throw ArgumentError('Unknown enum value to decode: $data');
|
throw ArgumentError('Unknown enum value to decode: $data');
|
||||||
|
|||||||
+20
-20
@@ -10,17 +10,17 @@
|
|||||||
|
|
||||||
part of openapi.api;
|
part of openapi.api;
|
||||||
|
|
||||||
class MergePersonDto {
|
class MergeFaceClusterDto {
|
||||||
/// Returns a new [MergePersonDto] instance.
|
/// Returns a new [MergeFaceClusterDto] instance.
|
||||||
MergePersonDto({
|
MergeFaceClusterDto({
|
||||||
this.ids = const [],
|
this.ids = const [],
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Person IDs to merge
|
/// Face cluster IDs to merge
|
||||||
List<String> ids;
|
List<String> ids;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) => identical(this, other) || other is MergePersonDto &&
|
bool operator ==(Object other) => identical(this, other) || other is MergeFaceClusterDto &&
|
||||||
_deepEquality.equals(other.ids, ids);
|
_deepEquality.equals(other.ids, ids);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -29,7 +29,7 @@ class MergePersonDto {
|
|||||||
(ids.hashCode);
|
(ids.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'MergePersonDto[ids=$ids]';
|
String toString() => 'MergeFaceClusterDto[ids=$ids]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
@@ -37,15 +37,15 @@ class MergePersonDto {
|
|||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a new [MergePersonDto] instance and imports its values from
|
/// Returns a new [MergeFaceClusterDto] instance and imports its values from
|
||||||
/// [value] if it's a [Map], null otherwise.
|
/// [value] if it's a [Map], null otherwise.
|
||||||
// ignore: prefer_constructors_over_static_methods
|
// ignore: prefer_constructors_over_static_methods
|
||||||
static MergePersonDto? fromJson(dynamic value) {
|
static MergeFaceClusterDto? fromJson(dynamic value) {
|
||||||
upgradeDto(value, "MergePersonDto");
|
upgradeDto(value, "MergeFaceClusterDto");
|
||||||
if (value is Map) {
|
if (value is Map) {
|
||||||
final json = value.cast<String, dynamic>();
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
return MergePersonDto(
|
return MergeFaceClusterDto(
|
||||||
ids: json[r'ids'] is Iterable
|
ids: json[r'ids'] is Iterable
|
||||||
? (json[r'ids'] as Iterable).cast<String>().toList(growable: false)
|
? (json[r'ids'] as Iterable).cast<String>().toList(growable: false)
|
||||||
: const [],
|
: const [],
|
||||||
@@ -54,11 +54,11 @@ class MergePersonDto {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static List<MergePersonDto> listFromJson(dynamic json, {bool growable = false,}) {
|
static List<MergeFaceClusterDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
final result = <MergePersonDto>[];
|
final result = <MergeFaceClusterDto>[];
|
||||||
if (json is List && json.isNotEmpty) {
|
if (json is List && json.isNotEmpty) {
|
||||||
for (final row in json) {
|
for (final row in json) {
|
||||||
final value = MergePersonDto.fromJson(row);
|
final value = MergeFaceClusterDto.fromJson(row);
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
result.add(value);
|
result.add(value);
|
||||||
}
|
}
|
||||||
@@ -67,12 +67,12 @@ class MergePersonDto {
|
|||||||
return result.toList(growable: growable);
|
return result.toList(growable: growable);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Map<String, MergePersonDto> mapFromJson(dynamic json) {
|
static Map<String, MergeFaceClusterDto> mapFromJson(dynamic json) {
|
||||||
final map = <String, MergePersonDto>{};
|
final map = <String, MergeFaceClusterDto>{};
|
||||||
if (json is Map && json.isNotEmpty) {
|
if (json is Map && json.isNotEmpty) {
|
||||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
for (final entry in json.entries) {
|
for (final entry in json.entries) {
|
||||||
final value = MergePersonDto.fromJson(entry.value);
|
final value = MergeFaceClusterDto.fromJson(entry.value);
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
map[entry.key] = value;
|
map[entry.key] = value;
|
||||||
}
|
}
|
||||||
@@ -81,14 +81,14 @@ class MergePersonDto {
|
|||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
// maps a json object with a list of MergePersonDto-objects as value to a dart map
|
// maps a json object with a list of MergeFaceClusterDto-objects as value to a dart map
|
||||||
static Map<String, List<MergePersonDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
static Map<String, List<MergeFaceClusterDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
final map = <String, List<MergePersonDto>>{};
|
final map = <String, List<MergeFaceClusterDto>>{};
|
||||||
if (json is Map && json.isNotEmpty) {
|
if (json is Map && json.isNotEmpty) {
|
||||||
// ignore: parameter_assignments
|
// ignore: parameter_assignments
|
||||||
json = json.cast<String, dynamic>();
|
json = json.cast<String, dynamic>();
|
||||||
for (final entry in json.entries) {
|
for (final entry in json.entries) {
|
||||||
map[entry.key] = MergePersonDto.listFromJson(entry.value, growable: growable,);
|
map[entry.key] = MergeFaceClusterDto.listFromJson(entry.value, growable: growable,);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
+14
-1
@@ -15,6 +15,7 @@ class PersonResponseDto {
|
|||||||
PersonResponseDto({
|
PersonResponseDto({
|
||||||
required this.birthDate,
|
required this.birthDate,
|
||||||
this.color,
|
this.color,
|
||||||
|
required this.faceClusterId,
|
||||||
required this.id,
|
required this.id,
|
||||||
this.isFavorite,
|
this.isFavorite,
|
||||||
required this.isHidden,
|
required this.isHidden,
|
||||||
@@ -35,6 +36,9 @@ class PersonResponseDto {
|
|||||||
///
|
///
|
||||||
String? color;
|
String? color;
|
||||||
|
|
||||||
|
/// Face cluster ID
|
||||||
|
String? faceClusterId;
|
||||||
|
|
||||||
/// Person ID
|
/// Person ID
|
||||||
String id;
|
String id;
|
||||||
|
|
||||||
@@ -69,6 +73,7 @@ class PersonResponseDto {
|
|||||||
bool operator ==(Object other) => identical(this, other) || other is PersonResponseDto &&
|
bool operator ==(Object other) => identical(this, other) || other is PersonResponseDto &&
|
||||||
other.birthDate == birthDate &&
|
other.birthDate == birthDate &&
|
||||||
other.color == color &&
|
other.color == color &&
|
||||||
|
other.faceClusterId == faceClusterId &&
|
||||||
other.id == id &&
|
other.id == id &&
|
||||||
other.isFavorite == isFavorite &&
|
other.isFavorite == isFavorite &&
|
||||||
other.isHidden == isHidden &&
|
other.isHidden == isHidden &&
|
||||||
@@ -81,6 +86,7 @@ class PersonResponseDto {
|
|||||||
// ignore: unnecessary_parenthesis
|
// ignore: unnecessary_parenthesis
|
||||||
(birthDate == null ? 0 : birthDate!.hashCode) +
|
(birthDate == null ? 0 : birthDate!.hashCode) +
|
||||||
(color == null ? 0 : color!.hashCode) +
|
(color == null ? 0 : color!.hashCode) +
|
||||||
|
(faceClusterId == null ? 0 : faceClusterId!.hashCode) +
|
||||||
(id.hashCode) +
|
(id.hashCode) +
|
||||||
(isFavorite == null ? 0 : isFavorite!.hashCode) +
|
(isFavorite == null ? 0 : isFavorite!.hashCode) +
|
||||||
(isHidden.hashCode) +
|
(isHidden.hashCode) +
|
||||||
@@ -89,7 +95,7 @@ class PersonResponseDto {
|
|||||||
(updatedAt == null ? 0 : updatedAt!.hashCode);
|
(updatedAt == null ? 0 : updatedAt!.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'PersonResponseDto[birthDate=$birthDate, color=$color, id=$id, isFavorite=$isFavorite, isHidden=$isHidden, name=$name, thumbnailPath=$thumbnailPath, updatedAt=$updatedAt]';
|
String toString() => 'PersonResponseDto[birthDate=$birthDate, color=$color, faceClusterId=$faceClusterId, id=$id, isFavorite=$isFavorite, isHidden=$isHidden, name=$name, thumbnailPath=$thumbnailPath, updatedAt=$updatedAt]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
@@ -102,6 +108,11 @@ class PersonResponseDto {
|
|||||||
json[r'color'] = this.color;
|
json[r'color'] = this.color;
|
||||||
} else {
|
} else {
|
||||||
// json[r'color'] = null;
|
// json[r'color'] = null;
|
||||||
|
}
|
||||||
|
if (this.faceClusterId != null) {
|
||||||
|
json[r'faceClusterId'] = this.faceClusterId;
|
||||||
|
} else {
|
||||||
|
// json[r'faceClusterId'] = null;
|
||||||
}
|
}
|
||||||
json[r'id'] = this.id;
|
json[r'id'] = this.id;
|
||||||
if (this.isFavorite != null) {
|
if (this.isFavorite != null) {
|
||||||
@@ -131,6 +142,7 @@ class PersonResponseDto {
|
|||||||
return PersonResponseDto(
|
return PersonResponseDto(
|
||||||
birthDate: mapDateTime(json, r'birthDate', r''),
|
birthDate: mapDateTime(json, r'birthDate', r''),
|
||||||
color: mapValueOfType<String>(json, r'color'),
|
color: mapValueOfType<String>(json, r'color'),
|
||||||
|
faceClusterId: mapValueOfType<String>(json, r'faceClusterId'),
|
||||||
id: mapValueOfType<String>(json, r'id')!,
|
id: mapValueOfType<String>(json, r'id')!,
|
||||||
isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
|
isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
|
||||||
isHidden: mapValueOfType<bool>(json, r'isHidden')!,
|
isHidden: mapValueOfType<bool>(json, r'isHidden')!,
|
||||||
@@ -185,6 +197,7 @@ class PersonResponseDto {
|
|||||||
/// The list of required keys that must be present in a JSON.
|
/// The list of required keys that must be present in a JSON.
|
||||||
static const requiredKeys = <String>{
|
static const requiredKeys = <String>{
|
||||||
'birthDate',
|
'birthDate',
|
||||||
|
'faceClusterId',
|
||||||
'id',
|
'id',
|
||||||
'isHidden',
|
'isHidden',
|
||||||
'name',
|
'name',
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.18
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
part of openapi.api;
|
||||||
|
|
||||||
|
class SharingOptionsResponseDto {
|
||||||
|
/// Returns a new [SharingOptionsResponseDto] instance.
|
||||||
|
SharingOptionsResponseDto({
|
||||||
|
required this.inTimeline,
|
||||||
|
this.permissions = const [],
|
||||||
|
});
|
||||||
|
|
||||||
|
bool inTimeline;
|
||||||
|
|
||||||
|
List<SharingPermission> permissions;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is SharingOptionsResponseDto &&
|
||||||
|
other.inTimeline == inTimeline &&
|
||||||
|
_deepEquality.equals(other.permissions, permissions);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(inTimeline.hashCode) +
|
||||||
|
(permissions.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'SharingOptionsResponseDto[inTimeline=$inTimeline, permissions=$permissions]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final json = <String, dynamic>{};
|
||||||
|
json[r'inTimeline'] = this.inTimeline;
|
||||||
|
json[r'permissions'] = this.permissions;
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [SharingOptionsResponseDto] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static SharingOptionsResponseDto? fromJson(dynamic value) {
|
||||||
|
upgradeDto(value, "SharingOptionsResponseDto");
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
return SharingOptionsResponseDto(
|
||||||
|
inTimeline: mapValueOfType<bool>(json, r'inTimeline')!,
|
||||||
|
permissions: SharingPermission.listFromJson(json[r'permissions']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<SharingOptionsResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <SharingOptionsResponseDto>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = SharingOptionsResponseDto.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, SharingOptionsResponseDto> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, SharingOptionsResponseDto>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = SharingOptionsResponseDto.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of SharingOptionsResponseDto-objects as value to a dart map
|
||||||
|
static Map<String, List<SharingOptionsResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<SharingOptionsResponseDto>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
json = json.cast<String, dynamic>();
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
map[entry.key] = SharingOptionsResponseDto.listFromJson(entry.value, growable: growable,);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
'inTimeline',
|
||||||
|
'permissions',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
+112
@@ -0,0 +1,112 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.18
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
part of openapi.api;
|
||||||
|
|
||||||
|
/// Sharing permission schema
|
||||||
|
class SharingPermission {
|
||||||
|
/// Instantiate a new enum with the provided [value].
|
||||||
|
const SharingPermission._(this.value);
|
||||||
|
|
||||||
|
/// The underlying value of this enum member.
|
||||||
|
final String value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => value;
|
||||||
|
|
||||||
|
String toJson() => value;
|
||||||
|
|
||||||
|
static const all = SharingPermission._(r'all');
|
||||||
|
static const assetPeriodRead = SharingPermission._(r'asset.read');
|
||||||
|
static const assetPeriodUpdate = SharingPermission._(r'asset.update');
|
||||||
|
static const assetPeriodEdit = SharingPermission._(r'asset.edit');
|
||||||
|
static const assetPeriodDelete = SharingPermission._(r'asset.delete');
|
||||||
|
static const assetPeriodShare = SharingPermission._(r'asset.share');
|
||||||
|
static const exifPeriodRead = SharingPermission._(r'exif.read');
|
||||||
|
static const personPeriodRead = SharingPermission._(r'person.read');
|
||||||
|
static const personPeriodUpdate = SharingPermission._(r'person.update');
|
||||||
|
static const personPeriodMerge = SharingPermission._(r'person.merge');
|
||||||
|
static const personPeriodDelete = SharingPermission._(r'person.delete');
|
||||||
|
|
||||||
|
/// List of all possible values in this [enum][SharingPermission].
|
||||||
|
static const values = <SharingPermission>[
|
||||||
|
all,
|
||||||
|
assetPeriodRead,
|
||||||
|
assetPeriodUpdate,
|
||||||
|
assetPeriodEdit,
|
||||||
|
assetPeriodDelete,
|
||||||
|
assetPeriodShare,
|
||||||
|
exifPeriodRead,
|
||||||
|
personPeriodRead,
|
||||||
|
personPeriodUpdate,
|
||||||
|
personPeriodMerge,
|
||||||
|
personPeriodDelete,
|
||||||
|
];
|
||||||
|
|
||||||
|
static SharingPermission? fromJson(dynamic value) => SharingPermissionTypeTransformer().decode(value);
|
||||||
|
|
||||||
|
static List<SharingPermission> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <SharingPermission>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = SharingPermission.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transformation class that can [encode] an instance of [SharingPermission] to String,
|
||||||
|
/// and [decode] dynamic data back to [SharingPermission].
|
||||||
|
class SharingPermissionTypeTransformer {
|
||||||
|
factory SharingPermissionTypeTransformer() => _instance ??= const SharingPermissionTypeTransformer._();
|
||||||
|
|
||||||
|
const SharingPermissionTypeTransformer._();
|
||||||
|
|
||||||
|
String encode(SharingPermission data) => data.value;
|
||||||
|
|
||||||
|
/// Decodes a [dynamic value][data] to a SharingPermission.
|
||||||
|
///
|
||||||
|
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
|
||||||
|
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
|
||||||
|
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
|
||||||
|
///
|
||||||
|
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
|
||||||
|
/// and users are still using an old app with the old code.
|
||||||
|
SharingPermission? decode(dynamic data, {bool allowNull = true}) {
|
||||||
|
if (data != null) {
|
||||||
|
switch (data) {
|
||||||
|
case r'all': return SharingPermission.all;
|
||||||
|
case r'asset.read': return SharingPermission.assetPeriodRead;
|
||||||
|
case r'asset.update': return SharingPermission.assetPeriodUpdate;
|
||||||
|
case r'asset.edit': return SharingPermission.assetPeriodEdit;
|
||||||
|
case r'asset.delete': return SharingPermission.assetPeriodDelete;
|
||||||
|
case r'asset.share': return SharingPermission.assetPeriodShare;
|
||||||
|
case r'exif.read': return SharingPermission.exifPeriodRead;
|
||||||
|
case r'person.read': return SharingPermission.personPeriodRead;
|
||||||
|
case r'person.update': return SharingPermission.personPeriodUpdate;
|
||||||
|
case r'person.merge': return SharingPermission.personPeriodMerge;
|
||||||
|
case r'person.delete': return SharingPermission.personPeriodDelete;
|
||||||
|
default:
|
||||||
|
if (!allowNull) {
|
||||||
|
throw ArgumentError('Unknown enum value to decode: $data');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Singleton [SharingPermissionTypeTransformer] instance.
|
||||||
|
static SharingPermissionTypeTransformer? _instance;
|
||||||
|
}
|
||||||
|
|
||||||
+14
-14
@@ -19,11 +19,11 @@ class SyncAssetFaceV2 {
|
|||||||
required this.boundingBoxY1,
|
required this.boundingBoxY1,
|
||||||
required this.boundingBoxY2,
|
required this.boundingBoxY2,
|
||||||
required this.deletedAt,
|
required this.deletedAt,
|
||||||
|
required this.faceClusterId,
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.imageHeight,
|
required this.imageHeight,
|
||||||
required this.imageWidth,
|
required this.imageWidth,
|
||||||
required this.isVisible,
|
required this.isVisible,
|
||||||
required this.personId,
|
|
||||||
required this.sourceType,
|
required this.sourceType,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -57,6 +57,9 @@ class SyncAssetFaceV2 {
|
|||||||
/// Face deleted at
|
/// Face deleted at
|
||||||
DateTime? deletedAt;
|
DateTime? deletedAt;
|
||||||
|
|
||||||
|
/// Person ID
|
||||||
|
String? faceClusterId;
|
||||||
|
|
||||||
/// Asset face ID
|
/// Asset face ID
|
||||||
String id;
|
String id;
|
||||||
|
|
||||||
@@ -75,9 +78,6 @@ class SyncAssetFaceV2 {
|
|||||||
/// Is the face visible in the asset
|
/// Is the face visible in the asset
|
||||||
bool isVisible;
|
bool isVisible;
|
||||||
|
|
||||||
/// Person ID
|
|
||||||
String? personId;
|
|
||||||
|
|
||||||
/// Source type
|
/// Source type
|
||||||
String sourceType;
|
String sourceType;
|
||||||
|
|
||||||
@@ -89,11 +89,11 @@ class SyncAssetFaceV2 {
|
|||||||
other.boundingBoxY1 == boundingBoxY1 &&
|
other.boundingBoxY1 == boundingBoxY1 &&
|
||||||
other.boundingBoxY2 == boundingBoxY2 &&
|
other.boundingBoxY2 == boundingBoxY2 &&
|
||||||
other.deletedAt == deletedAt &&
|
other.deletedAt == deletedAt &&
|
||||||
|
other.faceClusterId == faceClusterId &&
|
||||||
other.id == id &&
|
other.id == id &&
|
||||||
other.imageHeight == imageHeight &&
|
other.imageHeight == imageHeight &&
|
||||||
other.imageWidth == imageWidth &&
|
other.imageWidth == imageWidth &&
|
||||||
other.isVisible == isVisible &&
|
other.isVisible == isVisible &&
|
||||||
other.personId == personId &&
|
|
||||||
other.sourceType == sourceType;
|
other.sourceType == sourceType;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -105,15 +105,15 @@ class SyncAssetFaceV2 {
|
|||||||
(boundingBoxY1.hashCode) +
|
(boundingBoxY1.hashCode) +
|
||||||
(boundingBoxY2.hashCode) +
|
(boundingBoxY2.hashCode) +
|
||||||
(deletedAt == null ? 0 : deletedAt!.hashCode) +
|
(deletedAt == null ? 0 : deletedAt!.hashCode) +
|
||||||
|
(faceClusterId == null ? 0 : faceClusterId!.hashCode) +
|
||||||
(id.hashCode) +
|
(id.hashCode) +
|
||||||
(imageHeight.hashCode) +
|
(imageHeight.hashCode) +
|
||||||
(imageWidth.hashCode) +
|
(imageWidth.hashCode) +
|
||||||
(isVisible.hashCode) +
|
(isVisible.hashCode) +
|
||||||
(personId == null ? 0 : personId!.hashCode) +
|
|
||||||
(sourceType.hashCode);
|
(sourceType.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'SyncAssetFaceV2[assetId=$assetId, boundingBoxX1=$boundingBoxX1, boundingBoxX2=$boundingBoxX2, boundingBoxY1=$boundingBoxY1, boundingBoxY2=$boundingBoxY2, deletedAt=$deletedAt, id=$id, imageHeight=$imageHeight, imageWidth=$imageWidth, isVisible=$isVisible, personId=$personId, sourceType=$sourceType]';
|
String toString() => 'SyncAssetFaceV2[assetId=$assetId, boundingBoxX1=$boundingBoxX1, boundingBoxX2=$boundingBoxX2, boundingBoxY1=$boundingBoxY1, boundingBoxY2=$boundingBoxY2, deletedAt=$deletedAt, faceClusterId=$faceClusterId, id=$id, imageHeight=$imageHeight, imageWidth=$imageWidth, isVisible=$isVisible, sourceType=$sourceType]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
@@ -128,16 +128,16 @@ class SyncAssetFaceV2 {
|
|||||||
: this.deletedAt!.toUtc().toIso8601String();
|
: this.deletedAt!.toUtc().toIso8601String();
|
||||||
} else {
|
} else {
|
||||||
// json[r'deletedAt'] = null;
|
// json[r'deletedAt'] = null;
|
||||||
|
}
|
||||||
|
if (this.faceClusterId != null) {
|
||||||
|
json[r'faceClusterId'] = this.faceClusterId;
|
||||||
|
} else {
|
||||||
|
// json[r'faceClusterId'] = null;
|
||||||
}
|
}
|
||||||
json[r'id'] = this.id;
|
json[r'id'] = this.id;
|
||||||
json[r'imageHeight'] = this.imageHeight;
|
json[r'imageHeight'] = this.imageHeight;
|
||||||
json[r'imageWidth'] = this.imageWidth;
|
json[r'imageWidth'] = this.imageWidth;
|
||||||
json[r'isVisible'] = this.isVisible;
|
json[r'isVisible'] = this.isVisible;
|
||||||
if (this.personId != null) {
|
|
||||||
json[r'personId'] = this.personId;
|
|
||||||
} else {
|
|
||||||
// json[r'personId'] = null;
|
|
||||||
}
|
|
||||||
json[r'sourceType'] = this.sourceType;
|
json[r'sourceType'] = this.sourceType;
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
@@ -157,11 +157,11 @@ class SyncAssetFaceV2 {
|
|||||||
boundingBoxY1: mapValueOfType<int>(json, r'boundingBoxY1')!,
|
boundingBoxY1: mapValueOfType<int>(json, r'boundingBoxY1')!,
|
||||||
boundingBoxY2: mapValueOfType<int>(json, r'boundingBoxY2')!,
|
boundingBoxY2: mapValueOfType<int>(json, r'boundingBoxY2')!,
|
||||||
deletedAt: mapDateTime(json, r'deletedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'),
|
deletedAt: mapDateTime(json, r'deletedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'),
|
||||||
|
faceClusterId: mapValueOfType<String>(json, r'faceClusterId'),
|
||||||
id: mapValueOfType<String>(json, r'id')!,
|
id: mapValueOfType<String>(json, r'id')!,
|
||||||
imageHeight: mapValueOfType<int>(json, r'imageHeight')!,
|
imageHeight: mapValueOfType<int>(json, r'imageHeight')!,
|
||||||
imageWidth: mapValueOfType<int>(json, r'imageWidth')!,
|
imageWidth: mapValueOfType<int>(json, r'imageWidth')!,
|
||||||
isVisible: mapValueOfType<bool>(json, r'isVisible')!,
|
isVisible: mapValueOfType<bool>(json, r'isVisible')!,
|
||||||
personId: mapValueOfType<String>(json, r'personId'),
|
|
||||||
sourceType: mapValueOfType<String>(json, r'sourceType')!,
|
sourceType: mapValueOfType<String>(json, r'sourceType')!,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -216,11 +216,11 @@ class SyncAssetFaceV2 {
|
|||||||
'boundingBoxY1',
|
'boundingBoxY1',
|
||||||
'boundingBoxY2',
|
'boundingBoxY2',
|
||||||
'deletedAt',
|
'deletedAt',
|
||||||
|
'faceClusterId',
|
||||||
'id',
|
'id',
|
||||||
'imageHeight',
|
'imageHeight',
|
||||||
'imageWidth',
|
'imageWidth',
|
||||||
'isVisible',
|
'isVisible',
|
||||||
'personId',
|
|
||||||
'sourceType',
|
'sourceType',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.18
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
part of openapi.api;
|
||||||
|
|
||||||
|
class UpdateSharingOptionsDto {
|
||||||
|
/// Returns a new [UpdateSharingOptionsDto] instance.
|
||||||
|
UpdateSharingOptionsDto({
|
||||||
|
required this.inTimeline,
|
||||||
|
this.permissions = const [],
|
||||||
|
});
|
||||||
|
|
||||||
|
bool inTimeline;
|
||||||
|
|
||||||
|
List<SharingPermission> permissions;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is UpdateSharingOptionsDto &&
|
||||||
|
other.inTimeline == inTimeline &&
|
||||||
|
_deepEquality.equals(other.permissions, permissions);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(inTimeline.hashCode) +
|
||||||
|
(permissions.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'UpdateSharingOptionsDto[inTimeline=$inTimeline, permissions=$permissions]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final json = <String, dynamic>{};
|
||||||
|
json[r'inTimeline'] = this.inTimeline;
|
||||||
|
json[r'permissions'] = this.permissions;
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [UpdateSharingOptionsDto] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static UpdateSharingOptionsDto? fromJson(dynamic value) {
|
||||||
|
upgradeDto(value, "UpdateSharingOptionsDto");
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
return UpdateSharingOptionsDto(
|
||||||
|
inTimeline: mapValueOfType<bool>(json, r'inTimeline')!,
|
||||||
|
permissions: SharingPermission.listFromJson(json[r'permissions']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<UpdateSharingOptionsDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <UpdateSharingOptionsDto>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = UpdateSharingOptionsDto.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, UpdateSharingOptionsDto> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, UpdateSharingOptionsDto>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = UpdateSharingOptionsDto.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of UpdateSharingOptionsDto-objects as value to a dart map
|
||||||
|
static Map<String, List<UpdateSharingOptionsDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<UpdateSharingOptionsDto>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
json = json.cast<String, dynamic>();
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
map[entry.key] = UpdateSharingOptionsDto.listFromJson(entry.value, growable: growable,);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
'inTimeline',
|
||||||
|
'permissions',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@@ -11,7 +11,14 @@ import 'package:pigeon/pigeon.dart';
|
|||||||
dartPackageName: 'immich_mobile',
|
dartPackageName: 'immich_mobile',
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
enum PlatformAssetPlaybackStyle { unknown, image, video, imageAnimated, livePhoto, videoLooping }
|
enum PlatformAssetPlaybackStyle {
|
||||||
|
unknown,
|
||||||
|
image,
|
||||||
|
video,
|
||||||
|
imageAnimated,
|
||||||
|
livePhoto,
|
||||||
|
videoLooping,
|
||||||
|
}
|
||||||
|
|
||||||
class PlatformAsset {
|
class PlatformAsset {
|
||||||
final String id;
|
final String id;
|
||||||
@@ -135,9 +142,6 @@ abstract class NativeSyncApi {
|
|||||||
@TaskQueue(type: TaskQueueType.serialBackgroundThread)
|
@TaskQueue(type: TaskQueueType.serialBackgroundThread)
|
||||||
Map<String, List<PlatformAsset>> getTrashedAssets();
|
Map<String, List<PlatformAsset>> getTrashedAssets();
|
||||||
|
|
||||||
@async
|
|
||||||
bool restoreFromTrashById(String mediaId, int type);
|
|
||||||
|
|
||||||
@TaskQueue(type: TaskQueueType.serialBackgroundThread)
|
@TaskQueue(type: TaskQueueType.serialBackgroundThread)
|
||||||
List<CloudIdResult> getCloudIdForAssetIds(List<String> assetIds);
|
List<CloudIdResult> getCloudIdForAssetIds(List<String> assetIds);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,4 @@ abstract class NetworkApi {
|
|||||||
int getClientPointer();
|
int getClientPointer();
|
||||||
|
|
||||||
void setRequestHeaders(Map<String, String> headers, List<String> serverUrls, String? token);
|
void setRequestHeaders(Map<String, String> headers, List<String> serverUrls, String? token);
|
||||||
|
|
||||||
String getAppGroupId();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
import 'package:pigeon/pigeon.dart';
|
|
||||||
|
|
||||||
@ConfigurePigeon(
|
|
||||||
PigeonOptions(
|
|
||||||
dartOut: 'lib/platform/permission_api.g.dart',
|
|
||||||
swiftOut: 'ios/Runner/Permission/PermissionApi.g.swift',
|
|
||||||
swiftOptions: SwiftOptions(),
|
|
||||||
kotlinOut: 'android/app/src/main/kotlin/app/alextran/immich/permission/PermissionApi.g.kt',
|
|
||||||
kotlinOptions: KotlinOptions(package: 'app.alextran.immich.permission'),
|
|
||||||
dartOptions: DartOptions(),
|
|
||||||
dartPackageName: 'immich_mobile',
|
|
||||||
),
|
|
||||||
)
|
|
||||||
@HostApi()
|
|
||||||
abstract class PermissionApi {
|
|
||||||
bool hasManageMediaPermission();
|
|
||||||
|
|
||||||
@async
|
|
||||||
bool requestManageMediaPermission();
|
|
||||||
|
|
||||||
@async
|
|
||||||
bool manageMediaPermission();
|
|
||||||
}
|
|
||||||
@@ -10,15 +10,17 @@ import 'package:immich_mobile/entities/store.entity.dart';
|
|||||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart';
|
||||||
import 'package:immich_mobile/platform/native_sync_api.g.dart';
|
import 'package:immich_mobile/platform/native_sync_api.g.dart';
|
||||||
import 'package:immich_mobile/repositories/asset_media.repository.dart';
|
import 'package:immich_mobile/repositories/local_files_manager.repository.dart';
|
||||||
import 'package:mocktail/mocktail.dart';
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
|
||||||
import '../../domain/service.mock.dart';
|
import '../../domain/service.mock.dart';
|
||||||
import '../../fixtures/asset.stub.dart';
|
import '../../fixtures/asset.stub.dart';
|
||||||
import '../../infrastructure/repository.mock.dart';
|
import '../../infrastructure/repository.mock.dart';
|
||||||
|
import '../../mocks/asset_entity.mock.dart';
|
||||||
import '../../repository.mocks.dart';
|
import '../../repository.mocks.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
@@ -26,8 +28,8 @@ void main() {
|
|||||||
late DriftLocalAlbumRepository mockLocalAlbumRepository;
|
late DriftLocalAlbumRepository mockLocalAlbumRepository;
|
||||||
late DriftLocalAssetRepository mockLocalAssetRepository;
|
late DriftLocalAssetRepository mockLocalAssetRepository;
|
||||||
late DriftTrashedLocalAssetRepository mockTrashedLocalAssetRepository;
|
late DriftTrashedLocalAssetRepository mockTrashedLocalAssetRepository;
|
||||||
late AssetMediaRepository mockAssetMediaRepository;
|
late LocalFilesManagerRepository mockLocalFilesManager;
|
||||||
late MockPermissionRepository mockPermissionRepository;
|
late StorageRepository mockStorageRepository;
|
||||||
late MockNativeSyncApi mockNativeSyncApi;
|
late MockNativeSyncApi mockNativeSyncApi;
|
||||||
late Drift db;
|
late Drift db;
|
||||||
|
|
||||||
@@ -49,8 +51,8 @@ void main() {
|
|||||||
mockLocalAlbumRepository = MockLocalAlbumRepository();
|
mockLocalAlbumRepository = MockLocalAlbumRepository();
|
||||||
mockLocalAssetRepository = MockLocalAssetRepository();
|
mockLocalAssetRepository = MockLocalAssetRepository();
|
||||||
mockTrashedLocalAssetRepository = MockTrashedLocalAssetRepository();
|
mockTrashedLocalAssetRepository = MockTrashedLocalAssetRepository();
|
||||||
mockAssetMediaRepository = MockAssetMediaRepository();
|
mockLocalFilesManager = MockLocalFilesManagerRepository();
|
||||||
mockPermissionRepository = MockPermissionRepository();
|
mockStorageRepository = MockStorageRepository();
|
||||||
mockNativeSyncApi = MockNativeSyncApi();
|
mockNativeSyncApi = MockNativeSyncApi();
|
||||||
|
|
||||||
when(() => mockNativeSyncApi.shouldFullSync()).thenAnswer((_) async => false);
|
when(() => mockNativeSyncApi.shouldFullSync()).thenAnswer((_) async => false);
|
||||||
@@ -63,28 +65,25 @@ void main() {
|
|||||||
when(() => mockTrashedLocalAssetRepository.getToTrash()).thenAnswer((_) async => {});
|
when(() => mockTrashedLocalAssetRepository.getToTrash()).thenAnswer((_) async => {});
|
||||||
when(() => mockTrashedLocalAssetRepository.applyRestoredAssets(any())).thenAnswer((_) async {});
|
when(() => mockTrashedLocalAssetRepository.applyRestoredAssets(any())).thenAnswer((_) async {});
|
||||||
when(() => mockTrashedLocalAssetRepository.trashLocalAsset(any())).thenAnswer((_) async {});
|
when(() => mockTrashedLocalAssetRepository.trashLocalAsset(any())).thenAnswer((_) async {});
|
||||||
when(() => mockAssetMediaRepository.deleteAll(any())).thenAnswer((invocation) async {
|
when(() => mockLocalFilesManager.moveToTrash(any<List<String>>())).thenAnswer((_) async => true);
|
||||||
final ids = invocation.positionalArguments.first as List<String>;
|
|
||||||
return ids;
|
|
||||||
});
|
|
||||||
|
|
||||||
sut = LocalSyncService(
|
sut = LocalSyncService(
|
||||||
localAlbumRepository: mockLocalAlbumRepository,
|
localAlbumRepository: mockLocalAlbumRepository,
|
||||||
localAssetRepository: mockLocalAssetRepository,
|
localAssetRepository: mockLocalAssetRepository,
|
||||||
trashedLocalAssetRepository: mockTrashedLocalAssetRepository,
|
trashedLocalAssetRepository: mockTrashedLocalAssetRepository,
|
||||||
assetMediaRepository: mockAssetMediaRepository,
|
localFilesManager: mockLocalFilesManager,
|
||||||
permissionRepository: mockPermissionRepository,
|
storageRepository: mockStorageRepository,
|
||||||
nativeSyncApi: mockNativeSyncApi,
|
nativeSyncApi: mockNativeSyncApi,
|
||||||
);
|
);
|
||||||
|
|
||||||
await Store.put(StoreKey.manageLocalMediaAndroid, false);
|
await Store.put(StoreKey.manageLocalMediaAndroid, false);
|
||||||
when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => false);
|
when(() => mockLocalFilesManager.hasManageMediaPermission()).thenAnswer((_) async => false);
|
||||||
});
|
});
|
||||||
|
|
||||||
group('LocalSyncService - syncTrashedAssets gating', () {
|
group('LocalSyncService - syncTrashedAssets gating', () {
|
||||||
test('invokes syncTrashedAssets when Android flag enabled and permission granted', () async {
|
test('invokes syncTrashedAssets when Android flag enabled and permission granted', () async {
|
||||||
await Store.put(StoreKey.manageLocalMediaAndroid, true);
|
await Store.put(StoreKey.manageLocalMediaAndroid, true);
|
||||||
when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => true);
|
when(() => mockLocalFilesManager.hasManageMediaPermission()).thenAnswer((_) async => true);
|
||||||
|
|
||||||
await sut.sync();
|
await sut.sync();
|
||||||
|
|
||||||
@@ -94,7 +93,7 @@ void main() {
|
|||||||
|
|
||||||
test('skips syncTrashedAssets when store flag disabled', () async {
|
test('skips syncTrashedAssets when store flag disabled', () async {
|
||||||
await Store.put(StoreKey.manageLocalMediaAndroid, false);
|
await Store.put(StoreKey.manageLocalMediaAndroid, false);
|
||||||
when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => true);
|
when(() => mockLocalFilesManager.hasManageMediaPermission()).thenAnswer((_) async => true);
|
||||||
|
|
||||||
await sut.sync();
|
await sut.sync();
|
||||||
|
|
||||||
@@ -103,7 +102,7 @@ void main() {
|
|||||||
|
|
||||||
test('skips syncTrashedAssets when MANAGE_MEDIA permission absent', () async {
|
test('skips syncTrashedAssets when MANAGE_MEDIA permission absent', () async {
|
||||||
await Store.put(StoreKey.manageLocalMediaAndroid, true);
|
await Store.put(StoreKey.manageLocalMediaAndroid, true);
|
||||||
when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => false);
|
when(() => mockLocalFilesManager.hasManageMediaPermission()).thenAnswer((_) async => false);
|
||||||
|
|
||||||
await sut.sync();
|
await sut.sync();
|
||||||
|
|
||||||
@@ -115,7 +114,7 @@ void main() {
|
|||||||
addTearDown(() => debugDefaultTargetPlatformOverride = TargetPlatform.android);
|
addTearDown(() => debugDefaultTargetPlatformOverride = TargetPlatform.android);
|
||||||
|
|
||||||
await Store.put(StoreKey.manageLocalMediaAndroid, true);
|
await Store.put(StoreKey.manageLocalMediaAndroid, true);
|
||||||
when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => true);
|
when(() => mockLocalFilesManager.hasManageMediaPermission()).thenAnswer((_) async => true);
|
||||||
|
|
||||||
await sut.sync();
|
await sut.sync();
|
||||||
|
|
||||||
@@ -132,13 +131,13 @@ void main() {
|
|||||||
durationMs: 0,
|
durationMs: 0,
|
||||||
orientation: 0,
|
orientation: 0,
|
||||||
isFavorite: false,
|
isFavorite: false,
|
||||||
playbackStyle: PlatformAssetPlaybackStyle.image,
|
playbackStyle: PlatformAssetPlaybackStyle.image
|
||||||
);
|
);
|
||||||
|
|
||||||
final assetsToRestore = [LocalAssetStub.image1];
|
final assetsToRestore = [LocalAssetStub.image1];
|
||||||
when(() => mockTrashedLocalAssetRepository.getToRestore()).thenAnswer((_) async => assetsToRestore);
|
when(() => mockTrashedLocalAssetRepository.getToRestore()).thenAnswer((_) async => assetsToRestore);
|
||||||
final restoredIds = ['image1'];
|
final restoredIds = ['image1'];
|
||||||
when(() => mockAssetMediaRepository.restoreAssetsFromTrash(any())).thenAnswer((invocation) async {
|
when(() => mockLocalFilesManager.restoreAssetsFromTrash(any())).thenAnswer((invocation) async {
|
||||||
final Iterable<LocalAsset> requested = invocation.positionalArguments.first as Iterable<LocalAsset>;
|
final Iterable<LocalAsset> requested = invocation.positionalArguments.first as Iterable<LocalAsset>;
|
||||||
expect(requested, orderedEquals(assetsToRestore));
|
expect(requested, orderedEquals(assetsToRestore));
|
||||||
return restoredIds;
|
return restoredIds;
|
||||||
@@ -151,6 +150,10 @@ void main() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final assetEntity = MockAssetEntity();
|
||||||
|
when(() => assetEntity.getMediaUrl()).thenAnswer((_) async => 'content://local-trash');
|
||||||
|
when(() => mockStorageRepository.getAssetEntityForAsset(localAssetToTrash)).thenAnswer((_) async => assetEntity);
|
||||||
|
|
||||||
await sut.processTrashedAssets({
|
await sut.processTrashedAssets({
|
||||||
'album-a': [platformAsset],
|
'album-a': [platformAsset],
|
||||||
});
|
});
|
||||||
@@ -165,11 +168,12 @@ void main() {
|
|||||||
expect(trashedEntry.asset.name, platformAsset.name);
|
expect(trashedEntry.asset.name, platformAsset.name);
|
||||||
verify(() => mockTrashedLocalAssetRepository.getToTrash()).called(1);
|
verify(() => mockTrashedLocalAssetRepository.getToTrash()).called(1);
|
||||||
|
|
||||||
verify(() => mockAssetMediaRepository.restoreAssetsFromTrash(any())).called(1);
|
verify(() => mockLocalFilesManager.restoreAssetsFromTrash(any())).called(1);
|
||||||
verify(() => mockTrashedLocalAssetRepository.applyRestoredAssets(restoredIds)).called(1);
|
verify(() => mockTrashedLocalAssetRepository.applyRestoredAssets(restoredIds)).called(1);
|
||||||
|
|
||||||
final moveArgs = verify(() => mockAssetMediaRepository.deleteAll(captureAny())).captured.single as List<String>;
|
verify(() => mockStorageRepository.getAssetEntityForAsset(localAssetToTrash)).called(1);
|
||||||
expect(moveArgs, ['local-trash']);
|
final moveArgs = verify(() => mockLocalFilesManager.moveToTrash(captureAny())).captured.single as List<String>;
|
||||||
|
expect(moveArgs, ['content://local-trash']);
|
||||||
final trashArgs =
|
final trashArgs =
|
||||||
verify(() => mockTrashedLocalAssetRepository.trashLocalAsset(captureAny())).captured.single
|
verify(() => mockTrashedLocalAssetRepository.trashLocalAsset(captureAny())).captured.single
|
||||||
as Map<String, List<LocalAsset>>;
|
as Map<String, List<LocalAsset>>;
|
||||||
@@ -177,26 +181,6 @@ void main() {
|
|||||||
expect(trashArgs['album-a'], [localAssetToTrash]);
|
expect(trashArgs['album-a'], [localAssetToTrash]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('records only local assets that were moved to device trash', () async {
|
|
||||||
final movedAsset = LocalAssetStub.image1.copyWith(id: 'moved-local', checksum: 'checksum-moved');
|
|
||||||
final skippedAsset = LocalAssetStub.image2.copyWith(id: 'skipped-local', checksum: 'checksum-skipped');
|
|
||||||
when(() => mockTrashedLocalAssetRepository.getToTrash()).thenAnswer(
|
|
||||||
(_) async => {
|
|
||||||
'album-a': [movedAsset],
|
|
||||||
'album-b': [skippedAsset],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
when(() => mockAssetMediaRepository.deleteAll(any())).thenAnswer((_) async => ['moved-local']);
|
|
||||||
|
|
||||||
await sut.processTrashedAssets({});
|
|
||||||
|
|
||||||
final trashArgs =
|
|
||||||
verify(() => mockTrashedLocalAssetRepository.trashLocalAsset(captureAny())).captured.single
|
|
||||||
as Map<String, List<LocalAsset>>;
|
|
||||||
expect(trashArgs.keys, ['album-a']);
|
|
||||||
expect(trashArgs['album-a'], [movedAsset]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('does not attempt restore when repository has no assets to restore', () async {
|
test('does not attempt restore when repository has no assets to restore', () async {
|
||||||
when(() => mockTrashedLocalAssetRepository.getToRestore()).thenAnswer((_) async => []);
|
when(() => mockTrashedLocalAssetRepository.getToRestore()).thenAnswer((_) async => []);
|
||||||
|
|
||||||
@@ -206,7 +190,7 @@ void main() {
|
|||||||
verify(() => mockTrashedLocalAssetRepository.processTrashSnapshot(captureAny())).captured.single
|
verify(() => mockTrashedLocalAssetRepository.processTrashSnapshot(captureAny())).captured.single
|
||||||
as Iterable<TrashedAsset>;
|
as Iterable<TrashedAsset>;
|
||||||
expect(trashedSnapshot, isEmpty);
|
expect(trashedSnapshot, isEmpty);
|
||||||
verifyNever(() => mockAssetMediaRepository.restoreAssetsFromTrash(any()));
|
verifyNever(() => mockLocalFilesManager.restoreAssetsFromTrash(any()));
|
||||||
verifyNever(() => mockTrashedLocalAssetRepository.applyRestoredAssets(any()));
|
verifyNever(() => mockTrashedLocalAssetRepository.applyRestoredAssets(any()));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -215,7 +199,7 @@ void main() {
|
|||||||
|
|
||||||
await sut.processTrashedAssets({});
|
await sut.processTrashedAssets({});
|
||||||
|
|
||||||
verifyNever(() => mockAssetMediaRepository.deleteAll(any()));
|
verifyNever(() => mockLocalFilesManager.moveToTrash(any()));
|
||||||
verifyNever(() => mockTrashedLocalAssetRepository.trashLocalAsset(any()));
|
verifyNever(() => mockTrashedLocalAssetRepository.trashLocalAsset(any()));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -231,7 +215,7 @@ void main() {
|
|||||||
isFavorite: false,
|
isFavorite: false,
|
||||||
createdAt: 1700000000,
|
createdAt: 1700000000,
|
||||||
updatedAt: 1732000000,
|
updatedAt: 1732000000,
|
||||||
playbackStyle: PlatformAssetPlaybackStyle.image,
|
playbackStyle: PlatformAssetPlaybackStyle.image
|
||||||
);
|
);
|
||||||
|
|
||||||
final localAsset = platformAsset.toLocalAsset();
|
final localAsset = platformAsset.toLocalAsset();
|
||||||
|
|||||||
@@ -12,11 +12,12 @@ import 'package:immich_mobile/domain/services/sync_stream.service.dart';
|
|||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/asset_media.repository.dart';
|
import 'package:immich_mobile/repositories/local_files_manager.repository.dart';
|
||||||
import 'package:immich_mobile/utils/semver.dart';
|
import 'package:immich_mobile/utils/semver.dart';
|
||||||
import 'package:mocktail/mocktail.dart';
|
import 'package:mocktail/mocktail.dart';
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
@@ -25,6 +26,7 @@ import '../../api.mocks.dart';
|
|||||||
import '../../fixtures/asset.stub.dart';
|
import '../../fixtures/asset.stub.dart';
|
||||||
import '../../fixtures/sync_stream.stub.dart';
|
import '../../fixtures/sync_stream.stub.dart';
|
||||||
import '../../infrastructure/repository.mock.dart';
|
import '../../infrastructure/repository.mock.dart';
|
||||||
|
import '../../mocks/asset_entity.mock.dart';
|
||||||
import '../../repository.mocks.dart';
|
import '../../repository.mocks.dart';
|
||||||
import '../../service.mocks.dart';
|
import '../../service.mocks.dart';
|
||||||
|
|
||||||
@@ -50,8 +52,8 @@ void main() {
|
|||||||
late SyncApiRepository mockSyncApiRepo;
|
late SyncApiRepository mockSyncApiRepo;
|
||||||
late DriftLocalAssetRepository mockLocalAssetRepo;
|
late DriftLocalAssetRepository mockLocalAssetRepo;
|
||||||
late DriftTrashedLocalAssetRepository mockTrashedLocalAssetRepo;
|
late DriftTrashedLocalAssetRepository mockTrashedLocalAssetRepo;
|
||||||
late AssetMediaRepository mockAssetMediaRepo;
|
late LocalFilesManagerRepository mockLocalFilesManagerRepo;
|
||||||
late MockPermissionRepository mockPermissionRepo;
|
late StorageRepository mockStorageRepo;
|
||||||
late MockApiService mockApi;
|
late MockApiService mockApi;
|
||||||
late MockServerApi mockServerApi;
|
late MockServerApi mockServerApi;
|
||||||
late MockSyncMigrationRepository mockSyncMigrationRepo;
|
late MockSyncMigrationRepository mockSyncMigrationRepo;
|
||||||
@@ -84,8 +86,8 @@ void main() {
|
|||||||
mockSyncApiRepo = MockSyncApiRepository();
|
mockSyncApiRepo = MockSyncApiRepository();
|
||||||
mockLocalAssetRepo = MockLocalAssetRepository();
|
mockLocalAssetRepo = MockLocalAssetRepository();
|
||||||
mockTrashedLocalAssetRepo = MockTrashedLocalAssetRepository();
|
mockTrashedLocalAssetRepo = MockTrashedLocalAssetRepository();
|
||||||
mockAssetMediaRepo = MockAssetMediaRepository();
|
mockLocalFilesManagerRepo = MockLocalFilesManagerRepository();
|
||||||
mockPermissionRepo = MockPermissionRepository();
|
mockStorageRepo = MockStorageRepository();
|
||||||
mockAbortCallbackWrapper = _MockAbortCallbackWrapper();
|
mockAbortCallbackWrapper = _MockAbortCallbackWrapper();
|
||||||
mockResetCallbackWrapper = _MockAbortCallbackWrapper();
|
mockResetCallbackWrapper = _MockAbortCallbackWrapper();
|
||||||
mockApi = MockApiService();
|
mockApi = MockApiService();
|
||||||
@@ -157,8 +159,8 @@ void main() {
|
|||||||
syncStreamRepository: mockSyncStreamRepo,
|
syncStreamRepository: mockSyncStreamRepo,
|
||||||
localAssetRepository: mockLocalAssetRepo,
|
localAssetRepository: mockLocalAssetRepo,
|
||||||
trashedLocalAssetRepository: mockTrashedLocalAssetRepo,
|
trashedLocalAssetRepository: mockTrashedLocalAssetRepo,
|
||||||
assetMediaRepository: mockAssetMediaRepo,
|
localFilesManager: mockLocalFilesManagerRepo,
|
||||||
permissionRepository: mockPermissionRepo,
|
storageRepository: mockStorageRepo,
|
||||||
api: mockApi,
|
api: mockApi,
|
||||||
syncMigrationRepository: mockSyncMigrationRepo,
|
syncMigrationRepository: mockSyncMigrationRepo,
|
||||||
);
|
);
|
||||||
@@ -168,12 +170,10 @@ void main() {
|
|||||||
when(() => mockTrashedLocalAssetRepo.getToRestore()).thenAnswer((_) async => []);
|
when(() => mockTrashedLocalAssetRepo.getToRestore()).thenAnswer((_) async => []);
|
||||||
when(() => mockTrashedLocalAssetRepo.applyRestoredAssets(any())).thenAnswer((_) async {});
|
when(() => mockTrashedLocalAssetRepo.applyRestoredAssets(any())).thenAnswer((_) async {});
|
||||||
hasManageMediaPermission = false;
|
hasManageMediaPermission = false;
|
||||||
when(() => mockPermissionRepo.hasManageMediaPermission()).thenAnswer((_) async => hasManageMediaPermission);
|
when(() => mockLocalFilesManagerRepo.hasManageMediaPermission()).thenAnswer((_) async => hasManageMediaPermission);
|
||||||
when(() => mockAssetMediaRepo.deleteAll(any())).thenAnswer((invocation) async {
|
when(() => mockLocalFilesManagerRepo.moveToTrash(any())).thenAnswer((_) async => true);
|
||||||
final ids = invocation.positionalArguments.first as List<String>;
|
when(() => mockLocalFilesManagerRepo.restoreAssetsFromTrash(any())).thenAnswer((_) async => []);
|
||||||
return ids;
|
when(() => mockStorageRepo.getAssetEntityForAsset(any())).thenAnswer((_) async => null);
|
||||||
});
|
|
||||||
when(() => mockAssetMediaRepo.restoreAssetsFromTrash(any())).thenAnswer((_) async => []);
|
|
||||||
await Store.put(StoreKey.manageLocalMediaAndroid, false);
|
await Store.put(StoreKey.manageLocalMediaAndroid, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -241,8 +241,8 @@ void main() {
|
|||||||
syncStreamRepository: mockSyncStreamRepo,
|
syncStreamRepository: mockSyncStreamRepo,
|
||||||
localAssetRepository: mockLocalAssetRepo,
|
localAssetRepository: mockLocalAssetRepo,
|
||||||
trashedLocalAssetRepository: mockTrashedLocalAssetRepo,
|
trashedLocalAssetRepository: mockTrashedLocalAssetRepo,
|
||||||
assetMediaRepository: mockAssetMediaRepo,
|
localFilesManager: mockLocalFilesManagerRepo,
|
||||||
permissionRepository: mockPermissionRepo,
|
storageRepository: mockStorageRepo,
|
||||||
cancelChecker: cancellationChecker.call,
|
cancelChecker: cancellationChecker.call,
|
||||||
api: mockApi,
|
api: mockApi,
|
||||||
syncMigrationRepository: mockSyncMigrationRepo,
|
syncMigrationRepository: mockSyncMigrationRepo,
|
||||||
@@ -282,8 +282,8 @@ void main() {
|
|||||||
syncStreamRepository: mockSyncStreamRepo,
|
syncStreamRepository: mockSyncStreamRepo,
|
||||||
localAssetRepository: mockLocalAssetRepo,
|
localAssetRepository: mockLocalAssetRepo,
|
||||||
trashedLocalAssetRepository: mockTrashedLocalAssetRepo,
|
trashedLocalAssetRepository: mockTrashedLocalAssetRepo,
|
||||||
assetMediaRepository: mockAssetMediaRepo,
|
localFilesManager: mockLocalFilesManagerRepo,
|
||||||
permissionRepository: mockPermissionRepo,
|
storageRepository: mockStorageRepo,
|
||||||
cancelChecker: cancellationChecker.call,
|
cancelChecker: cancellationChecker.call,
|
||||||
api: mockApi,
|
api: mockApi,
|
||||||
syncMigrationRepository: mockSyncMigrationRepo,
|
syncMigrationRepository: mockSyncMigrationRepo,
|
||||||
@@ -424,10 +424,18 @@ void main() {
|
|||||||
return assetsByAlbum;
|
return assetsByAlbum;
|
||||||
});
|
});
|
||||||
|
|
||||||
when(() => mockAssetMediaRepo.deleteAll(any())).thenAnswer((invocation) async {
|
final localEntity = MockAssetEntity();
|
||||||
final ids = invocation.positionalArguments.first as List<String>;
|
when(() => localEntity.getMediaUrl()).thenAnswer((_) async => 'content://local-only');
|
||||||
expect(ids, unorderedEquals(['local-only', 'merged-local']));
|
when(() => mockStorageRepo.getAssetEntityForAsset(localAsset)).thenAnswer((_) async => localEntity);
|
||||||
return ids;
|
|
||||||
|
final mergedEntity = MockAssetEntity();
|
||||||
|
when(() => mergedEntity.getMediaUrl()).thenAnswer((_) async => 'content://merged-local');
|
||||||
|
when(() => mockStorageRepo.getAssetEntityForAsset(mergedAsset)).thenAnswer((_) async => mergedEntity);
|
||||||
|
|
||||||
|
when(() => mockLocalFilesManagerRepo.moveToTrash(any())).thenAnswer((invocation) async {
|
||||||
|
final urls = invocation.positionalArguments.first as List<String>;
|
||||||
|
expect(urls, unorderedEquals(['content://local-only', 'content://merged-local']));
|
||||||
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
final events = [
|
final events = [
|
||||||
@@ -453,51 +461,10 @@ void main() {
|
|||||||
|
|
||||||
await simulateEvents(events);
|
await simulateEvents(events);
|
||||||
|
|
||||||
final trashArgs =
|
verify(() => mockTrashedLocalAssetRepo.trashLocalAsset(assetsByAlbum)).called(1);
|
||||||
verify(() => mockTrashedLocalAssetRepo.trashLocalAsset(captureAny())).captured.single
|
|
||||||
as Map<String, List<LocalAsset>>;
|
|
||||||
expect(trashArgs.keys, unorderedEquals(['album-a', 'album-b']));
|
|
||||||
expect(trashArgs['album-a'], [localAsset]);
|
|
||||||
expect(trashArgs['album-b'], [mergedAsset]);
|
|
||||||
verify(() => mockAssetMediaRepo.deleteAll(any())).called(1);
|
|
||||||
verify(() => mockSyncApiRepo.ack(['asset-remote-only-3'])).called(1);
|
verify(() => mockSyncApiRepo.ack(['asset-remote-only-3'])).called(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("records only assets that were moved to device trash", () async {
|
|
||||||
final movedAsset = LocalAssetStub.image1.copyWith(id: 'moved-local', checksum: 'checksum-moved');
|
|
||||||
final skippedAsset = LocalAssetStub.image2.copyWith(id: 'skipped-local', checksum: 'checksum-skipped');
|
|
||||||
when(() => mockLocalAssetRepo.getAssetsFromBackupAlbums(any())).thenAnswer(
|
|
||||||
(_) async => {
|
|
||||||
'album-a': [movedAsset],
|
|
||||||
'album-b': [skippedAsset],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
when(() => mockAssetMediaRepo.deleteAll(any())).thenAnswer((_) async => ['moved-local']);
|
|
||||||
|
|
||||||
final events = [
|
|
||||||
SyncStreamStub.assetTrashed(
|
|
||||||
id: 'remote-moved',
|
|
||||||
checksum: movedAsset.checksum!,
|
|
||||||
ack: 'asset-remote-moved',
|
|
||||||
trashedAt: DateTime(2025, 5, 1),
|
|
||||||
),
|
|
||||||
SyncStreamStub.assetTrashed(
|
|
||||||
id: 'remote-skipped',
|
|
||||||
checksum: skippedAsset.checksum!,
|
|
||||||
ack: 'asset-remote-skipped',
|
|
||||||
trashedAt: DateTime(2025, 5, 2),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
await simulateEvents(events);
|
|
||||||
|
|
||||||
final trashArgs =
|
|
||||||
verify(() => mockTrashedLocalAssetRepo.trashLocalAsset(captureAny())).captured.single
|
|
||||||
as Map<String, List<LocalAsset>>;
|
|
||||||
expect(trashArgs.keys, ['album-a']);
|
|
||||||
expect(trashArgs['album-a'], [movedAsset]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("skips device trashing when no local assets match the remote trash payload", () async {
|
test("skips device trashing when no local assets match the remote trash payload", () async {
|
||||||
final events = [
|
final events = [
|
||||||
SyncStreamStub.assetTrashed(
|
SyncStreamStub.assetTrashed(
|
||||||
@@ -511,7 +478,7 @@ void main() {
|
|||||||
await simulateEvents(events);
|
await simulateEvents(events);
|
||||||
|
|
||||||
verify(() => mockLocalAssetRepo.getAssetsFromBackupAlbums(any())).called(1);
|
verify(() => mockLocalAssetRepo.getAssetsFromBackupAlbums(any())).called(1);
|
||||||
verifyNever(() => mockAssetMediaRepo.deleteAll(any()));
|
verifyNever(() => mockLocalFilesManagerRepo.moveToTrash(any()));
|
||||||
verifyNever(() => mockTrashedLocalAssetRepo.trashLocalAsset(any()));
|
verifyNever(() => mockTrashedLocalAssetRepo.trashLocalAsset(any()));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -527,7 +494,7 @@ void main() {
|
|||||||
await simulateEvents(events);
|
await simulateEvents(events);
|
||||||
|
|
||||||
verify(() => mockLocalAssetRepo.getAssetsFromBackupAlbums(any())).called(1);
|
verify(() => mockLocalAssetRepo.getAssetsFromBackupAlbums(any())).called(1);
|
||||||
verifyNever(() => mockAssetMediaRepo.deleteAll(any()));
|
verifyNever(() => mockLocalFilesManagerRepo.moveToTrash(any()));
|
||||||
verify(() => mockSyncStreamRepo.deleteAssetsV1(any())).called(1);
|
verify(() => mockSyncStreamRepo.deleteAssetsV1(any())).called(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -538,7 +505,7 @@ void main() {
|
|||||||
when(() => mockTrashedLocalAssetRepo.getToRestore()).thenAnswer((_) async => trashedAssets);
|
when(() => mockTrashedLocalAssetRepo.getToRestore()).thenAnswer((_) async => trashedAssets);
|
||||||
|
|
||||||
final restoredIds = ['trashed-1'];
|
final restoredIds = ['trashed-1'];
|
||||||
when(() => mockAssetMediaRepo.restoreAssetsFromTrash(any())).thenAnswer((invocation) async {
|
when(() => mockLocalFilesManagerRepo.restoreAssetsFromTrash(any())).thenAnswer((invocation) async {
|
||||||
final Iterable<LocalAsset> requestedAssets = invocation.positionalArguments.first as Iterable<LocalAsset>;
|
final Iterable<LocalAsset> requestedAssets = invocation.positionalArguments.first as Iterable<LocalAsset>;
|
||||||
expect(requestedAssets, orderedEquals(trashedAssets));
|
expect(requestedAssets, orderedEquals(trashedAssets));
|
||||||
return restoredIds;
|
return restoredIds;
|
||||||
|
|||||||
@@ -3,17 +3,17 @@ import 'package:immich_mobile/repositories/asset_media.repository.dart';
|
|||||||
import 'package:immich_mobile/repositories/auth.repository.dart';
|
import 'package:immich_mobile/repositories/auth.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/auth_api.repository.dart';
|
import 'package:immich_mobile/repositories/auth_api.repository.dart';
|
||||||
import 'package:immich_mobile/domain/services/tag.service.dart';
|
import 'package:immich_mobile/domain/services/tag.service.dart';
|
||||||
import 'package:immich_mobile/repositories/permission.repository.dart';
|
import 'package:immich_mobile/repositories/local_files_manager.repository.dart';
|
||||||
import 'package:mocktail/mocktail.dart';
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
|
||||||
class MockAssetApiRepository extends Mock implements AssetApiRepository {}
|
class MockAssetApiRepository extends Mock implements AssetApiRepository {}
|
||||||
|
|
||||||
class MockAssetMediaRepository extends Mock implements AssetMediaRepository {}
|
class MockAssetMediaRepository extends Mock implements AssetMediaRepository {}
|
||||||
|
|
||||||
class MockPermissionRepository extends Mock implements IPermissionRepository {}
|
|
||||||
|
|
||||||
class MockAuthApiRepository extends Mock implements AuthApiRepository {}
|
class MockAuthApiRepository extends Mock implements AuthApiRepository {}
|
||||||
|
|
||||||
class MockAuthRepository extends Mock implements AuthRepository {}
|
class MockAuthRepository extends Mock implements AuthRepository {}
|
||||||
|
|
||||||
|
class MockLocalFilesManagerRepository extends Mock implements LocalFilesManagerRepository {}
|
||||||
|
|
||||||
class MockTagService extends Mock implements TagService {}
|
class MockTagService extends Mock implements TagService {}
|
||||||
|
|||||||
@@ -2277,6 +2277,121 @@
|
|||||||
"x-immich-permission": "album.read"
|
"x-immich-permission": "album.read"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/albums/{id}/user/self": {
|
||||||
|
"get": {
|
||||||
|
"description": "Get the own sharing permissions in a specific album.",
|
||||||
|
"operationId": "getOwnAlbumUser",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"format": "uuid",
|
||||||
|
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/SharingOptionsResponseDto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearer": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cookie": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"api_key": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"summary": "Get own sharing permissions",
|
||||||
|
"tags": [
|
||||||
|
"Albums"
|
||||||
|
],
|
||||||
|
"x-immich-history": [
|
||||||
|
{
|
||||||
|
"version": "v3",
|
||||||
|
"state": "Added"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "v3",
|
||||||
|
"state": "Stable"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"x-immich-permission": "albumAsset.create",
|
||||||
|
"x-immich-state": "Stable"
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"description": "Change the own sharing permissions in a specific album.",
|
||||||
|
"operationId": "updateOwnAlbumUser",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"format": "uuid",
|
||||||
|
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/UpdateSharingOptionsDto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearer": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cookie": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"api_key": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"summary": "Update own sharing permissions",
|
||||||
|
"tags": [
|
||||||
|
"Albums"
|
||||||
|
],
|
||||||
|
"x-immich-history": [
|
||||||
|
{
|
||||||
|
"version": "v3",
|
||||||
|
"state": "Added"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "v3",
|
||||||
|
"state": "Stable"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"x-immich-permission": "albumAsset.create",
|
||||||
|
"x-immich-state": "Stable"
|
||||||
|
}
|
||||||
|
},
|
||||||
"/albums/{id}/user/{userId}": {
|
"/albums/{id}/user/{userId}": {
|
||||||
"delete": {
|
"delete": {
|
||||||
"description": "Remove a user from an album. Use an ID of \"me\" to leave a shared album.",
|
"description": "Remove a user from an album. Use an ID of \"me\" to leave a shared album.",
|
||||||
@@ -8345,7 +8460,7 @@
|
|||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/components/schemas/MergePersonDto"
|
"$ref": "#/components/schemas/MergeFaceClusterDto"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -16942,6 +17057,12 @@
|
|||||||
},
|
},
|
||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
|
"permissions": {
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/SharingPermission"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
"resized": {
|
"resized": {
|
||||||
"description": "Is resized",
|
"description": "Is resized",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
@@ -17013,6 +17134,7 @@
|
|||||||
"originalFileName",
|
"originalFileName",
|
||||||
"originalPath",
|
"originalPath",
|
||||||
"ownerId",
|
"ownerId",
|
||||||
|
"permissions",
|
||||||
"thumbhash",
|
"thumbhash",
|
||||||
"type",
|
"type",
|
||||||
"updatedAt",
|
"updatedAt",
|
||||||
@@ -18072,6 +18194,7 @@
|
|||||||
"DatabaseBackup",
|
"DatabaseBackup",
|
||||||
"FacialRecognitionQueueAll",
|
"FacialRecognitionQueueAll",
|
||||||
"FacialRecognition",
|
"FacialRecognition",
|
||||||
|
"FacialRecognitionMerge",
|
||||||
"FileDelete",
|
"FileDelete",
|
||||||
"FileMigrationQueueAll",
|
"FileMigrationQueueAll",
|
||||||
"LibraryDeleteCheck",
|
"LibraryDeleteCheck",
|
||||||
@@ -18481,7 +18604,8 @@
|
|||||||
"user-cleanup",
|
"user-cleanup",
|
||||||
"memory-cleanup",
|
"memory-cleanup",
|
||||||
"memory-create",
|
"memory-create",
|
||||||
"backup-database"
|
"backup-database",
|
||||||
|
"person-group-merge"
|
||||||
],
|
],
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@@ -18807,10 +18931,10 @@
|
|||||||
},
|
},
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"MergePersonDto": {
|
"MergeFaceClusterDto": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"ids": {
|
"ids": {
|
||||||
"description": "Person IDs to merge",
|
"description": "Face cluster IDs to merge",
|
||||||
"items": {
|
"items": {
|
||||||
"format": "uuid",
|
"format": "uuid",
|
||||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||||
@@ -19835,6 +19959,11 @@
|
|||||||
],
|
],
|
||||||
"x-immich-state": "Stable"
|
"x-immich-state": "Stable"
|
||||||
},
|
},
|
||||||
|
"faceClusterId": {
|
||||||
|
"description": "Face cluster ID",
|
||||||
|
"nullable": true,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"description": "Person ID",
|
"description": "Person ID",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@@ -19885,6 +20014,7 @@
|
|||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"birthDate",
|
"birthDate",
|
||||||
|
"faceClusterId",
|
||||||
"id",
|
"id",
|
||||||
"isHidden",
|
"isHidden",
|
||||||
"name",
|
"name",
|
||||||
@@ -21797,6 +21927,41 @@
|
|||||||
},
|
},
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"SharingOptionsResponseDto": {
|
||||||
|
"properties": {
|
||||||
|
"inTimeline": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"permissions": {
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/SharingPermission"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"inTimeline",
|
||||||
|
"permissions"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"SharingPermission": {
|
||||||
|
"description": "Sharing permission schema",
|
||||||
|
"enum": [
|
||||||
|
"all",
|
||||||
|
"asset.read",
|
||||||
|
"asset.update",
|
||||||
|
"asset.edit",
|
||||||
|
"asset.delete",
|
||||||
|
"asset.share",
|
||||||
|
"exif.read",
|
||||||
|
"person.read",
|
||||||
|
"person.update",
|
||||||
|
"person.merge",
|
||||||
|
"person.delete"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"SignUpDto": {
|
"SignUpDto": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"email": {
|
"email": {
|
||||||
@@ -22893,6 +23058,11 @@
|
|||||||
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"faceClusterId": {
|
||||||
|
"description": "Person ID",
|
||||||
|
"nullable": true,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"description": "Asset face ID",
|
"description": "Asset face ID",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@@ -22913,11 +23083,6 @@
|
|||||||
"description": "Is the face visible in the asset",
|
"description": "Is the face visible in the asset",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"personId": {
|
|
||||||
"description": "Person ID",
|
|
||||||
"nullable": true,
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"sourceType": {
|
"sourceType": {
|
||||||
"description": "Source type",
|
"description": "Source type",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@@ -22930,11 +23095,11 @@
|
|||||||
"boundingBoxY1",
|
"boundingBoxY1",
|
||||||
"boundingBoxY2",
|
"boundingBoxY2",
|
||||||
"deletedAt",
|
"deletedAt",
|
||||||
|
"faceClusterId",
|
||||||
"id",
|
"id",
|
||||||
"imageHeight",
|
"imageHeight",
|
||||||
"imageWidth",
|
"imageWidth",
|
||||||
"isVisible",
|
"isVisible",
|
||||||
"personId",
|
|
||||||
"sourceType"
|
"sourceType"
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
@@ -25426,6 +25591,24 @@
|
|||||||
},
|
},
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"UpdateSharingOptionsDto": {
|
||||||
|
"properties": {
|
||||||
|
"inTimeline": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"permissions": {
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/SharingPermission"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"inTimeline",
|
||||||
|
"permissions"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"UsageByUserDto": {
|
"UsageByUserDto": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"photos": {
|
"photos": {
|
||||||
|
|||||||
@@ -555,6 +555,14 @@ export type MapMarkerResponseDto = {
|
|||||||
/** State/Province name */
|
/** State/Province name */
|
||||||
state: string | null;
|
state: string | null;
|
||||||
};
|
};
|
||||||
|
export type SharingOptionsResponseDto = {
|
||||||
|
inTimeline: boolean;
|
||||||
|
permissions: SharingPermission[];
|
||||||
|
};
|
||||||
|
export type UpdateSharingOptionsDto = {
|
||||||
|
inTimeline: boolean;
|
||||||
|
permissions: SharingPermission[];
|
||||||
|
};
|
||||||
export type UpdateAlbumUserDto = {
|
export type UpdateAlbumUserDto = {
|
||||||
role: AlbumUserRole;
|
role: AlbumUserRole;
|
||||||
};
|
};
|
||||||
@@ -792,6 +800,8 @@ export type PersonResponseDto = {
|
|||||||
birthDate: string | null;
|
birthDate: string | null;
|
||||||
/** Person color (hex) */
|
/** Person color (hex) */
|
||||||
color?: string;
|
color?: string;
|
||||||
|
/** Face cluster ID */
|
||||||
|
faceClusterId: string | null;
|
||||||
/** Person ID */
|
/** Person ID */
|
||||||
id: string;
|
id: string;
|
||||||
/** Is favorite */
|
/** Is favorite */
|
||||||
@@ -875,6 +885,7 @@ export type AssetResponseDto = {
|
|||||||
/** Owner user ID */
|
/** Owner user ID */
|
||||||
ownerId: string;
|
ownerId: string;
|
||||||
people?: PersonResponseDto[];
|
people?: PersonResponseDto[];
|
||||||
|
permissions: SharingPermission[];
|
||||||
/** Is resized */
|
/** Is resized */
|
||||||
resized?: boolean;
|
resized?: boolean;
|
||||||
stack?: (AssetStackResponseDto) | null;
|
stack?: (AssetStackResponseDto) | null;
|
||||||
@@ -1460,8 +1471,8 @@ export type PersonUpdateDto = {
|
|||||||
/** Person name */
|
/** Person name */
|
||||||
name?: string;
|
name?: string;
|
||||||
};
|
};
|
||||||
export type MergePersonDto = {
|
export type MergeFaceClusterDto = {
|
||||||
/** Person IDs to merge */
|
/** Face cluster IDs to merge */
|
||||||
ids: string[];
|
ids: string[];
|
||||||
};
|
};
|
||||||
export type AssetFaceUpdateItem = {
|
export type AssetFaceUpdateItem = {
|
||||||
@@ -2922,6 +2933,8 @@ export type SyncAssetFaceV2 = {
|
|||||||
boundingBoxY2: number;
|
boundingBoxY2: number;
|
||||||
/** Face deleted at */
|
/** Face deleted at */
|
||||||
deletedAt: string | null;
|
deletedAt: string | null;
|
||||||
|
/** Person ID */
|
||||||
|
faceClusterId: string | null;
|
||||||
/** Asset face ID */
|
/** Asset face ID */
|
||||||
id: string;
|
id: string;
|
||||||
/** Image height */
|
/** Image height */
|
||||||
@@ -2930,8 +2943,6 @@ export type SyncAssetFaceV2 = {
|
|||||||
imageWidth: number;
|
imageWidth: number;
|
||||||
/** Is the face visible in the asset */
|
/** Is the face visible in the asset */
|
||||||
isVisible: boolean;
|
isVisible: boolean;
|
||||||
/** Person ID */
|
|
||||||
personId: string | null;
|
|
||||||
/** Source type */
|
/** Source type */
|
||||||
sourceType: string;
|
sourceType: string;
|
||||||
};
|
};
|
||||||
@@ -3727,6 +3738,32 @@ export function getAlbumMapMarkers({ id, key, slug }: {
|
|||||||
...opts
|
...opts
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Get own sharing permissions
|
||||||
|
*/
|
||||||
|
export function getOwnAlbumUser({ id }: {
|
||||||
|
id: string;
|
||||||
|
}, opts?: Oazapfts.RequestOpts) {
|
||||||
|
return oazapfts.ok(oazapfts.fetchJson<{
|
||||||
|
status: 200;
|
||||||
|
data: SharingOptionsResponseDto;
|
||||||
|
}>(`/albums/${encodeURIComponent(id)}/user/self`, {
|
||||||
|
...opts
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Update own sharing permissions
|
||||||
|
*/
|
||||||
|
export function updateOwnAlbumUser({ id, updateSharingOptionsDto }: {
|
||||||
|
id: string;
|
||||||
|
updateSharingOptionsDto: UpdateSharingOptionsDto;
|
||||||
|
}, opts?: Oazapfts.RequestOpts) {
|
||||||
|
return oazapfts.ok(oazapfts.fetchText(`/albums/${encodeURIComponent(id)}/user/self`, oazapfts.json({
|
||||||
|
...opts,
|
||||||
|
method: "PUT",
|
||||||
|
body: updateSharingOptionsDto
|
||||||
|
})));
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Remove user from album
|
* Remove user from album
|
||||||
*/
|
*/
|
||||||
@@ -5131,9 +5168,9 @@ export function updatePerson({ id, personUpdateDto }: {
|
|||||||
/**
|
/**
|
||||||
* Merge people
|
* Merge people
|
||||||
*/
|
*/
|
||||||
export function mergePerson({ id, mergePersonDto }: {
|
export function mergePerson({ id, mergeFaceClusterDto }: {
|
||||||
id: string;
|
id: string;
|
||||||
mergePersonDto: MergePersonDto;
|
mergeFaceClusterDto: MergeFaceClusterDto;
|
||||||
}, opts?: Oazapfts.RequestOpts) {
|
}, opts?: Oazapfts.RequestOpts) {
|
||||||
return oazapfts.ok(oazapfts.fetchJson<{
|
return oazapfts.ok(oazapfts.fetchJson<{
|
||||||
status: 200;
|
status: 200;
|
||||||
@@ -5141,7 +5178,7 @@ export function mergePerson({ id, mergePersonDto }: {
|
|||||||
}>(`/people/${encodeURIComponent(id)}/merge`, oazapfts.json({
|
}>(`/people/${encodeURIComponent(id)}/merge`, oazapfts.json({
|
||||||
...opts,
|
...opts,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: mergePersonDto
|
body: mergeFaceClusterDto
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -6788,6 +6825,19 @@ export enum BulkIdErrorReason {
|
|||||||
Unknown = "unknown",
|
Unknown = "unknown",
|
||||||
Validation = "validation"
|
Validation = "validation"
|
||||||
}
|
}
|
||||||
|
export enum SharingPermission {
|
||||||
|
All = "all",
|
||||||
|
AssetRead = "asset.read",
|
||||||
|
AssetUpdate = "asset.update",
|
||||||
|
AssetEdit = "asset.edit",
|
||||||
|
AssetDelete = "asset.delete",
|
||||||
|
AssetShare = "asset.share",
|
||||||
|
ExifRead = "exif.read",
|
||||||
|
PersonRead = "person.read",
|
||||||
|
PersonUpdate = "person.update",
|
||||||
|
PersonMerge = "person.merge",
|
||||||
|
PersonDelete = "person.delete"
|
||||||
|
}
|
||||||
export enum Permission {
|
export enum Permission {
|
||||||
All = "all",
|
All = "all",
|
||||||
ActivityCreate = "activity.create",
|
ActivityCreate = "activity.create",
|
||||||
@@ -6995,7 +7045,8 @@ export enum ManualJobName {
|
|||||||
UserCleanup = "user-cleanup",
|
UserCleanup = "user-cleanup",
|
||||||
MemoryCleanup = "memory-cleanup",
|
MemoryCleanup = "memory-cleanup",
|
||||||
MemoryCreate = "memory-create",
|
MemoryCreate = "memory-create",
|
||||||
BackupDatabase = "backup-database"
|
BackupDatabase = "backup-database",
|
||||||
|
PersonGroupMerge = "person-group-merge"
|
||||||
}
|
}
|
||||||
export enum QueueName {
|
export enum QueueName {
|
||||||
ThumbnailGeneration = "thumbnailGeneration",
|
ThumbnailGeneration = "thumbnailGeneration",
|
||||||
@@ -7072,6 +7123,7 @@ export enum JobName {
|
|||||||
DatabaseBackup = "DatabaseBackup",
|
DatabaseBackup = "DatabaseBackup",
|
||||||
FacialRecognitionQueueAll = "FacialRecognitionQueueAll",
|
FacialRecognitionQueueAll = "FacialRecognitionQueueAll",
|
||||||
FacialRecognition = "FacialRecognition",
|
FacialRecognition = "FacialRecognition",
|
||||||
|
FacialRecognitionMerge = "FacialRecognitionMerge",
|
||||||
FileDelete = "FileDelete",
|
FileDelete = "FileDelete",
|
||||||
FileMigrationQueueAll = "FileMigrationQueueAll",
|
FileMigrationQueueAll = "FileMigrationQueueAll",
|
||||||
LibraryDeleteCheck = "LibraryDeleteCheck",
|
LibraryDeleteCheck = "LibraryDeleteCheck",
|
||||||
|
|||||||
Generated
+5
-5
@@ -758,8 +758,8 @@ importers:
|
|||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../packages/sdk
|
version: link:../packages/sdk
|
||||||
'@immich/ui':
|
'@immich/ui':
|
||||||
specifier: ^0.79.0
|
specifier: ^0.77.0
|
||||||
version: 0.79.0(@sveltejs/kit@2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))
|
version: 0.77.3(@sveltejs/kit@2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))
|
||||||
'@mapbox/mapbox-gl-rtl-text':
|
'@mapbox/mapbox-gl-rtl-text':
|
||||||
specifier: 0.4.0
|
specifier: 0.4.0
|
||||||
version: 0.4.0
|
version: 0.4.0
|
||||||
@@ -3204,8 +3204,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-O1SJ+BbeFVsUTF4af1MfagJZM+lPgLjI8lQ3SZNjpo8SGJReSbUl2ii03OKuGni/G0yp2GnRLpOTNSHYGtVrcg==}
|
resolution: {integrity: sha512-O1SJ+BbeFVsUTF4af1MfagJZM+lPgLjI8lQ3SZNjpo8SGJReSbUl2ii03OKuGni/G0yp2GnRLpOTNSHYGtVrcg==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
'@immich/ui@0.79.0':
|
'@immich/ui@0.77.3':
|
||||||
resolution: {integrity: sha512-UEQZrP8CTc4Kth1xCV8/6Xmk1P51GQKISC7vKqcrM0BO0fxCaNwJK8Ocn6R8baVqH52JYfPb1yQR9bweBnCjXw==}
|
resolution: {integrity: sha512-h3jrYE3JyGDOwXF7A4tVUHenP0L7TsDV22FyFInBTdwlWjjXoknwE1HWeTvvLxLeMuO5SHCZ9Q2D2al84xVjNw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@sveltejs/kit': ^2.13.0
|
'@sveltejs/kit': ^2.13.0
|
||||||
svelte: ^5.0.0
|
svelte: ^5.0.0
|
||||||
@@ -15879,7 +15879,7 @@ snapshots:
|
|||||||
pg-connection-string: 2.13.0
|
pg-connection-string: 2.13.0
|
||||||
postgres: 3.4.9
|
postgres: 3.4.9
|
||||||
|
|
||||||
'@immich/ui@0.79.0(@sveltejs/kit@2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))':
|
'@immich/ui@0.77.3(@sveltejs/kit@2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@internationalized/date': 3.12.1
|
'@internationalized/date': 3.12.1
|
||||||
'@mdi/js': 7.4.47
|
'@mdi/js': 7.4.47
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
GetAlbumsDto,
|
GetAlbumsDto,
|
||||||
UpdateAlbumDto,
|
UpdateAlbumDto,
|
||||||
UpdateAlbumUserDto,
|
UpdateAlbumUserDto,
|
||||||
|
UpdateSharingPermissionsDto as UpdateSharingOptionsDto,
|
||||||
} from 'src/dtos/album.dto';
|
} from 'src/dtos/album.dto';
|
||||||
import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto';
|
import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
@@ -165,6 +166,33 @@ export class AlbumController {
|
|||||||
return this.service.addUsers(auth, id, dto);
|
return this.service.addUsers(auth, id, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get(':id/user/self')
|
||||||
|
@Authenticated({ permission: Permission.AlbumAssetCreate })
|
||||||
|
@Endpoint({
|
||||||
|
summary: 'Get own sharing permissions',
|
||||||
|
description: 'Get the own sharing permissions in a specific album.',
|
||||||
|
history: new HistoryBuilder().added('v3').stable('v3'),
|
||||||
|
})
|
||||||
|
getOwnAlbumUser(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto) {
|
||||||
|
return this.service.getSelf(auth, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put(':id/user/self')
|
||||||
|
@Authenticated({ permission: Permission.AlbumAssetCreate })
|
||||||
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
|
@Endpoint({
|
||||||
|
summary: 'Update own sharing permissions',
|
||||||
|
description: 'Change the own sharing permissions in a specific album.',
|
||||||
|
history: new HistoryBuilder().added('v3').stable('v3'),
|
||||||
|
})
|
||||||
|
updateOwnAlbumUser(
|
||||||
|
@Auth() auth: AuthDto,
|
||||||
|
@Param() { id }: UUIDParamDto,
|
||||||
|
@Body() dto: UpdateSharingOptionsDto,
|
||||||
|
): Promise<void> {
|
||||||
|
return this.service.updateSelf(auth, id, dto);
|
||||||
|
}
|
||||||
|
|
||||||
@Put(':id/user/:userId')
|
@Put(':id/user/:userId')
|
||||||
@Authenticated({ permission: Permission.AlbumUserUpdate })
|
@Authenticated({ permission: Permission.AlbumUserUpdate })
|
||||||
@HttpCode(HttpStatus.NO_CONTENT)
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto';
|
|||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
import {
|
import {
|
||||||
AssetFaceUpdateDto,
|
AssetFaceUpdateDto,
|
||||||
MergePersonDto,
|
MergeFaceClusterDto,
|
||||||
PeopleResponseDto,
|
PeopleResponseDto,
|
||||||
PeopleUpdateDto,
|
PeopleUpdateDto,
|
||||||
PersonCreateDto,
|
PersonCreateDto,
|
||||||
@@ -182,7 +182,7 @@ export class PersonController {
|
|||||||
mergePerson(
|
mergePerson(
|
||||||
@Auth() auth: AuthDto,
|
@Auth() auth: AuthDto,
|
||||||
@Param() { id }: UUIDParamDto,
|
@Param() { id }: UUIDParamDto,
|
||||||
@Body() dto: MergePersonDto,
|
@Body() dto: MergeFaceClusterDto,
|
||||||
): Promise<BulkIdResponseDto[]> {
|
): Promise<BulkIdResponseDto[]> {
|
||||||
return this.service.mergePerson(auth, id, dto);
|
return this.service.mergePerson(auth, id, dto);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
MemoryType,
|
MemoryType,
|
||||||
Permission,
|
Permission,
|
||||||
SharedLinkType,
|
SharedLinkType,
|
||||||
|
SharingPermission,
|
||||||
SourceType,
|
SourceType,
|
||||||
UserAvatarColor,
|
UserAvatarColor,
|
||||||
UserStatus,
|
UserStatus,
|
||||||
@@ -209,6 +210,7 @@ export type Partner = {
|
|||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
updateId: string;
|
updateId: string;
|
||||||
inTimeline: boolean;
|
inTimeline: boolean;
|
||||||
|
permissions: SharingPermission[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Place = {
|
export type Place = {
|
||||||
@@ -252,6 +254,7 @@ export type Person = {
|
|||||||
faceAssetId: string | null;
|
faceAssetId: string | null;
|
||||||
isHidden: boolean;
|
isHidden: boolean;
|
||||||
thumbnailPath: string;
|
thumbnailPath: string;
|
||||||
|
faceClusterId: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AssetFace = {
|
export type AssetFace = {
|
||||||
@@ -264,7 +267,7 @@ export type AssetFace = {
|
|||||||
boundingBoxY2: number;
|
boundingBoxY2: number;
|
||||||
imageHeight: number;
|
imageHeight: number;
|
||||||
imageWidth: number;
|
imageWidth: number;
|
||||||
personId: string | null;
|
faceClusterId: string | null;
|
||||||
sourceType: SourceType;
|
sourceType: SourceType;
|
||||||
person?: ShallowDehydrateObject<Person> | null;
|
person?: ShallowDehydrateObject<Person> | null;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import { createZodDto } from 'nestjs-zod';
|
|||||||
import { AlbumUser, AuthSharedLink } from 'src/database';
|
import { AlbumUser, AuthSharedLink } from 'src/database';
|
||||||
import { BulkIdErrorReasonSchema } from 'src/dtos/asset-ids.response.dto';
|
import { BulkIdErrorReasonSchema } from 'src/dtos/asset-ids.response.dto';
|
||||||
import { MapAsset } from 'src/dtos/asset-response.dto';
|
import { MapAsset } from 'src/dtos/asset-response.dto';
|
||||||
import { UserResponseSchema, mapUser } from 'src/dtos/user.dto';
|
import { mapUser, UserResponseSchema } from 'src/dtos/user.dto';
|
||||||
import { AlbumUserRole, AlbumUserRoleSchema, AssetOrder, AssetOrderSchema } from 'src/enum';
|
import { AlbumUserRole, AlbumUserRoleSchema, AssetOrder, AssetOrderSchema, SharingPermissionSchema } from 'src/enum';
|
||||||
import { MaybeDehydrated } from 'src/types';
|
import { MaybeDehydrated } from 'src/types';
|
||||||
import { asDateString } from 'src/utils/date';
|
import { asDateString } from 'src/utils/date';
|
||||||
import { stringToBool } from 'src/validation';
|
import { stringToBool } from 'src/validation';
|
||||||
@@ -63,6 +63,14 @@ const UpdateAlbumSchema = z
|
|||||||
})
|
})
|
||||||
.meta({ id: 'UpdateAlbumDto' });
|
.meta({ id: 'UpdateAlbumDto' });
|
||||||
|
|
||||||
|
const UpdateSharingOptionsSchema = z
|
||||||
|
.object({ inTimeline: z.boolean(), permissions: z.array(SharingPermissionSchema) })
|
||||||
|
.meta({ id: 'UpdateSharingOptionsDto' });
|
||||||
|
|
||||||
|
const SharingOptionsResponseSchema = z
|
||||||
|
.object({ inTimeline: z.boolean(), permissions: z.array(SharingPermissionSchema) })
|
||||||
|
.meta({ id: 'SharingOptionsResponseDto' });
|
||||||
|
|
||||||
const GetAlbumsSchema = z
|
const GetAlbumsSchema = z
|
||||||
.object({
|
.object({
|
||||||
isOwned: stringToBool
|
isOwned: stringToBool
|
||||||
@@ -147,6 +155,8 @@ export class UpdateAlbumDto extends createZodDto(UpdateAlbumSchema) {}
|
|||||||
export class GetAlbumsDto extends createZodDto(GetAlbumsSchema) {}
|
export class GetAlbumsDto extends createZodDto(GetAlbumsSchema) {}
|
||||||
export class AlbumStatisticsResponseDto extends createZodDto(AlbumStatisticsResponseSchema) {}
|
export class AlbumStatisticsResponseDto extends createZodDto(AlbumStatisticsResponseSchema) {}
|
||||||
export class UpdateAlbumUserDto extends createZodDto(UpdateAlbumUserSchema) {}
|
export class UpdateAlbumUserDto extends createZodDto(UpdateAlbumUserSchema) {}
|
||||||
|
export class UpdateSharingPermissionsDto extends createZodDto(UpdateSharingOptionsSchema) {}
|
||||||
|
export class SharingPermissionsResponseDto extends createZodDto(SharingOptionsResponseSchema) {}
|
||||||
export class AlbumResponseDto extends createZodDto(AlbumResponseSchema) {}
|
export class AlbumResponseDto extends createZodDto(AlbumResponseSchema) {}
|
||||||
class AlbumUserResponseDto extends createZodDto(AlbumUserResponseSchema) {}
|
class AlbumUserResponseDto extends createZodDto(AlbumUserResponseSchema) {}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import {
|
|||||||
AssetVisibility,
|
AssetVisibility,
|
||||||
AssetVisibilitySchema,
|
AssetVisibilitySchema,
|
||||||
ChecksumAlgorithm,
|
ChecksumAlgorithm,
|
||||||
|
SharingPermission,
|
||||||
|
SharingPermissionSchema,
|
||||||
} from 'src/enum';
|
} from 'src/enum';
|
||||||
import { MaybeDehydrated } from 'src/types';
|
import { MaybeDehydrated } from 'src/types';
|
||||||
import { hexOrBufferToBase64 } from 'src/utils/bytes';
|
import { hexOrBufferToBase64 } from 'src/utils/bytes';
|
||||||
@@ -45,6 +47,7 @@ const SanitizedAssetResponseSchema = z
|
|||||||
hasMetadata: z.boolean().describe('Whether asset has metadata'),
|
hasMetadata: z.boolean().describe('Whether asset has metadata'),
|
||||||
width: z.int().min(0).nullable().describe('Asset width'),
|
width: z.int().min(0).nullable().describe('Asset width'),
|
||||||
height: z.int().min(0).nullable().describe('Asset height'),
|
height: z.int().min(0).nullable().describe('Asset height'),
|
||||||
|
permissions: z.array(SharingPermissionSchema),
|
||||||
})
|
})
|
||||||
.meta({ id: 'SanitizedAssetResponseDto' });
|
.meta({ id: 'SanitizedAssetResponseDto' });
|
||||||
|
|
||||||
@@ -113,6 +116,7 @@ export const AssetResponseSchema = SanitizedAssetResponseSchema.extend(
|
|||||||
.boolean()
|
.boolean()
|
||||||
.describe('Is edited')
|
.describe('Is edited')
|
||||||
.meta(new HistoryBuilder().added('v2.5.0').beta('v2.5.0').getExtensions()),
|
.meta(new HistoryBuilder().added('v2.5.0').beta('v2.5.0').getExtensions()),
|
||||||
|
permissions: z.array(SharingPermissionSchema),
|
||||||
}).shape,
|
}).shape,
|
||||||
).meta({ id: 'AssetResponseDto' });
|
).meta({ id: 'AssetResponseDto' });
|
||||||
|
|
||||||
@@ -154,6 +158,7 @@ export type MapAsset = {
|
|||||||
width: number | null;
|
width: number | null;
|
||||||
height: number | null;
|
height: number | null;
|
||||||
isEdited: boolean;
|
isEdited: boolean;
|
||||||
|
permissions?: { permission: SharingPermission }[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AssetMapOptions = {
|
export type AssetMapOptions = {
|
||||||
@@ -192,8 +197,16 @@ const mapStack = (entity: { stack?: Stack | null }) => {
|
|||||||
|
|
||||||
export function mapAsset(entity: MaybeDehydrated<MapAsset>, options: AssetMapOptions = {}): AssetResponseDto {
|
export function mapAsset(entity: MaybeDehydrated<MapAsset>, options: AssetMapOptions = {}): AssetResponseDto {
|
||||||
const { stripMetadata = false, withStack = false } = options;
|
const { stripMetadata = false, withStack = false } = options;
|
||||||
|
const permissions =
|
||||||
|
options.auth?.user.id === entity.ownerId
|
||||||
|
? [SharingPermission.All]
|
||||||
|
: (entity.permissions?.map(({ permission }) => permission) ?? []);
|
||||||
|
|
||||||
if (stripMetadata) {
|
if (
|
||||||
|
stripMetadata ||
|
||||||
|
(entity.permissions &&
|
||||||
|
!(permissions.includes(SharingPermission.All) || permissions.includes(SharingPermission.ExifRead)))
|
||||||
|
) {
|
||||||
const sanitizedAssetResponse: SanitizedAssetResponseDto = {
|
const sanitizedAssetResponse: SanitizedAssetResponseDto = {
|
||||||
id: entity.id,
|
id: entity.id,
|
||||||
type: entity.type,
|
type: entity.type,
|
||||||
@@ -205,6 +218,7 @@ export function mapAsset(entity: MaybeDehydrated<MapAsset>, options: AssetMapOpt
|
|||||||
hasMetadata: false,
|
hasMetadata: false,
|
||||||
width: entity.width,
|
width: entity.width,
|
||||||
height: entity.height,
|
height: entity.height,
|
||||||
|
permissions,
|
||||||
};
|
};
|
||||||
return sanitizedAssetResponse as AssetResponseDto;
|
return sanitizedAssetResponse as AssetResponseDto;
|
||||||
}
|
}
|
||||||
@@ -242,5 +256,6 @@ export function mapAsset(entity: MaybeDehydrated<MapAsset>, options: AssetMapOpt
|
|||||||
width: entity.width,
|
width: entity.width,
|
||||||
height: entity.height,
|
height: entity.height,
|
||||||
isEdited: entity.isEdited,
|
isEdited: entity.isEdited,
|
||||||
|
permissions,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { Selectable } from 'kysely';
|
|||||||
import { createZodDto } from 'nestjs-zod';
|
import { createZodDto } from 'nestjs-zod';
|
||||||
import { AssetFace, Person } from 'src/database';
|
import { AssetFace, Person } from 'src/database';
|
||||||
import { HistoryBuilder } from 'src/decorators';
|
import { HistoryBuilder } from 'src/decorators';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
|
||||||
import { AssetEditActionItem } from 'src/dtos/editing.dto';
|
import { AssetEditActionItem } from 'src/dtos/editing.dto';
|
||||||
import { SourceTypeSchema } from 'src/enum';
|
import { SourceTypeSchema } from 'src/enum';
|
||||||
import { AssetFaceTable } from 'src/schema/tables/asset-face.table';
|
import { AssetFaceTable } from 'src/schema/tables/asset-face.table';
|
||||||
@@ -40,11 +39,11 @@ const PeopleUpdateSchema = z
|
|||||||
})
|
})
|
||||||
.meta({ id: 'PeopleUpdateDto' });
|
.meta({ id: 'PeopleUpdateDto' });
|
||||||
|
|
||||||
const MergePersonSchema = z
|
const MergeFaceClusterSchema = z
|
||||||
.object({
|
.object({
|
||||||
ids: z.array(z.uuidv4()).describe('Person IDs to merge'),
|
ids: z.array(z.uuidv4()).describe('Face cluster IDs to merge'),
|
||||||
})
|
})
|
||||||
.meta({ id: 'MergePersonDto' });
|
.meta({ id: 'MergeFaceClusterDto' });
|
||||||
|
|
||||||
const PersonSearchSchema = z
|
const PersonSearchSchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -81,13 +80,14 @@ export const PersonResponseSchema = z
|
|||||||
.optional()
|
.optional()
|
||||||
.describe('Person color (hex)')
|
.describe('Person color (hex)')
|
||||||
.meta(new HistoryBuilder().added('v1.126.0').stable('v2').getExtensions()),
|
.meta(new HistoryBuilder().added('v1.126.0').stable('v2').getExtensions()),
|
||||||
|
faceClusterId: z.string().nullable().describe('Face cluster ID'),
|
||||||
})
|
})
|
||||||
.meta({ id: 'PersonResponseDto' });
|
.meta({ id: 'PersonResponseDto' });
|
||||||
|
|
||||||
export class PersonCreateDto extends createZodDto(PersonCreateSchema) {}
|
export class PersonCreateDto extends createZodDto(PersonCreateSchema) {}
|
||||||
export class PersonUpdateDto extends createZodDto(PersonUpdateSchema) {}
|
export class PersonUpdateDto extends createZodDto(PersonUpdateSchema) {}
|
||||||
export class PeopleUpdateDto extends createZodDto(PeopleUpdateSchema) {}
|
export class PeopleUpdateDto extends createZodDto(PeopleUpdateSchema) {}
|
||||||
export class MergePersonDto extends createZodDto(MergePersonSchema) {}
|
export class MergeFaceClusterDto extends createZodDto(MergeFaceClusterSchema) {}
|
||||||
export class PersonSearchDto extends createZodDto(PersonSearchSchema) {}
|
export class PersonSearchDto extends createZodDto(PersonSearchSchema) {}
|
||||||
export class PersonResponseDto extends createZodDto(PersonResponseSchema) {}
|
export class PersonResponseDto extends createZodDto(PersonResponseSchema) {}
|
||||||
|
|
||||||
@@ -179,6 +179,7 @@ export function mapPerson(person: MaybeDehydrated<Person>): PersonResponseDto {
|
|||||||
isFavorite: person.isFavorite,
|
isFavorite: person.isFavorite,
|
||||||
color: person.color ?? undefined,
|
color: person.color ?? undefined,
|
||||||
updatedAt: asDateString(person.updatedAt),
|
updatedAt: asDateString(person.updatedAt),
|
||||||
|
faceClusterId: person.faceClusterId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,12 +208,11 @@ function mapFacesWithoutPerson(
|
|||||||
|
|
||||||
export function mapFaces(
|
export function mapFaces(
|
||||||
face: AssetFace,
|
face: AssetFace,
|
||||||
auth: AuthDto,
|
|
||||||
edits?: AssetEditActionItem[],
|
edits?: AssetEditActionItem[],
|
||||||
assetDimensions?: ImageDimensions,
|
assetDimensions?: ImageDimensions,
|
||||||
): AssetFaceResponseDto {
|
): AssetFaceResponseDto {
|
||||||
return {
|
return {
|
||||||
...mapFacesWithoutPerson(face, edits, assetDimensions),
|
...mapFacesWithoutPerson(face, edits, assetDimensions),
|
||||||
person: face.person?.ownerId === auth.user.id ? mapPerson(face.person) : null,
|
person: face.person ? mapPerson(face.person) : null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -374,10 +374,13 @@ const SyncAssetFaceV1Schema = z
|
|||||||
})
|
})
|
||||||
.meta({ id: 'SyncAssetFaceV1' });
|
.meta({ id: 'SyncAssetFaceV1' });
|
||||||
|
|
||||||
const SyncAssetFaceV2Schema = SyncAssetFaceV1Schema.extend({
|
const SyncAssetFaceV2Schema = SyncAssetFaceV1Schema.omit({ personId: true })
|
||||||
deletedAt: isoDatetimeToDate.nullable().describe('Face deleted at'),
|
.extend({
|
||||||
isVisible: z.boolean().describe('Is the face visible in the asset'),
|
deletedAt: isoDatetimeToDate.nullable().describe('Face deleted at'),
|
||||||
}).meta({ id: 'SyncAssetFaceV2' });
|
isVisible: z.boolean().describe('Is the face visible in the asset'),
|
||||||
|
faceClusterId: z.string().nullable().describe('Person ID'),
|
||||||
|
})
|
||||||
|
.meta({ id: 'SyncAssetFaceV2' });
|
||||||
|
|
||||||
const SyncAssetFaceDeleteV1Schema = z
|
const SyncAssetFaceDeleteV1Schema = z
|
||||||
.object({ assetFaceId: z.string().describe('Asset face ID') })
|
.object({ assetFaceId: z.string().describe('Asset face ID') })
|
||||||
|
|||||||
@@ -306,6 +306,28 @@ export enum Permission {
|
|||||||
AdminAuthUnlinkAll = 'adminAuth.unlinkAll',
|
AdminAuthUnlinkAll = 'adminAuth.unlinkAll',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum SharingPermission {
|
||||||
|
All = 'all',
|
||||||
|
|
||||||
|
AssetRead = 'asset.read',
|
||||||
|
AssetUpdate = 'asset.update',
|
||||||
|
AssetEdit = 'asset.edit',
|
||||||
|
AssetDelete = 'asset.delete',
|
||||||
|
AssetShare = 'asset.share',
|
||||||
|
|
||||||
|
ExifRead = 'exif.read',
|
||||||
|
|
||||||
|
PersonRead = 'person.read',
|
||||||
|
PersonUpdate = 'person.update',
|
||||||
|
PersonMerge = 'person.merge',
|
||||||
|
PersonDelete = 'person.delete',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SharingPermissionSchema = z
|
||||||
|
.enum(SharingPermission)
|
||||||
|
.describe('Sharing permission schema')
|
||||||
|
.meta({ id: 'SharingPermission' });
|
||||||
|
|
||||||
export enum SharedLinkType {
|
export enum SharedLinkType {
|
||||||
Album = 'ALBUM',
|
Album = 'ALBUM',
|
||||||
|
|
||||||
@@ -404,6 +426,7 @@ export enum ManualJobName {
|
|||||||
MemoryCleanup = 'memory-cleanup',
|
MemoryCleanup = 'memory-cleanup',
|
||||||
MemoryCreate = 'memory-create',
|
MemoryCreate = 'memory-create',
|
||||||
BackupDatabase = 'backup-database',
|
BackupDatabase = 'backup-database',
|
||||||
|
PersonGroupMerge = 'person-group-merge',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ManualJobNameSchema = z.enum(ManualJobName).describe('Manual job name').meta({ id: 'ManualJobName' });
|
export const ManualJobNameSchema = z.enum(ManualJobName).describe('Manual job name').meta({ id: 'ManualJobName' });
|
||||||
@@ -813,6 +836,7 @@ export enum JobName {
|
|||||||
|
|
||||||
FacialRecognitionQueueAll = 'FacialRecognitionQueueAll',
|
FacialRecognitionQueueAll = 'FacialRecognitionQueueAll',
|
||||||
FacialRecognition = 'FacialRecognition',
|
FacialRecognition = 'FacialRecognition',
|
||||||
|
FacialRecognitionMerge = 'FacialRecognitionMerge',
|
||||||
|
|
||||||
FileDelete = 'FileDelete',
|
FileDelete = 'FileDelete',
|
||||||
FileMigrationQueueAll = 'FileMigrationQueueAll',
|
FileMigrationQueueAll = 'FileMigrationQueueAll',
|
||||||
|
|||||||
@@ -149,6 +149,40 @@ where
|
|||||||
"albumAssets"."livePhotoVideoId"
|
"albumAssets"."livePhotoVideoId"
|
||||||
] && array[$2]::uuid[]
|
] && array[$2]::uuid[]
|
||||||
|
|
||||||
|
-- AccessRepository.asset.checkSharedAccess
|
||||||
|
select
|
||||||
|
"album_asset"."assetId"
|
||||||
|
from
|
||||||
|
"album_asset"
|
||||||
|
inner join "album_user" on "album_asset"."albumId" = "album_user"."albumId"
|
||||||
|
and "album_user"."userId" = $1
|
||||||
|
where
|
||||||
|
"album_asset"."assetId" in ($2)
|
||||||
|
and "album_asset"."albumId" in (
|
||||||
|
select
|
||||||
|
"album_user"."albumId"
|
||||||
|
from
|
||||||
|
"album_user"
|
||||||
|
where
|
||||||
|
(
|
||||||
|
"album_user"."permissions" @> $3::sharing_permission_enum[]
|
||||||
|
or $4 = any ("album_user"."permissions")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
union
|
||||||
|
select
|
||||||
|
"asset"."id" as "assetId"
|
||||||
|
from
|
||||||
|
"partner"
|
||||||
|
inner join "asset" on "asset"."ownerId" = "partner"."sharedById"
|
||||||
|
and "asset"."id" in ($5)
|
||||||
|
where
|
||||||
|
"partner"."sharedWithId" = $6
|
||||||
|
and (
|
||||||
|
"partner"."permissions" @> $7::sharing_permission_enum[]
|
||||||
|
or $8 = any ("partner"."permissions")
|
||||||
|
)
|
||||||
|
|
||||||
-- AccessRepository.authDevice.checkOwnerAccess
|
-- AccessRepository.authDevice.checkOwnerAccess
|
||||||
select
|
select
|
||||||
"session"."id"
|
"session"."id"
|
||||||
|
|||||||
@@ -182,18 +182,25 @@ select
|
|||||||
from
|
from
|
||||||
(
|
(
|
||||||
select
|
select
|
||||||
"asset_face".*,
|
(
|
||||||
"person" as "person"
|
select
|
||||||
|
to_json(obj)
|
||||||
|
from
|
||||||
|
(
|
||||||
|
select
|
||||||
|
"person".*
|
||||||
|
from
|
||||||
|
"face_cluster"
|
||||||
|
inner join "person" on "person"."faceClusterId" = "face_cluster"."id"
|
||||||
|
where
|
||||||
|
"face_cluster"."id" = "asset_face"."faceClusterId"
|
||||||
|
limit
|
||||||
|
$1
|
||||||
|
) as obj
|
||||||
|
) as "person",
|
||||||
|
"asset_face".*
|
||||||
from
|
from
|
||||||
"asset_face"
|
"asset_face"
|
||||||
left join lateral (
|
|
||||||
select
|
|
||||||
"person".*
|
|
||||||
from
|
|
||||||
"person"
|
|
||||||
where
|
|
||||||
"asset_face"."personId" = "person"."id"
|
|
||||||
) as "person" on true
|
|
||||||
where
|
where
|
||||||
"asset_face"."assetId" = "asset"."id"
|
"asset_face"."assetId" = "asset"."id"
|
||||||
and "asset_face"."deletedAt" is null
|
and "asset_face"."deletedAt" is null
|
||||||
@@ -224,7 +231,7 @@ from
|
|||||||
"asset"
|
"asset"
|
||||||
left join "asset_exif" on "asset"."id" = "asset_exif"."assetId"
|
left join "asset_exif" on "asset"."id" = "asset_exif"."assetId"
|
||||||
where
|
where
|
||||||
"asset"."id" = any ($1::uuid[])
|
"asset"."id" = any ($2::uuid[])
|
||||||
|
|
||||||
-- AssetRepository.deleteAll
|
-- AssetRepository.deleteAll
|
||||||
delete from "asset"
|
delete from "asset"
|
||||||
@@ -290,13 +297,44 @@ limit
|
|||||||
|
|
||||||
-- AssetRepository.getById
|
-- AssetRepository.getById
|
||||||
select
|
select
|
||||||
"asset".*
|
"asset".*,
|
||||||
|
(
|
||||||
|
select
|
||||||
|
coalesce(json_agg(agg), '[]')
|
||||||
|
from
|
||||||
|
(
|
||||||
|
select distinct
|
||||||
|
unnest("album_user"."permissions") as "permission"
|
||||||
|
from
|
||||||
|
"album_user"
|
||||||
|
inner join "album_asset" on "album_user"."albumId" = "album_asset"."albumId"
|
||||||
|
where
|
||||||
|
"album_asset"."assetId" = "asset"."id"
|
||||||
|
and "album_user"."userId" = "asset"."ownerId"
|
||||||
|
and "album_user"."albumId" in (
|
||||||
|
select
|
||||||
|
"album_user"."albumId"
|
||||||
|
from
|
||||||
|
"album_user"
|
||||||
|
where
|
||||||
|
"album_user"."userId" = $1
|
||||||
|
)
|
||||||
|
union
|
||||||
|
select distinct
|
||||||
|
unnest("partner"."permissions") as "permission"
|
||||||
|
from
|
||||||
|
"partner"
|
||||||
|
where
|
||||||
|
"partner"."sharedById" = "asset"."ownerId"
|
||||||
|
and "partner"."sharedWithId" = $2
|
||||||
|
) as agg
|
||||||
|
) as "permissions"
|
||||||
from
|
from
|
||||||
"asset"
|
"asset"
|
||||||
where
|
where
|
||||||
"asset"."id" = $1::uuid
|
"asset"."id" = $3::uuid
|
||||||
limit
|
limit
|
||||||
$2
|
$4
|
||||||
|
|
||||||
-- AssetRepository.updateAll
|
-- AssetRepository.updateAll
|
||||||
update "asset"
|
update "asset"
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ select
|
|||||||
$1 as "one"
|
$1 as "one"
|
||||||
from
|
from
|
||||||
"asset_face"
|
"asset_face"
|
||||||
inner join "person" on "person"."id" = "asset_face"."personId"
|
inner join "person" on "person"."faceClusterId" = "asset_face"."faceClusterId"
|
||||||
where
|
where
|
||||||
"asset_face"."assetId" = "asset"."id"
|
"asset_face"."assetId" = "asset"."id"
|
||||||
and "person"."isHidden" = $2
|
and "person"."isHidden" = $2
|
||||||
@@ -86,7 +86,7 @@ select
|
|||||||
$1 as "one"
|
$1 as "one"
|
||||||
from
|
from
|
||||||
"asset_face"
|
"asset_face"
|
||||||
inner join "person" on "person"."id" = "asset_face"."personId"
|
inner join "person" on "person"."faceClusterId" = "asset_face"."faceClusterId"
|
||||||
where
|
where
|
||||||
"asset_face"."assetId" = "asset"."id"
|
"asset_face"."assetId" = "asset"."id"
|
||||||
and "person"."isHidden" = $2
|
and "person"."isHidden" = $2
|
||||||
|
|||||||
@@ -3,9 +3,6 @@
|
|||||||
-- PersonRepository.reassignFaces
|
-- PersonRepository.reassignFaces
|
||||||
update "asset_face"
|
update "asset_face"
|
||||||
set
|
set
|
||||||
"personId" = $1
|
|
||||||
where
|
|
||||||
"asset_face"."personId" = $2
|
|
||||||
|
|
||||||
-- PersonRepository.delete
|
-- PersonRepository.delete
|
||||||
delete from "person"
|
delete from "person"
|
||||||
@@ -24,27 +21,64 @@ limit
|
|||||||
3
|
3
|
||||||
|
|
||||||
-- PersonRepository.getAllForUser
|
-- PersonRepository.getAllForUser
|
||||||
select
|
select distinct
|
||||||
"person".*
|
on ("person"."faceClusterId") "person".*
|
||||||
from
|
from
|
||||||
"person"
|
"person"
|
||||||
inner join "asset_face" on "asset_face"."personId" = "person"."id"
|
inner join "asset_face" on "asset_face"."faceClusterId" = "person"."faceClusterId"
|
||||||
inner join "asset" on "asset_face"."assetId" = "asset"."id"
|
inner join "asset" on "asset_face"."assetId" = "asset"."id"
|
||||||
and "asset"."visibility" = 'timeline'
|
and "asset"."visibility" = 'timeline'
|
||||||
and "asset"."deletedAt" is null
|
and "asset"."deletedAt" is null
|
||||||
where
|
where
|
||||||
"person"."ownerId" = $1
|
(
|
||||||
|
"person"."ownerId" = $1
|
||||||
|
or (
|
||||||
|
exists (
|
||||||
|
select
|
||||||
|
from
|
||||||
|
"partner"
|
||||||
|
where
|
||||||
|
"partner"."sharedById" = "person"."ownerId"
|
||||||
|
and "partner"."sharedWithId" = $2
|
||||||
|
and (
|
||||||
|
$3 = any ("partner"."permissions")
|
||||||
|
or "partner"."permissions" @> $4
|
||||||
|
)
|
||||||
|
)
|
||||||
|
or exists (
|
||||||
|
select
|
||||||
|
from
|
||||||
|
"album_user"
|
||||||
|
where
|
||||||
|
"album_user"."albumId" in (
|
||||||
|
select
|
||||||
|
"album_user"."albumId"
|
||||||
|
from
|
||||||
|
"album_user"
|
||||||
|
where
|
||||||
|
"album_user"."userId" = $5
|
||||||
|
)
|
||||||
|
and "album_user"."userId" = "person"."ownerId"
|
||||||
|
and (
|
||||||
|
$6 = any ("album_user"."permissions")
|
||||||
|
or "album_user"."permissions" @> $7
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
and "asset_face"."deletedAt" is null
|
and "asset_face"."deletedAt" is null
|
||||||
and "asset_face"."isVisible" is true
|
and "asset_face"."isVisible" is true
|
||||||
and "person"."isHidden" = $2
|
and "person"."isHidden" = $8
|
||||||
group by
|
group by
|
||||||
"person"."id"
|
"person"."id"
|
||||||
having
|
having
|
||||||
(
|
(
|
||||||
"person"."name" != $3
|
"person"."name" != $9
|
||||||
or count("asset_face"."assetId") >= $4
|
or count("asset_face"."assetId") >= $10
|
||||||
)
|
)
|
||||||
order by
|
order by
|
||||||
|
"person"."faceClusterId",
|
||||||
|
"person"."ownerId" = $11 desc,
|
||||||
"person"."isHidden" asc,
|
"person"."isHidden" asc,
|
||||||
"person"."isFavorite" desc,
|
"person"."isFavorite" desc,
|
||||||
NULLIF(person.name, '') is null asc,
|
NULLIF(person.name, '') is null asc,
|
||||||
@@ -52,16 +86,16 @@ order by
|
|||||||
NULLIF(person.name, '') asc nulls last,
|
NULLIF(person.name, '') asc nulls last,
|
||||||
"person"."createdAt"
|
"person"."createdAt"
|
||||||
limit
|
limit
|
||||||
$5
|
$12
|
||||||
offset
|
offset
|
||||||
$6
|
$13
|
||||||
|
|
||||||
-- PersonRepository.getAllWithoutFaces
|
-- PersonRepository.getAllWithoutFaces
|
||||||
select
|
select
|
||||||
"person".*
|
"person".*
|
||||||
from
|
from
|
||||||
"person"
|
"person"
|
||||||
left join "asset_face" on "asset_face"."personId" = "person"."id"
|
left join "asset_face" on "asset_face"."faceClusterId" = "person"."faceClusterId"
|
||||||
where
|
where
|
||||||
"asset_face"."deletedAt" is null
|
"asset_face"."deletedAt" is null
|
||||||
and "asset_face"."isVisible" is true
|
and "asset_face"."isVisible" is true
|
||||||
@@ -83,15 +117,26 @@ select
|
|||||||
from
|
from
|
||||||
"person"
|
"person"
|
||||||
where
|
where
|
||||||
"person"."id" = "asset_face"."personId"
|
"person"."faceClusterId" = "asset_face"."faceClusterId"
|
||||||
|
order by
|
||||||
|
"person"."ownerId" = (
|
||||||
|
select
|
||||||
|
"asset"."ownerId"
|
||||||
|
from
|
||||||
|
"asset"
|
||||||
|
where
|
||||||
|
"asset"."id" = "asset_face"."assetId"
|
||||||
|
) desc
|
||||||
|
limit
|
||||||
|
$1
|
||||||
) as obj
|
) as obj
|
||||||
) as "person"
|
) as "person"
|
||||||
from
|
from
|
||||||
"asset_face"
|
"asset_face"
|
||||||
where
|
where
|
||||||
"asset_face"."assetId" = $1
|
"asset_face"."assetId" = $2
|
||||||
and "asset_face"."deletedAt" is null
|
and "asset_face"."deletedAt" is null
|
||||||
and "asset_face"."isVisible" = $2
|
and "asset_face"."isVisible" = $3
|
||||||
order by
|
order by
|
||||||
"asset_face"."boundingBoxX1" asc
|
"asset_face"."boundingBoxX1" asc
|
||||||
|
|
||||||
@@ -108,19 +153,30 @@ select
|
|||||||
from
|
from
|
||||||
"person"
|
"person"
|
||||||
where
|
where
|
||||||
"person"."id" = "asset_face"."personId"
|
"person"."faceClusterId" = "asset_face"."faceClusterId"
|
||||||
|
order by
|
||||||
|
"person"."ownerId" = (
|
||||||
|
select
|
||||||
|
"asset"."ownerId"
|
||||||
|
from
|
||||||
|
"asset"
|
||||||
|
where
|
||||||
|
"asset"."id" = "asset_face"."assetId"
|
||||||
|
) desc
|
||||||
|
limit
|
||||||
|
$1
|
||||||
) as obj
|
) as obj
|
||||||
) as "person"
|
) as "person"
|
||||||
from
|
from
|
||||||
"asset_face"
|
"asset_face"
|
||||||
where
|
where
|
||||||
"asset_face"."id" = $1
|
"asset_face"."id" = $2
|
||||||
and "asset_face"."deletedAt" is null
|
and "asset_face"."deletedAt" is null
|
||||||
|
|
||||||
-- PersonRepository.getFaceForFacialRecognitionJob
|
-- PersonRepository.getFaceForFacialRecognitionJob
|
||||||
select
|
select
|
||||||
"asset_face"."id",
|
"asset_face"."id",
|
||||||
"asset_face"."personId",
|
"asset_face"."faceClusterId",
|
||||||
"asset_face"."sourceType",
|
"asset_face"."sourceType",
|
||||||
(
|
(
|
||||||
select
|
select
|
||||||
@@ -190,7 +246,7 @@ where
|
|||||||
-- PersonRepository.reassignFace
|
-- PersonRepository.reassignFace
|
||||||
update "asset_face"
|
update "asset_face"
|
||||||
set
|
set
|
||||||
"personId" = $1
|
"faceClusterId" = $1
|
||||||
where
|
where
|
||||||
"asset_face"."id" = $2
|
"asset_face"."id" = $2
|
||||||
|
|
||||||
@@ -209,9 +265,10 @@ where
|
|||||||
"person"."ownerId" = $1
|
"person"."ownerId" = $1
|
||||||
and f_unaccent ("person"."name") %> f_unaccent ($2)
|
and f_unaccent ("person"."name") %> f_unaccent ($2)
|
||||||
order by
|
order by
|
||||||
f_unaccent ("person"."name") <->>> f_unaccent ($3)
|
f_unaccent ("person"."name") <->>> f_unaccent ($3),
|
||||||
|
"person"."ownerId" = $4 desc
|
||||||
limit
|
limit
|
||||||
$4
|
$5
|
||||||
|
|
||||||
-- PersonRepository.getDistinctNames
|
-- PersonRepository.getDistinctNames
|
||||||
select distinct
|
select distinct
|
||||||
@@ -234,9 +291,52 @@ from
|
|||||||
and "asset"."visibility" = 'timeline'
|
and "asset"."visibility" = 'timeline'
|
||||||
and "asset"."deletedAt" is null
|
and "asset"."deletedAt" is null
|
||||||
where
|
where
|
||||||
"asset_face"."deletedAt" is null
|
(
|
||||||
|
"asset"."ownerId" = $1
|
||||||
|
or exists (
|
||||||
|
select
|
||||||
|
from
|
||||||
|
"partner"
|
||||||
|
where
|
||||||
|
"partner"."sharedById" = "asset"."ownerId"
|
||||||
|
and "partner"."sharedWithId" = $2
|
||||||
|
and (
|
||||||
|
$3 = any ("partner"."permissions")
|
||||||
|
or "partner"."permissions" @> $4
|
||||||
|
)
|
||||||
|
)
|
||||||
|
or exists (
|
||||||
|
select
|
||||||
|
from
|
||||||
|
"album_asset"
|
||||||
|
inner join "album_user" on "album_user"."albumId" = "album_asset"."albumId"
|
||||||
|
and "album_user"."userId" = $5
|
||||||
|
where
|
||||||
|
"album_asset"."assetId" = "asset"."id"
|
||||||
|
and "album_user"."albumId" in (
|
||||||
|
select
|
||||||
|
"album_user"."albumId"
|
||||||
|
from
|
||||||
|
"album_user"
|
||||||
|
where
|
||||||
|
"album_user"."userId" = "asset"."ownerId"
|
||||||
|
and (
|
||||||
|
$6 = any ("album_user"."permissions")
|
||||||
|
or "album_user"."permissions" @> $7
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
and "asset_face"."deletedAt" is null
|
||||||
and "asset_face"."isVisible" is true
|
and "asset_face"."isVisible" is true
|
||||||
and "asset_face"."personId" = $1
|
and "asset_face"."faceClusterId" = (
|
||||||
|
select
|
||||||
|
"person"."faceClusterId"
|
||||||
|
from
|
||||||
|
"person"
|
||||||
|
where
|
||||||
|
"person"."id" = $8
|
||||||
|
)
|
||||||
|
|
||||||
-- PersonRepository.getNumberOfPeople
|
-- PersonRepository.getNumberOfPeople
|
||||||
select
|
select
|
||||||
@@ -256,7 +356,7 @@ where
|
|||||||
from
|
from
|
||||||
"asset_face"
|
"asset_face"
|
||||||
where
|
where
|
||||||
"asset_face"."personId" = "person"."id"
|
"asset_face"."faceClusterId" = "person"."faceClusterId"
|
||||||
and "asset_face"."deletedAt" is null
|
and "asset_face"."deletedAt" is null
|
||||||
and "asset_face"."isVisible" = $2
|
and "asset_face"."isVisible" = $2
|
||||||
and exists (
|
and exists (
|
||||||
@@ -269,7 +369,42 @@ where
|
|||||||
and "asset"."deletedAt" is null
|
and "asset"."deletedAt" is null
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
and "person"."ownerId" = $3
|
and (
|
||||||
|
"person"."ownerId" = $3
|
||||||
|
or (
|
||||||
|
exists (
|
||||||
|
select
|
||||||
|
from
|
||||||
|
"partner"
|
||||||
|
where
|
||||||
|
"partner"."sharedById" = "person"."ownerId"
|
||||||
|
and "partner"."sharedWithId" = $4
|
||||||
|
and (
|
||||||
|
$5 = any ("partner"."permissions")
|
||||||
|
or "partner"."permissions" @> $6
|
||||||
|
)
|
||||||
|
)
|
||||||
|
or exists (
|
||||||
|
select
|
||||||
|
from
|
||||||
|
"album_user"
|
||||||
|
where
|
||||||
|
"album_user"."albumId" in (
|
||||||
|
select
|
||||||
|
"album_user"."albumId"
|
||||||
|
from
|
||||||
|
"album_user"
|
||||||
|
where
|
||||||
|
"album_user"."userId" = $7
|
||||||
|
)
|
||||||
|
and "album_user"."userId" = "person"."ownerId"
|
||||||
|
and (
|
||||||
|
$8 = any ("album_user"."permissions")
|
||||||
|
or "album_user"."permissions" @> $9
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
-- PersonRepository.refreshFaces
|
-- PersonRepository.refreshFaces
|
||||||
with
|
with
|
||||||
@@ -299,14 +434,26 @@ select
|
|||||||
from
|
from
|
||||||
"person"
|
"person"
|
||||||
where
|
where
|
||||||
"person"."id" = "asset_face"."personId"
|
"person"."faceClusterId" = "asset_face"."faceClusterId"
|
||||||
|
order by
|
||||||
|
"person"."ownerId" = (
|
||||||
|
select
|
||||||
|
"asset"."ownerId"
|
||||||
|
from
|
||||||
|
"asset"
|
||||||
|
where
|
||||||
|
"asset"."id" = "asset_face"."assetId"
|
||||||
|
) desc
|
||||||
|
limit
|
||||||
|
$1
|
||||||
) as obj
|
) as obj
|
||||||
) as "person"
|
) as "person"
|
||||||
from
|
from
|
||||||
"asset_face"
|
"asset_face"
|
||||||
|
inner join "person" on "person"."faceClusterId" = "asset_face"."faceClusterId"
|
||||||
where
|
where
|
||||||
"asset_face"."assetId" in ($1)
|
"person"."id" in ($2)
|
||||||
and "asset_face"."personId" in ($2)
|
and "asset_face"."assetId" in ($3)
|
||||||
and "asset_face"."deletedAt" is null
|
and "asset_face"."deletedAt" is null
|
||||||
|
|
||||||
-- PersonRepository.getRandomFace
|
-- PersonRepository.getRandomFace
|
||||||
@@ -315,7 +462,7 @@ select
|
|||||||
from
|
from
|
||||||
"asset_face"
|
"asset_face"
|
||||||
where
|
where
|
||||||
"asset_face"."personId" = $1
|
"asset_face"."faceClusterId" = $1
|
||||||
and "asset_face"."deletedAt" is null
|
and "asset_face"."deletedAt" is null
|
||||||
and "asset_face"."isVisible" is true
|
and "asset_face"."isVisible" is true
|
||||||
|
|
||||||
@@ -351,8 +498,9 @@ select
|
|||||||
"asset_face"."id"
|
"asset_face"."id"
|
||||||
from
|
from
|
||||||
"asset_face"
|
"asset_face"
|
||||||
|
inner join "person" on "person"."faceClusterId" = "asset_face"."faceClusterId"
|
||||||
|
and "person"."id" = $1
|
||||||
inner join "asset" on "asset"."id" = "asset_face"."assetId"
|
inner join "asset" on "asset"."id" = "asset_face"."assetId"
|
||||||
and "asset"."isOffline" = $1
|
and "asset"."isOffline" = $2
|
||||||
where
|
where
|
||||||
"asset_face"."assetId" = $2
|
"asset_face"."assetId" = $3
|
||||||
and "asset_face"."personId" = $3
|
|
||||||
|
|||||||
@@ -10,15 +10,52 @@ where
|
|||||||
"asset"."visibility" = $1
|
"asset"."visibility" = $1
|
||||||
and "asset"."fileCreatedAt" >= $2
|
and "asset"."fileCreatedAt" >= $2
|
||||||
and "asset_exif"."lensModel" = $3
|
and "asset_exif"."lensModel" = $3
|
||||||
and "asset"."ownerId" = any ($4::uuid[])
|
and (
|
||||||
and "asset"."isFavorite" = $5
|
"asset"."ownerId" = $4
|
||||||
|
or exists (
|
||||||
|
select
|
||||||
|
from
|
||||||
|
"partner"
|
||||||
|
where
|
||||||
|
"partner"."sharedById" = "asset"."ownerId"
|
||||||
|
and "partner"."sharedWithId" = $5
|
||||||
|
and (
|
||||||
|
$6 = any ("partner"."permissions")
|
||||||
|
or "partner"."permissions" @> $7
|
||||||
|
)
|
||||||
|
and "partner"."inTimeline" = $8
|
||||||
|
)
|
||||||
|
or exists (
|
||||||
|
select
|
||||||
|
from
|
||||||
|
"album_asset"
|
||||||
|
inner join "album_user" on "album_user"."albumId" = "album_asset"."albumId"
|
||||||
|
and "album_user"."userId" = $9
|
||||||
|
where
|
||||||
|
"album_asset"."assetId" = "asset"."id"
|
||||||
|
and "album_user"."inTimeline" = $10
|
||||||
|
and "album_user"."albumId" in (
|
||||||
|
select
|
||||||
|
"album_user"."albumId"
|
||||||
|
from
|
||||||
|
"album_user"
|
||||||
|
where
|
||||||
|
"album_user"."userId" = "asset"."ownerId"
|
||||||
|
and (
|
||||||
|
$11 = any ("album_user"."permissions")
|
||||||
|
or "album_user"."permissions" @> $12
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
and "asset"."isFavorite" = $13
|
||||||
and "asset"."deletedAt" is null
|
and "asset"."deletedAt" is null
|
||||||
order by
|
order by
|
||||||
"asset"."fileCreatedAt" desc
|
"asset"."fileCreatedAt" desc
|
||||||
limit
|
limit
|
||||||
$6
|
$14
|
||||||
offset
|
offset
|
||||||
$7
|
$15
|
||||||
|
|
||||||
-- SearchRepository.searchStatistics
|
-- SearchRepository.searchStatistics
|
||||||
select
|
select
|
||||||
@@ -30,8 +67,45 @@ where
|
|||||||
"asset"."visibility" = $1
|
"asset"."visibility" = $1
|
||||||
and "asset"."fileCreatedAt" >= $2
|
and "asset"."fileCreatedAt" >= $2
|
||||||
and "asset_exif"."lensModel" = $3
|
and "asset_exif"."lensModel" = $3
|
||||||
and "asset"."ownerId" = any ($4::uuid[])
|
and (
|
||||||
and "asset"."isFavorite" = $5
|
"asset"."ownerId" = $4
|
||||||
|
or exists (
|
||||||
|
select
|
||||||
|
from
|
||||||
|
"partner"
|
||||||
|
where
|
||||||
|
"partner"."sharedById" = "asset"."ownerId"
|
||||||
|
and "partner"."sharedWithId" = $5
|
||||||
|
and (
|
||||||
|
$6 = any ("partner"."permissions")
|
||||||
|
or "partner"."permissions" @> $7
|
||||||
|
)
|
||||||
|
and "partner"."inTimeline" = $8
|
||||||
|
)
|
||||||
|
or exists (
|
||||||
|
select
|
||||||
|
from
|
||||||
|
"album_asset"
|
||||||
|
inner join "album_user" on "album_user"."albumId" = "album_asset"."albumId"
|
||||||
|
and "album_user"."userId" = $9
|
||||||
|
where
|
||||||
|
"album_asset"."assetId" = "asset"."id"
|
||||||
|
and "album_user"."inTimeline" = $10
|
||||||
|
and "album_user"."albumId" in (
|
||||||
|
select
|
||||||
|
"album_user"."albumId"
|
||||||
|
from
|
||||||
|
"album_user"
|
||||||
|
where
|
||||||
|
"album_user"."userId" = "asset"."ownerId"
|
||||||
|
and (
|
||||||
|
$11 = any ("album_user"."permissions")
|
||||||
|
or "album_user"."permissions" @> $12
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
and "asset"."isFavorite" = $13
|
||||||
and "asset"."deletedAt" is null
|
and "asset"."deletedAt" is null
|
||||||
|
|
||||||
-- SearchRepository.searchRandom
|
-- SearchRepository.searchRandom
|
||||||
@@ -44,13 +118,50 @@ where
|
|||||||
"asset"."visibility" = $1
|
"asset"."visibility" = $1
|
||||||
and "asset"."fileCreatedAt" >= $2
|
and "asset"."fileCreatedAt" >= $2
|
||||||
and "asset_exif"."lensModel" = $3
|
and "asset_exif"."lensModel" = $3
|
||||||
and "asset"."ownerId" = any ($4::uuid[])
|
and (
|
||||||
and "asset"."isFavorite" = $5
|
"asset"."ownerId" = $4
|
||||||
|
or exists (
|
||||||
|
select
|
||||||
|
from
|
||||||
|
"partner"
|
||||||
|
where
|
||||||
|
"partner"."sharedById" = "asset"."ownerId"
|
||||||
|
and "partner"."sharedWithId" = $5
|
||||||
|
and (
|
||||||
|
$6 = any ("partner"."permissions")
|
||||||
|
or "partner"."permissions" @> $7
|
||||||
|
)
|
||||||
|
and "partner"."inTimeline" = $8
|
||||||
|
)
|
||||||
|
or exists (
|
||||||
|
select
|
||||||
|
from
|
||||||
|
"album_asset"
|
||||||
|
inner join "album_user" on "album_user"."albumId" = "album_asset"."albumId"
|
||||||
|
and "album_user"."userId" = $9
|
||||||
|
where
|
||||||
|
"album_asset"."assetId" = "asset"."id"
|
||||||
|
and "album_user"."inTimeline" = $10
|
||||||
|
and "album_user"."albumId" in (
|
||||||
|
select
|
||||||
|
"album_user"."albumId"
|
||||||
|
from
|
||||||
|
"album_user"
|
||||||
|
where
|
||||||
|
"album_user"."userId" = "asset"."ownerId"
|
||||||
|
and (
|
||||||
|
$11 = any ("album_user"."permissions")
|
||||||
|
or "album_user"."permissions" @> $12
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
and "asset"."isFavorite" = $13
|
||||||
and "asset"."deletedAt" is null
|
and "asset"."deletedAt" is null
|
||||||
order by
|
order by
|
||||||
random()
|
random()
|
||||||
limit
|
limit
|
||||||
$6
|
$14
|
||||||
|
|
||||||
-- SearchRepository.searchLargeAssets
|
-- SearchRepository.searchLargeAssets
|
||||||
select
|
select
|
||||||
@@ -63,14 +174,51 @@ where
|
|||||||
"asset"."visibility" = $1
|
"asset"."visibility" = $1
|
||||||
and "asset"."fileCreatedAt" >= $2
|
and "asset"."fileCreatedAt" >= $2
|
||||||
and "asset_exif"."lensModel" = $3
|
and "asset_exif"."lensModel" = $3
|
||||||
and "asset"."ownerId" = any ($4::uuid[])
|
and (
|
||||||
and "asset"."isFavorite" = $5
|
"asset"."ownerId" = $4
|
||||||
|
or exists (
|
||||||
|
select
|
||||||
|
from
|
||||||
|
"partner"
|
||||||
|
where
|
||||||
|
"partner"."sharedById" = "asset"."ownerId"
|
||||||
|
and "partner"."sharedWithId" = $5
|
||||||
|
and (
|
||||||
|
$6 = any ("partner"."permissions")
|
||||||
|
or "partner"."permissions" @> $7
|
||||||
|
)
|
||||||
|
and "partner"."inTimeline" = $8
|
||||||
|
)
|
||||||
|
or exists (
|
||||||
|
select
|
||||||
|
from
|
||||||
|
"album_asset"
|
||||||
|
inner join "album_user" on "album_user"."albumId" = "album_asset"."albumId"
|
||||||
|
and "album_user"."userId" = $9
|
||||||
|
where
|
||||||
|
"album_asset"."assetId" = "asset"."id"
|
||||||
|
and "album_user"."inTimeline" = $10
|
||||||
|
and "album_user"."albumId" in (
|
||||||
|
select
|
||||||
|
"album_user"."albumId"
|
||||||
|
from
|
||||||
|
"album_user"
|
||||||
|
where
|
||||||
|
"album_user"."userId" = "asset"."ownerId"
|
||||||
|
and (
|
||||||
|
$11 = any ("album_user"."permissions")
|
||||||
|
or "album_user"."permissions" @> $12
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
and "asset"."isFavorite" = $13
|
||||||
and "asset"."deletedAt" is null
|
and "asset"."deletedAt" is null
|
||||||
and "asset_exif"."fileSizeInByte" > $6
|
and "asset_exif"."fileSizeInByte" > $14
|
||||||
order by
|
order by
|
||||||
"asset_exif"."fileSizeInByte" desc
|
"asset_exif"."fileSizeInByte" desc
|
||||||
limit
|
limit
|
||||||
$7
|
$15
|
||||||
|
|
||||||
-- SearchRepository.searchSmart
|
-- SearchRepository.searchSmart
|
||||||
begin
|
begin
|
||||||
@@ -86,15 +234,52 @@ where
|
|||||||
"asset"."visibility" = $1
|
"asset"."visibility" = $1
|
||||||
and "asset"."fileCreatedAt" >= $2
|
and "asset"."fileCreatedAt" >= $2
|
||||||
and "asset_exif"."lensModel" = $3
|
and "asset_exif"."lensModel" = $3
|
||||||
and "asset"."ownerId" = any ($4::uuid[])
|
and (
|
||||||
and "asset"."isFavorite" = $5
|
"asset"."ownerId" = $4
|
||||||
|
or exists (
|
||||||
|
select
|
||||||
|
from
|
||||||
|
"partner"
|
||||||
|
where
|
||||||
|
"partner"."sharedById" = "asset"."ownerId"
|
||||||
|
and "partner"."sharedWithId" = $5
|
||||||
|
and (
|
||||||
|
$6 = any ("partner"."permissions")
|
||||||
|
or "partner"."permissions" @> $7
|
||||||
|
)
|
||||||
|
and "partner"."inTimeline" = $8
|
||||||
|
)
|
||||||
|
or exists (
|
||||||
|
select
|
||||||
|
from
|
||||||
|
"album_asset"
|
||||||
|
inner join "album_user" on "album_user"."albumId" = "album_asset"."albumId"
|
||||||
|
and "album_user"."userId" = $9
|
||||||
|
where
|
||||||
|
"album_asset"."assetId" = "asset"."id"
|
||||||
|
and "album_user"."inTimeline" = $10
|
||||||
|
and "album_user"."albumId" in (
|
||||||
|
select
|
||||||
|
"album_user"."albumId"
|
||||||
|
from
|
||||||
|
"album_user"
|
||||||
|
where
|
||||||
|
"album_user"."userId" = "asset"."ownerId"
|
||||||
|
and (
|
||||||
|
$11 = any ("album_user"."permissions")
|
||||||
|
or "album_user"."permissions" @> $12
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
and "asset"."isFavorite" = $13
|
||||||
and "asset"."deletedAt" is null
|
and "asset"."deletedAt" is null
|
||||||
order by
|
order by
|
||||||
smart_search.embedding <=> $6
|
smart_search.embedding <=> $14
|
||||||
limit
|
limit
|
||||||
$7
|
$15
|
||||||
offset
|
offset
|
||||||
$8
|
$16
|
||||||
commit
|
commit
|
||||||
|
|
||||||
-- SearchRepository.getEmbedding
|
-- SearchRepository.getEmbedding
|
||||||
@@ -113,15 +298,30 @@ with
|
|||||||
"cte" as (
|
"cte" as (
|
||||||
select
|
select
|
||||||
"asset_face"."id",
|
"asset_face"."id",
|
||||||
"asset_face"."personId",
|
"asset_face"."faceClusterId",
|
||||||
face_search.embedding <=> $1 as "distance"
|
face_search.embedding <=> $1 as "distance",
|
||||||
|
"asset"."ownerId"
|
||||||
from
|
from
|
||||||
"asset_face"
|
"asset_face"
|
||||||
inner join "asset" on "asset"."id" = "asset_face"."assetId"
|
inner join "asset" on "asset"."id" = "asset_face"."assetId"
|
||||||
inner join "face_search" on "face_search"."faceId" = "asset_face"."id"
|
inner join "face_search" on "face_search"."faceId" = "asset_face"."id"
|
||||||
left join "person" on "person"."id" = "asset_face"."personId"
|
left join "person" on "person"."faceClusterId" = "asset_face"."faceClusterId"
|
||||||
where
|
where
|
||||||
"asset"."ownerId" = any ($2::uuid[])
|
"asset"."ownerId" in (
|
||||||
|
select
|
||||||
|
"user"."id"
|
||||||
|
from
|
||||||
|
"user"
|
||||||
|
where
|
||||||
|
"user"."trustedGroupId" in (
|
||||||
|
select
|
||||||
|
"user"."trustedGroupId"
|
||||||
|
from
|
||||||
|
"user"
|
||||||
|
where
|
||||||
|
"user"."id" = any ($2::uuid[])
|
||||||
|
)
|
||||||
|
)
|
||||||
and "asset"."deletedAt" is null
|
and "asset"."deletedAt" is null
|
||||||
order by
|
order by
|
||||||
"distance"
|
"distance"
|
||||||
|
|||||||
@@ -527,7 +527,7 @@ order by
|
|||||||
select
|
select
|
||||||
"asset_face"."id",
|
"asset_face"."id",
|
||||||
"assetId",
|
"assetId",
|
||||||
"personId",
|
"faceClusterId",
|
||||||
"imageWidth",
|
"imageWidth",
|
||||||
"imageHeight",
|
"imageHeight",
|
||||||
"boundingBoxX1",
|
"boundingBoxX1",
|
||||||
|
|||||||
@@ -397,3 +397,73 @@ set
|
|||||||
where
|
where
|
||||||
"user"."deletedAt" is null
|
"user"."deletedAt" is null
|
||||||
and "user"."id" = $2::uuid
|
and "user"."id" = $2::uuid
|
||||||
|
|
||||||
|
-- UserRepository.getInSameTrustedGroup
|
||||||
|
select
|
||||||
|
"user"."id"
|
||||||
|
from
|
||||||
|
"user"
|
||||||
|
where
|
||||||
|
"user"."trustedGroupId" = (
|
||||||
|
select
|
||||||
|
"user"."trustedGroupId"
|
||||||
|
from
|
||||||
|
"user"
|
||||||
|
where
|
||||||
|
"user"."id" = $1
|
||||||
|
)
|
||||||
|
|
||||||
|
-- UserRepository.mergeTrustedGroups
|
||||||
|
update "user"
|
||||||
|
set
|
||||||
|
"trustedGroupId" = "u"."trustedGroupId"
|
||||||
|
from
|
||||||
|
"user" as "u"
|
||||||
|
where
|
||||||
|
"u"."id" = $1
|
||||||
|
and "user"."trustedGroupId" = (
|
||||||
|
select
|
||||||
|
"user"."trustedGroupId"
|
||||||
|
from
|
||||||
|
"user"
|
||||||
|
where
|
||||||
|
"user"."id" = $2
|
||||||
|
and "user"."trustedGroupId" != "u"."trustedGroupId"
|
||||||
|
)
|
||||||
|
|
||||||
|
-- UserRepository.updateTrustedGroups
|
||||||
|
update "user"
|
||||||
|
set
|
||||||
|
"trustedGroupId" = uuid_generate_v4 ()
|
||||||
|
where
|
||||||
|
"user"."trustedGroupId" = (
|
||||||
|
select
|
||||||
|
"user"."trustedGroupId"
|
||||||
|
from
|
||||||
|
"user"
|
||||||
|
where
|
||||||
|
"user"."id" = $1
|
||||||
|
)
|
||||||
|
and "user"."id" != $2
|
||||||
|
and "user"."id" not in (
|
||||||
|
select
|
||||||
|
"partner"."sharedById" as "userId"
|
||||||
|
from
|
||||||
|
"partner"
|
||||||
|
where
|
||||||
|
"sharedWithId" = $3
|
||||||
|
union
|
||||||
|
select
|
||||||
|
"album_user"."userId"
|
||||||
|
from
|
||||||
|
"album_user"
|
||||||
|
where
|
||||||
|
"album_user"."albumId" in (
|
||||||
|
select
|
||||||
|
"album_user"."albumId"
|
||||||
|
from
|
||||||
|
"album_user"
|
||||||
|
where
|
||||||
|
"album_user"."userId" = $4
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ import { Injectable } from '@nestjs/common';
|
|||||||
import { Kysely, NotNull, sql } from 'kysely';
|
import { Kysely, NotNull, sql } from 'kysely';
|
||||||
import { InjectKysely } from 'nestjs-kysely';
|
import { InjectKysely } from 'nestjs-kysely';
|
||||||
import { ChunkedSet, DummyValue, GenerateSql } from 'src/decorators';
|
import { ChunkedSet, DummyValue, GenerateSql } from 'src/decorators';
|
||||||
import { AlbumUserRole, AssetVisibility } from 'src/enum';
|
import { AlbumUserRole, AssetVisibility, SharingPermission } from 'src/enum';
|
||||||
|
import { hasAssetPermissions } from 'src/repositories/asset.repository';
|
||||||
|
import { hasPermissions } from 'src/repositories/person.repository';
|
||||||
import { DB } from 'src/schema';
|
import { DB } from 'src/schema';
|
||||||
import { asUuid } from 'src/utils/database';
|
import { asUuid } from 'src/utils/database';
|
||||||
|
|
||||||
@@ -273,6 +275,46 @@ class AssetAccess {
|
|||||||
return allowedIds;
|
return allowedIds;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET, [SharingPermission.All]] })
|
||||||
|
async checkSharedAccess(userId: string, assetIds: Set<string>, permissions: SharingPermission[]) {
|
||||||
|
const ids = await this.db
|
||||||
|
.selectFrom('album_asset')
|
||||||
|
.select('album_asset.assetId')
|
||||||
|
.where('album_asset.assetId', 'in', [...assetIds])
|
||||||
|
.where('album_asset.albumId', 'in', (eb) =>
|
||||||
|
eb
|
||||||
|
.selectFrom('album_user')
|
||||||
|
.select('album_user.albumId')
|
||||||
|
.where((eb) =>
|
||||||
|
eb.or([
|
||||||
|
eb('album_user.permissions', '@>', sql<SharingPermission[]>`${permissions}::sharing_permission_enum[]`),
|
||||||
|
eb(eb.val(SharingPermission.All), '=', eb.fn.any('album_user.permissions')),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.innerJoin('album_user', (join) =>
|
||||||
|
join.onRef('album_asset.albumId', '=', 'album_user.albumId').on('album_user.userId', '=', userId),
|
||||||
|
)
|
||||||
|
.union((eb) =>
|
||||||
|
eb
|
||||||
|
.selectFrom('partner')
|
||||||
|
.where('partner.sharedWithId', '=', userId)
|
||||||
|
.where((eb) =>
|
||||||
|
eb.or([
|
||||||
|
eb('partner.permissions', '@>', sql<SharingPermission[]>`${permissions}::sharing_permission_enum[]`),
|
||||||
|
eb(eb.val(SharingPermission.All), '=', eb.fn.any('partner.permissions')),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
.innerJoin('asset', (join) =>
|
||||||
|
join.onRef('asset.ownerId', '=', 'partner.sharedById').on('asset.id', 'in', [...assetIds]),
|
||||||
|
)
|
||||||
|
.select('asset.id as assetId'),
|
||||||
|
)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
return new Set(ids.map(({ assetId }) => assetId));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AuthDeviceAccess {
|
class AuthDeviceAccess {
|
||||||
@@ -452,6 +494,37 @@ class PersonAccess {
|
|||||||
.execute()
|
.execute()
|
||||||
.then((faces) => new Set(faces.map((face) => face.id)));
|
.then((faces) => new Set(faces.map((face) => face.id)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async checkSharedAccess(userId: string, personIds: Set<string>, permissions: SharingPermission[]) {
|
||||||
|
if (personIds.size === 0) {
|
||||||
|
return new Set<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
const ids = await this.db
|
||||||
|
.selectFrom('person')
|
||||||
|
.select('person.id')
|
||||||
|
.where('person.id', 'in', [...personIds])
|
||||||
|
.where(hasPermissions(userId, permissions))
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
return new Set(ids.map(({ id }) => id));
|
||||||
|
}
|
||||||
|
|
||||||
|
async checkSharedFaceAccess(userId: string, faceIds: Set<string>, permissions: SharingPermission[]) {
|
||||||
|
if (faceIds.size === 0) {
|
||||||
|
return new Set<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
const ids = await this.db
|
||||||
|
.selectFrom('asset_face')
|
||||||
|
.select('asset_face.id')
|
||||||
|
.leftJoin('asset', (join) => join.onRef('asset.id', '=', 'asset_face.assetId'))
|
||||||
|
.where('asset_face.id', 'in', [...faceIds])
|
||||||
|
.where(hasAssetPermissions(userId, permissions))
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
return new Set(ids.map(({ id }) => id));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PartnerAccess {
|
class PartnerAccess {
|
||||||
|
|||||||
@@ -38,4 +38,13 @@ export class AlbumUserRepository {
|
|||||||
async delete({ userId, albumId }: AlbumPermissionId): Promise<void> {
|
async delete({ userId, albumId }: AlbumPermissionId): Promise<void> {
|
||||||
await this.db.deleteFrom('album_user').where('userId', '=', userId).where('albumId', '=', albumId).execute();
|
await this.db.deleteFrom('album_user').where('userId', '=', userId).where('albumId', '=', albumId).execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get({ userId, albumId }: AlbumPermissionId) {
|
||||||
|
return this.db
|
||||||
|
.selectFrom('album_user')
|
||||||
|
.select(['permissions', 'inTimeline'])
|
||||||
|
.where('userId', '=', userId)
|
||||||
|
.where('albumId', '=', albumId)
|
||||||
|
.executeTakeFirstOrThrow();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,15 @@ import { InjectKysely } from 'nestjs-kysely';
|
|||||||
import { LockableProperty, Stack } from 'src/database';
|
import { LockableProperty, Stack } from 'src/database';
|
||||||
import { Chunked, ChunkedArray, DummyValue, GenerateSql } from 'src/decorators';
|
import { Chunked, ChunkedArray, DummyValue, GenerateSql } from 'src/decorators';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
import { AssetFileType, AssetOrder, AssetOrderBy, AssetStatus, AssetType, AssetVisibility } from 'src/enum';
|
import {
|
||||||
|
AssetFileType,
|
||||||
|
AssetOrder,
|
||||||
|
AssetOrderBy,
|
||||||
|
AssetStatus,
|
||||||
|
AssetType,
|
||||||
|
AssetVisibility,
|
||||||
|
SharingPermission,
|
||||||
|
} from 'src/enum';
|
||||||
import { DB } from 'src/schema';
|
import { DB } from 'src/schema';
|
||||||
import { AssetAudioTable, AssetKeyframeTable, AssetVideoTable } from 'src/schema/tables/asset-av.table';
|
import { AssetAudioTable, AssetKeyframeTable, AssetVideoTable } from 'src/schema/tables/asset-av.table';
|
||||||
import { AssetExifTable } from 'src/schema/tables/asset-exif.table';
|
import { AssetExifTable } from 'src/schema/tables/asset-exif.table';
|
||||||
@@ -41,6 +49,7 @@ import {
|
|||||||
withFiles,
|
withFiles,
|
||||||
withLibrary,
|
withLibrary,
|
||||||
withOwner,
|
withOwner,
|
||||||
|
withPermissions,
|
||||||
withSmartSearch,
|
withSmartSearch,
|
||||||
withTagId,
|
withTagId,
|
||||||
withTags,
|
withTags,
|
||||||
@@ -165,6 +174,47 @@ const withBoundingBox = <T>(qb: SelectQueryBuilder<DB, 'asset' | 'asset_exif', T
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const hasAssetPermissions =
|
||||||
|
(userId: string, permissions: SharingPermission[], ignoreTimelineVisibility: boolean = false) =>
|
||||||
|
(eb: ExpressionBuilder<DB, 'asset'>) =>
|
||||||
|
eb.or([
|
||||||
|
eb('asset.ownerId', '=', userId),
|
||||||
|
eb.exists(
|
||||||
|
eb
|
||||||
|
.selectFrom('partner')
|
||||||
|
.whereRef('partner.sharedById', '=', 'asset.ownerId')
|
||||||
|
.where('partner.sharedWithId', '=', userId)
|
||||||
|
.where((eb) =>
|
||||||
|
eb.or([
|
||||||
|
eb(eb.val(SharingPermission.All), '=', eb.fn.any('partner.permissions')),
|
||||||
|
eb('partner.permissions', '@>', eb.val(permissions)),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
.$if(!ignoreTimelineVisibility, (qb) => qb.where('partner.inTimeline', '=', true)),
|
||||||
|
),
|
||||||
|
eb.exists(
|
||||||
|
eb
|
||||||
|
.selectFrom('album_asset')
|
||||||
|
.whereRef('album_asset.assetId', '=', 'asset.id')
|
||||||
|
.innerJoin('album_user', (join) =>
|
||||||
|
join.onRef('album_user.albumId', '=', 'album_asset.albumId').on('album_user.userId', '=', userId),
|
||||||
|
)
|
||||||
|
.$if(!ignoreTimelineVisibility, (qb) => qb.where('album_user.inTimeline', '=', true))
|
||||||
|
.where('album_user.albumId', 'in', (eb) =>
|
||||||
|
eb
|
||||||
|
.selectFrom('album_user')
|
||||||
|
.select('album_user.albumId')
|
||||||
|
.whereRef('album_user.userId', '=', 'asset.ownerId')
|
||||||
|
.where((eb) =>
|
||||||
|
eb.or([
|
||||||
|
eb(eb.val(SharingPermission.All), '=', eb.fn.any('album_user.permissions')),
|
||||||
|
eb('album_user.permissions', '@>', eb.val(permissions)),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AssetRepository {
|
export class AssetRepository {
|
||||||
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
||||||
@@ -556,17 +606,22 @@ export class AssetRepository {
|
|||||||
.executeTakeFirst();
|
.executeTakeFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GenerateSql({ params: [DummyValue.UUID] })
|
@GenerateSql({ params: [DummyValue.UUID, {}, DummyValue.UUID] })
|
||||||
getById(
|
getById(
|
||||||
id: string,
|
id: string,
|
||||||
{ exifInfo, faces, files, library, owner, smartSearch, stack, tags, edits }: GetByIdsRelations = {},
|
{ exifInfo, faces, files, library, owner, smartSearch, stack, tags, edits }: GetByIdsRelations = {},
|
||||||
|
userId?: string,
|
||||||
) {
|
) {
|
||||||
return this.db
|
return this.db
|
||||||
.selectFrom('asset')
|
.selectFrom('asset')
|
||||||
.selectAll('asset')
|
.selectAll('asset')
|
||||||
.where('asset.id', '=', asUuid(id))
|
.where('asset.id', '=', asUuid(id))
|
||||||
.$if(!!exifInfo, withExif)
|
.$if(!!exifInfo, withExif)
|
||||||
.$if(!!faces, (qb) => qb.select(faces?.person ? withFacesAndPeople : withFaces).$narrowType<{ faces: NotNull }>())
|
.$if(!!faces, (qb) =>
|
||||||
|
qb
|
||||||
|
.select(faces?.person ? (eb) => withFacesAndPeople(eb, { userId }) : withFaces)
|
||||||
|
.$narrowType<{ faces: NotNull }>(),
|
||||||
|
)
|
||||||
.$if(!!library, (qb) => qb.select(withLibrary))
|
.$if(!!library, (qb) => qb.select(withLibrary))
|
||||||
.$if(!!owner, (qb) => qb.select(withOwner))
|
.$if(!!owner, (qb) => qb.select(withOwner))
|
||||||
.$if(!!smartSearch, withSmartSearch)
|
.$if(!!smartSearch, withSmartSearch)
|
||||||
@@ -602,6 +657,7 @@ export class AssetRepository {
|
|||||||
.$if(!!files, (qb) => qb.select(withFiles))
|
.$if(!!files, (qb) => qb.select(withFiles))
|
||||||
.$if(!!tags, (qb) => qb.select(withTags))
|
.$if(!!tags, (qb) => qb.select(withTags))
|
||||||
.$if(!!edits, (qb) => qb.select(withEdits))
|
.$if(!!edits, (qb) => qb.select(withEdits))
|
||||||
|
.$if(!!userId, (qb) => qb.select(withPermissions(userId!)))
|
||||||
.limit(1)
|
.limit(1)
|
||||||
.executeTakeFirst();
|
.executeTakeFirst();
|
||||||
}
|
}
|
||||||
@@ -744,7 +800,9 @@ export class AssetRepository {
|
|||||||
)
|
)
|
||||||
.where((eb) => eb.or([eb('asset.stackId', 'is', null), eb(eb.table('stack'), 'is not', null)])),
|
.where((eb) => eb.or([eb('asset.stackId', 'is', null), eb(eb.table('stack'), 'is not', null)])),
|
||||||
)
|
)
|
||||||
.$if(!!options.userIds, (qb) => qb.where('asset.ownerId', '=', anyUuid(options.userIds!)))
|
.$if(!!options.userIds, (qb) =>
|
||||||
|
qb.where(hasAssetPermissions(options.userIds![0], [SharingPermission.AssetRead], !!options.personId)),
|
||||||
|
)
|
||||||
.$if(options.isFavorite !== undefined, (qb) => qb.where('asset.isFavorite', '=', options.isFavorite!))
|
.$if(options.isFavorite !== undefined, (qb) => qb.where('asset.isFavorite', '=', options.isFavorite!))
|
||||||
.$if(!!options.assetType, (qb) => qb.where('asset.type', '=', options.assetType!))
|
.$if(!!options.assetType, (qb) => qb.where('asset.type', '=', options.assetType!))
|
||||||
.$if(options.isDuplicate !== undefined, (qb) =>
|
.$if(options.isDuplicate !== undefined, (qb) =>
|
||||||
@@ -829,7 +887,9 @@ export class AssetRepository {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
.$if(!!options.personId, (qb) => hasPeople(qb, [options.personId!]))
|
.$if(!!options.personId, (qb) => hasPeople(qb, [options.personId!]))
|
||||||
.$if(!!options.userIds, (qb) => qb.where('asset.ownerId', '=', anyUuid(options.userIds!)))
|
.$if(!!options.userIds, (qb) =>
|
||||||
|
qb.where(hasAssetPermissions(options.userIds![0], [SharingPermission.AssetRead], !!options.personId)),
|
||||||
|
)
|
||||||
.$if(options.isFavorite !== undefined, (qb) => qb.where('asset.isFavorite', '=', options.isFavorite!))
|
.$if(options.isFavorite !== undefined, (qb) => qb.where('asset.isFavorite', '=', options.isFavorite!))
|
||||||
.$if(!!options.withStacked, (qb) =>
|
.$if(!!options.withStacked, (qb) =>
|
||||||
qb
|
qb
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { getKeyByValue, getMethodNames, ImmichStartupError } from 'src/utils/mis
|
|||||||
type JobMapItem = {
|
type JobMapItem = {
|
||||||
jobName: JobName;
|
jobName: JobName;
|
||||||
queueName: QueueName;
|
queueName: QueueName;
|
||||||
handler: (job: JobOf<any>) => Promise<JobStatus>;
|
handler: (job?: JobOf<any>) => Promise<JobStatus>;
|
||||||
label: string;
|
label: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -95,14 +95,17 @@ export class JobRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async run({ name, data }: JobItem) {
|
async run(job: JobItem) {
|
||||||
const item = this.handlers[name as JobName];
|
const item = this.handlers[job.name];
|
||||||
if (!item) {
|
if (!item) {
|
||||||
this.logger.warn(`Skipping unknown job: "${name}"`);
|
this.logger.warn(`Skipping unknown job: "${job.name}"`);
|
||||||
return JobStatus.Skipped;
|
return JobStatus.Skipped;
|
||||||
}
|
}
|
||||||
|
|
||||||
return item.handler(data);
|
if ('data' in job) {
|
||||||
|
return item.handler(job.data);
|
||||||
|
}
|
||||||
|
return item.handler();
|
||||||
}
|
}
|
||||||
|
|
||||||
setConcurrency(queueName: QueueName, concurrency: number) {
|
setConcurrency(queueName: QueueName, concurrency: number) {
|
||||||
@@ -167,7 +170,7 @@ export class JobRepository {
|
|||||||
const queueName = this.getQueueName(item.name);
|
const queueName = this.getQueueName(item.name);
|
||||||
const job = {
|
const job = {
|
||||||
name: item.name,
|
name: item.name,
|
||||||
data: item.data || {},
|
data: ('data' in item ? item.data : undefined) || {},
|
||||||
options: this.getJobOptions(item) || undefined,
|
options: this.getJobOptions(item) || undefined,
|
||||||
} as JobItem & { data: any; options: JobsOptions | undefined };
|
} as JobItem & { data: any; options: JobsOptions | undefined };
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export class MemoryRepository implements IBulkAsset {
|
|||||||
eb.exists(
|
eb.exists(
|
||||||
eb
|
eb
|
||||||
.selectFrom('asset_face')
|
.selectFrom('asset_face')
|
||||||
.innerJoin('person', 'person.id', 'asset_face.personId')
|
.innerJoin('person', 'person.faceClusterId', 'asset_face.faceClusterId')
|
||||||
.select((eb) => eb.val(1).as('one'))
|
.select((eb) => eb.val(1).as('one'))
|
||||||
.whereRef('asset_face.assetId', '=', 'asset.id')
|
.whereRef('asset_face.assetId', '=', 'asset.id')
|
||||||
.where('person.isHidden', '=', true),
|
.where('person.isHidden', '=', true),
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import { jsonObjectFrom } from 'kysely/helpers/postgres';
|
|||||||
import { InjectKysely } from 'nestjs-kysely';
|
import { InjectKysely } from 'nestjs-kysely';
|
||||||
import { AssetFace } from 'src/database';
|
import { AssetFace } from 'src/database';
|
||||||
import { Chunked, ChunkedArray, DummyValue, GenerateSql } from 'src/decorators';
|
import { Chunked, ChunkedArray, DummyValue, GenerateSql } from 'src/decorators';
|
||||||
import { AssetFileType, AssetVisibility, SourceType } from 'src/enum';
|
import { AssetFileType, AssetVisibility, SharingPermission, SourceType } from 'src/enum';
|
||||||
|
import { hasAssetPermissions } from 'src/repositories/asset.repository';
|
||||||
import { DB } from 'src/schema';
|
import { DB } from 'src/schema';
|
||||||
import { AssetFaceTable } from 'src/schema/tables/asset-face.table';
|
import { AssetFaceTable } from 'src/schema/tables/asset-face.table';
|
||||||
import { FaceSearchTable } from 'src/schema/tables/face-search.table';
|
import { FaceSearchTable } from 'src/schema/tables/face-search.table';
|
||||||
@@ -33,9 +34,9 @@ export interface AssetFaceId {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateFacesData {
|
export interface UpdateFacesData {
|
||||||
oldPersonId?: string;
|
oldFaceClusterId?: string;
|
||||||
faceIds?: string[];
|
faceIds?: string[];
|
||||||
newPersonId: string;
|
newFaceClusterId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PersonStatistics {
|
export interface PersonStatistics {
|
||||||
@@ -54,7 +55,7 @@ export interface GetAllPeopleOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface GetAllFacesOptions {
|
export interface GetAllFacesOptions {
|
||||||
personId?: string | null;
|
faceClusterId?: string | null;
|
||||||
assetId?: string;
|
assetId?: string;
|
||||||
sourceType?: SourceType;
|
sourceType?: SourceType;
|
||||||
}
|
}
|
||||||
@@ -63,9 +64,27 @@ export type UnassignFacesOptions = DeleteFacesOptions;
|
|||||||
|
|
||||||
export type SelectFaceOptions = (keyof Selectable<AssetFaceTable>)[];
|
export type SelectFaceOptions = (keyof Selectable<AssetFaceTable>)[];
|
||||||
|
|
||||||
const withPerson = (eb: ExpressionBuilder<DB, 'asset_face'>) => {
|
const withPerson = (eb: ExpressionBuilder<DB, 'asset_face'>, userId?: string) => {
|
||||||
return jsonObjectFrom(
|
return jsonObjectFrom(
|
||||||
eb.selectFrom('person').selectAll('person').whereRef('person.id', '=', 'asset_face.personId'),
|
eb
|
||||||
|
.selectFrom('person')
|
||||||
|
.selectAll('person')
|
||||||
|
.whereRef('person.faceClusterId', '=', 'asset_face.faceClusterId')
|
||||||
|
.$if(!!userId, (qb) =>
|
||||||
|
qb.where((eb) =>
|
||||||
|
eb.or([eb('person.ownerId', '=', userId!), hasPermissions(userId!, [SharingPermission.PersonRead])(eb)]),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.orderBy(
|
||||||
|
(eb) =>
|
||||||
|
eb(
|
||||||
|
'person.ownerId',
|
||||||
|
'=',
|
||||||
|
eb.selectFrom('asset').select('asset.ownerId').whereRef('asset.id', '=', 'asset_face.assetId'),
|
||||||
|
),
|
||||||
|
'desc',
|
||||||
|
)
|
||||||
|
.limit(1),
|
||||||
).as('person');
|
).as('person');
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -75,16 +94,47 @@ const withFaceSearch = (eb: ExpressionBuilder<DB, 'asset_face'>) => {
|
|||||||
).as('faceSearch');
|
).as('faceSearch');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const hasPermissions =
|
||||||
|
(userId: string, permissions: SharingPermission[]) => (eb: ExpressionBuilder<DB, 'person'>) =>
|
||||||
|
eb.or([
|
||||||
|
eb.exists((eb) =>
|
||||||
|
eb
|
||||||
|
.selectFrom('partner')
|
||||||
|
.whereRef('partner.sharedById', '=', 'person.ownerId')
|
||||||
|
.where('partner.sharedWithId', '=', userId)
|
||||||
|
.where((eb) =>
|
||||||
|
eb.or([
|
||||||
|
eb(eb.val(SharingPermission.All), '=', eb.fn.any('partner.permissions')),
|
||||||
|
eb('partner.permissions', '@>', eb.val(permissions)),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
eb.exists((eb) =>
|
||||||
|
eb
|
||||||
|
.selectFrom('album_user')
|
||||||
|
.where('album_user.albumId', 'in', (eb) =>
|
||||||
|
eb.selectFrom('album_user').select('album_user.albumId').where('album_user.userId', '=', userId),
|
||||||
|
)
|
||||||
|
.whereRef('album_user.userId', '=', 'person.ownerId')
|
||||||
|
.where((eb) =>
|
||||||
|
eb.or([
|
||||||
|
eb(eb.val(SharingPermission.All), '=', eb.fn.any('album_user.permissions')),
|
||||||
|
eb('album_user.permissions', '@>', eb.val(permissions)),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PersonRepository {
|
export class PersonRepository {
|
||||||
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
||||||
|
|
||||||
@GenerateSql({ params: [{ oldPersonId: DummyValue.UUID, newPersonId: DummyValue.UUID }] })
|
@GenerateSql({ params: [{ oldPersonId: DummyValue.UUID, newPersonId: DummyValue.UUID }] })
|
||||||
async reassignFaces({ oldPersonId, faceIds, newPersonId }: UpdateFacesData): Promise<number> {
|
async reassignFaces({ oldFaceClusterId, faceIds, newFaceClusterId }: UpdateFacesData): Promise<number> {
|
||||||
const result = await this.db
|
const result = await this.db
|
||||||
.updateTable('asset_face')
|
.updateTable('asset_face')
|
||||||
.set({ personId: newPersonId })
|
.set({ faceClusterId: newFaceClusterId })
|
||||||
.$if(!!oldPersonId, (qb) => qb.where('asset_face.personId', '=', oldPersonId!))
|
.$if(!!oldFaceClusterId, (qb) => qb.where('asset_face.faceClusterId', '=', oldFaceClusterId!))
|
||||||
.$if(!!faceIds, (qb) => qb.where('asset_face.id', 'in', faceIds!))
|
.$if(!!faceIds, (qb) => qb.where('asset_face.id', 'in', faceIds!))
|
||||||
.executeTakeFirst();
|
.executeTakeFirst();
|
||||||
|
|
||||||
@@ -94,7 +144,7 @@ export class PersonRepository {
|
|||||||
async unassignFaces({ sourceType }: UnassignFacesOptions): Promise<void> {
|
async unassignFaces({ sourceType }: UnassignFacesOptions): Promise<void> {
|
||||||
await this.db
|
await this.db
|
||||||
.updateTable('asset_face')
|
.updateTable('asset_face')
|
||||||
.set({ personId: null })
|
.set({ faceClusterId: null })
|
||||||
.where('asset_face.sourceType', '=', sourceType)
|
.where('asset_face.sourceType', '=', sourceType)
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
@@ -117,8 +167,8 @@ export class PersonRepository {
|
|||||||
return this.db
|
return this.db
|
||||||
.selectFrom('asset_face')
|
.selectFrom('asset_face')
|
||||||
.selectAll('asset_face')
|
.selectAll('asset_face')
|
||||||
.$if(options.personId === null, (qb) => qb.where('asset_face.personId', 'is', null))
|
.$if(options.faceClusterId === null, (qb) => qb.where('asset_face.faceClusterId', 'is', null))
|
||||||
.$if(!!options.personId, (qb) => qb.where('asset_face.personId', '=', options.personId!))
|
.$if(!!options.faceClusterId, (qb) => qb.where('asset_face.faceClusterId', '=', options.faceClusterId!))
|
||||||
.$if(!!options.sourceType, (qb) => qb.where('asset_face.sourceType', '=', options.sourceType!))
|
.$if(!!options.sourceType, (qb) => qb.where('asset_face.sourceType', '=', options.sourceType!))
|
||||||
.$if(!!options.assetId, (qb) => qb.where('asset_face.assetId', '=', options.assetId!))
|
.$if(!!options.assetId, (qb) => qb.where('asset_face.assetId', '=', options.assetId!))
|
||||||
.where('asset_face.deletedAt', 'is', null)
|
.where('asset_face.deletedAt', 'is', null)
|
||||||
@@ -153,16 +203,20 @@ export class PersonRepository {
|
|||||||
const items = await this.db
|
const items = await this.db
|
||||||
.selectFrom('person')
|
.selectFrom('person')
|
||||||
.selectAll('person')
|
.selectAll('person')
|
||||||
.innerJoin('asset_face', 'asset_face.personId', 'person.id')
|
.innerJoin('asset_face', 'asset_face.faceClusterId', 'person.faceClusterId')
|
||||||
.innerJoin('asset', (join) =>
|
.innerJoin('asset', (join) =>
|
||||||
join
|
join
|
||||||
.onRef('asset_face.assetId', '=', 'asset.id')
|
.onRef('asset_face.assetId', '=', 'asset.id')
|
||||||
.on('asset.visibility', '=', sql.lit(AssetVisibility.Timeline))
|
.on('asset.visibility', '=', sql.lit(AssetVisibility.Timeline))
|
||||||
.on('asset.deletedAt', 'is', null),
|
.on('asset.deletedAt', 'is', null),
|
||||||
)
|
)
|
||||||
.where('person.ownerId', '=', userId)
|
.where((eb) =>
|
||||||
|
eb.or([eb('person.ownerId', '=', userId), hasPermissions(userId, [SharingPermission.PersonRead])(eb)]),
|
||||||
|
)
|
||||||
.where('asset_face.deletedAt', 'is', null)
|
.where('asset_face.deletedAt', 'is', null)
|
||||||
.where('asset_face.isVisible', 'is', true)
|
.where('asset_face.isVisible', 'is', true)
|
||||||
|
.orderBy('person.faceClusterId')
|
||||||
|
.orderBy((eb) => eb('person.ownerId', '=', userId), 'desc')
|
||||||
.orderBy('person.isHidden', 'asc')
|
.orderBy('person.isHidden', 'asc')
|
||||||
.orderBy('person.isFavorite', 'desc')
|
.orderBy('person.isFavorite', 'desc')
|
||||||
.having((eb) =>
|
.having((eb) =>
|
||||||
@@ -171,6 +225,7 @@ export class PersonRepository {
|
|||||||
eb((innerEb) => innerEb.fn.count('asset_face.assetId'), '>=', options?.minimumFaceCount || 1),
|
eb((innerEb) => innerEb.fn.count('asset_face.assetId'), '>=', options?.minimumFaceCount || 1),
|
||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
|
.distinctOn('person.faceClusterId')
|
||||||
.groupBy('person.id')
|
.groupBy('person.id')
|
||||||
.$if(!!options?.closestFaceAssetId, (qb) =>
|
.$if(!!options?.closestFaceAssetId, (qb) =>
|
||||||
qb.orderBy((eb) =>
|
qb.orderBy((eb) =>
|
||||||
@@ -209,7 +264,7 @@ export class PersonRepository {
|
|||||||
return this.db
|
return this.db
|
||||||
.selectFrom('person')
|
.selectFrom('person')
|
||||||
.selectAll('person')
|
.selectAll('person')
|
||||||
.leftJoin('asset_face', 'asset_face.personId', 'person.id')
|
.leftJoin('asset_face', 'asset_face.faceClusterId', 'person.faceClusterId')
|
||||||
.where('asset_face.deletedAt', 'is', null)
|
.where('asset_face.deletedAt', 'is', null)
|
||||||
.where('asset_face.isVisible', 'is', true)
|
.where('asset_face.isVisible', 'is', true)
|
||||||
.having((eb) => eb.fn.count('asset_face.assetId'), '=', 0)
|
.having((eb) => eb.fn.count('asset_face.assetId'), '=', 0)
|
||||||
@@ -218,13 +273,13 @@ export class PersonRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GenerateSql({ params: [DummyValue.UUID] })
|
@GenerateSql({ params: [DummyValue.UUID] })
|
||||||
getFaces(assetId: string, options?: { isVisible?: boolean }) {
|
getFaces(assetId: string, options: { isVisible?: boolean; userId?: string } = {}) {
|
||||||
const isVisible = options === undefined ? true : options.isVisible;
|
const { isVisible = true, userId } = options;
|
||||||
|
|
||||||
return this.db
|
return this.db
|
||||||
.selectFrom('asset_face')
|
.selectFrom('asset_face')
|
||||||
.selectAll('asset_face')
|
.selectAll('asset_face')
|
||||||
.select(withPerson)
|
.select((eb) => withPerson(eb, userId))
|
||||||
.where('asset_face.assetId', '=', assetId)
|
.where('asset_face.assetId', '=', assetId)
|
||||||
.where('asset_face.deletedAt', 'is', null)
|
.where('asset_face.deletedAt', 'is', null)
|
||||||
.$if(isVisible !== undefined, (qb) => qb.where('asset_face.isVisible', '=', isVisible!))
|
.$if(isVisible !== undefined, (qb) => qb.where('asset_face.isVisible', '=', isVisible!))
|
||||||
@@ -248,7 +303,7 @@ export class PersonRepository {
|
|||||||
getFaceForFacialRecognitionJob(id: string) {
|
getFaceForFacialRecognitionJob(id: string) {
|
||||||
return this.db
|
return this.db
|
||||||
.selectFrom('asset_face')
|
.selectFrom('asset_face')
|
||||||
.select(['asset_face.id', 'asset_face.personId', 'asset_face.sourceType'])
|
.select(['asset_face.id', 'asset_face.faceClusterId', 'asset_face.sourceType'])
|
||||||
.select((eb) =>
|
.select((eb) =>
|
||||||
jsonObjectFrom(
|
jsonObjectFrom(
|
||||||
eb
|
eb
|
||||||
@@ -289,10 +344,10 @@ export class PersonRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] })
|
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] })
|
||||||
async reassignFace(assetFaceId: string, newPersonId: string): Promise<number> {
|
async reassignFace(assetFaceId: string, newFaceClusterId: string): Promise<number> {
|
||||||
const result = await this.db
|
const result = await this.db
|
||||||
.updateTable('asset_face')
|
.updateTable('asset_face')
|
||||||
.set({ personId: newPersonId })
|
.set({ faceClusterId: newFaceClusterId })
|
||||||
.where('asset_face.id', '=', assetFaceId)
|
.where('asset_face.id', '=', assetFaceId)
|
||||||
.executeTakeFirst();
|
.executeTakeFirst();
|
||||||
|
|
||||||
@@ -318,6 +373,7 @@ export class PersonRepository {
|
|||||||
.where('person.ownerId', '=', userId)
|
.where('person.ownerId', '=', userId)
|
||||||
.where(() => sql`f_unaccent("person"."name") %> f_unaccent(${personName})`)
|
.where(() => sql`f_unaccent("person"."name") %> f_unaccent(${personName})`)
|
||||||
.orderBy(sql`f_unaccent("person"."name") <->>> f_unaccent(${personName})`)
|
.orderBy(sql`f_unaccent("person"."name") <->>> f_unaccent(${personName})`)
|
||||||
|
.orderBy((eb) => eb('person.ownerId', '=', userId), 'desc')
|
||||||
.limit(100)
|
.limit(100)
|
||||||
.$if(!withHidden, (qb) => qb.where('person.isHidden', '=', false))
|
.$if(!withHidden, (qb) => qb.where('person.isHidden', '=', false))
|
||||||
.execute();
|
.execute();
|
||||||
@@ -335,7 +391,7 @@ export class PersonRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GenerateSql({ params: [DummyValue.UUID] })
|
@GenerateSql({ params: [DummyValue.UUID] })
|
||||||
async getStatistics(personId: string): Promise<PersonStatistics> {
|
async getStatistics(userId: string, personId: string): Promise<PersonStatistics> {
|
||||||
const result = await this.db
|
const result = await this.db
|
||||||
.selectFrom('asset_face')
|
.selectFrom('asset_face')
|
||||||
.leftJoin('asset', (join) =>
|
.leftJoin('asset', (join) =>
|
||||||
@@ -344,10 +400,13 @@ export class PersonRepository {
|
|||||||
.on('asset.visibility', '=', sql.lit(AssetVisibility.Timeline))
|
.on('asset.visibility', '=', sql.lit(AssetVisibility.Timeline))
|
||||||
.on('asset.deletedAt', 'is', null),
|
.on('asset.deletedAt', 'is', null),
|
||||||
)
|
)
|
||||||
|
.where(hasAssetPermissions(userId, [SharingPermission.AssetRead], true))
|
||||||
.select((eb) => eb.fn.count(eb.fn('distinct', ['asset.id'])).as('count'))
|
.select((eb) => eb.fn.count(eb.fn('distinct', ['asset.id'])).as('count'))
|
||||||
.where('asset_face.deletedAt', 'is', null)
|
.where('asset_face.deletedAt', 'is', null)
|
||||||
.where('asset_face.isVisible', 'is', true)
|
.where('asset_face.isVisible', 'is', true)
|
||||||
.where('asset_face.personId', '=', personId)
|
.where('asset_face.faceClusterId', '=', (eb) =>
|
||||||
|
eb.selectFrom('person').select('person.faceClusterId').where('person.id', '=', personId),
|
||||||
|
)
|
||||||
.executeTakeFirst();
|
.executeTakeFirst();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -364,7 +423,7 @@ export class PersonRepository {
|
|||||||
eb.exists((eb) =>
|
eb.exists((eb) =>
|
||||||
eb
|
eb
|
||||||
.selectFrom('asset_face')
|
.selectFrom('asset_face')
|
||||||
.whereRef('asset_face.personId', '=', 'person.id')
|
.whereRef('asset_face.faceClusterId', '=', 'person.faceClusterId')
|
||||||
.where('asset_face.deletedAt', 'is', null)
|
.where('asset_face.deletedAt', 'is', null)
|
||||||
.where('asset_face.isVisible', '=', true)
|
.where('asset_face.isVisible', '=', true)
|
||||||
.where((eb) =>
|
.where((eb) =>
|
||||||
@@ -378,13 +437,20 @@ export class PersonRepository {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.where('person.ownerId', '=', userId)
|
.where((eb) =>
|
||||||
|
eb.or([eb('person.ownerId', '=', userId), hasPermissions(userId, [SharingPermission.PersonRead])(eb)]),
|
||||||
|
)
|
||||||
.select((eb) => eb.fn.coalesce(eb.fn.countAll<number>(), zero).as('total'))
|
.select((eb) => eb.fn.coalesce(eb.fn.countAll<number>(), zero).as('total'))
|
||||||
.select((eb) => eb.fn.coalesce(eb.fn.countAll<number>().filterWhere('isHidden', '=', true), zero).as('hidden'))
|
.select((eb) => eb.fn.coalesce(eb.fn.countAll<number>().filterWhere('isHidden', '=', true), zero).as('hidden'))
|
||||||
.executeTakeFirstOrThrow();
|
.executeTakeFirstOrThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
create(person: Insertable<PersonTable>) {
|
async create(person: Insertable<PersonTable>) {
|
||||||
|
if (!person.faceClusterId) {
|
||||||
|
const { id } = await this.db.insertInto('face_cluster').defaultValues().returning('id').executeTakeFirstOrThrow();
|
||||||
|
person.faceClusterId = id;
|
||||||
|
}
|
||||||
|
|
||||||
return this.db.insertInto('person').values(person).returningAll().executeTakeFirstOrThrow();
|
return this.db.insertInto('person').values(person).returningAll().executeTakeFirstOrThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -475,8 +541,9 @@ export class PersonRepository {
|
|||||||
.selectFrom('asset_face')
|
.selectFrom('asset_face')
|
||||||
.selectAll('asset_face')
|
.selectAll('asset_face')
|
||||||
.select(withPerson)
|
.select(withPerson)
|
||||||
|
.innerJoin('person', (join) => join.onRef('person.faceClusterId', '=', 'asset_face.faceClusterId'))
|
||||||
|
.where('person.id', 'in', personIds)
|
||||||
.where('asset_face.assetId', 'in', assetIds)
|
.where('asset_face.assetId', 'in', assetIds)
|
||||||
.where('asset_face.personId', 'in', personIds)
|
|
||||||
.where('asset_face.deletedAt', 'is', null)
|
.where('asset_face.deletedAt', 'is', null)
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
@@ -486,7 +553,12 @@ export class PersonRepository {
|
|||||||
return this.db
|
return this.db
|
||||||
.selectFrom('asset_face')
|
.selectFrom('asset_face')
|
||||||
.selectAll('asset_face')
|
.selectAll('asset_face')
|
||||||
.where('asset_face.personId', '=', personId)
|
.innerJoin('person', (join) =>
|
||||||
|
join.onRef('asset_face.faceClusterId', '=', 'person.faceClusterId').on('person.id', '=', personId),
|
||||||
|
)
|
||||||
|
.where('asset_face.assetId', 'in', (eb) =>
|
||||||
|
eb.selectFrom('asset').select('asset.id').whereRef('asset.ownerId', '=', 'person.ownerId'),
|
||||||
|
)
|
||||||
.where('asset_face.deletedAt', 'is', null)
|
.where('asset_face.deletedAt', 'is', null)
|
||||||
.where('asset_face.isVisible', 'is', true)
|
.where('asset_face.isVisible', 'is', true)
|
||||||
.executeTakeFirst();
|
.executeTakeFirst();
|
||||||
@@ -573,8 +645,14 @@ export class PersonRepository {
|
|||||||
.selectFrom('asset_face')
|
.selectFrom('asset_face')
|
||||||
.select('asset_face.id')
|
.select('asset_face.id')
|
||||||
.where('asset_face.assetId', '=', assetId)
|
.where('asset_face.assetId', '=', assetId)
|
||||||
.where('asset_face.personId', '=', personId)
|
.innerJoin('person', (join) =>
|
||||||
|
join.onRef('person.faceClusterId', '=', 'asset_face.faceClusterId').on('person.id', '=', personId),
|
||||||
|
)
|
||||||
.innerJoin('asset', (join) => join.onRef('asset.id', '=', 'asset_face.assetId').on('asset.isOffline', '=', false))
|
.innerJoin('asset', (join) => join.onRef('asset.id', '=', 'asset_face.assetId').on('asset.isOffline', '=', false))
|
||||||
.executeTakeFirst();
|
.executeTakeFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getByFaceClusterId(faceClusterId: string) {
|
||||||
|
return this.db.selectFrom('person').selectAll().where('person.faceClusterId', '=', faceClusterId).execute();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -325,15 +325,23 @@ export class SearchRepository {
|
|||||||
.selectFrom('asset_face')
|
.selectFrom('asset_face')
|
||||||
.select([
|
.select([
|
||||||
'asset_face.id',
|
'asset_face.id',
|
||||||
'asset_face.personId',
|
'asset_face.faceClusterId',
|
||||||
sql<number>`face_search.embedding <=> ${embedding}`.as('distance'),
|
sql<number>`face_search.embedding <=> ${embedding}`.as('distance'),
|
||||||
])
|
])
|
||||||
.innerJoin('asset', 'asset.id', 'asset_face.assetId')
|
.innerJoin('asset', 'asset.id', 'asset_face.assetId')
|
||||||
|
.select('asset.ownerId')
|
||||||
.innerJoin('face_search', 'face_search.faceId', 'asset_face.id')
|
.innerJoin('face_search', 'face_search.faceId', 'asset_face.id')
|
||||||
.leftJoin('person', 'person.id', 'asset_face.personId')
|
.leftJoin('person', 'person.faceClusterId', 'asset_face.faceClusterId')
|
||||||
.where('asset.ownerId', '=', anyUuid(userIds))
|
.where('asset.ownerId', 'in', (eb) =>
|
||||||
|
eb
|
||||||
|
.selectFrom('user')
|
||||||
|
.select('user.id')
|
||||||
|
.where('user.trustedGroupId', 'in', (eb) =>
|
||||||
|
eb.selectFrom('user').select('user.trustedGroupId').where('user.id', '=', anyUuid(userIds)),
|
||||||
|
),
|
||||||
|
)
|
||||||
.where('asset.deletedAt', 'is', null)
|
.where('asset.deletedAt', 'is', null)
|
||||||
.$if(!!hasPerson, (qb) => qb.where('asset_face.personId', 'is not', null))
|
.$if(!!hasPerson, (qb) => qb.where('asset_face.faceClusterId', 'is not', null))
|
||||||
.$if(!!minBirthDate, (qb) =>
|
.$if(!!minBirthDate, (qb) =>
|
||||||
qb.where((eb) =>
|
qb.where((eb) =>
|
||||||
eb.or([eb('person.birthDate', 'is', null), eb('person.birthDate', '<=', minBirthDate!)]),
|
eb.or([eb('person.birthDate', 'is', null), eb('person.birthDate', '<=', minBirthDate!)]),
|
||||||
|
|||||||
@@ -443,7 +443,7 @@ class AssetFaceSync extends BaseSync {
|
|||||||
.select([
|
.select([
|
||||||
'asset_face.id',
|
'asset_face.id',
|
||||||
'assetId',
|
'assetId',
|
||||||
'personId',
|
'faceClusterId',
|
||||||
'imageWidth',
|
'imageWidth',
|
||||||
'imageHeight',
|
'imageHeight',
|
||||||
'boundingBoxX1',
|
'boundingBoxX1',
|
||||||
|
|||||||
@@ -325,4 +325,61 @@ export class UserRepository {
|
|||||||
|
|
||||||
await query.execute();
|
await query.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID] })
|
||||||
|
async getInSameTrustedGroup(userId: string) {
|
||||||
|
return this.db
|
||||||
|
.selectFrom('user')
|
||||||
|
.select('user.id')
|
||||||
|
.where('user.trustedGroupId', '=', (eb) =>
|
||||||
|
eb.selectFrom('user').select('user.trustedGroupId').where('user.id', '=', userId),
|
||||||
|
)
|
||||||
|
.execute()
|
||||||
|
.then((result) => result.map(({ id }) => id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [{ userId: DummyValue.UUID, userIdToMerge: DummyValue.UUID }] })
|
||||||
|
async mergeTrustedGroups({ userId, userIdToMerge }: { userId: string; userIdToMerge: string }) {
|
||||||
|
return this.db
|
||||||
|
.updateTable('user')
|
||||||
|
.from('user as u')
|
||||||
|
.where('u.id', '=', userId)
|
||||||
|
.where('user.trustedGroupId', '=', (eb) =>
|
||||||
|
eb
|
||||||
|
.selectFrom('user')
|
||||||
|
.select('user.trustedGroupId')
|
||||||
|
.where('user.id', '=', userIdToMerge)
|
||||||
|
.whereRef('user.trustedGroupId', '!=', 'u.trustedGroupId'),
|
||||||
|
)
|
||||||
|
.set((eb) => ({
|
||||||
|
trustedGroupId: eb.ref('u.trustedGroupId'),
|
||||||
|
}))
|
||||||
|
.executeTakeFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID] })
|
||||||
|
async updateTrustedGroups(userId: string) {
|
||||||
|
return this.db
|
||||||
|
.updateTable('user')
|
||||||
|
.set((eb) => ({ trustedGroupId: eb.fn('uuid_generate_v4') }))
|
||||||
|
.where('user.trustedGroupId', '=', (eb) =>
|
||||||
|
eb.selectFrom('user').select('user.trustedGroupId').where('user.id', '=', userId),
|
||||||
|
)
|
||||||
|
.where('user.id', '!=', userId)
|
||||||
|
.where('user.id', 'not in', (eb) =>
|
||||||
|
eb
|
||||||
|
.selectFrom('partner')
|
||||||
|
.select('partner.sharedById as userId')
|
||||||
|
.where('sharedWithId', '=', userId)
|
||||||
|
.union((eb) =>
|
||||||
|
eb
|
||||||
|
.selectFrom('album_user')
|
||||||
|
.select('album_user.userId')
|
||||||
|
.where('album_user.albumId', 'in', (eb) =>
|
||||||
|
eb.selectFrom('album_user').select('album_user.albumId').where('album_user.userId', '=', userId),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.executeTakeFirst();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
AssetStatus,
|
AssetStatus,
|
||||||
AssetVisibility,
|
AssetVisibility,
|
||||||
ChecksumAlgorithm,
|
ChecksumAlgorithm,
|
||||||
|
SharingPermission,
|
||||||
SourceType,
|
SourceType,
|
||||||
VideoSegmentCodec,
|
VideoSegmentCodec,
|
||||||
} from 'src/enum';
|
} from 'src/enum';
|
||||||
@@ -37,3 +38,8 @@ export const video_stream_variant_codec_enum = registerEnum({
|
|||||||
name: 'video_stream_variant_codec_enum',
|
name: 'video_stream_variant_codec_enum',
|
||||||
values: Object.values(VideoSegmentCodec),
|
values: Object.values(VideoSegmentCodec),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const sharing_permission_enum = registerEnum({
|
||||||
|
name: 'sharing_permission_enum',
|
||||||
|
values: Object.values(SharingPermission),
|
||||||
|
});
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
asset_face_source_type,
|
asset_face_source_type,
|
||||||
asset_visibility_enum,
|
asset_visibility_enum,
|
||||||
assets_status_enum,
|
assets_status_enum,
|
||||||
|
sharing_permission_enum,
|
||||||
} from 'src/schema/enums';
|
} from 'src/schema/enums';
|
||||||
import {
|
import {
|
||||||
album_user_after_insert,
|
album_user_after_insert,
|
||||||
@@ -45,6 +46,7 @@ import { AssetMetadataAuditTable } from 'src/schema/tables/asset-metadata-audit.
|
|||||||
import { AssetMetadataTable } from 'src/schema/tables/asset-metadata.table';
|
import { AssetMetadataTable } from 'src/schema/tables/asset-metadata.table';
|
||||||
import { AssetOcrTable } from 'src/schema/tables/asset-ocr.table';
|
import { AssetOcrTable } from 'src/schema/tables/asset-ocr.table';
|
||||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||||
|
import { FaceClusterTable } from 'src/schema/tables/face-cluster.table';
|
||||||
import { FaceSearchTable } from 'src/schema/tables/face-search.table';
|
import { FaceSearchTable } from 'src/schema/tables/face-search.table';
|
||||||
import { GeodataPlacesTable } from 'src/schema/tables/geodata-places.table';
|
import { GeodataPlacesTable } from 'src/schema/tables/geodata-places.table';
|
||||||
import { LibraryTable } from 'src/schema/tables/library.table';
|
import { LibraryTable } from 'src/schema/tables/library.table';
|
||||||
@@ -110,6 +112,7 @@ export class ImmichDatabase {
|
|||||||
AssetTable,
|
AssetTable,
|
||||||
AssetFileTable,
|
AssetFileTable,
|
||||||
AssetExifTable,
|
AssetExifTable,
|
||||||
|
FaceClusterTable,
|
||||||
FaceSearchTable,
|
FaceSearchTable,
|
||||||
GeodataPlacesTable,
|
GeodataPlacesTable,
|
||||||
LibraryTable,
|
LibraryTable,
|
||||||
@@ -170,7 +173,13 @@ export class ImmichDatabase {
|
|||||||
asset_face_audit,
|
asset_face_audit,
|
||||||
];
|
];
|
||||||
|
|
||||||
enum = [album_user_role_enum, assets_status_enum, asset_face_source_type, asset_visibility_enum];
|
enum = [
|
||||||
|
album_user_role_enum,
|
||||||
|
assets_status_enum,
|
||||||
|
asset_face_source_type,
|
||||||
|
asset_visibility_enum,
|
||||||
|
sharing_permission_enum,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Migrations {
|
export interface Migrations {
|
||||||
@@ -211,6 +220,7 @@ export interface DB {
|
|||||||
ocr_search: OcrSearchTable;
|
ocr_search: OcrSearchTable;
|
||||||
|
|
||||||
face_search: FaceSearchTable;
|
face_search: FaceSearchTable;
|
||||||
|
face_cluster: FaceClusterTable;
|
||||||
|
|
||||||
geodata_places: GeodataPlacesTable;
|
geodata_places: GeodataPlacesTable;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { Kysely, sql } from 'kysely';
|
||||||
|
|
||||||
|
export async function up(db: Kysely<any>): Promise<void> {
|
||||||
|
await sql`CREATE TYPE "sharing_permission_enum" AS ENUM ('all','asset.read','asset.update','asset.edit','asset.delete','asset.share','exif.read','person.read','person.update','person.merge','person.delete');`.execute(db);
|
||||||
|
await sql`ALTER TABLE "user" ADD "trustedGroupId" uuid NOT NULL DEFAULT uuid_generate_v4();`.execute(db);
|
||||||
|
await sql`ALTER TABLE "album_user" ADD "permissions" sharing_permission_enum[] NOT NULL DEFAULT '{asset.read,exif.read}';`.execute(db);
|
||||||
|
await sql`ALTER TABLE "album_user" ADD "inTimeline" boolean NOT NULL DEFAULT false;`.execute(db);
|
||||||
|
await sql`ALTER TABLE "partner" ADD "permissions" sharing_permission_enum[] NOT NULL DEFAULT '{all}';`.execute(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(db: Kysely<any>): Promise<void> {
|
||||||
|
await sql`DROP TYPE "sharing_permission_enum";`.execute(db);
|
||||||
|
await sql`ALTER TABLE "partner" DROP COLUMN "permissions";`.execute(db);
|
||||||
|
await sql`ALTER TABLE "user" DROP COLUMN "trustedGroupId";`.execute(db);
|
||||||
|
await sql`ALTER TABLE "album_user" DROP COLUMN "permissions";`.execute(db);
|
||||||
|
await sql`ALTER TABLE "album_user" DROP COLUMN "inTimeline";`.execute(db);
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { Kysely, sql } from 'kysely';
|
||||||
|
|
||||||
|
export async function up(db: Kysely<any>): Promise<void> {
|
||||||
|
await sql`ALTER TABLE "asset_face" RENAME COLUMN "personId" TO "faceClusterId";`.execute(db);
|
||||||
|
await sql`CREATE INDEX "asset_face_faceClusterId_assetId_idx" ON "asset_face" ("faceClusterId", "assetId");`.execute(db);
|
||||||
|
await sql`CREATE INDEX "asset_face_faceClusterId_assetId_notDeleted_isVisible_idx" ON "asset_face" ("faceClusterId", "assetId") WHERE ("deletedAt" IS NULL AND "isVisible" IS TRUE);`.execute(db);
|
||||||
|
await sql`CREATE INDEX "asset_face_assetId_faceClusterId_idx" ON "asset_face" ("assetId", "faceClusterId");`.execute(db);
|
||||||
|
await sql`DROP INDEX "asset_face_personId_assetId_notDeleted_isVisible_idx";`.execute(db);
|
||||||
|
await sql`DROP INDEX "asset_face_assetId_personId_idx";`.execute(db);
|
||||||
|
await sql`DROP INDEX "asset_face_personId_assetId_idx";`.execute(db);
|
||||||
|
await sql`CREATE TABLE "face_cluster" (
|
||||||
|
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
|
||||||
|
"createdAt" timestamp with time zone NOT NULL DEFAULT now(),
|
||||||
|
"updatedAt" timestamp with time zone NOT NULL DEFAULT now(),
|
||||||
|
"updateId" uuid NOT NULL DEFAULT immich_uuid_v7(),
|
||||||
|
CONSTRAINT "face_cluster_pkey" PRIMARY KEY ("id")
|
||||||
|
);`.execute(db);
|
||||||
|
await sql`ALTER TABLE "asset_face" ADD CONSTRAINT "asset_face_faceClusterId_fkey" FOREIGN KEY ("faceClusterId") REFERENCES "face_cluster" ("id") ON UPDATE CASCADE ON DELETE SET NULL;`.execute(db);
|
||||||
|
await sql`ALTER TABLE "asset_face" DROP CONSTRAINT "asset_face_personId_fkey";`.execute(db);
|
||||||
|
await sql`ALTER TABLE "person" ADD "faceClusterId" uuid;`.execute(db);
|
||||||
|
await sql`CREATE INDEX "person_faceClusterId_idx" ON "person" ("faceClusterId");`.execute(db);
|
||||||
|
await sql`ALTER TABLE "person" ADD CONSTRAINT "person_faceClusterId_fkey" FOREIGN KEY ("faceClusterId") REFERENCES "face_cluster" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
||||||
|
await sql`CREATE INDEX "face_cluster_updateId_idx" ON "face_cluster" ("updateId");`.execute(db);
|
||||||
|
await sql`CREATE OR REPLACE TRIGGER "face_cluster_updatedAt"
|
||||||
|
BEFORE UPDATE ON "face_cluster"
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION updated_at();`.execute(db);
|
||||||
|
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_face_cluster_updatedAt', '{"type":"trigger","name":"face_cluster_updatedAt","sql":"CREATE OR REPLACE TRIGGER \\"face_cluster_updatedAt\\"\\n BEFORE UPDATE ON \\"face_cluster\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db);
|
||||||
|
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('index_asset_face_faceClusterId_assetId_notDeleted_isVisible_idx', '{"type":"index","name":"asset_face_faceClusterId_assetId_notDeleted_isVisible_idx","sql":"CREATE INDEX \\"asset_face_faceClusterId_assetId_notDeleted_isVisible_idx\\" ON \\"asset_face\\" (\\"faceClusterId\\", \\"assetId\\") WHERE (\\"deletedAt\\" IS NULL AND \\"isVisible\\" IS TRUE);"}'::jsonb);`.execute(db);
|
||||||
|
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'index_asset_face_personId_assetId_notDeleted_isVisible_idx';`.execute(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(db: Kysely<any>): Promise<void> {
|
||||||
|
await sql`ALTER TABLE "person" DROP COLUMN "faceClusterId";`.execute(db);
|
||||||
|
await sql`DROP INDEX "person_faceClusterId_idx";`.execute(db);
|
||||||
|
await sql`ALTER TABLE "person" DROP CONSTRAINT "person_faceClusterId_fkey";`.execute(db);
|
||||||
|
await sql`ALTER TABLE "asset_face" RENAME COLUMN "faceClusterId" TO "personId";`.execute(db);
|
||||||
|
await sql`CREATE INDEX "asset_face_personId_assetId_notDeleted_isVisible_idx" ON "asset_face" ("personId", "assetId") WHERE ((("deletedAt" IS NULL) AND ("isVisible" IS TRUE)));`.execute(db);
|
||||||
|
await sql`CREATE INDEX "asset_face_assetId_personId_idx" ON "asset_face" ("assetId", "personId");`.execute(db);
|
||||||
|
await sql`CREATE INDEX "asset_face_personId_assetId_idx" ON "asset_face" ("personId", "assetId");`.execute(db);
|
||||||
|
await sql`DROP INDEX "asset_face_faceClusterId_assetId_idx";`.execute(db);
|
||||||
|
await sql`DROP INDEX "asset_face_faceClusterId_assetId_notDeleted_isVisible_idx";`.execute(db);
|
||||||
|
await sql`DROP INDEX "asset_face_assetId_faceClusterId_idx";`.execute(db);
|
||||||
|
await sql`ALTER TABLE "asset_face" ADD CONSTRAINT "asset_face_personId_fkey" FOREIGN KEY ("personId") REFERENCES "person" ("id") ON UPDATE CASCADE ON DELETE SET NULL;`.execute(db);
|
||||||
|
await sql`ALTER TABLE "asset_face" DROP CONSTRAINT "asset_face_faceClusterId_fkey";`.execute(db);
|
||||||
|
await sql`DROP TABLE "face_cluster";`.execute(db);
|
||||||
|
await sql`DROP TRIGGER "face_cluster_updatedAt" ON "face_cluster";`.execute(db);
|
||||||
|
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('index_asset_face_personId_assetId_notDeleted_isVisible_idx', '{"sql":"CREATE INDEX \\"asset_face_personId_assetId_notDeleted_isVisible_idx\\" ON \\"asset_face\\" (\\"personId\\", \\"assetId\\") WHERE (\\"deletedAt\\" IS NULL AND \\"isVisible\\" IS TRUE);","name":"asset_face_personId_assetId_notDeleted_isVisible_idx","type":"index"}'::jsonb);`.execute(db);
|
||||||
|
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_face_cluster_updatedAt';`.execute(db);
|
||||||
|
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'index_asset_face_faceClusterId_assetId_notDeleted_isVisible_idx';`.execute(db);
|
||||||
|
}
|
||||||
@@ -11,8 +11,8 @@ import {
|
|||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
} from '@immich/sql-tools';
|
} from '@immich/sql-tools';
|
||||||
import { CreateIdColumn, UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators';
|
import { CreateIdColumn, UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators';
|
||||||
import { AlbumUserRole } from 'src/enum';
|
import { AlbumUserRole, SharingPermission } from 'src/enum';
|
||||||
import { album_user_role_enum } from 'src/schema/enums';
|
import { album_user_role_enum, sharing_permission_enum } from 'src/schema/enums';
|
||||||
import { album_user_after_insert, album_user_delete_audit } from 'src/schema/functions';
|
import { album_user_after_insert, album_user_delete_audit } from 'src/schema/functions';
|
||||||
import { AlbumTable } from 'src/schema/tables/album.table';
|
import { AlbumTable } from 'src/schema/tables/album.table';
|
||||||
import { UserTable } from 'src/schema/tables/user.table';
|
import { UserTable } from 'src/schema/tables/user.table';
|
||||||
@@ -69,4 +69,14 @@ export class AlbumUserTable {
|
|||||||
|
|
||||||
@UpdateDateColumn()
|
@UpdateDateColumn()
|
||||||
updatedAt!: Generated<Timestamp>;
|
updatedAt!: Generated<Timestamp>;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
array: true,
|
||||||
|
enum: sharing_permission_enum,
|
||||||
|
default: [SharingPermission.AssetRead, SharingPermission.ExifRead],
|
||||||
|
})
|
||||||
|
permissions!: Generated<SharingPermission[]>;
|
||||||
|
|
||||||
|
@Column({ type: 'boolean', default: false })
|
||||||
|
inTimeline!: Generated<boolean>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { SourceType } from 'src/enum';
|
|||||||
import { asset_face_source_type } from 'src/schema/enums';
|
import { asset_face_source_type } from 'src/schema/enums';
|
||||||
import { asset_face_audit } from 'src/schema/functions';
|
import { asset_face_audit } from 'src/schema/functions';
|
||||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||||
import { PersonTable } from 'src/schema/tables/person.table';
|
import { FaceClusterTable } from 'src/schema/tables/face-cluster.table';
|
||||||
|
|
||||||
@Table({ name: 'asset_face' })
|
@Table({ name: 'asset_face' })
|
||||||
@UpdatedAtTrigger('asset_face_updatedAt')
|
@UpdatedAtTrigger('asset_face_updatedAt')
|
||||||
@@ -26,13 +26,13 @@ import { PersonTable } from 'src/schema/tables/person.table';
|
|||||||
when: 'pg_trigger_depth() = 0',
|
when: 'pg_trigger_depth() = 0',
|
||||||
})
|
})
|
||||||
// schemaFromDatabase does not preserve column order
|
// schemaFromDatabase does not preserve column order
|
||||||
@Index({ name: 'asset_face_assetId_personId_idx', columns: ['assetId', 'personId'] })
|
@Index({ name: 'asset_face_assetId_faceClusterId_idx', columns: ['assetId', 'faceClusterId'] })
|
||||||
@Index({
|
@Index({
|
||||||
name: 'asset_face_personId_assetId_notDeleted_isVisible_idx',
|
name: 'asset_face_faceClusterId_assetId_notDeleted_isVisible_idx',
|
||||||
columns: ['personId', 'assetId'],
|
columns: ['faceClusterId', 'assetId'],
|
||||||
where: '"deletedAt" IS NULL AND "isVisible" IS TRUE',
|
where: '"deletedAt" IS NULL AND "isVisible" IS TRUE',
|
||||||
})
|
})
|
||||||
@Index({ columns: ['personId', 'assetId'] })
|
@Index({ columns: ['faceClusterId', 'assetId'] })
|
||||||
export class AssetFaceTable {
|
export class AssetFaceTable {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
id!: Generated<string>;
|
id!: Generated<string>;
|
||||||
@@ -45,14 +45,14 @@ export class AssetFaceTable {
|
|||||||
})
|
})
|
||||||
assetId!: string;
|
assetId!: string;
|
||||||
|
|
||||||
@ForeignKeyColumn(() => PersonTable, {
|
@ForeignKeyColumn(() => FaceClusterTable, {
|
||||||
onDelete: 'SET NULL',
|
onDelete: 'SET NULL',
|
||||||
onUpdate: 'CASCADE',
|
onUpdate: 'CASCADE',
|
||||||
nullable: true,
|
nullable: true,
|
||||||
// [personId, assetId] makes this redundant
|
// [faceClusterId, assetId] makes this redundant
|
||||||
index: false,
|
index: false,
|
||||||
})
|
})
|
||||||
personId!: string | null;
|
faceClusterId!: string | null;
|
||||||
|
|
||||||
@Column({ default: 0, type: 'integer' })
|
@Column({ default: 0, type: 'integer' })
|
||||||
imageWidth!: Generated<number>;
|
imageWidth!: Generated<number>;
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import {
|
||||||
|
CreateDateColumn,
|
||||||
|
Generated,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Table,
|
||||||
|
Timestamp,
|
||||||
|
UpdateDateColumn,
|
||||||
|
} from '@immich/sql-tools';
|
||||||
|
import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators';
|
||||||
|
|
||||||
|
@Table('face_cluster')
|
||||||
|
@UpdatedAtTrigger('face_cluster_updatedAt')
|
||||||
|
export class FaceClusterTable {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id!: Generated<string>;
|
||||||
|
|
||||||
|
@CreateDateColumn()
|
||||||
|
createdAt!: Generated<Timestamp>;
|
||||||
|
|
||||||
|
@UpdateDateColumn()
|
||||||
|
updatedAt!: Generated<Timestamp>;
|
||||||
|
|
||||||
|
@UpdateIdColumn({ index: true })
|
||||||
|
updateId!: Generated<string>;
|
||||||
|
}
|
||||||
@@ -9,6 +9,8 @@ import {
|
|||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
} from '@immich/sql-tools';
|
} from '@immich/sql-tools';
|
||||||
import { CreateIdColumn, UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators';
|
import { CreateIdColumn, UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators';
|
||||||
|
import { SharingPermission } from 'src/enum';
|
||||||
|
import { sharing_permission_enum } from 'src/schema/enums';
|
||||||
import { partner_delete_audit } from 'src/schema/functions';
|
import { partner_delete_audit } from 'src/schema/functions';
|
||||||
import { UserTable } from 'src/schema/tables/user.table';
|
import { UserTable } from 'src/schema/tables/user.table';
|
||||||
|
|
||||||
@@ -46,4 +48,7 @@ export class PartnerTable {
|
|||||||
|
|
||||||
@UpdateIdColumn({ index: true })
|
@UpdateIdColumn({ index: true })
|
||||||
updateId!: Generated<string>;
|
updateId!: Generated<string>;
|
||||||
|
|
||||||
|
@Column({ array: true, enum: sharing_permission_enum, default: [SharingPermission.All] })
|
||||||
|
permissions!: Generated<SharingPermission[]>;
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user