mirror of
https://github.com/immich-app/immich.git
synced 2026-05-29 19:12:32 -04:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3c9becd9ea |
@@ -16,7 +16,7 @@ services:
|
|||||||
- ${UPLOAD_LOCATION:-upload-devcontainer-volume}${UPLOAD_LOCATION:+/photos}:/data
|
- ${UPLOAD_LOCATION:-upload-devcontainer-volume}${UPLOAD_LOCATION:+/photos}:/data
|
||||||
- /etc/localtime:/etc/localtime:ro
|
- /etc/localtime:/etc/localtime:ro
|
||||||
- pnpm_store_server:/buildcache/pnpm-store
|
- pnpm_store_server:/buildcache/pnpm-store
|
||||||
- ../packages/plugin-core:/build/plugins/immich-plugin-core
|
- ../packages/plugins:/build/corePlugin
|
||||||
immich-web:
|
immich-web:
|
||||||
env_file: !reset []
|
env_file: !reset []
|
||||||
immich-machine-learning:
|
immich-machine-learning:
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ log "Preparing Immich Web Frontend"
|
|||||||
log ""
|
log ""
|
||||||
run_cmd pnpm --filter @immich/sdk install
|
run_cmd pnpm --filter @immich/sdk install
|
||||||
run_cmd pnpm --filter @immich/sdk build
|
run_cmd pnpm --filter @immich/sdk build
|
||||||
run_cmd pnpm --filter @immich/plugin-sdk install
|
|
||||||
run_cmd pnpm --filter @immich/plugin-sdk build
|
|
||||||
run_cmd pnpm --filter immich-web install
|
run_cmd pnpm --filter immich-web install
|
||||||
|
|
||||||
log "Starting Immich Web Frontend"
|
log "Starting Immich Web Frontend"
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ jobs:
|
|||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- name: Setup Mise
|
||||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
@@ -159,14 +159,14 @@ jobs:
|
|||||||
|
|
||||||
- name: Comment APK download link on PR
|
- name: Comment APK download link on PR
|
||||||
if: ${{ github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork }}
|
if: ${{ github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork }}
|
||||||
uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0
|
uses: mshick/add-pr-comment@8e4927817251f1ff60c001f04568532b38e0b4a0 # v3.11.0
|
||||||
env:
|
env:
|
||||||
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||||
APK_URL: ${{ steps.upload-apk.outputs.artifact-url }}
|
APK_URL: ${{ steps.upload-apk.outputs.artifact-url }}
|
||||||
with:
|
with:
|
||||||
id: mobile-android-apk
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
token: ${{ steps.token.outputs.token }}
|
message-id: 'mobile-android-apk'
|
||||||
body: |
|
message: |
|
||||||
📱 **Android release APK (universal)** — `${{ env.HEAD_SHA }}`
|
📱 **Android release APK (universal)** — `${{ env.HEAD_SHA }}`
|
||||||
|
|
||||||
Download: ${{ env.APK_URL }}
|
Download: ${{ env.APK_URL }}
|
||||||
@@ -216,7 +216,7 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Setup Mise
|
- name: Setup Mise
|
||||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
@@ -230,12 +230,8 @@ jobs:
|
|||||||
- name: Generate platform APIs
|
- name: Generate platform APIs
|
||||||
run: mise //mobile:codegen:pigeon
|
run: mise //mobile:codegen:pigeon
|
||||||
|
|
||||||
- name: Resolve iOS Swift Packages
|
|
||||||
working-directory: ./mobile
|
|
||||||
run: flutter build ios --config-only --no-codesign
|
|
||||||
|
|
||||||
- name: Setup Ruby
|
- name: Setup Ruby
|
||||||
uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0
|
uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0
|
||||||
with:
|
with:
|
||||||
ruby-version: '3.3'
|
ruby-version: '3.3'
|
||||||
bundler-cache: true
|
bundler-cache: true
|
||||||
@@ -292,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
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Check for breaking API changes
|
- name: Check for breaking API changes
|
||||||
uses: oasdiff/oasdiff-action/breaking@6147a58e5d1249a12f42fc864ab791d571a30015 # v0.0.47
|
uses: oasdiff/oasdiff-action/breaking@26ccb332c67a45ca649de9faf60552ef1b8260d9 # v0.0.46
|
||||||
with:
|
with:
|
||||||
base: https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json
|
base: https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json
|
||||||
revision: open-api/immich-openapi-specs.json
|
revision: open-api/immich-openapi-specs.json
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ jobs:
|
|||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- name: Setup Mise
|
||||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ jobs:
|
|||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
|
uses: github/codeql-action/init@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
@@ -70,7 +70,7 @@ jobs:
|
|||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
|
uses: github/codeql-action/autobuild@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||||
@@ -83,6 +83,6 @@ jobs:
|
|||||||
# ./location_of_script_within_repo/buildscript.sh
|
# ./location_of_script_within_repo/buildscript.sh
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
|
uses: github/codeql-action/analyze@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
|
||||||
with:
|
with:
|
||||||
category: '/language:${{matrix.language}}'
|
category: '/language:${{matrix.language}}'
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ jobs:
|
|||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- name: Setup Mise
|
||||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ jobs:
|
|||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- name: Setup Mise
|
||||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
@@ -213,11 +213,12 @@ jobs:
|
|||||||
run: 'mise run //deployment:tf apply'
|
run: 'mise run //deployment:tf apply'
|
||||||
|
|
||||||
- name: Comment
|
- name: Comment
|
||||||
uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0
|
uses: actions-cool/maintain-one-comment@909842216bc8e8658364c572ec52100f4c2cc50a # v3.3.0
|
||||||
if: ${{ steps.parameters.outputs.event == 'pr' }}
|
if: ${{ steps.parameters.outputs.event == 'pr' }}
|
||||||
with:
|
with:
|
||||||
id: docs-pr-url
|
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
number: ${{ fromJson(needs.checks.outputs.parameters).pr_number }}
|
number: ${{ fromJson(needs.checks.outputs.parameters).pr_number }}
|
||||||
body: |
|
body: |
|
||||||
📖 Documentation deployed to [${{ steps.docs-output.outputs.subdomain }}](https://${{ steps.docs-output.outputs.subdomain }})
|
📖 Documentation deployed to [${{ steps.docs-output.outputs.subdomain }}](https://${{ steps.docs-output.outputs.subdomain }})
|
||||||
|
emojis: 'rocket'
|
||||||
|
body-include: '<!-- Docs PR URL -->'
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ jobs:
|
|||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- name: Setup Mise
|
||||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
@@ -44,8 +44,9 @@ jobs:
|
|||||||
run: 'mise run //deployment:tf destroy -- -refresh=false'
|
run: 'mise run //deployment:tf destroy -- -refresh=false'
|
||||||
|
|
||||||
- name: Comment
|
- name: Comment
|
||||||
uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0
|
uses: actions-cool/maintain-one-comment@909842216bc8e8658364c572ec52100f4c2cc50a # v3.3.0
|
||||||
with:
|
with:
|
||||||
id: docs-pr-url
|
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
number: ${{ github.event.number }}
|
||||||
delete: true
|
delete: true
|
||||||
|
body-include: '<!-- Docs PR URL -->'
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ jobs:
|
|||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- name: Setup Mise
|
||||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ jobs:
|
|||||||
- name: Generate a token
|
- name: Generate a token
|
||||||
id: generate_token
|
id: generate_token
|
||||||
if: ${{ inputs.skip != true }}
|
if: ${{ inputs.skip != true }}
|
||||||
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|||||||
@@ -13,4 +13,3 @@ jobs:
|
|||||||
actions: read
|
actions: read
|
||||||
contents: read
|
contents: read
|
||||||
security-events: write
|
security-events: write
|
||||||
secrets: inherit
|
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ jobs:
|
|||||||
ref: main
|
ref: main
|
||||||
|
|
||||||
- name: Setup Mise
|
- name: Setup Mise
|
||||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
@@ -119,7 +119,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Generate a token
|
- name: Generate a token
|
||||||
id: generate-token
|
id: generate-token
|
||||||
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|||||||
@@ -19,11 +19,11 @@ jobs:
|
|||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0
|
- uses: mshick/add-pr-comment@8e4927817251f1ff60c001f04568532b38e0b4a0 # v3.11.0
|
||||||
with:
|
with:
|
||||||
id: preview-status
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
token: ${{ steps.token.outputs.token }}
|
message-id: 'preview-status'
|
||||||
body: 'Deploying preview environment to https://pr-${{ github.event.pull_request.number }}.preview.internal.immich.build/'
|
message: 'Deploying preview environment to https://pr-${{ github.event.pull_request.number }}.preview.internal.immich.build/'
|
||||||
|
|
||||||
remove-label:
|
remove-label:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -48,16 +48,16 @@ jobs:
|
|||||||
name: 'preview'
|
name: 'preview'
|
||||||
})
|
})
|
||||||
|
|
||||||
- uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0
|
- uses: mshick/add-pr-comment@8e4927817251f1ff60c001f04568532b38e0b4a0 # v3.11.0
|
||||||
if: ${{ github.event.pull_request.head.repo.fork }}
|
if: ${{ github.event.pull_request.head.repo.fork }}
|
||||||
with:
|
with:
|
||||||
id: preview-status
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
token: ${{ steps.token.outputs.token }}
|
message-id: 'preview-status'
|
||||||
body: 'PRs from forks cannot have preview environments.'
|
message: 'PRs from forks cannot have preview environments.'
|
||||||
|
|
||||||
- uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0
|
- uses: mshick/add-pr-comment@8e4927817251f1ff60c001f04568532b38e0b4a0 # v3.11.0
|
||||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||||
with:
|
with:
|
||||||
id: preview-status
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
token: ${{ steps.token.outputs.token }}
|
message-id: 'preview-status'
|
||||||
body: 'Preview environment has been removed.'
|
message: 'Preview environment has been removed.'
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ jobs:
|
|||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- name: Setup Mise
|
||||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ jobs:
|
|||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- name: Setup Mise
|
||||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
|||||||
+27
-30
@@ -30,32 +30,25 @@ jobs:
|
|||||||
filters: |
|
filters: |
|
||||||
i18n:
|
i18n:
|
||||||
- 'i18n/**'
|
- 'i18n/**'
|
||||||
- 'mise.toml'
|
|
||||||
web:
|
web:
|
||||||
- 'web/**'
|
- 'web/**'
|
||||||
- 'i18n/**'
|
- 'i18n/**'
|
||||||
- 'packages/sdk/**'
|
- 'packages/sdk/**'
|
||||||
- 'pnpm-lock.yaml'
|
- 'pnpm-lock.yaml'
|
||||||
- 'mise.toml'
|
|
||||||
server:
|
server:
|
||||||
- 'server/**'
|
- 'server/**'
|
||||||
- 'pnpm-lock.yaml'
|
- 'pnpm-lock.yaml'
|
||||||
- 'mise.toml'
|
|
||||||
cli:
|
cli:
|
||||||
- 'packages/cli/**'
|
- 'packages/cli/**'
|
||||||
- 'packages/sdk/**'
|
- 'packages/sdk/**'
|
||||||
- 'pnpm-lock.yaml'
|
- 'pnpm-lock.yaml'
|
||||||
- 'mise.toml'
|
|
||||||
e2e:
|
e2e:
|
||||||
- 'e2e/**'
|
- 'e2e/**'
|
||||||
- 'pnpm-lock.yaml'
|
- 'pnpm-lock.yaml'
|
||||||
- 'mise.toml'
|
|
||||||
mobile:
|
mobile:
|
||||||
- 'mobile/**'
|
- 'mobile/**'
|
||||||
- 'mise.toml'
|
|
||||||
machine-learning:
|
machine-learning:
|
||||||
- 'machine-learning/**'
|
- 'machine-learning/**'
|
||||||
- 'mise.toml'
|
|
||||||
.github:
|
.github:
|
||||||
- '.github/**'
|
- '.github/**'
|
||||||
force-filters: |
|
force-filters: |
|
||||||
@@ -69,6 +62,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: ./server
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
@@ -83,12 +79,12 @@ jobs:
|
|||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- name: Setup Mise
|
||||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Run ci-unit
|
- name: Run ci-unit
|
||||||
run: mise run //server:ci-unit
|
run: mise run ci-unit
|
||||||
|
|
||||||
cli-unit-tests:
|
cli-unit-tests:
|
||||||
name: Unit Test CLI
|
name: Unit Test CLI
|
||||||
@@ -114,7 +110,7 @@ jobs:
|
|||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- name: Setup Mise
|
||||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
@@ -145,7 +141,7 @@ jobs:
|
|||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- name: Setup Mise
|
||||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
@@ -189,7 +185,7 @@ jobs:
|
|||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- name: Setup Mise
|
||||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
@@ -227,7 +223,7 @@ jobs:
|
|||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- name: Setup Mise
|
||||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
@@ -255,7 +251,7 @@ jobs:
|
|||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- name: Setup Mise
|
||||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
@@ -305,7 +301,7 @@ jobs:
|
|||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- name: Setup Mise
|
||||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
@@ -338,7 +334,7 @@ jobs:
|
|||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- name: Setup Mise
|
||||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
@@ -384,7 +380,7 @@ jobs:
|
|||||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||||
|
|
||||||
- name: Setup packages
|
- name: Setup packages
|
||||||
run: pnpm --filter @immich/sdk --filter @immich/cli install --frozen-lockfile && pnpm --filter @immich/sdk --filter @immich/cli build
|
run: pnpm --filter "@immich/*" install --frozen-lockfile && pnpm --filter "@immich/*" build
|
||||||
|
|
||||||
- name: Run setup web
|
- name: Run setup web
|
||||||
run: pnpm install --frozen-lockfile && pnpm exec svelte-kit sync
|
run: pnpm install --frozen-lockfile && pnpm exec svelte-kit sync
|
||||||
@@ -557,7 +553,7 @@ jobs:
|
|||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- name: Setup Mise
|
||||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
@@ -594,7 +590,7 @@ jobs:
|
|||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- name: Setup Mise
|
||||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
@@ -625,7 +621,7 @@ jobs:
|
|||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- name: Setup Mise
|
||||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
@@ -676,12 +672,13 @@ jobs:
|
|||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- name: Setup Mise
|
||||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Install server dependencies
|
- name: Install server dependencies
|
||||||
run: SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm --filter immich install --frozen-lockfile
|
run: SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm --filter immich install --frozen-lockfile
|
||||||
|
|
||||||
- name: Run API generation
|
- name: Run API generation
|
||||||
run: mise //:open-api
|
run: mise //:open-api
|
||||||
working-directory: open-api
|
working-directory: open-api
|
||||||
@@ -720,6 +717,9 @@ jobs:
|
|||||||
--health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
|
--health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
|
||||||
ports:
|
ports:
|
||||||
- 5432:5432
|
- 5432:5432
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: ./server
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||||
@@ -734,28 +734,25 @@ jobs:
|
|||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- name: Setup Mise
|
||||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||||
with:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Install server dependencies
|
- name: Install server dependencies
|
||||||
run: SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm install --frozen-lockfile
|
run: SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Build plugins
|
|
||||||
run: mise //:plugins
|
|
||||||
|
|
||||||
- name: Build the app
|
- name: Build the app
|
||||||
run: mise //server:build
|
run: pnpm build
|
||||||
|
|
||||||
- name: Run existing migrations
|
- name: Run existing migrations
|
||||||
run: pnpm --filter immich migrations:run
|
run: pnpm migrations:run
|
||||||
|
|
||||||
- name: Test npm run schema:reset command works
|
- name: Test npm run schema:reset command works
|
||||||
run: pnpm --filter immich schema:reset
|
run: pnpm schema:reset
|
||||||
|
|
||||||
- name: Generate new migrations
|
- name: Generate new migrations
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: pnpm --filter migrations:generate src/TestMigration
|
run: pnpm migrations:generate src/TestMigration
|
||||||
|
|
||||||
- name: Find file changes
|
- name: Find file changes
|
||||||
uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4
|
uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4
|
||||||
@@ -771,7 +768,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "ERROR: Generated migration files not up to date!"
|
echo "ERROR: Generated migration files not up to date!"
|
||||||
echo "Changed files: ${CHANGED_FILES}"
|
echo "Changed files: ${CHANGED_FILES}"
|
||||||
cat ./server/src/*-TestMigration.ts
|
cat ./src/*-TestMigration.ts
|
||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
- name: Run SQL generation
|
- name: Run SQL generation
|
||||||
|
|||||||
@@ -1,46 +1,46 @@
|
|||||||
dev:
|
dev:
|
||||||
@printf "This command has been removed. Please use:\n\n mise dev # or mise //:dev from another directory\n\n" >&2 && exit 1
|
@trap 'make dev-down' EXIT; COMPOSE_BAKE=true docker compose -f ./docker/docker-compose.dev.yml up --remove-orphans
|
||||||
|
|
||||||
dev-down:
|
dev-down:
|
||||||
@printf "This command has been removed. Please use:\n\n mise dev-down # or mise //:dev-down from another directory\n\n" >&2 && exit 1
|
docker compose -f ./docker/docker-compose.dev.yml down --remove-orphans
|
||||||
|
|
||||||
dev-update:
|
dev-update:
|
||||||
@printf "This command has been removed. Please use:\n\n mise dev-update # or mise //:dev-update from another directory\n\n" >&2 && exit 1
|
@trap 'make dev-down' EXIT; COMPOSE_BAKE=true docker compose -f ./docker/docker-compose.dev.yml up --build -V --remove-orphans
|
||||||
|
|
||||||
dev-scale:
|
dev-scale:
|
||||||
@printf "This command has been removed. Please use:\n\n mise dev-scale # or mise //:dev-scale from another directory\n\n" >&2 && exit 1
|
@trap 'make dev-down' EXIT; COMPOSE_BAKE=true docker compose -f ./docker/docker-compose.dev.yml up --build -V --scale immich-server=3 --remove-orphans
|
||||||
|
|
||||||
dev-docs:
|
dev-docs:
|
||||||
npm --prefix docs run start
|
npm --prefix docs run start
|
||||||
|
|
||||||
.PHONY: e2e
|
.PHONY: e2e
|
||||||
e2e:
|
e2e:
|
||||||
@printf "This command has been removed. Please use:\n\n mise e2e # or mise //:e2e from another directory\n\n" >&2 && exit 1
|
@trap 'make e2e-down' EXIT; COMPOSE_BAKE=true docker compose -f ./e2e/docker-compose.yml up --remove-orphans
|
||||||
|
|
||||||
e2e-dev:
|
e2e-dev:
|
||||||
@printf "This command has been removed. Please use:\n\n mise e2e-dev # or mise //:e2e-dev from another directory\n\n" >&2 && exit 1
|
@trap 'make e2e-down' EXIT; COMPOSE_BAKE=true docker compose -f ./e2e/docker-compose.dev.yml up --remove-orphans
|
||||||
|
|
||||||
e2e-update:
|
e2e-update:
|
||||||
@printf "This command has been removed. Please use:\n\n mise e2e-update # or mise //:e2e-update from another directory\n\n" >&2 && exit 1
|
@trap 'make e2e-down' EXIT; COMPOSE_BAKE=true docker compose -f ./e2e/docker-compose.yml up --build -V --remove-orphans
|
||||||
|
|
||||||
e2e-down:
|
e2e-down:
|
||||||
@printf "This command has been removed. Please use:\n\n mise e2e-down # or mise //:e2e-down from another directory\n\n" >&2 && exit 1
|
docker compose -f ./e2e/docker-compose.yml down --remove-orphans
|
||||||
|
|
||||||
prod:
|
prod:
|
||||||
@printf "This command has been removed. Please use:\n\n mise prod # or mise //:prod from another directory\n\n" >&2 && exit 1
|
@trap 'make prod-down' EXIT; COMPOSE_BAKE=true docker compose -f ./docker/docker-compose.prod.yml up --build -V --remove-orphans
|
||||||
|
|
||||||
prod-down:
|
prod-down:
|
||||||
@printf "This command has been removed. Please use:\n\n mise prod-down # or mise //:prod-down from another directory\n\n" >&2 && exit 1
|
docker compose -f ./docker/docker-compose.prod.yml down --remove-orphans
|
||||||
|
|
||||||
prod-scale:
|
prod-scale:
|
||||||
@printf "This command has been removed. Please use:\n\n mise prod-scale # or mise //:prod-scale from another directory\n\n" >&2 && exit 1
|
@trap 'make prod-down' EXIT; COMPOSE_BAKE=true docker compose -f ./docker/docker-compose.prod.yml up --build -V --scale immich-server=3 --scale immich-microservices=3 --remove-orphans
|
||||||
|
|
||||||
.PHONY: open-api
|
.PHONY: open-api
|
||||||
open-api:
|
open-api:
|
||||||
@printf "This command has been removed. Please use:\n\n mise open-api # or mise //:open-api from another directory\n\n" >&2 && exit 1
|
@printf "This command has been removed. Please use:\n\n mise open-api # or mise //:open-api from another directory\n\n"\n\n >&2 && exit 1
|
||||||
|
|
||||||
sql:
|
sql:
|
||||||
@printf "This command has been removed. Please use:\n\n mise sql # or mise //:sql from another directory\n\n" >&2 && exit 1
|
@printf "This command has been removed. Please use:\n\n mise sql # or mise //:sql from another directory\n\n"\n\n >&2 && exit 1
|
||||||
|
|
||||||
|
|
||||||
renovate:
|
renovate:
|
||||||
@@ -52,7 +52,16 @@ renovate:
|
|||||||
MODULES = e2e server web cli sdk docs .github
|
MODULES = e2e server web cli sdk docs .github
|
||||||
|
|
||||||
test-e2e:
|
test-e2e:
|
||||||
@printf "This command has been removed. Please use:\n\n mise //e2e:test # or mise //e2e:test-web for web tests, respectively\n\n" >&2 && exit 1
|
docker compose -f ./e2e/docker-compose.yml build
|
||||||
|
pnpm --filter immich-e2e run test
|
||||||
|
pnpm --filter immich-e2e run test:web
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
@printf "This command has been removed. Please use:\n\n mise clean # or mise //:clean from another directory\n\n" >&2 && exit 1
|
find . -name "node_modules" -type d -prune -exec rm -rf {} +
|
||||||
|
find . -name "dist" -type d -prune -exec rm -rf '{}' +
|
||||||
|
find . -name "build" -type d -prune -exec rm -rf '{}' +
|
||||||
|
find . -name ".svelte-kit" -type d -prune -exec rm -rf '{}' +
|
||||||
|
find . -name "coverage" -type d -prune -exec rm -rf '{}' +
|
||||||
|
find . -name ".pnpm-store" -type d -prune -exec rm -rf '{}' +
|
||||||
|
command -v docker >/dev/null 2>&1 && docker compose -f ./docker/docker-compose.dev.yml down -v --remove-orphans || true
|
||||||
|
command -v docker >/dev/null 2>&1 && docker compose -f ./e2e/docker-compose.yml down -v --remove-orphans || true
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
# @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html
|
|
||||||
|
|
||||||
[[tools.opentofu]]
|
|
||||||
version = "1.11.6"
|
|
||||||
backend = "aqua:opentofu/opentofu"
|
|
||||||
|
|
||||||
[tools.opentofu."platforms.linux-arm64"]
|
|
||||||
checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850"
|
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_arm64.tar.gz"
|
|
||||||
|
|
||||||
[tools.opentofu."platforms.linux-arm64-musl"]
|
|
||||||
checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850"
|
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_arm64.tar.gz"
|
|
||||||
|
|
||||||
[tools.opentofu."platforms.linux-x64"]
|
|
||||||
checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e"
|
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_amd64.tar.gz"
|
|
||||||
|
|
||||||
[tools.opentofu."platforms.linux-x64-musl"]
|
|
||||||
checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e"
|
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_amd64.tar.gz"
|
|
||||||
|
|
||||||
[tools.opentofu."platforms.macos-arm64"]
|
|
||||||
checksum = "sha256:62d7fa8539e13b444827aa0a3b90c5972da5c47e8f8882d9dcf2e430e78840c1"
|
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_darwin_arm64.tar.gz"
|
|
||||||
|
|
||||||
[tools.opentofu."platforms.macos-x64"]
|
|
||||||
checksum = "sha256:1408cdef1c380f914565e6b4bb70794c6b163f195fcb233357f3d6c5745906b6"
|
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_darwin_amd64.tar.gz"
|
|
||||||
|
|
||||||
[tools.opentofu."platforms.windows-x64"]
|
|
||||||
checksum = "sha256:27323f70c875b8251bfd7e61a4cffc3ebff4e56ed1e611b955016f0c7077367e"
|
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_windows_amd64.tar.gz"
|
|
||||||
|
|
||||||
[[tools.terragrunt]]
|
|
||||||
version = "1.0.3"
|
|
||||||
backend = "aqua:gruntwork-io/terragrunt"
|
|
||||||
|
|
||||||
[tools.terragrunt."platforms.linux-arm64"]
|
|
||||||
checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe"
|
|
||||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_arm64.tar.gz"
|
|
||||||
|
|
||||||
[tools.terragrunt."platforms.linux-arm64-musl"]
|
|
||||||
checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe"
|
|
||||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_arm64.tar.gz"
|
|
||||||
|
|
||||||
[tools.terragrunt."platforms.linux-x64"]
|
|
||||||
checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073"
|
|
||||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_amd64.tar.gz"
|
|
||||||
|
|
||||||
[tools.terragrunt."platforms.linux-x64-musl"]
|
|
||||||
checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073"
|
|
||||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_amd64.tar.gz"
|
|
||||||
|
|
||||||
[tools.terragrunt."platforms.macos-arm64"]
|
|
||||||
checksum = "sha256:aacb5be2ca5475300cbce246dfbd8a45eb47510fbaa70fab8561c49ef5db03aa"
|
|
||||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_darwin_arm64.tar.gz"
|
|
||||||
|
|
||||||
[tools.terragrunt."platforms.macos-x64"]
|
|
||||||
checksum = "sha256:3133c2251e191aede8e3dd2a5b3aee2e91c5f08f88f117aee40eed9a24c8ef6b"
|
|
||||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_darwin_amd64.tar.gz"
|
|
||||||
|
|
||||||
[tools.terragrunt."platforms.windows-x64"]
|
|
||||||
checksum = "sha256:183b2745b4e04980a6bfa4450ff81956a12596ca22d70f7aaa793980f5b036db"
|
|
||||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_windows_amd64.exe.tar.gz"
|
|
||||||
@@ -21,7 +21,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ..:/usr/src/app
|
- ..:/usr/src/app
|
||||||
# - ../../ui:/usr/src/ui
|
# - ../../ui:/usr/src/ui
|
||||||
- build_cache:/buildcache
|
- pnpm_cache:/buildcache/pnpm_cache
|
||||||
- server_node_modules:/usr/src/app/server/node_modules
|
- server_node_modules:/usr/src/app/server/node_modules
|
||||||
- web_node_modules:/usr/src/app/web/node_modules
|
- web_node_modules:/usr/src/app/web/node_modules
|
||||||
- github_node_modules:/usr/src/app/.github/node_modules
|
- github_node_modules:/usr/src/app/.github/node_modules
|
||||||
@@ -45,11 +45,11 @@ services:
|
|||||||
target: dev
|
target: dev
|
||||||
command:
|
command:
|
||||||
- |
|
- |
|
||||||
mise install
|
pnpm install
|
||||||
touch /tmp/init-complete
|
touch /tmp/init-complete
|
||||||
exec tail -f /dev/null
|
exec tail -f /dev/null
|
||||||
volumes:
|
volumes:
|
||||||
- build_cache:/buildcache
|
- pnpm_store_server:/buildcache/pnpm-store
|
||||||
restart: 'no'
|
restart: 'no'
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ['CMD', 'test', '-f', '/tmp/init-complete']
|
test: ['CMD', 'test', '-f', '/tmp/init-complete']
|
||||||
@@ -73,7 +73,8 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ${UPLOAD_LOCATION}/photos:/data
|
- ${UPLOAD_LOCATION}/photos:/data
|
||||||
- /etc/localtime:/etc/localtime:ro
|
- /etc/localtime:/etc/localtime:ro
|
||||||
- ../packages/plugin-core:/build/plugins/immich-plugin-core
|
- pnpm_store_server:/buildcache/pnpm-store
|
||||||
|
- ../packages/plugins:/build/corePlugin
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
@@ -121,6 +122,8 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- 3000:3000
|
- 3000:3000
|
||||||
- 24678:24678
|
- 24678:24678
|
||||||
|
volumes:
|
||||||
|
- pnpm_store_web:/buildcache/pnpm-store
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
depends_on:
|
depends_on:
|
||||||
immich-init:
|
immich-init:
|
||||||
@@ -200,7 +203,9 @@ volumes:
|
|||||||
model_cache:
|
model_cache:
|
||||||
prometheus_data:
|
prometheus_data:
|
||||||
grafana_data:
|
grafana_data:
|
||||||
build_cache:
|
pnpm_cache:
|
||||||
|
pnpm_store_server:
|
||||||
|
pnpm_store_web:
|
||||||
server_node_modules:
|
server_node_modules:
|
||||||
web_node_modules:
|
web_node_modules:
|
||||||
github_node_modules:
|
github_node_modules:
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ For organizations seeking to resell Immich, we have established the following gu
|
|||||||
|
|
||||||
When in doubt or if you have an edge case scenario, we encourage you to contact us directly via email to discuss the use of our trademark. We can provide clear guidance on what is acceptable and what is not. You can reach out at: questions@immich.app
|
When in doubt or if you have an edge case scenario, we encourage you to contact us directly via email to discuss the use of our trademark. We can provide clear guidance on what is acceptable and what is not. You can reach out at: questions@immich.app
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## User
|
## User
|
||||||
|
|
||||||
### How can I reset the admin password?
|
### How can I reset the admin password?
|
||||||
@@ -38,10 +36,6 @@ The admin password can be reset by running the [reset-admin-password](/administr
|
|||||||
|
|
||||||
You can see the list of all users by running [list-users](/administration/server-commands.md) Command on the Immich-server.
|
You can see the list of all users by running [list-users](/administration/server-commands.md) Command on the Immich-server.
|
||||||
|
|
||||||
### How can I change my profile picture?
|
|
||||||
|
|
||||||
View a single photo, press the three dots in the top-right to show context menu, and select "Set as profile picture". In the pop-up, use your mouse scroll wheel to zoom in the picture until it completely fills the circle. Click and drag the picture to align it to your liking. Press "Save" to save your changes.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Mobile App
|
## Mobile App
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ After making any changes in the `server/src/schema`, a database migration need t
|
|||||||
1. Run the command
|
1. Run the command
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mise //server:migrations generate <migration-name>
|
pnpm run migrations:generate <migration-name>
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Check if the migration file makes sense.
|
2. Check if the migration file makes sense.
|
||||||
@@ -18,7 +18,7 @@ The server will automatically detect `*.ts` file changes and restart. Part of th
|
|||||||
If you need to undo the most recently applied migration—for example, when developing or testing on schema changes—run:
|
If you need to undo the most recently applied migration—for example, when developing or testing on schema changes—run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mise //server:migrations revert
|
pnpm run migrations:revert
|
||||||
```
|
```
|
||||||
|
|
||||||
This command rolls back the latest migration and brings the database schema back to its previous state.
|
This command rolls back the latest migration and brings the database schema back to its previous state.
|
||||||
|
|||||||
@@ -252,33 +252,44 @@ To connect the mobile app to your Dev Container:
|
|||||||
|
|
||||||
The Dev Container supports multiple ways to run tests:
|
The Dev Container supports multiple ways to run tests:
|
||||||
|
|
||||||
|
#### Using Mise Commands (Recommended)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Server
|
# Run tests for specific components
|
||||||
mise //server:test # unit tests
|
mise run checklist # in `server/`, `web/`, `packages/cli`
|
||||||
mise //server:test-medium # medium / integration tests
|
|
||||||
|
|
||||||
# Web
|
|
||||||
mise //web:test # unit tests
|
|
||||||
|
|
||||||
# E2E
|
|
||||||
mise //e2e:test # API tests
|
|
||||||
mise //e2e:test-web # web UI tests (Playwright)
|
|
||||||
|
|
||||||
# Run all checks for a component
|
|
||||||
mise //server:checklist
|
|
||||||
mise //web:checklist
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Additional Commands
|
#### Using PNPM Directly
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Server tests
|
||||||
|
cd /workspaces/immich/server
|
||||||
|
pnpm test # Run all tests
|
||||||
|
pnpm run test:medium # Medium tests (integration tests)
|
||||||
|
pnpm run test:watch # Watch mode
|
||||||
|
pnpm run test:cov # Coverage report
|
||||||
|
|
||||||
|
# Web tests
|
||||||
|
cd /workspaces/immich/web
|
||||||
|
pnpm test # Run all tests
|
||||||
|
pnpm run test:watch # Watch mode
|
||||||
|
|
||||||
|
# E2E tests
|
||||||
|
cd /workspaces/immich/e2e
|
||||||
|
pnpm run test # Run API tests
|
||||||
|
pnpm run test:web # Run web UI tests
|
||||||
|
```
|
||||||
|
|
||||||
|
### Additional Make Commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# API generation
|
# API generation
|
||||||
mise //:open-api # Generate OpenAPI specs
|
make open-api # Generate OpenAPI specs
|
||||||
mise //:open-api-typescript # Generate TypeScript SDK
|
make open-api-typescript # Generate TypeScript SDK
|
||||||
mise //:open-api-dart # Generate Dart SDK
|
make open-api-dart # Generate Dart SDK
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
mise //server:sql # Sync database schema
|
mise sql # Sync database schema
|
||||||
```
|
```
|
||||||
|
|
||||||
### Debugging
|
### Debugging
|
||||||
|
|||||||
@@ -8,42 +8,34 @@ When contributing code through a pull request, please check the following:
|
|||||||
|
|
||||||
## Web Checks
|
## Web Checks
|
||||||
|
|
||||||
- [ ] `mise //web:lint` (linting via ESLint)
|
- [ ] `pnpm run lint` (linting via ESLint)
|
||||||
- [ ] `mise //web:format` (formatting via Prettier)
|
- [ ] `pnpm run format` (formatting via Prettier)
|
||||||
- [ ] `mise //web:check-svelte` (type checking via SvelteKit)
|
- [ ] `pnpm run check:svelte` (Type checking via SvelteKit)
|
||||||
- [ ] `mise //web:check-typescript` (type checking via `tsc`)
|
- [ ] `pnpm run check:typescript` (check typescript)
|
||||||
- [ ] `mise //web:test` (unit tests)
|
- [ ] `pnpm test` (unit tests)
|
||||||
|
|
||||||
:::tip AIO
|
:::tip AIO
|
||||||
Run all web checks with `mise //web:checklist`
|
Run all web checks with `pnpm run check:all`
|
||||||
:::
|
|
||||||
|
|
||||||
:::tip Auto Fix
|
|
||||||
Use `mise //web:lint-fix` and `mise //web:format-fix` to automatically correct some issues.
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
- [ ] `mise //docs:format` (formatting via Prettier)
|
- [ ] `pnpm run format` (formatting via Prettier)
|
||||||
- [ ] Update the `_redirects` file if you have renamed a page or removed it from the documentation.
|
- [ ] Update the `_redirects` file if you have renamed a page or removed it from the documentation.
|
||||||
|
|
||||||
:::tip Auto Fix
|
|
||||||
Use `mise //docs:format-fix` to automatically fix formatting.
|
|
||||||
:::
|
|
||||||
|
|
||||||
## Server Checks
|
## Server Checks
|
||||||
|
|
||||||
- [ ] `mise //server:lint` (linting via ESLint)
|
- [ ] `pnpm run lint` (linting via ESLint)
|
||||||
- [ ] `mise //server:format` (formatting via Prettier)
|
- [ ] `pnpm run format` (formatting via Prettier)
|
||||||
- [ ] `mise //server:check` (type checking via `tsc`)
|
- [ ] `pnpm run check` (Type checking via `tsc`)
|
||||||
- [ ] `mise //server:test` (unit tests)
|
- [ ] `pnpm test` (unit tests)
|
||||||
|
|
||||||
:::tip AIO
|
:::tip AIO
|
||||||
Run all server checks with `mise //server:checklist`
|
Run all server checks with `pnpm run check:all`
|
||||||
:::
|
:::
|
||||||
|
|
||||||
:::tip Auto Fix
|
:::tip Auto Fix
|
||||||
Use `mise //server:lint-fix` and `mise //server:format-fix` to automatically correct some issues.
|
You can use `pnpm run __:fix` to potentially correct some issues automatically for `pnpm run format` and `lint`.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## Mobile Checklist
|
## Mobile Checklist
|
||||||
@@ -61,17 +53,6 @@ Run all these commands at once with `mise //mobile:checklist`
|
|||||||
You can use `mise //mobile:lint-fix` to potentially correct some issues automatically for `mise //mobile:lint`.
|
You can use `mise //mobile:lint-fix` to potentially correct some issues automatically for `mise //mobile:lint`.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## Machine Learning Checklist
|
|
||||||
|
|
||||||
- [ ] `mise //machine-learning:lint` (linting via ruff)
|
|
||||||
- [ ] `mise //machine-learning:format` (formatting via ruff)
|
|
||||||
- [ ] `mise //machine-learning:check` (type checking via mypy)
|
|
||||||
- [ ] `mise //machine-learning:test` (unit tests via pytest)
|
|
||||||
|
|
||||||
:::tip AIO
|
|
||||||
Run all machine learning checks with `mise //machine-learning:checklist`
|
|
||||||
:::
|
|
||||||
|
|
||||||
## OpenAPI
|
## OpenAPI
|
||||||
|
|
||||||
The OpenAPI client libraries need to be regenerated whenever there are changes to the `immich-openapi-specs.json` file. Note that you should not modify this file directly as it is auto-generated. See [OpenAPI](/api.md) for more details.
|
The OpenAPI client libraries need to be regenerated whenever there are changes to the `immich-openapi-specs.json` file. Note that you should not modify this file directly as it is auto-generated. See [OpenAPI](/api.md) for more details.
|
||||||
|
|||||||
@@ -32,10 +32,6 @@ This environment includes the services below. Additional details are available i
|
|||||||
|
|
||||||
All the services are packaged to run as with single Docker Compose command.
|
All the services are packaged to run as with single Docker Compose command.
|
||||||
|
|
||||||
:::tip mise
|
|
||||||
[mise](https://mise.jdx.dev) is used throughout the project to manage tool versions and run tasks. [Install mise](https://mise.jdx.dev/installing-mise.html), then from the repo root run `mise trust` and `mise install` to get all required tools. Tasks for each service can be run from the repo root using `mise //namespace:task` (e.g. `mise //server:lint`). To list all available tasks, run `mise tasks ls --all`.
|
|
||||||
:::
|
|
||||||
|
|
||||||
### Server and web apps
|
### Server and web apps
|
||||||
|
|
||||||
1. Clone the project repo.
|
1. Clone the project repo.
|
||||||
@@ -60,23 +56,22 @@ You can access the web from `http://your-machine-ip:3000` or `http://localhost:3
|
|||||||
|
|
||||||
#### Connect web to a remote backend
|
#### Connect web to a remote backend
|
||||||
|
|
||||||
If you only want to do web development connected to an existing, remote backend, run from the repo root:
|
If you only want to do web development connected to an existing, remote backend, follow these steps:
|
||||||
|
|
||||||
|
1. Build the Immich SDK - `pnpm --filter @immich/sdk install && pnpm --filter @immich/sdk build`
|
||||||
|
2. Enter the web directory - `cd web/`
|
||||||
|
3. Install web dependencies - `pnpm i`
|
||||||
|
4. Start the web development server
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
IMMICH_SERVER_URL=https://demo.immich.app/ mise //web:start
|
IMMICH_SERVER_URL=https://demo.immich.app/ pnpm run dev
|
||||||
```
|
|
||||||
|
|
||||||
This will install all dependencies (including the SDK) and start the dev server in one step. To connect to the hosted demo server specifically, use the shorthand:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mise //web:start-demo
|
|
||||||
```
|
```
|
||||||
|
|
||||||
If you're using PowerShell on Windows you may need to set the env var separately like so:
|
If you're using PowerShell on Windows you may need to set the env var separately like so:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
$env:IMMICH_SERVER_URL = "https://demo.immich.app/"
|
$env:IMMICH_SERVER_URL = "https://demo.immich.app/"
|
||||||
mise //web:start
|
pnpm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `@immich/ui`
|
#### `@immich/ui`
|
||||||
@@ -95,16 +90,20 @@ To see local changes to `@immich/ui` in Immich, do the following:
|
|||||||
|
|
||||||
#### Setup
|
#### Setup
|
||||||
|
|
||||||
1. Run `mise //mobile:install` to install Flutter dependencies.
|
1. [Install mise](https://mise.jdx.dev/installing-mise.html).
|
||||||
2. Run `mise //mobile:translation` to generate the translation file.
|
2. Change to the immich (root) directory and trust the mise config with `mise trust`.
|
||||||
3. Change to the `mobile/` directory and run `flutter run` to start the app.
|
3. Install tools with mise: `mise install`.
|
||||||
|
4. Change to the `mobile/` directory.
|
||||||
|
5. Run `flutter pub get` to install the dependencies.
|
||||||
|
6. Run `make translation` to generate the translation file.
|
||||||
|
7. Run `flutter run` to start the app.
|
||||||
|
|
||||||
#### Translation
|
#### Translation
|
||||||
|
|
||||||
To add a new translation text, enter the key-value pair in the `i18n/en.json` in the root of the immich project. Then run:
|
To add a new translation text, enter the key-value pair in the `i18n/en.json` in the root of the immich project. Then, from the `mobile/` directory, run
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mise //mobile:translation
|
make translation
|
||||||
```
|
```
|
||||||
|
|
||||||
The mobile app asks you what backend to connect to. You can utilize the demo backend (https://demo.immich.app/) if you don't need to change server code or upload photos. Alternatively, you can run the server yourself per the instructions above.
|
The mobile app asks you what backend to connect to. You can utilize the demo backend (https://demo.immich.app/) if you don't need to change server code or upload photos. Alternatively, you can run the server yourself per the instructions above.
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
|
|
||||||
### Unit tests
|
### Unit tests
|
||||||
|
|
||||||
Unit tests are run with `mise //server:test`.
|
Unit are run by calling `pnpm run test` from the `server/` directory.
|
||||||
You need to run `mise //server:install` before _once_.
|
You need to run `pnpm install` (in `server/`) before _once_.
|
||||||
|
|
||||||
### End to end tests
|
### End to end tests
|
||||||
|
|
||||||
@@ -17,7 +17,8 @@ make e2e
|
|||||||
|
|
||||||
Before you can run the tests, you need to run the following commands _once_:
|
Before you can run the tests, you need to run the following commands _once_:
|
||||||
|
|
||||||
- `mise //e2e:ci-setup` (installs e2e, SDK, and CLI dependencies)
|
- `pnpm install`
|
||||||
|
- `pnpm --filter "@immich/*" build`
|
||||||
- `mise //:open-api`
|
- `mise //:open-api`
|
||||||
|
|
||||||
Once the test environment is running, the e2e tests can be run via:
|
Once the test environment is running, the e2e tests can be run via:
|
||||||
|
|||||||
@@ -154,33 +154,33 @@ Redis (Sentinel) URL example JSON before encoding:
|
|||||||
|
|
||||||
## Machine Learning
|
## Machine Learning
|
||||||
|
|
||||||
| Variable | Description | Default | Containers |
|
| Variable | Description | Default | Containers |
|
||||||
| :---------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------: | :--------------- |
|
| :---------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----------------------------: | :--------------- |
|
||||||
| `MACHINE_LEARNING_MODEL_TTL` | Inactivity time (s) before a model is unloaded (disabled if \<= 0) | `300` | machine learning |
|
| `MACHINE_LEARNING_MODEL_TTL` | Inactivity time (s) before a model is unloaded (disabled if \<= 0) | `300` | machine learning |
|
||||||
| `MACHINE_LEARNING_MODEL_TTL_POLL_S` | Interval (s) between checks for the model TTL (disabled if \<= 0) | `10` | machine learning |
|
| `MACHINE_LEARNING_MODEL_TTL_POLL_S` | Interval (s) between checks for the model TTL (disabled if \<= 0) | `10` | machine learning |
|
||||||
| `MACHINE_LEARNING_CACHE_FOLDER` | Directory where models are downloaded | `/cache` | machine learning |
|
| `MACHINE_LEARNING_CACHE_FOLDER` | Directory where models are downloaded | `/cache` | machine learning |
|
||||||
| `MACHINE_LEARNING_REQUEST_THREADS`<sup>\*1</sup> | Thread count of the request thread pool (disabled if \<= 0) | number of CPU cores | machine learning |
|
| `MACHINE_LEARNING_REQUEST_THREADS`<sup>\*1</sup> | Thread count of the request thread pool (disabled if \<= 0) | number of CPU cores | machine learning |
|
||||||
| `MACHINE_LEARNING_MODEL_INTER_OP_THREADS` | Number of parallel model operations | `1` | machine learning |
|
| `MACHINE_LEARNING_MODEL_INTER_OP_THREADS` | Number of parallel model operations | `1` | machine learning |
|
||||||
| `MACHINE_LEARNING_MODEL_INTRA_OP_THREADS` | Number of threads for each model operation | `2` | machine learning |
|
| `MACHINE_LEARNING_MODEL_INTRA_OP_THREADS` | Number of threads for each model operation | `2` | machine learning |
|
||||||
| `MACHINE_LEARNING_WORKERS`<sup>\*2</sup> | Number of worker processes to spawn | `1` | machine learning |
|
| `MACHINE_LEARNING_WORKERS`<sup>\*2</sup> | Number of worker processes to spawn | `1` | machine learning |
|
||||||
| `MACHINE_LEARNING_HTTP_KEEPALIVE_TIMEOUT_S`<sup>\*3</sup> | HTTP Keep-alive time in seconds | `2` | machine learning |
|
| `MACHINE_LEARNING_HTTP_KEEPALIVE_TIMEOUT_S`<sup>\*3</sup> | HTTP Keep-alive time in seconds | `2` | machine learning |
|
||||||
| `MACHINE_LEARNING_WORKER_TIMEOUT` | Maximum time (s) of unresponsiveness before a worker is killed | `300` (`900` if using ROCm) | machine learning |
|
| `MACHINE_LEARNING_WORKER_TIMEOUT` | Maximum time (s) of unresponsiveness before a worker is killed | `120` (`300` if using OpenVINO) | machine learning |
|
||||||
| `MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL` | Comma-separated list of (textual) CLIP model(s) to preload and cache | | machine learning |
|
| `MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL` | Comma-separated list of (textual) CLIP model(s) to preload and cache | | machine learning |
|
||||||
| `MACHINE_LEARNING_PRELOAD__CLIP__VISUAL` | Comma-separated list of (visual) CLIP model(s) to preload and cache | | machine learning |
|
| `MACHINE_LEARNING_PRELOAD__CLIP__VISUAL` | Comma-separated list of (visual) CLIP model(s) to preload and cache | | machine learning |
|
||||||
| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION` | Comma-separated list of (recognition) facial recognition model(s) to preload and cache | | machine learning |
|
| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION` | Comma-separated list of (recognition) facial recognition model(s) to preload and cache | | machine learning |
|
||||||
| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION` | Comma-separated list of (detection) facial recognition model(s) to preload and cache | | machine learning |
|
| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION` | Comma-separated list of (detection) facial recognition model(s) to preload and cache | | machine learning |
|
||||||
| `MACHINE_LEARNING_PRELOAD__OCR__RECOGNITION` | Comma-separated list of (recognition) OCR model(s) to preload and cache | | machine learning |
|
| `MACHINE_LEARNING_PRELOAD__OCR__RECOGNITION` | Comma-separated list of (recognition) OCR model(s) to preload and cache | | machine learning |
|
||||||
| `MACHINE_LEARNING_PRELOAD__OCR__DETECTION` | Comma-separated list of (detection) OCR model(s) to preload and cache | | machine learning |
|
| `MACHINE_LEARNING_PRELOAD__OCR__DETECTION` | Comma-separated list of (detection) OCR model(s) to preload and cache | | machine learning |
|
||||||
| `MACHINE_LEARNING_ANN` | Enable ARM-NN hardware acceleration if supported | `True` | machine learning |
|
| `MACHINE_LEARNING_ANN` | Enable ARM-NN hardware acceleration if supported | `True` | machine learning |
|
||||||
| `MACHINE_LEARNING_ANN_FP16_TURBO` | Execute operations in FP16 precision: increasing speed, reducing precision (applies only to ARM-NN) | `False` | machine learning |
|
| `MACHINE_LEARNING_ANN_FP16_TURBO` | Execute operations in FP16 precision: increasing speed, reducing precision (applies only to ARM-NN) | `False` | machine learning |
|
||||||
| `MACHINE_LEARNING_ANN_TUNING_LEVEL` | ARM-NN GPU tuning level (1: rapid, 2: normal, 3: exhaustive) | `2` | machine learning |
|
| `MACHINE_LEARNING_ANN_TUNING_LEVEL` | ARM-NN GPU tuning level (1: rapid, 2: normal, 3: exhaustive) | `2` | machine learning |
|
||||||
| `MACHINE_LEARNING_DEVICE_IDS`<sup>\*4</sup> | Device IDs to use in multi-GPU environments | `0` | machine learning |
|
| `MACHINE_LEARNING_DEVICE_IDS`<sup>\*4</sup> | Device IDs to use in multi-GPU environments | `0` | machine learning |
|
||||||
| `MACHINE_LEARNING_MAX_BATCH_SIZE__FACIAL_RECOGNITION` | Set the maximum number of faces that will be processed at once by the facial recognition model | None (`1` if using OpenVINO) | machine learning |
|
| `MACHINE_LEARNING_MAX_BATCH_SIZE__FACIAL_RECOGNITION` | Set the maximum number of faces that will be processed at once by the facial recognition model | None (`1` if using OpenVINO) | machine learning |
|
||||||
| `MACHINE_LEARNING_MAX_BATCH_SIZE__OCR` | Set the maximum number of boxes that will be processed at once by the OCR model | `6` | machine learning |
|
| `MACHINE_LEARNING_MAX_BATCH_SIZE__OCR` | Set the maximum number of boxes that will be processed at once by the OCR model | `6` | machine learning |
|
||||||
| `MACHINE_LEARNING_RKNN` | Enable RKNN hardware acceleration if supported | `True` | machine learning |
|
| `MACHINE_LEARNING_RKNN` | Enable RKNN hardware acceleration if supported | `True` | machine learning |
|
||||||
| `MACHINE_LEARNING_RKNN_THREADS` | How many threads of RKNN runtime should be spun up while inferencing. | `1` | machine learning |
|
| `MACHINE_LEARNING_RKNN_THREADS` | How many threads of RKNN runtime should be spun up while inferencing. | `1` | machine learning |
|
||||||
| `MACHINE_LEARNING_MODEL_ARENA` | Pre-allocates CPU memory to avoid memory fragmentation | true | machine learning |
|
| `MACHINE_LEARNING_MODEL_ARENA` | Pre-allocates CPU memory to avoid memory fragmentation | true | machine learning |
|
||||||
| `MACHINE_LEARNING_OPENVINO_PRECISION` | If set to FP16, uses half-precision floating-point operations for faster inference with reduced accuracy (one of [`FP16`, `FP32`], applies only to OpenVINO) | `FP32` | machine learning |
|
| `MACHINE_LEARNING_OPENVINO_PRECISION` | If set to FP16, uses half-precision floating-point operations for faster inference with reduced accuracy (one of [`FP16`, `FP32`], applies only to OpenVINO) | `FP32` | machine learning |
|
||||||
|
|
||||||
\*1: It is recommended to begin with this parameter when changing the concurrency levels of the machine learning service and then tune the other ones.
|
\*1: It is recommended to begin with this parameter when changing the concurrency levels of the machine learning service and then tune the other ones.
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const config = {
|
|||||||
url: 'https://docs.immich.app',
|
url: 'https://docs.immich.app',
|
||||||
baseUrl: '/',
|
baseUrl: '/',
|
||||||
onBrokenLinks: 'throw',
|
onBrokenLinks: 'throw',
|
||||||
|
onBrokenMarkdownLinks: 'warn',
|
||||||
favicon: 'img/favicon.png',
|
favicon: 'img/favicon.png',
|
||||||
|
|
||||||
// GitHub pages deployment config.
|
// GitHub pages deployment config.
|
||||||
@@ -28,9 +29,6 @@ const config = {
|
|||||||
// Mermaid diagrams
|
// Mermaid diagrams
|
||||||
markdown: {
|
markdown: {
|
||||||
mermaid: true,
|
mermaid: true,
|
||||||
hooks: {
|
|
||||||
onBrokenMarkdownLinks: 'warn',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
themes: ['@docusaurus/theme-mermaid'],
|
themes: ['@docusaurus/theme-mermaid'],
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
# @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html
|
|
||||||
|
|
||||||
[[tools.wrangler]]
|
|
||||||
version = "4.66.0"
|
|
||||||
backend = "npm:wrangler"
|
|
||||||
+2
-2
@@ -3,7 +3,7 @@ run = "pnpm install --filter documentation --frozen-lockfile"
|
|||||||
|
|
||||||
[tasks.start]
|
[tasks.start]
|
||||||
env._.path = "./node_modules/.bin"
|
env._.path = "./node_modules/.bin"
|
||||||
run = "docusaurus start --port 3005"
|
run = "docusaurus --port 3005"
|
||||||
|
|
||||||
[tasks.build]
|
[tasks.build]
|
||||||
env._.path = "./node_modules/.bin"
|
env._.path = "./node_modules/.bin"
|
||||||
@@ -28,4 +28,4 @@ run = "prettier --write ."
|
|||||||
run = "wrangler pages deploy build --project-name=${PROJECT_NAME} --branch=${BRANCH_NAME}"
|
run = "wrangler pages deploy build --project-name=${PROJECT_NAME} --branch=${BRANCH_NAME}"
|
||||||
|
|
||||||
[tools]
|
[tools]
|
||||||
wrangler = "4.91.0"
|
wrangler = "4.66.0"
|
||||||
|
|||||||
@@ -83,7 +83,9 @@ volumes:
|
|||||||
model_cache:
|
model_cache:
|
||||||
prometheus_data:
|
prometheus_data:
|
||||||
grafana_data:
|
grafana_data:
|
||||||
build_cache:
|
pnpm_cache:
|
||||||
|
pnpm_store_server:
|
||||||
|
pnpm_store_web:
|
||||||
server_node_modules:
|
server_node_modules:
|
||||||
web_node_modules:
|
web_node_modules:
|
||||||
github_node_modules:
|
github_node_modules:
|
||||||
|
|||||||
+1
-16
@@ -1,21 +1,11 @@
|
|||||||
[tasks.install]
|
[tasks.install]
|
||||||
run = "pnpm install --filter immich-e2e --frozen-lockfile"
|
run = "pnpm install --filter immich-e2e --frozen-lockfile"
|
||||||
|
|
||||||
[tasks.build]
|
|
||||||
dir = "{{ config_root }}"
|
|
||||||
run = "docker compose build"
|
|
||||||
|
|
||||||
[tasks.test]
|
[tasks.test]
|
||||||
depends = ["//e2e:build", "//e2e:ci-setup"]
|
|
||||||
env._.path = "./node_modules/.bin"
|
env._.path = "./node_modules/.bin"
|
||||||
run = "vitest --run"
|
run = "vitest --run"
|
||||||
|
|
||||||
[tasks.playwright-install]
|
|
||||||
env._.path = "./node_modules/.bin"
|
|
||||||
run = "playwright install"
|
|
||||||
|
|
||||||
[tasks."test-web"]
|
[tasks."test-web"]
|
||||||
depends = ["//e2e:build", "//e2e:ci-setup", "//e2e:playwright-install"]
|
|
||||||
env._.path = "./node_modules/.bin"
|
env._.path = "./node_modules/.bin"
|
||||||
run = "playwright test"
|
run = "playwright test"
|
||||||
|
|
||||||
@@ -40,12 +30,7 @@ run = "tsc --noEmit"
|
|||||||
|
|
||||||
|
|
||||||
[tasks.ci-setup]
|
[tasks.ci-setup]
|
||||||
depends = [
|
depends = ["//:sdk:install", "//:sdk:build", "//cli:install", "//cli:build"]
|
||||||
"//:sdk:install",
|
|
||||||
"//:sdk:build",
|
|
||||||
"//packages/cli:install",
|
|
||||||
"//packages/cli:build",
|
|
||||||
]
|
|
||||||
run = { task = ":install" }
|
run = { task = ":install" }
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -32,7 +32,7 @@
|
|||||||
"@playwright/test": "^1.44.1",
|
"@playwright/test": "^1.44.1",
|
||||||
"@socket.io/component-emitter": "^3.1.2",
|
"@socket.io/component-emitter": "^3.1.2",
|
||||||
"@types/luxon": "^3.4.2",
|
"@types/luxon": "^3.4.2",
|
||||||
"@types/node": "^24.12.4",
|
"@types/node": "^24.12.2",
|
||||||
"@types/pg": "^8.15.1",
|
"@types/pg": "^8.15.1",
|
||||||
"@types/pngjs": "^6.0.4",
|
"@types/pngjs": "^6.0.4",
|
||||||
"@types/supertest": "^7.0.0",
|
"@types/supertest": "^7.0.0",
|
||||||
|
|||||||
@@ -55,8 +55,8 @@ export function toColumnarFormat(assets: MockTimelineAsset[]): TimeBucketAssetRe
|
|||||||
result.duration.push(asset.duration);
|
result.duration.push(asset.duration);
|
||||||
result.projectionType.push(asset.projectionType);
|
result.projectionType.push(asset.projectionType);
|
||||||
result.livePhotoVideoId.push(asset.livePhotoVideoId);
|
result.livePhotoVideoId.push(asset.livePhotoVideoId);
|
||||||
result.city?.push(asset.city);
|
result.city.push(asset.city);
|
||||||
result.country?.push(asset.country);
|
result.country.push(asset.country);
|
||||||
result.visibility.push(asset.visibility);
|
result.visibility.push(asset.visibility);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -536,7 +536,7 @@ test.describe('Timeline', () => {
|
|||||||
force: false,
|
force: false,
|
||||||
ids: [assetToTrash.id],
|
ids: [assetToTrash.id],
|
||||||
});
|
});
|
||||||
await page.locator('#control-bar').getByLabel('Close').click();
|
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.locator('#control-bar').getByLabel('Close').click();
|
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.locator('#control-bar').getByLabel('Close').click();
|
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);
|
||||||
|
|||||||
+8
-35
@@ -22,12 +22,13 @@
|
|||||||
"add_birthday": "Add a birthday",
|
"add_birthday": "Add a birthday",
|
||||||
"add_endpoint": "Add endpoint",
|
"add_endpoint": "Add endpoint",
|
||||||
"add_exclusion_pattern": "Add exclusion pattern",
|
"add_exclusion_pattern": "Add exclusion pattern",
|
||||||
|
"add_filter": "Add filter",
|
||||||
|
"add_filter_description": "Click to add a filter condition",
|
||||||
"add_location": "Add location",
|
"add_location": "Add location",
|
||||||
"add_more_users": "Add more users",
|
"add_more_users": "Add more users",
|
||||||
"add_partner": "Add partner",
|
"add_partner": "Add partner",
|
||||||
"add_path": "Add path",
|
"add_path": "Add path",
|
||||||
"add_photos": "Add photos",
|
"add_photos": "Add photos",
|
||||||
"add_step": "Add step",
|
|
||||||
"add_tag": "Add tag",
|
"add_tag": "Add tag",
|
||||||
"add_to": "Add to…",
|
"add_to": "Add to…",
|
||||||
"add_to_album": "Add to album",
|
"add_to_album": "Add to album",
|
||||||
@@ -41,6 +42,7 @@
|
|||||||
"add_to_shared_album": "Add to shared album",
|
"add_to_shared_album": "Add to shared album",
|
||||||
"add_upload_to_stack": "Add upload to stack",
|
"add_upload_to_stack": "Add upload to stack",
|
||||||
"add_url": "Add URL",
|
"add_url": "Add URL",
|
||||||
|
"add_workflow_step": "Add workflow step",
|
||||||
"added_to_archive": "Added to archive",
|
"added_to_archive": "Added to archive",
|
||||||
"added_to_favorites": "Added to favorites",
|
"added_to_favorites": "Added to favorites",
|
||||||
"added_to_favorites_count": "Added {count, number} to favorites",
|
"added_to_favorites_count": "Added {count, number} to favorites",
|
||||||
@@ -698,7 +700,6 @@
|
|||||||
"birthdate_saved": "Date of birth saved successfully",
|
"birthdate_saved": "Date of birth saved successfully",
|
||||||
"birthdate_set_description": "Date of birth is used to calculate the age of this person at the time of a photo.",
|
"birthdate_set_description": "Date of birth is used to calculate the age of this person at the time of a photo.",
|
||||||
"blurred_background": "Blurred background",
|
"blurred_background": "Blurred background",
|
||||||
"browse_templates": "Browse templates",
|
|
||||||
"bugs_and_feature_requests": "Bugs & Feature Requests",
|
"bugs_and_feature_requests": "Bugs & Feature Requests",
|
||||||
"build": "Build",
|
"build": "Build",
|
||||||
"build_image": "Build Image",
|
"build_image": "Build Image",
|
||||||
@@ -732,7 +733,6 @@
|
|||||||
"cannot_update_the_description": "Cannot update the description",
|
"cannot_update_the_description": "Cannot update the description",
|
||||||
"cast": "Cast",
|
"cast": "Cast",
|
||||||
"cast_description": "Configure available cast destinations",
|
"cast_description": "Configure available cast destinations",
|
||||||
"change": "Change",
|
|
||||||
"change_date": "Change date",
|
"change_date": "Change date",
|
||||||
"change_description": "Change description",
|
"change_description": "Change description",
|
||||||
"change_display_order": "Change display order",
|
"change_display_order": "Change display order",
|
||||||
@@ -761,7 +761,6 @@
|
|||||||
"check_corrupt_asset_backup_description": "Run this check only over Wi-Fi and once all assets have been backed-up. The procedure might take a few minutes.",
|
"check_corrupt_asset_backup_description": "Run this check only over Wi-Fi and once all assets have been backed-up. The procedure might take a few minutes.",
|
||||||
"check_logs": "Check Logs",
|
"check_logs": "Check Logs",
|
||||||
"checksum": "Checksum",
|
"checksum": "Checksum",
|
||||||
"choose": "Choose",
|
|
||||||
"choose_matching_people_to_merge": "Choose matching people to merge",
|
"choose_matching_people_to_merge": "Choose matching people to merge",
|
||||||
"city": "City",
|
"city": "City",
|
||||||
"cleanup_confirm_description": "Immich found {count} assets (created before {date}) safely backed up to the server. Remove the local copies from this device?",
|
"cleanup_confirm_description": "Immich found {count} assets (created before {date}) safely backed up to the server. Remove the local copies from this device?",
|
||||||
@@ -779,7 +778,6 @@
|
|||||||
"clear": "Clear",
|
"clear": "Clear",
|
||||||
"clear_all": "Clear all",
|
"clear_all": "Clear all",
|
||||||
"clear_all_recent_searches": "Clear all recent searches",
|
"clear_all_recent_searches": "Clear all recent searches",
|
||||||
"clear_failed_count": "Clear failed ({count})",
|
|
||||||
"clear_file_cache": "Clear File Cache",
|
"clear_file_cache": "Clear File Cache",
|
||||||
"clear_message": "Clear message",
|
"clear_message": "Clear message",
|
||||||
"clear_value": "Clear value",
|
"clear_value": "Clear value",
|
||||||
@@ -811,7 +809,6 @@
|
|||||||
"comments_are_disabled": "Comments are disabled",
|
"comments_are_disabled": "Comments are disabled",
|
||||||
"common_create_new_album": "Create new album",
|
"common_create_new_album": "Create new album",
|
||||||
"completed": "Completed",
|
"completed": "Completed",
|
||||||
"configuration": "Configuration",
|
|
||||||
"confirm": "Confirm",
|
"confirm": "Confirm",
|
||||||
"confirm_admin_password": "Confirm Admin Password",
|
"confirm_admin_password": "Confirm Admin Password",
|
||||||
"confirm_delete_face": "Are you sure you want to delete {name} face from the asset?",
|
"confirm_delete_face": "Are you sure you want to delete {name} face from the asset?",
|
||||||
@@ -826,7 +823,6 @@
|
|||||||
"contain": "Contain",
|
"contain": "Contain",
|
||||||
"context": "Context",
|
"context": "Context",
|
||||||
"continue": "Continue",
|
"continue": "Continue",
|
||||||
"control_bottom_app_bar_add_tags": "Add Tags",
|
|
||||||
"control_bottom_app_bar_create_new_album": "Create new album",
|
"control_bottom_app_bar_create_new_album": "Create new album",
|
||||||
"control_bottom_app_bar_delete_from_immich": "Delete from Immich",
|
"control_bottom_app_bar_delete_from_immich": "Delete from Immich",
|
||||||
"control_bottom_app_bar_delete_from_local": "Delete from device",
|
"control_bottom_app_bar_delete_from_local": "Delete from device",
|
||||||
@@ -840,7 +836,6 @@
|
|||||||
"copy_error": "Copy error",
|
"copy_error": "Copy error",
|
||||||
"copy_file_path": "Copy file path",
|
"copy_file_path": "Copy file path",
|
||||||
"copy_image": "Copy Image",
|
"copy_image": "Copy Image",
|
||||||
"copy_json": "Copy JSON",
|
|
||||||
"copy_link": "Copy link",
|
"copy_link": "Copy link",
|
||||||
"copy_link_to_clipboard": "Copy link to clipboard",
|
"copy_link_to_clipboard": "Copy link to clipboard",
|
||||||
"copy_password": "Copy password",
|
"copy_password": "Copy password",
|
||||||
@@ -899,7 +894,6 @@
|
|||||||
"date_of_birth": "Date of birth",
|
"date_of_birth": "Date of birth",
|
||||||
"date_of_birth_saved": "Date of birth saved successfully",
|
"date_of_birth_saved": "Date of birth saved successfully",
|
||||||
"date_range": "Date range",
|
"date_range": "Date range",
|
||||||
"date_time_original": "Date/Time Original",
|
|
||||||
"day": "Day",
|
"day": "Day",
|
||||||
"days": "Days",
|
"days": "Days",
|
||||||
"deduplicate_all": "Deduplicate All",
|
"deduplicate_all": "Deduplicate All",
|
||||||
@@ -978,10 +972,7 @@
|
|||||||
"downloading_asset_filename": "Downloading asset {filename}",
|
"downloading_asset_filename": "Downloading asset {filename}",
|
||||||
"downloading_from_icloud": "Downloading from iCloud",
|
"downloading_from_icloud": "Downloading from iCloud",
|
||||||
"downloading_media": "Downloading media",
|
"downloading_media": "Downloading media",
|
||||||
"drag_to_reorder": "Drag to reorder",
|
|
||||||
"drop_files_to_upload": "Drop files anywhere to upload",
|
"drop_files_to_upload": "Drop files anywhere to upload",
|
||||||
"duplicate": "Duplicate",
|
|
||||||
"duplicate_workflow": "Duplicate workflow",
|
|
||||||
"duplicates": "Duplicates",
|
"duplicates": "Duplicates",
|
||||||
"duplicates_description": "Resolve each group by indicating which, if any, are duplicates.",
|
"duplicates_description": "Resolve each group by indicating which, if any, are duplicates.",
|
||||||
"duration": "Duration",
|
"duration": "Duration",
|
||||||
@@ -1083,7 +1074,6 @@
|
|||||||
"failed_to_remove_product_key": "Failed to remove product key",
|
"failed_to_remove_product_key": "Failed to remove product key",
|
||||||
"failed_to_reset_pin_code": "Failed to reset PIN code",
|
"failed_to_reset_pin_code": "Failed to reset PIN code",
|
||||||
"failed_to_stack_assets": "Failed to stack assets",
|
"failed_to_stack_assets": "Failed to stack assets",
|
||||||
"failed_to_tag_assets": "Failed to tag assets",
|
|
||||||
"failed_to_unstack_assets": "Failed to un-stack assets",
|
"failed_to_unstack_assets": "Failed to un-stack assets",
|
||||||
"failed_to_update_notification_status": "Failed to update notification status",
|
"failed_to_update_notification_status": "Failed to update notification status",
|
||||||
"incorrect_email_or_password": "Incorrect email or password",
|
"incorrect_email_or_password": "Incorrect email or password",
|
||||||
@@ -1203,13 +1193,11 @@
|
|||||||
"export_as_json": "Export as JSON",
|
"export_as_json": "Export as JSON",
|
||||||
"export_database": "Export Database",
|
"export_database": "Export Database",
|
||||||
"export_database_description": "Export the SQLite database",
|
"export_database_description": "Export the SQLite database",
|
||||||
"exposure_time": "Exposure Time",
|
|
||||||
"extension": "Extension",
|
"extension": "Extension",
|
||||||
"external": "External",
|
"external": "External",
|
||||||
"external_libraries": "External Libraries",
|
"external_libraries": "External Libraries",
|
||||||
"external_network": "External network",
|
"external_network": "External network",
|
||||||
"external_network_sheet_info": "When not on the preferred Wi-Fi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom",
|
"external_network_sheet_info": "When not on the preferred Wi-Fi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom",
|
||||||
"f_number": "F-Number",
|
|
||||||
"face_unassigned": "Unassigned",
|
"face_unassigned": "Unassigned",
|
||||||
"failed": "Failed",
|
"failed": "Failed",
|
||||||
"failed_count": "Failed: {count}",
|
"failed_count": "Failed: {count}",
|
||||||
@@ -1227,6 +1215,7 @@
|
|||||||
"features_setting_description": "Manage the app features",
|
"features_setting_description": "Manage the app features",
|
||||||
"file_name_or_extension": "File name or extension",
|
"file_name_or_extension": "File name or extension",
|
||||||
"file_name_text": "File name",
|
"file_name_text": "File name",
|
||||||
|
"file_name_with_value": "File name: {file_name}",
|
||||||
"file_size": "File size",
|
"file_size": "File size",
|
||||||
"filename": "Filename",
|
"filename": "Filename",
|
||||||
"filetype": "Filetype",
|
"filetype": "Filetype",
|
||||||
@@ -1239,7 +1228,6 @@
|
|||||||
"find_them_fast": "Find them fast by name with search",
|
"find_them_fast": "Find them fast by name with search",
|
||||||
"first": "First",
|
"first": "First",
|
||||||
"fix_incorrect_match": "Fix incorrect match",
|
"fix_incorrect_match": "Fix incorrect match",
|
||||||
"focal_length": "Focal Length",
|
|
||||||
"folder": "Folder",
|
"folder": "Folder",
|
||||||
"folder_not_found": "Folder not found",
|
"folder_not_found": "Folder not found",
|
||||||
"folders": "Folders",
|
"folders": "Folders",
|
||||||
@@ -1360,7 +1348,6 @@
|
|||||||
"ios_debug_info_no_sync_yet": "No background sync job has run yet",
|
"ios_debug_info_no_sync_yet": "No background sync job has run yet",
|
||||||
"ios_debug_info_processes_queued": "{count, plural, one {{count} background process queued} other {{count} background processes queued}}",
|
"ios_debug_info_processes_queued": "{count, plural, one {{count} background process queued} other {{count} background processes queued}}",
|
||||||
"ios_debug_info_processing_ran_at": "Processing ran {dateTime}",
|
"ios_debug_info_processing_ran_at": "Processing ran {dateTime}",
|
||||||
"iso": "ISO",
|
|
||||||
"items_count": "{count, plural, one {# item} other {# items}}",
|
"items_count": "{count, plural, one {# item} other {# items}}",
|
||||||
"jobs": "Jobs",
|
"jobs": "Jobs",
|
||||||
"json_editor": "JSON editor",
|
"json_editor": "JSON editor",
|
||||||
@@ -1593,7 +1580,6 @@
|
|||||||
"mobile_app": "Mobile App",
|
"mobile_app": "Mobile App",
|
||||||
"mobile_app_download_onboarding_note": "Download the companion mobile app using the following options",
|
"mobile_app_download_onboarding_note": "Download the companion mobile app using the following options",
|
||||||
"model": "Model",
|
"model": "Model",
|
||||||
"modify_date": "Modify Date",
|
|
||||||
"month": "Month",
|
"month": "Month",
|
||||||
"more": "More",
|
"more": "More",
|
||||||
"motion": "Motion",
|
"motion": "Motion",
|
||||||
@@ -1642,6 +1628,7 @@
|
|||||||
"next": "Next",
|
"next": "Next",
|
||||||
"next_memory": "Next memory",
|
"next_memory": "Next memory",
|
||||||
"no": "No",
|
"no": "No",
|
||||||
|
"no_actions_added": "No actions added yet",
|
||||||
"no_albums_found": "No albums found",
|
"no_albums_found": "No albums found",
|
||||||
"no_albums_message": "Create an album to organize your photos and videos",
|
"no_albums_message": "Create an album to organize your photos and videos",
|
||||||
"no_albums_with_name_yet": "It looks like you do not have any albums with this name yet.",
|
"no_albums_with_name_yet": "It looks like you do not have any albums with this name yet.",
|
||||||
@@ -1658,6 +1645,7 @@
|
|||||||
"no_exif_info_available": "No exif info available",
|
"no_exif_info_available": "No exif info available",
|
||||||
"no_explore_results_message": "Upload more photos to explore your collection.",
|
"no_explore_results_message": "Upload more photos to explore your collection.",
|
||||||
"no_favorites_message": "Add favorites to quickly find your best pictures and videos",
|
"no_favorites_message": "Add favorites to quickly find your best pictures and videos",
|
||||||
|
"no_filters_added": "No filters added yet",
|
||||||
"no_libraries_message": "Create an external library to view your photos and videos",
|
"no_libraries_message": "Create an external library to view your photos and videos",
|
||||||
"no_local_assets_found": "No local assets found with this checksum",
|
"no_local_assets_found": "No local assets found with this checksum",
|
||||||
"no_location_set": "No location set",
|
"no_location_set": "No location set",
|
||||||
@@ -1670,7 +1658,6 @@
|
|||||||
"no_results": "No results",
|
"no_results": "No results",
|
||||||
"no_results_description": "Try a synonym or more general keyword",
|
"no_results_description": "Try a synonym or more general keyword",
|
||||||
"no_shared_albums_message": "Create an album to share photos and videos with people in your network",
|
"no_shared_albums_message": "Create an album to share photos and videos with people in your network",
|
||||||
"no_steps": "No steps added yet",
|
|
||||||
"no_uploads_in_progress": "No uploads in progress",
|
"no_uploads_in_progress": "No uploads in progress",
|
||||||
"none": "None",
|
"none": "None",
|
||||||
"not_allowed": "Not allowed",
|
"not_allowed": "Not allowed",
|
||||||
@@ -1716,7 +1703,6 @@
|
|||||||
"organize_into_albums": "Organize into albums",
|
"organize_into_albums": "Organize into albums",
|
||||||
"organize_into_albums_description": "Put existing photos into albums using current sync settings",
|
"organize_into_albums_description": "Put existing photos into albums using current sync settings",
|
||||||
"organize_your_library": "Organize your library",
|
"organize_your_library": "Organize your library",
|
||||||
"orientation": "Orientation",
|
|
||||||
"original": "original",
|
"original": "original",
|
||||||
"other": "Other",
|
"other": "Other",
|
||||||
"other_devices": "Other devices",
|
"other_devices": "Other devices",
|
||||||
@@ -1808,8 +1794,6 @@
|
|||||||
"play_original_video_setting_description": "Prefer playback of original videos rather than transcoded videos. If original asset is not compatible it may not playback correctly.",
|
"play_original_video_setting_description": "Prefer playback of original videos rather than transcoded videos. If original asset is not compatible it may not playback correctly.",
|
||||||
"play_transcoded_video": "Play transcoded video",
|
"play_transcoded_video": "Play transcoded video",
|
||||||
"please_auth_to_access": "Please authenticate to access",
|
"please_auth_to_access": "Please authenticate to access",
|
||||||
"plugin_method_filter_type": "Filter",
|
|
||||||
"plugin_method_filter_type_description": "This method can filter events and conditionally prevent subsequent steps from running",
|
|
||||||
"port": "Port",
|
"port": "Port",
|
||||||
"preferences_settings_subtitle": "Manage the app's preferences",
|
"preferences_settings_subtitle": "Manage the app's preferences",
|
||||||
"preferences_settings_title": "Preferences",
|
"preferences_settings_title": "Preferences",
|
||||||
@@ -1831,7 +1815,6 @@
|
|||||||
"profile_drawer_readonly_mode": "Read-only mode enabled. Long-press the user avatar icon to exit.",
|
"profile_drawer_readonly_mode": "Read-only mode enabled. Long-press the user avatar icon to exit.",
|
||||||
"profile_image_of_user": "Profile image of {user}",
|
"profile_image_of_user": "Profile image of {user}",
|
||||||
"profile_picture_set": "Profile picture set.",
|
"profile_picture_set": "Profile picture set.",
|
||||||
"projection_type": "Projection Type",
|
|
||||||
"public_album": "Public album",
|
"public_album": "Public album",
|
||||||
"public_share": "Public Share",
|
"public_share": "Public Share",
|
||||||
"purchase_account_info": "Supporter",
|
"purchase_account_info": "Supporter",
|
||||||
@@ -2201,9 +2184,7 @@
|
|||||||
"show_in_timeline": "Show in timeline",
|
"show_in_timeline": "Show in timeline",
|
||||||
"show_in_timeline_setting_description": "Show photos and videos from this user in your timeline",
|
"show_in_timeline_setting_description": "Show photos and videos from this user in your timeline",
|
||||||
"show_keyboard_shortcuts": "Show keyboard shortcuts",
|
"show_keyboard_shortcuts": "Show keyboard shortcuts",
|
||||||
"show_less": "Show less",
|
|
||||||
"show_metadata": "Show metadata",
|
"show_metadata": "Show metadata",
|
||||||
"show_more_fields": "{count, plural, one {Show # more field} other {Show # more fields}}",
|
|
||||||
"show_or_hide_info": "Show or hide info",
|
"show_or_hide_info": "Show or hide info",
|
||||||
"show_password": "Show password",
|
"show_password": "Show password",
|
||||||
"show_person_options": "Show person options",
|
"show_person_options": "Show person options",
|
||||||
@@ -2255,11 +2236,6 @@
|
|||||||
"start_date_before_end_date": "Start date must be before end date",
|
"start_date_before_end_date": "Start date must be before end date",
|
||||||
"state": "State",
|
"state": "State",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"step_delete": "Delete step",
|
|
||||||
"step_delete_confirm": "Are you sure you want to delete this step?",
|
|
||||||
"step_details": "Step details",
|
|
||||||
"steps": "Steps",
|
|
||||||
"steps_count": "{count, plural, one {# step} other {# steps}}",
|
|
||||||
"stop_casting": "Stop casting",
|
"stop_casting": "Stop casting",
|
||||||
"stop_motion_photo": "Stop Motion Photo",
|
"stop_motion_photo": "Stop Motion Photo",
|
||||||
"stop_photo_sharing": "Stop sharing your photos?",
|
"stop_photo_sharing": "Stop sharing your photos?",
|
||||||
@@ -2353,7 +2329,7 @@
|
|||||||
"trash_page_title": "Trash ({count})",
|
"trash_page_title": "Trash ({count})",
|
||||||
"trashed_items_will_be_permanently_deleted_after": "Trashed items will be permanently deleted after {days, plural, one {# day} other {# days}}.",
|
"trashed_items_will_be_permanently_deleted_after": "Trashed items will be permanently deleted after {days, plural, one {# day} other {# days}}.",
|
||||||
"trigger": "Trigger",
|
"trigger": "Trigger",
|
||||||
"trigger_asset_uploaded": "Asset Upload",
|
"trigger_asset_uploaded": "Asset Uploaded",
|
||||||
"trigger_asset_uploaded_description": "Triggered when a new asset is uploaded",
|
"trigger_asset_uploaded_description": "Triggered when a new asset is uploaded",
|
||||||
"trigger_description": "An event that kicks off the workflow",
|
"trigger_description": "An event that kicks off the workflow",
|
||||||
"trigger_person_recognized": "Person Recognized",
|
"trigger_person_recognized": "Person Recognized",
|
||||||
@@ -2393,6 +2369,7 @@
|
|||||||
"unsupported_field_type": "Unsupported field type",
|
"unsupported_field_type": "Unsupported field type",
|
||||||
"unsupported_file_type": "File {file} can't be uploaded because its file type {type} is not supported.",
|
"unsupported_file_type": "File {file} can't be uploaded because its file type {type} is not supported.",
|
||||||
"untagged": "Untagged",
|
"untagged": "Untagged",
|
||||||
|
"untitled_workflow": "Untitled workflow",
|
||||||
"up_next": "Up next",
|
"up_next": "Up next",
|
||||||
"update_location_action_prompt": "Update the location of {count} selected assets with:",
|
"update_location_action_prompt": "Update the location of {count} selected assets with:",
|
||||||
"updated_at": "Updated",
|
"updated_at": "Updated",
|
||||||
@@ -2421,7 +2398,6 @@
|
|||||||
"use_browser_locale_description": "Format dates, times, and numbers based on your browser locale",
|
"use_browser_locale_description": "Format dates, times, and numbers based on your browser locale",
|
||||||
"use_current_connection": "Use current connection",
|
"use_current_connection": "Use current connection",
|
||||||
"use_custom_date_range": "Use custom date range instead",
|
"use_custom_date_range": "Use custom date range instead",
|
||||||
"use_template": "Use template",
|
|
||||||
"user": "User",
|
"user": "User",
|
||||||
"user_has_been_deleted": "This user has been deleted.",
|
"user_has_been_deleted": "This user has been deleted.",
|
||||||
"user_id": "User ID",
|
"user_id": "User ID",
|
||||||
@@ -2483,10 +2459,8 @@
|
|||||||
"week": "Week",
|
"week": "Week",
|
||||||
"welcome": "Welcome",
|
"welcome": "Welcome",
|
||||||
"welcome_to_immich": "Welcome to Immich",
|
"welcome_to_immich": "Welcome to Immich",
|
||||||
"when": "When",
|
|
||||||
"width": "Width",
|
"width": "Width",
|
||||||
"wifi_name": "Wi-Fi Name",
|
"wifi_name": "Wi-Fi Name",
|
||||||
"workflow": "Workflow",
|
|
||||||
"workflow_delete_prompt": "Are you sure you want to delete this workflow?",
|
"workflow_delete_prompt": "Are you sure you want to delete this workflow?",
|
||||||
"workflow_deleted": "Workflow deleted",
|
"workflow_deleted": "Workflow deleted",
|
||||||
"workflow_description": "Workflow description",
|
"workflow_description": "Workflow description",
|
||||||
@@ -2496,7 +2470,6 @@
|
|||||||
"workflow_name": "Workflow name",
|
"workflow_name": "Workflow name",
|
||||||
"workflow_navigation_prompt": "Are you sure you want to leave without saving your changes?",
|
"workflow_navigation_prompt": "Are you sure you want to leave without saving your changes?",
|
||||||
"workflow_summary": "Workflow summary",
|
"workflow_summary": "Workflow summary",
|
||||||
"workflow_templates": "Workflow templates",
|
|
||||||
"workflow_update_success": "Workflow updated successfully",
|
"workflow_update_success": "Workflow updated successfully",
|
||||||
"workflow_updated": "Workflow updated",
|
"workflow_updated": "Workflow updated",
|
||||||
"workflows": "Workflows",
|
"workflows": "Workflows",
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from pathlib import Path
|
|||||||
from socket import socket
|
from socket import socket
|
||||||
|
|
||||||
from gunicorn.arbiter import Arbiter
|
from gunicorn.arbiter import Arbiter
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel
|
||||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from rich.logging import RichHandler
|
from rich.logging import RichHandler
|
||||||
@@ -42,10 +42,6 @@ class MaxBatchSize(BaseModel):
|
|||||||
ocr: int | None = None
|
ocr: int | None = None
|
||||||
|
|
||||||
|
|
||||||
def default_worker_timeout() -> int:
|
|
||||||
return 900 if os.environ.get("DEVICE") == "rocm" else 300
|
|
||||||
|
|
||||||
|
|
||||||
class Settings(BaseSettings):
|
class Settings(BaseSettings):
|
||||||
model_config = SettingsConfigDict(
|
model_config = SettingsConfigDict(
|
||||||
env_prefix="MACHINE_LEARNING_",
|
env_prefix="MACHINE_LEARNING_",
|
||||||
@@ -58,7 +54,7 @@ class Settings(BaseSettings):
|
|||||||
model_ttl: int = 300
|
model_ttl: int = 300
|
||||||
model_ttl_poll_s: int = 10
|
model_ttl_poll_s: int = 10
|
||||||
workers: int = 1
|
workers: int = 1
|
||||||
worker_timeout: int = Field(default_factory=default_worker_timeout)
|
worker_timeout: int = 300
|
||||||
http_keepalive_timeout_s: int = 2
|
http_keepalive_timeout_s: int = 2
|
||||||
test_full: bool = False
|
test_full: bool = False
|
||||||
request_threads: int = os.cpu_count() or 4
|
request_threads: int = os.cpu_count() or 4
|
||||||
|
|||||||
@@ -89,10 +89,4 @@ class FaceRecognizer(InferenceModel):
|
|||||||
@property
|
@property
|
||||||
def _batch_size_default(self) -> int | None:
|
def _batch_size_default(self) -> int | None:
|
||||||
providers = ort.get_available_providers()
|
providers = ort.get_available_providers()
|
||||||
if (
|
return None if self.model_format == ModelFormat.ONNX and "OpenVINOExecutionProvider" not in providers else 1
|
||||||
self.model_format == ModelFormat.ONNX
|
|
||||||
and "MIGraphXExecutionProvider" not in providers
|
|
||||||
and "OpenVINOExecutionProvider" not in providers
|
|
||||||
):
|
|
||||||
return None
|
|
||||||
return 1
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from threading import Lock
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
@@ -13,37 +12,6 @@ from immich_ml.schemas import ModelPrecision, SessionNode
|
|||||||
|
|
||||||
from ..config import log, settings
|
from ..config import log, settings
|
||||||
|
|
||||||
MigraphxInputSignature = tuple[tuple[str, str, tuple[int, ...]], ...]
|
|
||||||
|
|
||||||
_migraphx_registry_lock = Lock()
|
|
||||||
_migraphx_model_locks: dict[str, Lock] = {}
|
|
||||||
_migraphx_compiled_inputs: set[tuple[str, MigraphxInputSignature]] = set()
|
|
||||||
|
|
||||||
|
|
||||||
def _migraphx_get_model_lock(model_key: str) -> Lock:
|
|
||||||
with _migraphx_registry_lock:
|
|
||||||
lock = _migraphx_model_locks.get(model_key)
|
|
||||||
if lock is None:
|
|
||||||
lock = Lock()
|
|
||||||
_migraphx_model_locks[model_key] = lock
|
|
||||||
return lock
|
|
||||||
|
|
||||||
|
|
||||||
def _migraphx_has_compiled_input(key: tuple[str, MigraphxInputSignature]) -> bool:
|
|
||||||
with _migraphx_registry_lock:
|
|
||||||
return key in _migraphx_compiled_inputs
|
|
||||||
|
|
||||||
|
|
||||||
def _migraphx_mark_compiled_input(key: tuple[str, MigraphxInputSignature]) -> None:
|
|
||||||
with _migraphx_registry_lock:
|
|
||||||
_migraphx_compiled_inputs.add(key)
|
|
||||||
|
|
||||||
|
|
||||||
def _migraphx_input_signature(
|
|
||||||
input_feed: dict[str, NDArray[np.float32]] | dict[str, NDArray[np.int32]],
|
|
||||||
) -> MigraphxInputSignature:
|
|
||||||
return tuple((name, str(value.dtype), tuple(value.shape)) for name, value in sorted(input_feed.items()))
|
|
||||||
|
|
||||||
|
|
||||||
class OrtSession:
|
class OrtSession:
|
||||||
session: ort.InferenceSession
|
session: ort.InferenceSession
|
||||||
@@ -80,21 +48,7 @@ class OrtSession:
|
|||||||
input_feed: dict[str, NDArray[np.float32]] | dict[str, NDArray[np.int32]],
|
input_feed: dict[str, NDArray[np.float32]] | dict[str, NDArray[np.int32]],
|
||||||
run_options: Any = None,
|
run_options: Any = None,
|
||||||
) -> list[NDArray[np.float32]]:
|
) -> list[NDArray[np.float32]]:
|
||||||
if "MIGraphXExecutionProvider" in self.providers:
|
outputs: list[NDArray[np.float32]] = self.session.run(output_names, input_feed, run_options)
|
||||||
model_key = self.model_path.resolve().as_posix()
|
|
||||||
input_key = (model_key, _migraphx_input_signature(input_feed))
|
|
||||||
if not _migraphx_has_compiled_input(input_key):
|
|
||||||
model_lock = _migraphx_get_model_lock(model_key)
|
|
||||||
with model_lock:
|
|
||||||
if not _migraphx_has_compiled_input(input_key):
|
|
||||||
outputs: list[NDArray[np.float32]] = self.session.run(output_names, input_feed, run_options)
|
|
||||||
_migraphx_mark_compiled_input(input_key)
|
|
||||||
return outputs
|
|
||||||
|
|
||||||
outputs = self.session.run(output_names, input_feed, run_options)
|
|
||||||
return outputs
|
|
||||||
|
|
||||||
outputs = self.session.run(output_names, input_feed, run_options)
|
|
||||||
return outputs
|
return outputs
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
# @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html
|
|
||||||
|
|
||||||
[[tools.python]]
|
|
||||||
version = "3.11.15"
|
|
||||||
backend = "core:python"
|
|
||||||
|
|
||||||
[tools.python."platforms.linux-arm64"]
|
|
||||||
checksum = "sha256:243f794278eff6adba96ed3677ec6877175df84c25f140e17f09f9be82d0f12a"
|
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260510/cpython-3.11.15+20260510-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
|
||||||
provenance = "github-attestations"
|
|
||||||
|
|
||||||
[tools.python."platforms.linux-arm64-musl"]
|
|
||||||
checksum = "sha256:52b4c52094ff8b383a45c694acf4c5c0e883152be6d5229a35a8186ce907c6eb"
|
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260510/cpython-3.11.15+20260510-aarch64-unknown-linux-musl-install_only_stripped.tar.gz"
|
|
||||||
provenance = "github-attestations"
|
|
||||||
|
|
||||||
[tools.python."platforms.linux-x64"]
|
|
||||||
checksum = "sha256:171dffd8c0f66e8a0725364a7428015b22fc18dd298b24f541392e17dd0e561f"
|
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260510/cpython-3.11.15+20260510-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
|
||||||
provenance = "github-attestations"
|
|
||||||
|
|
||||||
[tools.python."platforms.linux-x64-musl"]
|
|
||||||
checksum = "sha256:2ac90fef8917ebd14826a6d667593a06cf0ae5f745ba9b1147dc086dd35f5284"
|
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260510/cpython-3.11.15+20260510-x86_64-unknown-linux-musl-install_only_stripped.tar.gz"
|
|
||||||
provenance = "github-attestations"
|
|
||||||
|
|
||||||
[tools.python."platforms.macos-arm64"]
|
|
||||||
checksum = "sha256:fdfc363b538662eb7441a14e06f72c4a992c56af7f401f5730ea5081f8f8ad6e"
|
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260510/cpython-3.11.15+20260510-aarch64-apple-darwin-install_only_stripped.tar.gz"
|
|
||||||
provenance = "github-attestations"
|
|
||||||
|
|
||||||
[tools.python."platforms.macos-x64"]
|
|
||||||
checksum = "sha256:5f1eb247cbca2c0ad5ccbf6d299a4f54b31b5c63b492d74c3531dc4344a42f88"
|
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260510/cpython-3.11.15+20260510-x86_64-apple-darwin-install_only_stripped.tar.gz"
|
|
||||||
provenance = "github-attestations"
|
|
||||||
|
|
||||||
[tools.python."platforms.windows-x64"]
|
|
||||||
checksum = "sha256:756d7f148498b8822f6aedf44a020613576f09983161f346ad36dcef6238cdc3"
|
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260510/cpython-3.11.15+20260510-x86_64-pc-windows-msvc-install_only_stripped.tar.gz"
|
|
||||||
provenance = "github-attestations"
|
|
||||||
|
|
||||||
[[tools.uv]]
|
|
||||||
version = "0.8.15"
|
|
||||||
backend = "aqua:astral-sh/uv"
|
|
||||||
|
|
||||||
[tools.uv."platforms.linux-arm64"]
|
|
||||||
checksum = "sha256:23ea21a05c62c4c307ce691f29bff2f15c94c4f07f2b83d9b356f0664bc8b3a2"
|
|
||||||
url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-aarch64-unknown-linux-musl.tar.gz"
|
|
||||||
|
|
||||||
[tools.uv."platforms.linux-arm64-musl"]
|
|
||||||
checksum = "sha256:23ea21a05c62c4c307ce691f29bff2f15c94c4f07f2b83d9b356f0664bc8b3a2"
|
|
||||||
url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-aarch64-unknown-linux-musl.tar.gz"
|
|
||||||
|
|
||||||
[tools.uv."platforms.linux-x64"]
|
|
||||||
checksum = "sha256:d0fec58f3124e05e0a1af0f6541abfce4333253cdaf23c7b6bb2e6128bf138ea"
|
|
||||||
url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-x86_64-unknown-linux-musl.tar.gz"
|
|
||||||
|
|
||||||
[tools.uv."platforms.linux-x64-musl"]
|
|
||||||
checksum = "sha256:d0fec58f3124e05e0a1af0f6541abfce4333253cdaf23c7b6bb2e6128bf138ea"
|
|
||||||
url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-x86_64-unknown-linux-musl.tar.gz"
|
|
||||||
|
|
||||||
[tools.uv."platforms.macos-arm64"]
|
|
||||||
checksum = "sha256:103367962c5cb00bf7370d84cbaa3fec5a9807be9cc833ea9d8eea400c119fa2"
|
|
||||||
url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-aarch64-apple-darwin.tar.gz"
|
|
||||||
|
|
||||||
[tools.uv."platforms.macos-x64"]
|
|
||||||
checksum = "sha256:2bbef70982e97dfc36454de173f35ec1a5e83ae11e3885df6a50db3fd76171cb"
|
|
||||||
url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-x86_64-apple-darwin.tar.gz"
|
|
||||||
|
|
||||||
[tools.uv."platforms.windows-x64"]
|
|
||||||
checksum = "sha256:459d95892a5cc5c21779532f4f41b9238594b79e312a5142da2148ecfa10e705"
|
|
||||||
url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-x86_64-pc-windows-msvc.zip"
|
|
||||||
@@ -10,7 +10,7 @@ dependencies = [
|
|||||||
"fastapi>=0.95.2,<1.0",
|
"fastapi>=0.95.2,<1.0",
|
||||||
"gunicorn>=21.1.0",
|
"gunicorn>=21.1.0",
|
||||||
"huggingface-hub>=1.0,<2.0",
|
"huggingface-hub>=1.0,<2.0",
|
||||||
"insightface>=0.7.3,<2.0",
|
"insightface>=0.7.3,<1.0",
|
||||||
"numpy>=2.4.0,<3.0",
|
"numpy>=2.4.0,<3.0",
|
||||||
"opencv-python-headless>=4.7.0.72,<5.0",
|
"opencv-python-headless>=4.7.0.72,<5.0",
|
||||||
"orjson>=3.9.5",
|
"orjson>=3.9.5",
|
||||||
|
|||||||
@@ -35,37 +35,7 @@ from immich_ml.sessions.ort import OrtSession
|
|||||||
from immich_ml.sessions.rknn import RknnSession, run_inference
|
from immich_ml.sessions.rknn import RknnSession, run_inference
|
||||||
|
|
||||||
|
|
||||||
class FakeLock:
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self.enter = mock.Mock()
|
|
||||||
self.exit = mock.Mock()
|
|
||||||
|
|
||||||
def __enter__(self) -> None:
|
|
||||||
self.enter()
|
|
||||||
|
|
||||||
def __exit__(self, *args: object) -> None:
|
|
||||||
self.exit(*args)
|
|
||||||
|
|
||||||
|
|
||||||
class TestBase:
|
class TestBase:
|
||||||
def test_sets_default_worker_timeout(self, monkeypatch: MonkeyPatch) -> None:
|
|
||||||
monkeypatch.delenv("DEVICE", raising=False)
|
|
||||||
monkeypatch.delenv("MACHINE_LEARNING_WORKER_TIMEOUT", raising=False)
|
|
||||||
|
|
||||||
assert Settings().worker_timeout == 300
|
|
||||||
|
|
||||||
def test_sets_rocm_default_worker_timeout(self, monkeypatch: MonkeyPatch) -> None:
|
|
||||||
monkeypatch.setenv("DEVICE", "rocm")
|
|
||||||
monkeypatch.delenv("MACHINE_LEARNING_WORKER_TIMEOUT", raising=False)
|
|
||||||
|
|
||||||
assert Settings().worker_timeout == 900
|
|
||||||
|
|
||||||
def test_worker_timeout_env_override(self, monkeypatch: MonkeyPatch) -> None:
|
|
||||||
monkeypatch.setenv("DEVICE", "rocm")
|
|
||||||
monkeypatch.setenv("MACHINE_LEARNING_WORKER_TIMEOUT", "1200")
|
|
||||||
|
|
||||||
assert Settings().worker_timeout == 1200
|
|
||||||
|
|
||||||
def test_sets_default_cache_dir(self) -> None:
|
def test_sets_default_cache_dir(self) -> None:
|
||||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai")
|
encoder = OpenClipTextualEncoder("ViT-B-32__openai")
|
||||||
|
|
||||||
@@ -443,52 +413,6 @@ class TestOrtSession:
|
|||||||
|
|
||||||
assert sess_options is session.sess_options
|
assert sess_options is session.sess_options
|
||||||
|
|
||||||
def test_serializes_rocm_first_run_for_new_input_signature(self, mocker: MockerFixture) -> None:
|
|
||||||
lock = FakeLock()
|
|
||||||
get_model_lock = mocker.patch("immich_ml.sessions.ort._migraphx_get_model_lock", return_value=lock)
|
|
||||||
mocker.patch("immich_ml.sessions.ort._migraphx_compiled_inputs", set())
|
|
||||||
mocker.patch("immich_ml.sessions.ort.Path.mkdir")
|
|
||||||
session = OrtSession("/cache/ViT-B-32__openai/model.onnx", providers=["MIGraphXExecutionProvider"])
|
|
||||||
input_feed = {"input": np.random.rand(1, 3, 224, 224).astype(np.float32)}
|
|
||||||
|
|
||||||
session.run(None, input_feed)
|
|
||||||
session.run(None, input_feed)
|
|
||||||
|
|
||||||
lock.enter.assert_called_once()
|
|
||||||
lock.exit.assert_called_once()
|
|
||||||
get_model_lock.assert_called_once()
|
|
||||||
session.session.run.assert_has_calls([mock.call(None, input_feed, None), mock.call(None, input_feed, None)])
|
|
||||||
|
|
||||||
def test_serializes_rocm_run_for_each_new_input_signature(self, mocker: MockerFixture) -> None:
|
|
||||||
lock = FakeLock()
|
|
||||||
mocker.patch("immich_ml.sessions.ort._migraphx_get_model_lock", return_value=lock)
|
|
||||||
mocker.patch("immich_ml.sessions.ort._migraphx_compiled_inputs", set())
|
|
||||||
mocker.patch("immich_ml.sessions.ort.Path.mkdir")
|
|
||||||
session = OrtSession("/cache/ViT-B-32__openai/model.onnx", providers=["MIGraphXExecutionProvider"])
|
|
||||||
input_feed = {"input": np.random.rand(1, 3, 224, 224).astype(np.float32)}
|
|
||||||
new_shape_input_feed = {"input": np.random.rand(2, 3, 224, 224).astype(np.float32)}
|
|
||||||
|
|
||||||
session.run(None, input_feed)
|
|
||||||
session.run(None, new_shape_input_feed)
|
|
||||||
|
|
||||||
assert lock.enter.call_count == 2
|
|
||||||
assert lock.exit.call_count == 2
|
|
||||||
session.session.run.assert_has_calls(
|
|
||||||
[mock.call(None, input_feed, None), mock.call(None, new_shape_input_feed, None)]
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_does_not_serialize_non_rocm_run(self, mocker: MockerFixture) -> None:
|
|
||||||
lock = FakeLock()
|
|
||||||
get_model_lock = mocker.patch("immich_ml.sessions.ort._migraphx_get_model_lock", return_value=lock)
|
|
||||||
session = OrtSession("/cache/ViT-B-32__openai/model.onnx", providers=["CPUExecutionProvider"])
|
|
||||||
input_feed = {"input": np.random.rand(1, 3, 224, 224).astype(np.float32)}
|
|
||||||
|
|
||||||
session.run(None, input_feed)
|
|
||||||
|
|
||||||
get_model_lock.assert_not_called()
|
|
||||||
lock.enter.assert_not_called()
|
|
||||||
session.session.run.assert_called_once_with(None, input_feed, None)
|
|
||||||
|
|
||||||
|
|
||||||
class TestAnnSession:
|
class TestAnnSession:
|
||||||
def test_creates_ann_session(self, ann_session: mock.Mock, info: mock.Mock) -> None:
|
def test_creates_ann_session(self, ann_session: mock.Mock, info: mock.Mock) -> None:
|
||||||
@@ -959,34 +883,6 @@ class TestFaceRecognition:
|
|||||||
onnx.load.assert_not_called()
|
onnx.load.assert_not_called()
|
||||||
onnx.save.assert_not_called()
|
onnx.save.assert_not_called()
|
||||||
|
|
||||||
def test_recognition_does_not_add_batch_axis_for_migraphx(
|
|
||||||
self, ort_session: mock.Mock, path: mock.Mock, mocker: MockerFixture
|
|
||||||
) -> None:
|
|
||||||
onnx = mocker.patch("immich_ml.models.facial_recognition.recognition.onnx", autospec=True)
|
|
||||||
update_dims = mocker.patch(
|
|
||||||
"immich_ml.models.facial_recognition.recognition.update_inputs_outputs_dims", autospec=True
|
|
||||||
)
|
|
||||||
mocker.patch("immich_ml.models.base.InferenceModel.download")
|
|
||||||
mocker.patch("immich_ml.models.facial_recognition.recognition.ArcFaceONNX")
|
|
||||||
mocker.patch(
|
|
||||||
"immich_ml.models.facial_recognition.recognition.ort.get_available_providers",
|
|
||||||
return_value=["MIGraphXExecutionProvider", "CPUExecutionProvider"],
|
|
||||||
)
|
|
||||||
path.return_value.__truediv__.return_value.__truediv__.return_value.suffix = ".onnx"
|
|
||||||
|
|
||||||
inputs = [SimpleNamespace(name="input.1", shape=(1, 3, 224, 224))]
|
|
||||||
outputs = [SimpleNamespace(name="output.1", shape=(1, 800))]
|
|
||||||
ort_session.return_value.get_inputs.return_value = inputs
|
|
||||||
ort_session.return_value.get_outputs.return_value = outputs
|
|
||||||
|
|
||||||
face_recognizer = FaceRecognizer("buffalo_s", cache_dir=path)
|
|
||||||
face_recognizer.load()
|
|
||||||
|
|
||||||
assert face_recognizer.batch_size == 1
|
|
||||||
update_dims.assert_not_called()
|
|
||||||
onnx.load.assert_not_called()
|
|
||||||
onnx.save.assert_not_called()
|
|
||||||
|
|
||||||
def test_set_custom_max_batch_size(self, mocker: MockerFixture) -> None:
|
def test_set_custom_max_batch_size(self, mocker: MockerFixture) -> None:
|
||||||
mocker.patch.object(settings, "max_batch_size", MaxBatchSize(facial_recognition=2))
|
mocker.patch.object(settings, "max_batch_size", MaxBatchSize(facial_recognition=2))
|
||||||
|
|
||||||
|
|||||||
Generated
+1
-1
@@ -1004,7 +1004,7 @@ requires-dist = [
|
|||||||
{ name = "fastapi", specifier = ">=0.95.2,<1.0" },
|
{ name = "fastapi", specifier = ">=0.95.2,<1.0" },
|
||||||
{ name = "gunicorn", specifier = ">=21.1.0" },
|
{ name = "gunicorn", specifier = ">=21.1.0" },
|
||||||
{ name = "huggingface-hub", specifier = ">=1.0,<2.0" },
|
{ name = "huggingface-hub", specifier = ">=1.0,<2.0" },
|
||||||
{ name = "insightface", specifier = ">=0.7.3,<2.0" },
|
{ name = "insightface", specifier = ">=0.7.3,<1.0" },
|
||||||
{ name = "numpy", specifier = ">=2.4.0,<3.0" },
|
{ name = "numpy", specifier = ">=2.4.0,<3.0" },
|
||||||
{ name = "onnxruntime", marker = "extra == 'armnn'", specifier = ">=1.23.2,<2" },
|
{ name = "onnxruntime", marker = "extra == 'armnn'", specifier = ">=1.23.2,<2" },
|
||||||
{ name = "onnxruntime", marker = "extra == 'cpu'", specifier = ">=1.23.2,<2" },
|
{ name = "onnxruntime", marker = "extra == 'cpu'", specifier = ">=1.23.2,<2" },
|
||||||
|
|||||||
@@ -1,354 +0,0 @@
|
|||||||
# @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html
|
|
||||||
|
|
||||||
[[tools."aqua:flutter/flutter"]]
|
|
||||||
version = "3.44.0"
|
|
||||||
backend = "aqua:flutter/flutter"
|
|
||||||
|
|
||||||
[tools."aqua:flutter/flutter"."platforms.linux-arm64"]
|
|
||||||
url = "https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.44.0-stable.tar.xz"
|
|
||||||
|
|
||||||
[tools."aqua:flutter/flutter"."platforms.linux-arm64-musl"]
|
|
||||||
url = "https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.44.0-stable.tar.xz"
|
|
||||||
|
|
||||||
[tools."aqua:flutter/flutter"."platforms.linux-x64"]
|
|
||||||
url = "https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.44.0-stable.tar.xz"
|
|
||||||
|
|
||||||
[tools."aqua:flutter/flutter"."platforms.linux-x64-musl"]
|
|
||||||
url = "https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.44.0-stable.tar.xz"
|
|
||||||
|
|
||||||
[tools."aqua:flutter/flutter"."platforms.macos-arm64"]
|
|
||||||
checksum = "blake3:fb03aa5d9790205c948922ec3f0751c16e4575b09d6ae9dd4fbeb664a69f0e00"
|
|
||||||
url = "https://storage.googleapis.com/flutter_infra_release/releases/stable/macos/flutter_macos_arm64_3.44.0-stable.zip"
|
|
||||||
|
|
||||||
[tools."aqua:flutter/flutter"."platforms.macos-x64"]
|
|
||||||
url = "https://storage.googleapis.com/flutter_infra_release/releases/stable/macos/flutter_macos_3.44.0-stable.zip"
|
|
||||||
|
|
||||||
[tools."aqua:flutter/flutter"."platforms.windows-x64"]
|
|
||||||
url = "https://storage.googleapis.com/flutter_infra_release/releases/stable/windows/flutter_windows_3.44.0-stable.zip"
|
|
||||||
|
|
||||||
[[tools.flutter]]
|
|
||||||
version = "3.41.9-stable"
|
|
||||||
backend = "asdf:flutter"
|
|
||||||
|
|
||||||
[[tools."github:CQLabs/homebrew-dcm"]]
|
|
||||||
version = "1.37.0"
|
|
||||||
backend = "github:CQLabs/homebrew-dcm"
|
|
||||||
|
|
||||||
[tools."github:CQLabs/homebrew-dcm"."platforms.linux-arm64"]
|
|
||||||
checksum = "sha256:253da2512b149913dfe345bf9a62a79acb2d730f66e71162ba4a92dfc4224b82"
|
|
||||||
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"
|
|
||||||
|
|
||||||
[tools."github:CQLabs/homebrew-dcm"."platforms.linux-arm64-musl"]
|
|
||||||
checksum = "sha256:253da2512b149913dfe345bf9a62a79acb2d730f66e71162ba4a92dfc4224b82"
|
|
||||||
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"
|
|
||||||
|
|
||||||
[tools."github:CQLabs/homebrew-dcm"."platforms.linux-x64"]
|
|
||||||
checksum = "sha256:477e086d4099c12f21e5ccd83b005d5fb945dd4cac4fd127fd9a08d7649af1cf"
|
|
||||||
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"
|
|
||||||
|
|
||||||
[tools."github:CQLabs/homebrew-dcm"."platforms.linux-x64-musl"]
|
|
||||||
checksum = "sha256:477e086d4099c12f21e5ccd83b005d5fb945dd4cac4fd127fd9a08d7649af1cf"
|
|
||||||
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"
|
|
||||||
|
|
||||||
[tools."github:CQLabs/homebrew-dcm"."platforms.macos-arm64"]
|
|
||||||
checksum = "sha256:30bede64367d09067093cc57af6ec9496d7717898138ded5cb98a16ac8dd9d93"
|
|
||||||
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"
|
|
||||||
|
|
||||||
[tools."github:CQLabs/homebrew-dcm"."platforms.macos-x64"]
|
|
||||||
checksum = "sha256:e56cb99872be7445a4de1d37e5438ca70e3bcd83be7a2b9b385e3538881f8068"
|
|
||||||
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"
|
|
||||||
|
|
||||||
[tools."github:CQLabs/homebrew-dcm"."platforms.windows-x64"]
|
|
||||||
checksum = "sha256:f133470daa3fb0427f039b424392af7e917d7e7db6b556aa2a968ab0e31587da"
|
|
||||||
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"
|
|
||||||
|
|
||||||
[[tools."github:extism/cli"]]
|
|
||||||
version = "1.6.3"
|
|
||||||
backend = "github:extism/cli"
|
|
||||||
|
|
||||||
[tools."github:extism/cli"."platforms.linux-arm64"]
|
|
||||||
checksum = "sha256:d92f830c9be39637569feacb04e9750c28848df6d9a219db94152a9b4eb9452b"
|
|
||||||
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"
|
|
||||||
|
|
||||||
[tools."github:extism/cli"."platforms.linux-arm64-musl"]
|
|
||||||
checksum = "sha256:d92f830c9be39637569feacb04e9750c28848df6d9a219db94152a9b4eb9452b"
|
|
||||||
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"
|
|
||||||
|
|
||||||
[tools."github:extism/cli"."platforms.linux-x64"]
|
|
||||||
checksum = "sha256:34e7ae9bfded6e2c32dee83f70a4e50d34f9d3e80d1762b09625fe82e214d02d"
|
|
||||||
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"
|
|
||||||
|
|
||||||
[tools."github:extism/cli"."platforms.linux-x64-musl"]
|
|
||||||
checksum = "sha256:34e7ae9bfded6e2c32dee83f70a4e50d34f9d3e80d1762b09625fe82e214d02d"
|
|
||||||
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"
|
|
||||||
|
|
||||||
[tools."github:extism/cli"."platforms.macos-arm64"]
|
|
||||||
checksum = "sha256:b4ddbc575b5ac000115247f781723f9b9f284ed87b29c600539d72161b5b29fc"
|
|
||||||
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"
|
|
||||||
|
|
||||||
[tools."github:extism/cli"."platforms.macos-x64"]
|
|
||||||
checksum = "sha256:9a2f71b6e6009685a622cc3084e52d2a1a8e23c98d29ffa72e666e9dc699855f"
|
|
||||||
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"
|
|
||||||
|
|
||||||
[tools."github:extism/cli"."platforms.windows-x64"]
|
|
||||||
checksum = "sha256:47e4ed2782445b2b08a4d1ac127211588f8b4d1fc25fd6481d4cb65151b5213c"
|
|
||||||
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"
|
|
||||||
|
|
||||||
[[tools."github:extism/js-pdk"]]
|
|
||||||
version = "1.6.0"
|
|
||||||
backend = "github:extism/js-pdk"
|
|
||||||
|
|
||||||
[tools."github:extism/js-pdk"."platforms.linux-arm64"]
|
|
||||||
checksum = "sha256:15a186250e68d6bff4ec839fff275d45a90e383a69209dcc1239eb9e3aee6e1b"
|
|
||||||
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"
|
|
||||||
|
|
||||||
[tools."github:extism/js-pdk"."platforms.linux-arm64-musl"]
|
|
||||||
checksum = "sha256:15a186250e68d6bff4ec839fff275d45a90e383a69209dcc1239eb9e3aee6e1b"
|
|
||||||
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"
|
|
||||||
|
|
||||||
[tools."github:extism/js-pdk"."platforms.linux-x64"]
|
|
||||||
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_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353223119"
|
|
||||||
|
|
||||||
[tools."github:extism/js-pdk"."platforms.linux-x64-musl"]
|
|
||||||
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_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353223119"
|
|
||||||
|
|
||||||
[tools."github:extism/js-pdk"."platforms.macos-arm64"]
|
|
||||||
checksum = "sha256:548e25bda3971a07c32d78a249135cf8cb7b3eede101e878e06e53e01ac2e0ce"
|
|
||||||
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"
|
|
||||||
|
|
||||||
[tools."github:extism/js-pdk"."platforms.macos-x64"]
|
|
||||||
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_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353223239"
|
|
||||||
|
|
||||||
[tools."github:extism/js-pdk"."platforms.windows-x64"]
|
|
||||||
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_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353224133"
|
|
||||||
|
|
||||||
[[tools."github:jellyfin/jellyfin-ffmpeg"]]
|
|
||||||
version = "7.1.3-6"
|
|
||||||
backend = "github:jellyfin/jellyfin-ffmpeg"
|
|
||||||
|
|
||||||
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-arm64"]
|
|
||||||
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_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048876"
|
|
||||||
|
|
||||||
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-arm64-musl"]
|
|
||||||
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_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048876"
|
|
||||||
|
|
||||||
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-x64"]
|
|
||||||
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_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048879"
|
|
||||||
|
|
||||||
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-x64-musl"]
|
|
||||||
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_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048879"
|
|
||||||
|
|
||||||
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.macos-arm64"]
|
|
||||||
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_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/408995838"
|
|
||||||
|
|
||||||
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.macos-x64"]
|
|
||||||
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_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/408995889"
|
|
||||||
|
|
||||||
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.windows-x64"]
|
|
||||||
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_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409036094"
|
|
||||||
|
|
||||||
[[tools."github:webassembly/binaryen"]]
|
|
||||||
version = "version_124"
|
|
||||||
backend = "github:webassembly/binaryen"
|
|
||||||
|
|
||||||
[tools."github:webassembly/binaryen"."platforms.linux-arm64"]
|
|
||||||
checksum = "sha256:6291bd9a57d8e046f3bc099a4db386c147433a87f71c783a901c5b1792e38de3"
|
|
||||||
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"
|
|
||||||
|
|
||||||
[tools."github:webassembly/binaryen"."platforms.linux-arm64-musl"]
|
|
||||||
checksum = "sha256:6291bd9a57d8e046f3bc099a4db386c147433a87f71c783a901c5b1792e38de3"
|
|
||||||
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"
|
|
||||||
|
|
||||||
[tools."github:webassembly/binaryen"."platforms.linux-x64"]
|
|
||||||
checksum = "sha256:0290c3779fedf592b8da0ded3032ff55c41a2b7bfa2d6bf7b7bac6f0e6e28963"
|
|
||||||
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"
|
|
||||||
|
|
||||||
[tools."github:webassembly/binaryen"."platforms.linux-x64-musl"]
|
|
||||||
checksum = "sha256:0290c3779fedf592b8da0ded3032ff55c41a2b7bfa2d6bf7b7bac6f0e6e28963"
|
|
||||||
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"
|
|
||||||
|
|
||||||
[tools."github:webassembly/binaryen"."platforms.macos-arm64"]
|
|
||||||
checksum = "sha256:86a2c960ff62c6d2ea6009d1f89745c22c70100d394a095eab45eb941bdaa24c"
|
|
||||||
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"
|
|
||||||
|
|
||||||
[tools."github:webassembly/binaryen"."platforms.macos-x64"]
|
|
||||||
checksum = "sha256:b389bb0731758d86c3cb266d01d28a12725c23bd3cabc3df34faa162af0887e9"
|
|
||||||
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"
|
|
||||||
|
|
||||||
[tools."github:webassembly/binaryen"."platforms.windows-x64"]
|
|
||||||
checksum = "sha256:b5e1d2a1ad3c03229ddc89823848f4a1c11f9c6402a51fa26f0aaa5f1d7a2203"
|
|
||||||
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"
|
|
||||||
|
|
||||||
[[tools.java]]
|
|
||||||
version = "21.0.2"
|
|
||||||
backend = "core:java"
|
|
||||||
|
|
||||||
[tools.java."platforms.linux-arm64"]
|
|
||||||
checksum = "sha256:08db1392a48d4eb5ea5315cf8f18b89dbaf36cda663ba882cf03c704c9257ec2"
|
|
||||||
url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_linux-aarch64_bin.tar.gz"
|
|
||||||
|
|
||||||
[tools.java."platforms.linux-x64"]
|
|
||||||
checksum = "sha256:a2def047a73941e01a73739f92755f86b895811afb1f91243db214cff5bdac3f"
|
|
||||||
url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_linux-x64_bin.tar.gz"
|
|
||||||
|
|
||||||
[tools.java."platforms.macos-arm64"]
|
|
||||||
checksum = "sha256:b3d588e16ec1e0ef9805d8a696591bd518a5cea62567da8f53b5ce32d11d22e4"
|
|
||||||
url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_macos-aarch64_bin.tar.gz"
|
|
||||||
|
|
||||||
[tools.java."platforms.macos-x64"]
|
|
||||||
checksum = "sha256:8fd09e15dc406387a0aba70bf5d99692874e999bf9cd9208b452b5d76ac922d3"
|
|
||||||
url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_macos-x64_bin.tar.gz"
|
|
||||||
|
|
||||||
[tools.java."platforms.windows-x64"]
|
|
||||||
checksum = "sha256:b6c17e747ae78cdd6de4d7532b3164b277daee97c007d3eaa2b39cca99882664"
|
|
||||||
url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_windows-x64_bin.zip"
|
|
||||||
|
|
||||||
[[tools.node]]
|
|
||||||
version = "24.15.0"
|
|
||||||
backend = "core:node"
|
|
||||||
|
|
||||||
[tools.node."platforms.linux-arm64"]
|
|
||||||
checksum = "sha256:73afc234d558c24919875f51c2d1ea002a2ada4ea6f83601a383869fefa64eed"
|
|
||||||
url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-linux-arm64.tar.gz"
|
|
||||||
|
|
||||||
[tools.node."platforms.linux-arm64-musl"]
|
|
||||||
checksum = "sha256:31e98aa960a067da91edffd5d93bc46657b5d2a8029612c359f5f2ac0060152a"
|
|
||||||
url = "https://unofficial-builds.nodejs.org/download/release/v24.15.0/node-v24.15.0-linux-arm64-musl.tar.gz"
|
|
||||||
|
|
||||||
[tools.node."platforms.linux-x64"]
|
|
||||||
checksum = "sha256:44836872d9aec49f1e6b52a9a922872db9a2b02d235a616a5681b6a85fec8d89"
|
|
||||||
url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-linux-x64.tar.gz"
|
|
||||||
|
|
||||||
[tools.node."platforms.linux-x64-musl"]
|
|
||||||
checksum = "sha256:f55af5bd489c5347b113ca6594cae00a54b30ba57ac5875324311bfc6f4762e3"
|
|
||||||
url = "https://unofficial-builds.nodejs.org/download/release/v24.15.0/node-v24.15.0-linux-x64-musl.tar.gz"
|
|
||||||
|
|
||||||
[tools.node."platforms.macos-arm64"]
|
|
||||||
checksum = "sha256:372331b969779ab5d15b949884fc6eaf88d5afe87bde8ba881d6400b9100ffc4"
|
|
||||||
url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-darwin-arm64.tar.gz"
|
|
||||||
|
|
||||||
[tools.node."platforms.macos-x64"]
|
|
||||||
checksum = "sha256:ffd5ee293467927f3ee731a553eb88fd1f48cf74eebc2d74a6babe4af228673b"
|
|
||||||
url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-darwin-x64.tar.gz"
|
|
||||||
|
|
||||||
[tools.node."platforms.windows-x64"]
|
|
||||||
checksum = "sha256:cc5149eabd53779ce1e7bdc5401643622d0c7e6800ade18928a767e940bb0e62"
|
|
||||||
url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-win-x64.zip"
|
|
||||||
|
|
||||||
[[tools."npm:oazapfts"]]
|
|
||||||
version = "7.5.0"
|
|
||||||
backend = "npm:oazapfts"
|
|
||||||
|
|
||||||
[[tools.opentofu]]
|
|
||||||
version = "1.11.6"
|
|
||||||
backend = "aqua:opentofu/opentofu"
|
|
||||||
|
|
||||||
[tools.opentofu."platforms.linux-arm64"]
|
|
||||||
checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850"
|
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_arm64.tar.gz"
|
|
||||||
|
|
||||||
[tools.opentofu."platforms.linux-arm64-musl"]
|
|
||||||
checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850"
|
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_arm64.tar.gz"
|
|
||||||
|
|
||||||
[tools.opentofu."platforms.linux-x64"]
|
|
||||||
checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e"
|
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_amd64.tar.gz"
|
|
||||||
|
|
||||||
[tools.opentofu."platforms.linux-x64-musl"]
|
|
||||||
checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e"
|
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_amd64.tar.gz"
|
|
||||||
|
|
||||||
[tools.opentofu."platforms.macos-arm64"]
|
|
||||||
checksum = "sha256:62d7fa8539e13b444827aa0a3b90c5972da5c47e8f8882d9dcf2e430e78840c1"
|
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_darwin_arm64.tar.gz"
|
|
||||||
|
|
||||||
[tools.opentofu."platforms.macos-x64"]
|
|
||||||
checksum = "sha256:1408cdef1c380f914565e6b4bb70794c6b163f195fcb233357f3d6c5745906b6"
|
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_darwin_amd64.tar.gz"
|
|
||||||
|
|
||||||
[tools.opentofu."platforms.windows-x64"]
|
|
||||||
checksum = "sha256:27323f70c875b8251bfd7e61a4cffc3ebff4e56ed1e611b955016f0c7077367e"
|
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_windows_amd64.tar.gz"
|
|
||||||
|
|
||||||
[[tools.pnpm]]
|
|
||||||
version = "10.33.4"
|
|
||||||
backend = "aqua:pnpm/pnpm"
|
|
||||||
|
|
||||||
[[tools.terragrunt]]
|
|
||||||
version = "1.0.3"
|
|
||||||
backend = "aqua:gruntwork-io/terragrunt"
|
|
||||||
|
|
||||||
[tools.terragrunt."platforms.linux-arm64"]
|
|
||||||
checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe"
|
|
||||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_arm64.tar.gz"
|
|
||||||
|
|
||||||
[tools.terragrunt."platforms.linux-arm64-musl"]
|
|
||||||
checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe"
|
|
||||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_arm64.tar.gz"
|
|
||||||
|
|
||||||
[tools.terragrunt."platforms.linux-x64"]
|
|
||||||
checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073"
|
|
||||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_amd64.tar.gz"
|
|
||||||
|
|
||||||
[tools.terragrunt."platforms.linux-x64-musl"]
|
|
||||||
checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073"
|
|
||||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_amd64.tar.gz"
|
|
||||||
|
|
||||||
[tools.terragrunt."platforms.macos-arm64"]
|
|
||||||
checksum = "sha256:aacb5be2ca5475300cbce246dfbd8a45eb47510fbaa70fab8561c49ef5db03aa"
|
|
||||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_darwin_arm64.tar.gz"
|
|
||||||
|
|
||||||
[tools.terragrunt."platforms.macos-x64"]
|
|
||||||
checksum = "sha256:3133c2251e191aede8e3dd2a5b3aee2e91c5f08f88f117aee40eed9a24c8ef6b"
|
|
||||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_darwin_amd64.tar.gz"
|
|
||||||
|
|
||||||
[tools.terragrunt."platforms.windows-x64"]
|
|
||||||
checksum = "sha256:183b2745b4e04980a6bfa4450ff81956a12596ca22d70f7aaa793980f5b036db"
|
|
||||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_windows_amd64.exe.tar.gz"
|
|
||||||
@@ -2,7 +2,7 @@ experimental_monorepo_root = true
|
|||||||
|
|
||||||
[monorepo]
|
[monorepo]
|
||||||
config_roots = [
|
config_roots = [
|
||||||
"packages/plugin-core",
|
"packages/plugins",
|
||||||
"server",
|
"server",
|
||||||
"packages/cli",
|
"packages/cli",
|
||||||
"deployment",
|
"deployment",
|
||||||
@@ -16,28 +16,18 @@ config_roots = [
|
|||||||
|
|
||||||
[tools]
|
[tools]
|
||||||
node = "24.15.0"
|
node = "24.15.0"
|
||||||
"aqua:flutter/flutter" = "3.44.0"
|
flutter = "3.41.9"
|
||||||
pnpm = "10.33.4"
|
pnpm = "10.33.1"
|
||||||
terragrunt = "1.0.3"
|
terragrunt = "1.0.3"
|
||||||
opentofu = "1.11.6"
|
opentofu = "1.11.6"
|
||||||
java = "21.0.2"
|
java = "21.0.2"
|
||||||
"npm:oazapfts" = "7.5.0"
|
"npm:oazapfts" = "7.5.0"
|
||||||
"github:extism/cli" = "1.6.3"
|
|
||||||
"github:webassembly/binaryen" = "version_124"
|
|
||||||
"github:extism/js-pdk" = "1.6.0"
|
|
||||||
|
|
||||||
[tools."github:CQLabs/homebrew-dcm"]
|
[tools."github:CQLabs/homebrew-dcm"]
|
||||||
version = "1.37.0"
|
version = "1.37.0"
|
||||||
bin = "dcm"
|
bin = "dcm"
|
||||||
postinstall = "chmod +x \"$MISE_TOOL_INSTALL_PATH/dcm\" || true"
|
postinstall = "chmod +x \"$MISE_TOOL_INSTALL_PATH/dcm\" || true"
|
||||||
|
|
||||||
[tools."github:CQLabs/homebrew-dcm".platforms]
|
|
||||||
linux-x64 = { asset_pattern = "dcm-linux-x64-release.zip" }
|
|
||||||
linux-arm64 = { asset_pattern = "dcm-linux-arm-release.zip" }
|
|
||||||
macos-x64 = { asset_pattern = "dcm-macos-x64-release.zip" }
|
|
||||||
macos-arm64 = { asset_pattern = "dcm-macos-arm-release.zip" }
|
|
||||||
windows-x64 = { asset_pattern = "dcm-windows-release.zip" }
|
|
||||||
|
|
||||||
[tools."github:jellyfin/jellyfin-ffmpeg"]
|
[tools."github:jellyfin/jellyfin-ffmpeg"]
|
||||||
version = "7.1.3-6"
|
version = "7.1.3-6"
|
||||||
|
|
||||||
@@ -50,13 +40,6 @@ macos-arm64 = { asset_pattern = "jellyfin-ffmpeg_*_portable_macarm64-gpl.tar.xz"
|
|||||||
[settings]
|
[settings]
|
||||||
experimental = true
|
experimental = true
|
||||||
pin = true
|
pin = true
|
||||||
lockfile = true
|
|
||||||
|
|
||||||
[tasks.plugins]
|
|
||||||
run = [
|
|
||||||
"pnpm --filter @immich/plugin-sdk --filter @immich/plugin-core install --frozen-lockfile",
|
|
||||||
"pnpm --filter @immich/plugin-sdk --filter @immich/plugin-core build",
|
|
||||||
]
|
|
||||||
|
|
||||||
[tasks.open-api-typescript]
|
[tasks.open-api-typescript]
|
||||||
run = [
|
run = [
|
||||||
@@ -72,84 +55,17 @@ run = "bash ./bin/generate-dart-sdk.sh"
|
|||||||
[tasks.open-api]
|
[tasks.open-api]
|
||||||
env = { SHARP_IGNORE_GLOBAL_LIBVIPS = true }
|
env = { SHARP_IGNORE_GLOBAL_LIBVIPS = true }
|
||||||
run = [
|
run = [
|
||||||
{ task = "//:plugins" },
|
|
||||||
{ task = "//server:install" },
|
{ task = "//server:install" },
|
||||||
{ task = "//server:build" },
|
{ task = "//server:build" },
|
||||||
{ task = "//server:sync-open-api" },
|
{ task = "//server:sync-open-api" },
|
||||||
{ task = ":open-api-typescript" },
|
{ task = ":open-api-typescript"},
|
||||||
{ task = ":open-api-dart" },
|
{ task = ":open-api-dart"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[tasks.sql]
|
[tasks.sql]
|
||||||
dir = "server"
|
dir = "server"
|
||||||
run = "node ./dist/bin/sync-sql.js"
|
run = "node ./dist/bin/sync-sql.js"
|
||||||
|
|
||||||
# TODO dev, prod, and e2e should be de-duplicated by using env but for some reason I ran into issues
|
|
||||||
[tasks.dev]
|
|
||||||
depends = "//:plugins"
|
|
||||||
dir = "docker"
|
|
||||||
interactive = true
|
|
||||||
env = { COMPOSE_BAKE = true }
|
|
||||||
run = "docker compose -f ./docker-compose.dev.yml up --remove-orphans"
|
|
||||||
depends_post = "//:dev-down"
|
|
||||||
|
|
||||||
[tasks.dev-update]
|
|
||||||
run = { task = "//:dev", args = ["--build", "-V"] }
|
|
||||||
|
|
||||||
[tasks.dev-scale]
|
|
||||||
run = { task = "//:dev", args = ["--build", "-V", "--scale immich-server=3"] }
|
|
||||||
|
|
||||||
[tasks.dev-down]
|
|
||||||
dir = "docker"
|
|
||||||
run = "docker compose -f ./docker-compose.dev.yml down --remove-orphans"
|
|
||||||
|
|
||||||
[tasks.prod]
|
|
||||||
depends = "//:plugins"
|
|
||||||
dir = "docker"
|
|
||||||
interactive = true
|
|
||||||
env = { COMPOSE_BAKE = true }
|
|
||||||
run = "docker compose -f ./docker-compose.prod.yml up --remove-orphans"
|
|
||||||
depends_post = "//:prod-down"
|
|
||||||
|
|
||||||
[tasks.prod-scale]
|
|
||||||
run = { task = "//:prod", args = [
|
|
||||||
"--build",
|
|
||||||
"-V",
|
|
||||||
"--scale immich-server=3",
|
|
||||||
"--scale immich-microservices",
|
|
||||||
] }
|
|
||||||
|
|
||||||
[tasks.prod-down]
|
|
||||||
dir = "docker"
|
|
||||||
run = "docker compose -f ./docker-compose.prod.yml down --remove-orphans"
|
|
||||||
|
|
||||||
[tasks.e2e]
|
|
||||||
depends = "//:plugins"
|
|
||||||
dir = "e2e"
|
|
||||||
interactive = true
|
|
||||||
env = { COMPOSE_BAKE = true }
|
|
||||||
run = "docker compose -f ./docker-compose.yml up --remove-orphans"
|
|
||||||
depends_post = "//:e2e-down"
|
|
||||||
|
|
||||||
[tasks.e2e-dev]
|
|
||||||
depends = "//:plugins"
|
|
||||||
dir = "e2e"
|
|
||||||
interactive = true
|
|
||||||
env = { COMPOSE_BAKE = true }
|
|
||||||
run = "docker compose -f ./docker-compose.dev.yml up --remove-orphans"
|
|
||||||
depends_post = "//:e2e-dev-down"
|
|
||||||
|
|
||||||
[tasks.e2e-update]
|
|
||||||
run = { task = "//:e2e", args = ["--build", '-V'] }
|
|
||||||
|
|
||||||
[tasks.e2e-down]
|
|
||||||
dir = "e2e"
|
|
||||||
run = "docker compose -f ./docker-compose.yml down --remove-orphans"
|
|
||||||
|
|
||||||
[tasks.e2e-dev-down]
|
|
||||||
dir = "e2e"
|
|
||||||
run = "docker compose -f ./docker-compose.dev.yml down --remove-orphans"
|
|
||||||
|
|
||||||
# SDK tasks
|
# SDK tasks
|
||||||
[tasks."sdk:install"]
|
[tasks."sdk:install"]
|
||||||
dir = "packages/sdk"
|
dir = "packages/sdk"
|
||||||
@@ -165,14 +81,3 @@ run = "pnpm format"
|
|||||||
|
|
||||||
[tasks."i18n:format-fix"]
|
[tasks."i18n:format-fix"]
|
||||||
run = "pnpm format:fix"
|
run = "pnpm format:fix"
|
||||||
|
|
||||||
[tasks.clean]
|
|
||||||
run = [
|
|
||||||
"find . -name 'node_modules' -type d -prune -exec rm -rf '{}' +",
|
|
||||||
"find . -name 'dist' -type d -prune -exec rm -rf '{}' +",
|
|
||||||
"find . -name 'build' -type d -prune -exec rm -rf '{}' +",
|
|
||||||
"find . -name '.svelte-kit' -type d -prune -exec rm -rf '{}' +",
|
|
||||||
"find . -name 'coverage' -type d -prune -exec rm -rf '{}' +",
|
|
||||||
"find . -name '.pnpm-store' -type d -prune -exec rm -rf '{}' +",
|
|
||||||
{ task = "//:*-down" },
|
|
||||||
]
|
|
||||||
|
|||||||
Vendored
+1
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"dart.flutterSdkPath": ".fvm/versions/3.41.9",
|
||||||
"dart.lineLength": 120,
|
"dart.lineLength": 120,
|
||||||
"[dart]": {
|
"[dart]": {
|
||||||
"editor.rulers": [
|
"editor.rulers": [
|
||||||
|
|||||||
@@ -89,13 +89,6 @@ flutter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
constraints {
|
|
||||||
implementation("androidx.glance:glance-appwidget") {
|
|
||||||
version { strictly libs.versions.glance.get() }
|
|
||||||
because 'home_widget requests 1.+ which can resolve to pre-releases incompatible with our compileSdk/AGP'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
implementation libs.okhttp
|
implementation libs.okhttp
|
||||||
implementation libs.cronet.embedded
|
implementation libs.cronet.embedded
|
||||||
implementation libs.media3.datasource.okhttp
|
implementation libs.media3.datasource.okhttp
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -23,8 +23,6 @@ import java.io.IOException
|
|||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
private const val MAX_PREALLOC_BYTES = 128 * 1024 * 1024
|
|
||||||
|
|
||||||
private class RemoteRequest(val cancellationSignal: CancellationSignal)
|
private class RemoteRequest(val cancellationSignal: CancellationSignal)
|
||||||
|
|
||||||
class RemoteImagesImpl(context: Context) : RemoteImageApi {
|
class RemoteImagesImpl(context: Context) : RemoteImageApi {
|
||||||
@@ -230,6 +228,7 @@ private class CronetImageFetcher : ImageFetcher {
|
|||||||
private val onComplete: () -> Unit,
|
private val onComplete: () -> Unit,
|
||||||
) : UrlRequest.Callback() {
|
) : UrlRequest.Callback() {
|
||||||
private var buffer: NativeByteBuffer? = null
|
private var buffer: NativeByteBuffer? = null
|
||||||
|
private var wrapped: ByteBuffer? = null
|
||||||
private var error: Exception? = null
|
private var error: Exception? = null
|
||||||
|
|
||||||
override fun onRedirectReceived(request: UrlRequest, info: UrlResponseInfo, newUrl: String) {
|
override fun onRedirectReceived(request: UrlRequest, info: UrlResponseInfo, newUrl: String) {
|
||||||
@@ -243,16 +242,15 @@ private class CronetImageFetcher : ImageFetcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Content-Length is a size hint only. With Content-Encoding (gzip/br/...),
|
|
||||||
// Cronet auto-decompresses and writes decompressed bytes to our buffer, which
|
|
||||||
// may exceed the wire/compressed Content-Length. Always use the growable
|
|
||||||
// buffer path so we can't overflow.
|
|
||||||
val contentLength = info.allHeaders["content-length"]?.firstOrNull()?.toIntOrNull() ?: 0
|
val contentLength = info.allHeaders["content-length"]?.firstOrNull()?.toIntOrNull() ?: 0
|
||||||
// Cap the up-front alloc: Content-Length is untrusted and can be huge or near
|
if (contentLength > 0) {
|
||||||
// Int.MAX_VALUE (overflowing `+1`). For larger responses the grow path takes over.
|
buffer = NativeByteBuffer(contentLength + 1)
|
||||||
val initialSize = if (contentLength in 1..MAX_PREALLOC_BYTES) contentLength + 1 else INITIAL_BUFFER_SIZE
|
wrapped = NativeBuffer.wrap(buffer!!.pointer, contentLength + 1)
|
||||||
buffer = NativeByteBuffer(initialSize)
|
request.read(wrapped)
|
||||||
request.read(buffer!!.wrapRemaining())
|
} else {
|
||||||
|
buffer = NativeByteBuffer(INITIAL_BUFFER_SIZE)
|
||||||
|
request.read(buffer!!.wrapRemaining())
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
error = e
|
error = e
|
||||||
return request.cancel()
|
return request.cancel()
|
||||||
@@ -265,14 +263,14 @@ private class CronetImageFetcher : ImageFetcher {
|
|||||||
byteBuffer: ByteBuffer
|
byteBuffer: ByteBuffer
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
// Always pass a fresh wrap so byteBuffer.position() represents only the
|
val buf = if (wrapped == null) {
|
||||||
// bytes Cronet wrote in this iteration. Reusing the caller-supplied
|
buffer!!.run {
|
||||||
// ByteBuffer breaks advance(): Cronet's position keeps accumulating
|
advance(byteBuffer.position())
|
||||||
// across reads, which would double-count previous iterations' bytes.
|
ensureHeadroom()
|
||||||
val buf = buffer!!.run {
|
wrapRemaining()
|
||||||
advance(byteBuffer.position())
|
}
|
||||||
ensureHeadroom()
|
} else {
|
||||||
wrapRemaining()
|
wrapped
|
||||||
}
|
}
|
||||||
request.read(buf)
|
request.read(buf)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -282,6 +280,7 @@ private class CronetImageFetcher : ImageFetcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onSucceeded(request: UrlRequest, info: UrlResponseInfo) {
|
override fun onSucceeded(request: UrlRequest, info: UrlResponseInfo) {
|
||||||
|
wrapped?.let { buffer!!.advance(it.position()) }
|
||||||
onSuccess(buffer!!)
|
onSuccess(buffer!!)
|
||||||
onComplete()
|
onComplete()
|
||||||
}
|
}
|
||||||
|
|||||||
-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> {
|
||||||
|
|||||||
@@ -5,7 +5,3 @@ android.nonTransitiveRClass=false
|
|||||||
android.nonFinalResIds=false
|
android.nonFinalResIds=false
|
||||||
org.gradle.caching=true
|
org.gradle.caching=true
|
||||||
org.gradle.parallel=true
|
org.gradle.parallel=true
|
||||||
# This builtInKotlin flag was added automatically by Flutter migrator
|
|
||||||
android.builtInKotlin=false
|
|
||||||
# This newDsl flag was added automatically by Flutter migrator
|
|
||||||
android.newDsl=false
|
|
||||||
|
|||||||
@@ -1,23 +1,58 @@
|
|||||||
PODS:
|
PODS:
|
||||||
|
- background_downloader (0.0.1):
|
||||||
|
- Flutter
|
||||||
- bonsoir_darwin (0.0.1):
|
- bonsoir_darwin (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
|
- connectivity_plus (0.0.1):
|
||||||
|
- Flutter
|
||||||
- cupertino_http (0.0.1):
|
- cupertino_http (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
|
- device_info_plus (0.0.1):
|
||||||
|
- Flutter
|
||||||
- Flutter (1.0.0)
|
- Flutter (1.0.0)
|
||||||
- flutter_local_notifications (0.0.1):
|
- flutter_local_notifications (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- flutter_native_splash (2.4.3):
|
||||||
|
- Flutter
|
||||||
- flutter_secure_storage (6.0.0):
|
- flutter_secure_storage (6.0.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- flutter_udid (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- KeychainAccess
|
||||||
|
- flutter_web_auth_2 (5.0.0):
|
||||||
|
- Flutter
|
||||||
- fluttertoast (0.0.2):
|
- fluttertoast (0.0.2):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- geolocator_apple (1.2.0):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
- home_widget (0.0.1):
|
- home_widget (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- image_picker_ios (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- integration_test (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- KeychainAccess (4.2.2)
|
||||||
|
- local_auth_darwin (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
|
- MapLibre (6.14.0)
|
||||||
|
- maplibre_gl (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- MapLibre (= 6.14.0)
|
||||||
- native_video_player (1.0.0):
|
- native_video_player (1.0.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- network_info_plus (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- package_info_plus (0.4.5):
|
||||||
|
- Flutter
|
||||||
- permission_handler_apple (9.3.0):
|
- permission_handler_apple (9.3.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- photo_manager (3.9.0):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
- share_handler_ios (0.0.14):
|
- share_handler_ios (0.0.14):
|
||||||
- Flutter
|
- Flutter
|
||||||
- share_handler_ios/share_handler_ios_models (= 0.0.14)
|
- share_handler_ios/share_handler_ios_models (= 0.0.14)
|
||||||
@@ -26,56 +61,144 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
- share_handler_ios_models
|
- share_handler_ios_models
|
||||||
- share_handler_ios_models (0.0.9)
|
- share_handler_ios_models (0.0.9)
|
||||||
|
- share_plus (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- shared_preferences_foundation (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
|
- url_launcher_ios (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- wakelock_plus (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
|
- background_downloader (from `.symlinks/plugins/background_downloader/ios`)
|
||||||
- bonsoir_darwin (from `.symlinks/plugins/bonsoir_darwin/darwin`)
|
- bonsoir_darwin (from `.symlinks/plugins/bonsoir_darwin/darwin`)
|
||||||
|
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||||
- cupertino_http (from `.symlinks/plugins/cupertino_http/darwin`)
|
- cupertino_http (from `.symlinks/plugins/cupertino_http/darwin`)
|
||||||
|
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
||||||
|
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
||||||
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
||||||
|
- flutter_udid (from `.symlinks/plugins/flutter_udid/ios`)
|
||||||
|
- flutter_web_auth_2 (from `.symlinks/plugins/flutter_web_auth_2/ios`)
|
||||||
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
||||||
|
- geolocator_apple (from `.symlinks/plugins/geolocator_apple/darwin`)
|
||||||
- home_widget (from `.symlinks/plugins/home_widget/ios`)
|
- home_widget (from `.symlinks/plugins/home_widget/ios`)
|
||||||
|
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||||
|
- integration_test (from `.symlinks/plugins/integration_test/ios`)
|
||||||
|
- local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`)
|
||||||
|
- maplibre_gl (from `.symlinks/plugins/maplibre_gl/ios`)
|
||||||
- native_video_player (from `.symlinks/plugins/native_video_player/ios`)
|
- native_video_player (from `.symlinks/plugins/native_video_player/ios`)
|
||||||
|
- network_info_plus (from `.symlinks/plugins/network_info_plus/ios`)
|
||||||
|
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||||
|
- photo_manager (from `.symlinks/plugins/photo_manager/darwin`)
|
||||||
- share_handler_ios (from `.symlinks/plugins/share_handler_ios/ios`)
|
- share_handler_ios (from `.symlinks/plugins/share_handler_ios/ios`)
|
||||||
- share_handler_ios_models (from `.symlinks/plugins/share_handler_ios/ios/Models`)
|
- share_handler_ios_models (from `.symlinks/plugins/share_handler_ios/ios/Models`)
|
||||||
|
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||||
|
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||||
|
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||||
|
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
||||||
|
|
||||||
|
SPEC REPOS:
|
||||||
|
trunk:
|
||||||
|
- KeychainAccess
|
||||||
|
- MapLibre
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
|
background_downloader:
|
||||||
|
:path: ".symlinks/plugins/background_downloader/ios"
|
||||||
bonsoir_darwin:
|
bonsoir_darwin:
|
||||||
:path: ".symlinks/plugins/bonsoir_darwin/darwin"
|
:path: ".symlinks/plugins/bonsoir_darwin/darwin"
|
||||||
|
connectivity_plus:
|
||||||
|
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||||
cupertino_http:
|
cupertino_http:
|
||||||
:path: ".symlinks/plugins/cupertino_http/darwin"
|
:path: ".symlinks/plugins/cupertino_http/darwin"
|
||||||
|
device_info_plus:
|
||||||
|
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||||
Flutter:
|
Flutter:
|
||||||
:path: Flutter
|
:path: Flutter
|
||||||
flutter_local_notifications:
|
flutter_local_notifications:
|
||||||
:path: ".symlinks/plugins/flutter_local_notifications/ios"
|
:path: ".symlinks/plugins/flutter_local_notifications/ios"
|
||||||
|
flutter_native_splash:
|
||||||
|
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
||||||
flutter_secure_storage:
|
flutter_secure_storage:
|
||||||
:path: ".symlinks/plugins/flutter_secure_storage/ios"
|
:path: ".symlinks/plugins/flutter_secure_storage/ios"
|
||||||
|
flutter_udid:
|
||||||
|
:path: ".symlinks/plugins/flutter_udid/ios"
|
||||||
|
flutter_web_auth_2:
|
||||||
|
:path: ".symlinks/plugins/flutter_web_auth_2/ios"
|
||||||
fluttertoast:
|
fluttertoast:
|
||||||
:path: ".symlinks/plugins/fluttertoast/ios"
|
:path: ".symlinks/plugins/fluttertoast/ios"
|
||||||
|
geolocator_apple:
|
||||||
|
:path: ".symlinks/plugins/geolocator_apple/darwin"
|
||||||
home_widget:
|
home_widget:
|
||||||
:path: ".symlinks/plugins/home_widget/ios"
|
:path: ".symlinks/plugins/home_widget/ios"
|
||||||
|
image_picker_ios:
|
||||||
|
:path: ".symlinks/plugins/image_picker_ios/ios"
|
||||||
|
integration_test:
|
||||||
|
:path: ".symlinks/plugins/integration_test/ios"
|
||||||
|
local_auth_darwin:
|
||||||
|
:path: ".symlinks/plugins/local_auth_darwin/darwin"
|
||||||
|
maplibre_gl:
|
||||||
|
:path: ".symlinks/plugins/maplibre_gl/ios"
|
||||||
native_video_player:
|
native_video_player:
|
||||||
:path: ".symlinks/plugins/native_video_player/ios"
|
:path: ".symlinks/plugins/native_video_player/ios"
|
||||||
|
network_info_plus:
|
||||||
|
:path: ".symlinks/plugins/network_info_plus/ios"
|
||||||
|
package_info_plus:
|
||||||
|
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||||
permission_handler_apple:
|
permission_handler_apple:
|
||||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||||
|
photo_manager:
|
||||||
|
:path: ".symlinks/plugins/photo_manager/darwin"
|
||||||
share_handler_ios:
|
share_handler_ios:
|
||||||
:path: ".symlinks/plugins/share_handler_ios/ios"
|
:path: ".symlinks/plugins/share_handler_ios/ios"
|
||||||
share_handler_ios_models:
|
share_handler_ios_models:
|
||||||
:path: ".symlinks/plugins/share_handler_ios/ios/Models"
|
:path: ".symlinks/plugins/share_handler_ios/ios/Models"
|
||||||
|
share_plus:
|
||||||
|
:path: ".symlinks/plugins/share_plus/ios"
|
||||||
|
shared_preferences_foundation:
|
||||||
|
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||||
|
url_launcher_ios:
|
||||||
|
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||||
|
wakelock_plus:
|
||||||
|
:path: ".symlinks/plugins/wakelock_plus/ios"
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
|
background_downloader: 50e91d979067b82081aba359d7d916b3ba5fadad
|
||||||
bonsoir_darwin: 29c7ccf356646118844721f36e1de4b61f6cbd0e
|
bonsoir_darwin: 29c7ccf356646118844721f36e1de4b61f6cbd0e
|
||||||
|
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
|
||||||
cupertino_http: 94ac07f5ff090b8effa6c5e2c47871d48ab7c86c
|
cupertino_http: 94ac07f5ff090b8effa6c5e2c47871d48ab7c86c
|
||||||
|
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
|
||||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||||
flutter_local_notifications: ad39620c743ea4c15127860f4b5641649a988100
|
flutter_local_notifications: ad39620c743ea4c15127860f4b5641649a988100
|
||||||
|
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
|
||||||
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
|
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
|
||||||
|
flutter_udid: 92a5d31fe0526b7b6002a2318df702e12e7eb300
|
||||||
|
flutter_web_auth_2: 646fc9df97a01c59e5eea99b237da2c6360f8439
|
||||||
fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1
|
fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1
|
||||||
|
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
|
||||||
home_widget: f169fc41fd807b4d46ab6615dc44d62adbf9f64f
|
home_widget: f169fc41fd807b4d46ab6615dc44d62adbf9f64f
|
||||||
|
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
|
||||||
|
integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
|
||||||
|
KeychainAccess: c0c4f7f38f6fc7bbe58f5702e25f7bd2f65abf51
|
||||||
|
local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb
|
||||||
|
MapLibre: 69e572367f4ef6287e18246cfafc39c80cdcabcd
|
||||||
|
maplibre_gl: 3c924e44725147b03dda33430ad216005b40555f
|
||||||
native_video_player: b65c58951ede2f93d103a25366bdebca95081265
|
native_video_player: b65c58951ede2f93d103a25366bdebca95081265
|
||||||
|
network_info_plus: cf61925ab5205dce05a4f0895989afdb6aade5fc
|
||||||
|
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
||||||
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
||||||
|
photo_manager: 25fd77df14f4f0ba5ef99e2c61814dde77e2bceb
|
||||||
share_handler_ios: e2244e990f826b2c8eaa291ac3831569438ba0fb
|
share_handler_ios: e2244e990f826b2c8eaa291ac3831569438ba0fb
|
||||||
share_handler_ios_models: fc638c9b4330dc7f082586c92aee9dfa0b87b871
|
share_handler_ios_models: fc638c9b4330dc7f082586c92aee9dfa0b87b871
|
||||||
|
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
||||||
|
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||||
|
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
||||||
|
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
|
||||||
|
|
||||||
PODFILE CHECKSUM: 938abbae4114b9c2140c550a2a0d8f7c674f5dfe
|
PODFILE CHECKSUM: 938abbae4114b9c2140c550a2a0d8f7c674f5dfe
|
||||||
|
|
||||||
|
|||||||
@@ -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 */; };
|
||||||
@@ -39,7 +37,6 @@
|
|||||||
FEE084F82EC172460045228E /* SQLiteData in Frameworks */ = {isa = PBXBuildFile; productRef = FEE084F72EC172460045228E /* SQLiteData */; };
|
FEE084F82EC172460045228E /* SQLiteData in Frameworks */ = {isa = PBXBuildFile; productRef = FEE084F72EC172460045228E /* SQLiteData */; };
|
||||||
FEE084FB2EC1725A0045228E /* RawStructuredFieldValues in Frameworks */ = {isa = PBXBuildFile; productRef = FEE084FA2EC1725A0045228E /* RawStructuredFieldValues */; };
|
FEE084FB2EC1725A0045228E /* RawStructuredFieldValues in Frameworks */ = {isa = PBXBuildFile; productRef = FEE084FA2EC1725A0045228E /* RawStructuredFieldValues */; };
|
||||||
FEE084FD2EC1725A0045228E /* StructuredFieldValues in Frameworks */ = {isa = PBXBuildFile; productRef = FEE084FC2EC1725A0045228E /* StructuredFieldValues */; };
|
FEE084FD2EC1725A0045228E /* StructuredFieldValues in Frameworks */ = {isa = PBXBuildFile; productRef = FEE084FC2EC1725A0045228E /* StructuredFieldValues */; };
|
||||||
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
|
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@@ -108,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; };
|
||||||
@@ -130,7 +125,6 @@
|
|||||||
FE5499F72F1198DE006016CB /* RemoteImagesImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteImagesImpl.swift; sourceTree = "<group>"; };
|
FE5499F72F1198DE006016CB /* RemoteImagesImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteImagesImpl.swift; sourceTree = "<group>"; };
|
||||||
FE5FE4AD2F30FBC000A71243 /* ImageProcessing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageProcessing.swift; sourceTree = "<group>"; };
|
FE5FE4AD2F30FBC000A71243 /* ImageProcessing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageProcessing.swift; sourceTree = "<group>"; };
|
||||||
FEAFA8722E4D42F4001E47FE /* Thumbhash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Thumbhash.swift; sourceTree = "<group>"; };
|
FEAFA8722E4D42F4001E47FE /* Thumbhash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Thumbhash.swift; sourceTree = "<group>"; };
|
||||||
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
|
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||||
@@ -195,7 +189,6 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
|
|
||||||
FEE084F82EC172460045228E /* SQLiteData in Frameworks */,
|
FEE084F82EC172460045228E /* SQLiteData in Frameworks */,
|
||||||
FEE084FB2EC1725A0045228E /* RawStructuredFieldValues in Frameworks */,
|
FEE084FB2EC1725A0045228E /* RawStructuredFieldValues in Frameworks */,
|
||||||
FEE084FD2EC1725A0045228E /* StructuredFieldValues in Frameworks */,
|
FEE084FD2EC1725A0045228E /* StructuredFieldValues in Frameworks */,
|
||||||
@@ -250,7 +243,6 @@
|
|||||||
9740EEB11CF90186004384FC /* Flutter */ = {
|
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
|
|
||||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||||
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||||
@@ -291,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 */,
|
||||||
@@ -326,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 = (
|
||||||
@@ -364,9 +346,6 @@
|
|||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
97C146ED1CF9000F007C117D /* Runner */ = {
|
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||||
packageProductDependencies = (
|
|
||||||
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
|
|
||||||
);
|
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
@@ -470,7 +449,6 @@
|
|||||||
);
|
);
|
||||||
mainGroup = 97C146E51CF9000F007C117D;
|
mainGroup = 97C146E51CF9000F007C117D;
|
||||||
packageReferences = (
|
packageReferences = (
|
||||||
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
|
|
||||||
FEE084F62EC172460045228E /* XCRemoteSwiftPackageReference "sqlite-data" */,
|
FEE084F62EC172460045228E /* XCRemoteSwiftPackageReference "sqlite-data" */,
|
||||||
FEE084F92EC1725A0045228E /* XCRemoteSwiftPackageReference "swift-http-structured-headers" */,
|
FEE084F92EC1725A0045228E /* XCRemoteSwiftPackageReference "swift-http-structured-headers" */,
|
||||||
);
|
);
|
||||||
@@ -641,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 */,
|
||||||
@@ -742,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;
|
||||||
@@ -775,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;
|
||||||
@@ -825,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;
|
||||||
@@ -885,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;
|
||||||
@@ -920,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;
|
||||||
@@ -949,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;
|
||||||
@@ -1104,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;
|
||||||
@@ -1147,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;
|
||||||
@@ -1187,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;
|
||||||
@@ -1293,17 +1272,7 @@
|
|||||||
package = FEE084F92EC1725A0045228E /* XCRemoteSwiftPackageReference "swift-http-structured-headers" */;
|
package = FEE084F92EC1725A0045228E /* XCRemoteSwiftPackageReference "swift-http-structured-headers" */;
|
||||||
productName = StructuredFieldValues;
|
productName = StructuredFieldValues;
|
||||||
};
|
};
|
||||||
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
|
|
||||||
isa = XCSwiftPackageProductDependency;
|
|
||||||
productName = FlutterGeneratedPluginSwiftPackage;
|
|
||||||
};
|
|
||||||
/* End XCSwiftPackageProductDependency section */
|
/* End XCSwiftPackageProductDependency section */
|
||||||
/* Begin XCLocalSwiftPackageReference section */
|
|
||||||
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {
|
|
||||||
isa = XCLocalSwiftPackageReference;
|
|
||||||
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
|
|
||||||
};
|
|
||||||
/* End XCLocalSwiftPackageReference section */
|
|
||||||
};
|
};
|
||||||
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-19
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"originHash" : "9be33bfaa68721646604aefff3cabbdaf9a193da192aae024c265065671f6c49",
|
||||||
"pins" : [
|
"pins" : [
|
||||||
{
|
{
|
||||||
"identity" : "combine-schedulers",
|
"identity" : "combine-schedulers",
|
||||||
@@ -18,24 +19,6 @@
|
|||||||
"version" : "7.8.0"
|
"version" : "7.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"identity" : "keychainaccess",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/kishikawakatsumi/KeychainAccess",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "84e546727d66f1adc5439debad16270d0fdd04e7",
|
|
||||||
"version" : "4.2.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "maplibre-gl-native-distribution",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/maplibre/maplibre-gl-native-distribution.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "60d9bb85c94ce6e7fc4406cd32529fd12bdb7809",
|
|
||||||
"version" : "6.14.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"identity" : "sqlite-data",
|
"identity" : "sqlite-data",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
@@ -163,5 +146,5 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"version" : 2
|
"version" : 3
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,24 +5,6 @@
|
|||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
buildImplicitDependencies = "YES">
|
buildImplicitDependencies = "YES">
|
||||||
<PreActions>
|
|
||||||
<ExecutionAction
|
|
||||||
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
|
||||||
<ActionContent
|
|
||||||
title = "Run Prepare Flutter Framework Script"
|
|
||||||
scriptText = "/bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" prepare ">
|
|
||||||
<EnvironmentBuildable>
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
|
||||||
BuildableName = "Immich.app"
|
|
||||||
BlueprintName = "Runner"
|
|
||||||
ReferencedContainer = "container:Runner.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</EnvironmentBuildable>
|
|
||||||
</ActionContent>
|
|
||||||
</ExecutionAction>
|
|
||||||
</PreActions>
|
|
||||||
<BuildActionEntries>
|
<BuildActionEntries>
|
||||||
<BuildActionEntry
|
<BuildActionEntry
|
||||||
buildForTesting = "YES"
|
buildForTesting = "YES"
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"originHash" : "9be33bfaa68721646604aefff3cabbdaf9a193da192aae024c265065671f6c49",
|
||||||
"pins" : [
|
"pins" : [
|
||||||
{
|
{
|
||||||
"identity" : "combine-schedulers",
|
"identity" : "combine-schedulers",
|
||||||
@@ -18,24 +19,6 @@
|
|||||||
"version" : "7.9.0"
|
"version" : "7.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"identity" : "keychainaccess",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/kishikawakatsumi/KeychainAccess",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "84e546727d66f1adc5439debad16270d0fdd04e7",
|
|
||||||
"version" : "4.2.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "maplibre-gl-native-distribution",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/maplibre/maplibre-gl-native-distribution.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "60d9bb85c94ce6e7fc4406cd32529fd12bdb7809",
|
|
||||||
"version" : "6.14.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"identity" : "sqlite-data",
|
"identity" : "sqlite-data",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
@@ -163,5 +146,5 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"version" : 2
|
"version" : 3
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
|
|||||||
|
|
||||||
var domainAlbum = PlatformAlbum(
|
var domainAlbum = PlatformAlbum(
|
||||||
id: album.localIdentifier,
|
id: album.localIdentifier,
|
||||||
name: album.localizedTitle ?? album.localIdentifier,
|
name: album.localizedTitle!,
|
||||||
updatedAt: nil,
|
updatedAt: nil,
|
||||||
isCloud: isCloud,
|
isCloud: isCloud,
|
||||||
assetCount: Int64(assets.count)
|
assetCount: Int64(assets.count)
|
||||||
@@ -382,10 +382,6 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
|
|||||||
func getTrashedAssets() throws -> [String: [PlatformAsset]] {
|
func getTrashedAssets() throws -> [String: [PlatformAsset]] {
|
||||||
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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
@@ -106,7 +97,7 @@ end
|
|||||||
if version_number
|
if version_number
|
||||||
increment_version_number(version_number: version_number)
|
increment_version_number(version_number: version_number)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Increment build number
|
# Increment build number
|
||||||
increment_build_number(
|
increment_build_number(
|
||||||
build_number: latest_testflight_build_number(
|
build_number: latest_testflight_build_number(
|
||||||
@@ -115,14 +106,14 @@ end
|
|||||||
) + 1,
|
) + 1,
|
||||||
xcodeproj: "./Runner.xcodeproj"
|
xcodeproj: "./Runner.xcodeproj"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Build the app
|
# Build the app
|
||||||
build_app(
|
build_app(
|
||||||
scheme: "Runner",
|
scheme: "Runner",
|
||||||
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,
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ const String kBackupLivePhotoGroup = 'backup_live_photo_group';
|
|||||||
const String kDownloadGroupImage = 'group_image';
|
const String kDownloadGroupImage = 'group_image';
|
||||||
const String kDownloadGroupVideo = 'group_video';
|
const String kDownloadGroupVideo = 'group_video';
|
||||||
const String kDownloadGroupLivePhoto = 'group_livephoto';
|
const String kDownloadGroupLivePhoto = 'group_livephoto';
|
||||||
const String kShareDownloadGroup = 'group_share';
|
|
||||||
|
|
||||||
// Timeline constants
|
// Timeline constants
|
||||||
const int kTimelineNoneSegmentSize = 120;
|
const int kTimelineNoneSegmentSize = 120;
|
||||||
@@ -31,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";
|
||||||
|
|||||||
@@ -18,7 +18,3 @@ enum CleanupStep { selectDate, scan, delete }
|
|||||||
enum AssetKeepType { none, photosOnly, videosOnly }
|
enum AssetKeepType { none, photosOnly, videosOnly }
|
||||||
|
|
||||||
enum AssetDateAggregation { start, end }
|
enum AssetDateAggregation { start, end }
|
||||||
|
|
||||||
enum SlideshowLook { contain, cover, blurredBackground }
|
|
||||||
|
|
||||||
enum SlideshowDirection { forward, backward, shuffle }
|
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
|
||||||
|
|
||||||
class AlbumConfig {
|
|
||||||
final AlbumSortMode sortMode;
|
|
||||||
final bool isReverse;
|
|
||||||
final bool isGrid;
|
|
||||||
|
|
||||||
const AlbumConfig({this.sortMode = AlbumSortMode.mostRecent, this.isReverse = true, this.isGrid = false});
|
|
||||||
|
|
||||||
AlbumConfig copyWith({AlbumSortMode? sortMode, bool? isReverse, bool? isGrid}) => AlbumConfig(
|
|
||||||
sortMode: sortMode ?? this.sortMode,
|
|
||||||
isReverse: isReverse ?? this.isReverse,
|
|
||||||
isGrid: isGrid ?? this.isGrid,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
(other is AlbumConfig && other.sortMode == sortMode && other.isReverse == isReverse && other.isGrid == isGrid);
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(sortMode, isReverse, isGrid);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => 'AlbumConfig(sortMode: $sortMode, isReverse: $isReverse, isGrid: $isGrid)';
|
|
||||||
}
|
|
||||||
@@ -1,201 +1,58 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:immich_mobile/constants/colors.dart';
|
|
||||||
import 'package:immich_mobile/constants/enums.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/config/album_config.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/config/backup_config.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/config/cleanup_config.dart';
|
import 'package:immich_mobile/domain/models/config/cleanup_config.dart';
|
||||||
import 'package:immich_mobile/domain/models/config/image_config.dart';
|
import 'package:immich_mobile/domain/models/config/image_config.dart';
|
||||||
import 'package:immich_mobile/domain/models/config/map_config.dart';
|
import 'package:immich_mobile/domain/models/config/map_config.dart';
|
||||||
import 'package:immich_mobile/domain/models/config/network_config.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/config/slideshow_config.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/config/theme_config.dart';
|
import 'package:immich_mobile/domain/models/config/theme_config.dart';
|
||||||
import 'package:immich_mobile/domain/models/config/timeline_config.dart';
|
import 'package:immich_mobile/domain/models/config/timeline_config.dart';
|
||||||
import 'package:immich_mobile/domain/models/config/viewer_config.dart';
|
import 'package:immich_mobile/domain/models/config/viewer_config.dart';
|
||||||
import 'package:immich_mobile/domain/models/log.model.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/metadata_key.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
|
||||||
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
|
||||||
|
|
||||||
const defaultConfig = AppConfig();
|
|
||||||
|
|
||||||
class AppConfig {
|
class AppConfig {
|
||||||
final LogLevel logLevel;
|
|
||||||
final ThemeConfig theme;
|
final ThemeConfig theme;
|
||||||
final CleanupConfig cleanup;
|
final CleanupConfig cleanup;
|
||||||
final MapConfig map;
|
final MapConfig map;
|
||||||
final TimelineConfig timeline;
|
final TimelineConfig timeline;
|
||||||
final ImageConfig image;
|
final ImageConfig image;
|
||||||
final ViewerConfig viewer;
|
final ViewerConfig viewer;
|
||||||
final SlideshowConfig slideshow;
|
|
||||||
final AlbumConfig album;
|
|
||||||
final BackupConfig backup;
|
|
||||||
final NetworkConfig network;
|
|
||||||
|
|
||||||
const AppConfig({
|
const AppConfig({
|
||||||
this.logLevel = .info,
|
|
||||||
this.theme = const .new(),
|
this.theme = const .new(),
|
||||||
this.cleanup = const .new(),
|
this.cleanup = const .new(),
|
||||||
this.map = const .new(),
|
this.map = const .new(),
|
||||||
this.timeline = const .new(),
|
this.timeline = const .new(),
|
||||||
this.image = const .new(),
|
this.image = const .new(),
|
||||||
this.viewer = const .new(),
|
this.viewer = const .new(),
|
||||||
this.slideshow = const .new(),
|
|
||||||
this.album = const .new(),
|
|
||||||
this.backup = const .new(),
|
|
||||||
this.network = const .new(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
AppConfig copyWith({
|
AppConfig copyWith({
|
||||||
LogLevel? logLevel,
|
|
||||||
ThemeConfig? theme,
|
ThemeConfig? theme,
|
||||||
CleanupConfig? cleanup,
|
CleanupConfig? cleanup,
|
||||||
MapConfig? map,
|
MapConfig? map,
|
||||||
TimelineConfig? timeline,
|
TimelineConfig? timeline,
|
||||||
ImageConfig? image,
|
ImageConfig? image,
|
||||||
ViewerConfig? viewer,
|
ViewerConfig? viewer,
|
||||||
SlideshowConfig? slideshow,
|
|
||||||
AlbumConfig? album,
|
|
||||||
BackupConfig? backup,
|
|
||||||
NetworkConfig? network,
|
|
||||||
}) => .new(
|
}) => .new(
|
||||||
logLevel: logLevel ?? this.logLevel,
|
|
||||||
theme: theme ?? this.theme,
|
theme: theme ?? this.theme,
|
||||||
cleanup: cleanup ?? this.cleanup,
|
cleanup: cleanup ?? this.cleanup,
|
||||||
map: map ?? this.map,
|
map: map ?? this.map,
|
||||||
timeline: timeline ?? this.timeline,
|
timeline: timeline ?? this.timeline,
|
||||||
image: image ?? this.image,
|
image: image ?? this.image,
|
||||||
viewer: viewer ?? this.viewer,
|
viewer: viewer ?? this.viewer,
|
||||||
slideshow: slideshow ?? this.slideshow,
|
|
||||||
album: album ?? this.album,
|
|
||||||
backup: backup ?? this.backup,
|
|
||||||
network: network ?? this.network,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
(other is AppConfig &&
|
(other is AppConfig &&
|
||||||
other.logLevel == logLevel &&
|
|
||||||
other.theme == theme &&
|
other.theme == theme &&
|
||||||
other.cleanup == cleanup &&
|
other.cleanup == cleanup &&
|
||||||
other.map == map &&
|
other.map == map &&
|
||||||
other.timeline == timeline &&
|
other.timeline == timeline &&
|
||||||
other.image == image &&
|
other.image == image &&
|
||||||
other.viewer == viewer &&
|
other.viewer == viewer);
|
||||||
other.slideshow == slideshow &&
|
|
||||||
other.album == album &&
|
|
||||||
other.backup == backup &&
|
|
||||||
other.network == network);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode => Object.hash(theme, cleanup, map, timeline, image, viewer);
|
||||||
Object.hash(logLevel, theme, cleanup, map, timeline, image, viewer, slideshow, album, backup, network);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() =>
|
String toString() =>
|
||||||
'AppConfig(logLevel: $logLevel, theme: $theme, cleanup: $cleanup, map: $map, timeline: $timeline, image: $image, viewer: $viewer, slideshow: $slideshow, album: $album, backup: $backup, network: $network)';
|
'AppConfig(theme: $theme, cleanup: $cleanup, map: $map, timeline: $timeline, image: $image, viewer: $viewer)';
|
||||||
|
|
||||||
T read<T extends Object>(MetadataKey<T> key) =>
|
|
||||||
(switch (key) {
|
|
||||||
.logLevel => logLevel,
|
|
||||||
.themePrimaryColor => theme.primaryColor,
|
|
||||||
.themeMode => theme.mode,
|
|
||||||
.themeDynamic => theme.dynamicTheme,
|
|
||||||
.themeColorfulInterface => theme.colorfulInterface,
|
|
||||||
.imagePreferRemote => image.preferRemote,
|
|
||||||
.imageLoadOriginal => image.loadOriginal,
|
|
||||||
.viewerLoopVideo => viewer.loopVideo,
|
|
||||||
.viewerLoadOriginalVideo => viewer.loadOriginalVideo,
|
|
||||||
.viewerAutoPlayVideo => viewer.autoPlayVideo,
|
|
||||||
.viewerTapToNavigate => viewer.tapToNavigate,
|
|
||||||
.networkAutoEndpointSwitching => network.autoEndpointSwitching,
|
|
||||||
.networkPreferredWifiName => network.preferredWifiName,
|
|
||||||
.networkLocalEndpoint => network.localEndpoint,
|
|
||||||
.networkExternalEndpointList => network.externalEndpointList,
|
|
||||||
.networkCustomHeaders => network.customHeaders,
|
|
||||||
.albumSortMode => album.sortMode,
|
|
||||||
.albumIsReverse => album.isReverse,
|
|
||||||
.albumIsGrid => album.isGrid,
|
|
||||||
.backupEnabled => backup.enabled,
|
|
||||||
.backupUseCellularForVideos => backup.useCellularForVideos,
|
|
||||||
.backupUseCellularForPhotos => backup.useCellularForPhotos,
|
|
||||||
.backupRequireCharging => backup.requireCharging,
|
|
||||||
.backupTriggerDelay => backup.triggerDelay,
|
|
||||||
.backupSyncAlbums => backup.syncAlbums,
|
|
||||||
.timelineTilesPerRow => timeline.tilesPerRow,
|
|
||||||
.timelineGroupAssetsBy => timeline.groupAssetsBy,
|
|
||||||
.timelineStorageIndicator => timeline.storageIndicator,
|
|
||||||
.mapShowFavoriteOnly => map.favoritesOnly,
|
|
||||||
.mapRelativeDate => map.relativeDays,
|
|
||||||
.mapIncludeArchived => map.includeArchived,
|
|
||||||
.mapThemeMode => map.themeMode,
|
|
||||||
.mapWithPartners => map.withPartners,
|
|
||||||
.cleanupKeepFavorites => cleanup.keepFavorites,
|
|
||||||
.cleanupKeepMediaType => cleanup.keepMediaType,
|
|
||||||
.cleanupKeepAlbumIds => cleanup.keepAlbumIds,
|
|
||||||
.cleanupCutoffDaysAgo => cleanup.cutoffDaysAgo,
|
|
||||||
.cleanupDefaultsInitialized => cleanup.defaultsInitialized,
|
|
||||||
.slideshowTransition => slideshow.transition,
|
|
||||||
.slideshowRepeat => slideshow.repeat,
|
|
||||||
.slideshowDuration => slideshow.duration,
|
|
||||||
.slideshowLook => slideshow.look,
|
|
||||||
.slideshowDirection => slideshow.direction,
|
|
||||||
})
|
|
||||||
as T;
|
|
||||||
|
|
||||||
factory AppConfig.fromEntries(Map<MetadataKey<Object>, Object> entries) {
|
|
||||||
var config = const AppConfig();
|
|
||||||
for (final MapEntry(key: key, value: value) in entries.entries) {
|
|
||||||
config = config.write(key, value);
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
AppConfig write<T extends Object>(MetadataKey<T> key, T value) {
|
|
||||||
return switch (key) {
|
|
||||||
.logLevel => copyWith(logLevel: value as LogLevel),
|
|
||||||
.themePrimaryColor => copyWith(theme: theme.copyWith(primaryColor: value as ImmichColorPreset)),
|
|
||||||
.themeMode => copyWith(theme: theme.copyWith(mode: value as ThemeMode)),
|
|
||||||
.themeDynamic => copyWith(theme: theme.copyWith(dynamicTheme: value as bool)),
|
|
||||||
.themeColorfulInterface => copyWith(theme: theme.copyWith(colorfulInterface: value as bool)),
|
|
||||||
.imagePreferRemote => copyWith(image: image.copyWith(preferRemote: value as bool)),
|
|
||||||
.imageLoadOriginal => copyWith(image: image.copyWith(loadOriginal: value as bool)),
|
|
||||||
.viewerLoopVideo => copyWith(viewer: viewer.copyWith(loopVideo: value as bool)),
|
|
||||||
.viewerLoadOriginalVideo => copyWith(viewer: viewer.copyWith(loadOriginalVideo: value as bool)),
|
|
||||||
.viewerAutoPlayVideo => copyWith(viewer: viewer.copyWith(autoPlayVideo: value as bool)),
|
|
||||||
.viewerTapToNavigate => copyWith(viewer: viewer.copyWith(tapToNavigate: value as bool)),
|
|
||||||
.networkAutoEndpointSwitching => copyWith(network: network.copyWith(autoEndpointSwitching: value as bool)),
|
|
||||||
.networkPreferredWifiName => copyWith(network: network.copyWith(preferredWifiName: (value as String))),
|
|
||||||
.networkLocalEndpoint => copyWith(network: network.copyWith(localEndpoint: (value as String))),
|
|
||||||
.networkExternalEndpointList => copyWith(network: network.copyWith(externalEndpointList: value as List<String>)),
|
|
||||||
.networkCustomHeaders => copyWith(network: network.copyWith(customHeaders: value as Map<String, String>)),
|
|
||||||
.albumSortMode => copyWith(album: album.copyWith(sortMode: value as AlbumSortMode)),
|
|
||||||
.albumIsReverse => copyWith(album: album.copyWith(isReverse: value as bool)),
|
|
||||||
.albumIsGrid => copyWith(album: album.copyWith(isGrid: value as bool)),
|
|
||||||
.backupEnabled => copyWith(backup: backup.copyWith(enabled: value as bool)),
|
|
||||||
.backupUseCellularForVideos => copyWith(backup: backup.copyWith(useCellularForVideos: value as bool)),
|
|
||||||
.backupUseCellularForPhotos => copyWith(backup: backup.copyWith(useCellularForPhotos: value as bool)),
|
|
||||||
.backupRequireCharging => copyWith(backup: backup.copyWith(requireCharging: value as bool)),
|
|
||||||
.backupTriggerDelay => copyWith(backup: backup.copyWith(triggerDelay: value as int)),
|
|
||||||
.backupSyncAlbums => copyWith(backup: backup.copyWith(syncAlbums: value as bool)),
|
|
||||||
.timelineTilesPerRow => copyWith(timeline: timeline.copyWith(tilesPerRow: value as int)),
|
|
||||||
.timelineGroupAssetsBy => copyWith(timeline: timeline.copyWith(groupAssetsBy: value as GroupAssetsBy)),
|
|
||||||
.timelineStorageIndicator => copyWith(timeline: timeline.copyWith(storageIndicator: value as bool)),
|
|
||||||
.mapShowFavoriteOnly => copyWith(map: map.copyWith(favoritesOnly: value as bool)),
|
|
||||||
.mapRelativeDate => copyWith(map: map.copyWith(relativeDays: value as int)),
|
|
||||||
.mapIncludeArchived => copyWith(map: map.copyWith(includeArchived: value as bool)),
|
|
||||||
.mapThemeMode => copyWith(map: map.copyWith(themeMode: value as ThemeMode)),
|
|
||||||
.mapWithPartners => copyWith(map: map.copyWith(withPartners: value as bool)),
|
|
||||||
.cleanupKeepFavorites => copyWith(cleanup: cleanup.copyWith(keepFavorites: value as bool)),
|
|
||||||
.cleanupKeepMediaType => copyWith(cleanup: cleanup.copyWith(keepMediaType: value as AssetKeepType)),
|
|
||||||
.cleanupKeepAlbumIds => copyWith(cleanup: cleanup.copyWith(keepAlbumIds: value as List<String>)),
|
|
||||||
.cleanupCutoffDaysAgo => copyWith(cleanup: cleanup.copyWith(cutoffDaysAgo: value as int)),
|
|
||||||
.cleanupDefaultsInitialized => copyWith(cleanup: cleanup.copyWith(defaultsInitialized: value as bool)),
|
|
||||||
.slideshowTransition => copyWith(slideshow: slideshow.copyWith(transition: value as bool)),
|
|
||||||
.slideshowRepeat => copyWith(slideshow: slideshow.copyWith(repeat: value as bool)),
|
|
||||||
.slideshowDuration => copyWith(slideshow: slideshow.copyWith(duration: value as int)),
|
|
||||||
.slideshowLook => copyWith(slideshow: slideshow.copyWith(look: value as SlideshowLook)),
|
|
||||||
.slideshowDirection => copyWith(slideshow: slideshow.copyWith(direction: value as SlideshowDirection)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
class BackupConfig {
|
|
||||||
final bool enabled;
|
|
||||||
final bool useCellularForVideos;
|
|
||||||
final bool useCellularForPhotos;
|
|
||||||
final bool requireCharging;
|
|
||||||
final int triggerDelay;
|
|
||||||
final bool syncAlbums;
|
|
||||||
|
|
||||||
const BackupConfig({
|
|
||||||
this.enabled = false,
|
|
||||||
this.useCellularForVideos = false,
|
|
||||||
this.useCellularForPhotos = false,
|
|
||||||
this.requireCharging = false,
|
|
||||||
this.triggerDelay = 30,
|
|
||||||
this.syncAlbums = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
BackupConfig copyWith({
|
|
||||||
bool? enabled,
|
|
||||||
bool? useCellularForVideos,
|
|
||||||
bool? useCellularForPhotos,
|
|
||||||
bool? requireCharging,
|
|
||||||
int? triggerDelay,
|
|
||||||
bool? syncAlbums,
|
|
||||||
}) => BackupConfig(
|
|
||||||
enabled: enabled ?? this.enabled,
|
|
||||||
useCellularForVideos: useCellularForVideos ?? this.useCellularForVideos,
|
|
||||||
useCellularForPhotos: useCellularForPhotos ?? this.useCellularForPhotos,
|
|
||||||
requireCharging: requireCharging ?? this.requireCharging,
|
|
||||||
triggerDelay: triggerDelay ?? this.triggerDelay,
|
|
||||||
syncAlbums: syncAlbums ?? this.syncAlbums,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
(other is BackupConfig &&
|
|
||||||
other.enabled == enabled &&
|
|
||||||
other.useCellularForVideos == useCellularForVideos &&
|
|
||||||
other.useCellularForPhotos == useCellularForPhotos &&
|
|
||||||
other.requireCharging == requireCharging &&
|
|
||||||
other.triggerDelay == triggerDelay &&
|
|
||||||
other.syncAlbums == syncAlbums);
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode =>
|
|
||||||
Object.hash(enabled, useCellularForVideos, useCellularForPhotos, requireCharging, triggerDelay, syncAlbums);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() =>
|
|
||||||
'BackupConfig(enabled: $enabled, useCellularForVideos: $useCellularForVideos, useCellularForPhotos: $useCellularForPhotos, requireCharging: $requireCharging, triggerDelay: $triggerDelay, syncAlbums: $syncAlbums)';
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
|
||||||
|
|
||||||
class NetworkConfig {
|
|
||||||
final bool autoEndpointSwitching;
|
|
||||||
final String preferredWifiName;
|
|
||||||
final String localEndpoint;
|
|
||||||
final List<String> externalEndpointList;
|
|
||||||
final Map<String, String> customHeaders;
|
|
||||||
|
|
||||||
const NetworkConfig({
|
|
||||||
this.autoEndpointSwitching = false,
|
|
||||||
this.preferredWifiName = '',
|
|
||||||
this.localEndpoint = '',
|
|
||||||
this.externalEndpointList = const [],
|
|
||||||
this.customHeaders = const {},
|
|
||||||
});
|
|
||||||
|
|
||||||
NetworkConfig copyWith({
|
|
||||||
bool? autoEndpointSwitching,
|
|
||||||
String? preferredWifiName,
|
|
||||||
String? localEndpoint,
|
|
||||||
List<String>? externalEndpointList,
|
|
||||||
Map<String, String>? customHeaders,
|
|
||||||
}) => NetworkConfig(
|
|
||||||
autoEndpointSwitching: autoEndpointSwitching ?? this.autoEndpointSwitching,
|
|
||||||
preferredWifiName: preferredWifiName ?? this.preferredWifiName,
|
|
||||||
localEndpoint: localEndpoint ?? this.localEndpoint,
|
|
||||||
externalEndpointList: externalEndpointList ?? this.externalEndpointList,
|
|
||||||
customHeaders: customHeaders ?? this.customHeaders,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
(other is NetworkConfig &&
|
|
||||||
other.autoEndpointSwitching == autoEndpointSwitching &&
|
|
||||||
other.preferredWifiName == preferredWifiName &&
|
|
||||||
other.localEndpoint == localEndpoint &&
|
|
||||||
listEquals(other.externalEndpointList, externalEndpointList) &&
|
|
||||||
mapEquals(other.customHeaders, customHeaders));
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(
|
|
||||||
autoEndpointSwitching,
|
|
||||||
preferredWifiName,
|
|
||||||
localEndpoint,
|
|
||||||
Object.hashAll(externalEndpointList),
|
|
||||||
Object.hashAllUnordered(customHeaders.entries.map((e) => Object.hash(e.key, e.value))),
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() =>
|
|
||||||
'NetworkConfig(autoEndpointSwitching: $autoEndpointSwitching, preferredWifiName: $preferredWifiName, localEndpoint: $localEndpoint, externalEndpointList: $externalEndpointList, customHeaders: $customHeaders)';
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import 'package:immich_mobile/constants/enums.dart';
|
|
||||||
|
|
||||||
class SlideshowConfig {
|
|
||||||
final bool transition;
|
|
||||||
final bool repeat;
|
|
||||||
final int duration;
|
|
||||||
final SlideshowLook look;
|
|
||||||
final SlideshowDirection direction;
|
|
||||||
|
|
||||||
const SlideshowConfig({
|
|
||||||
this.transition = true,
|
|
||||||
this.repeat = true,
|
|
||||||
this.duration = 5,
|
|
||||||
this.look = SlideshowLook.contain,
|
|
||||||
this.direction = SlideshowDirection.forward,
|
|
||||||
});
|
|
||||||
|
|
||||||
SlideshowConfig copyWith({
|
|
||||||
bool? transition,
|
|
||||||
bool? repeat,
|
|
||||||
int? duration,
|
|
||||||
SlideshowLook? look,
|
|
||||||
SlideshowDirection? direction,
|
|
||||||
}) => SlideshowConfig(
|
|
||||||
transition: transition ?? this.transition,
|
|
||||||
repeat: repeat ?? this.repeat,
|
|
||||||
duration: duration ?? this.duration,
|
|
||||||
look: look ?? this.look,
|
|
||||||
direction: direction ?? this.direction,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
(other is SlideshowConfig &&
|
|
||||||
other.transition == transition &&
|
|
||||||
other.repeat == repeat &&
|
|
||||||
other.duration == duration &&
|
|
||||||
other.look == look &&
|
|
||||||
other.direction == direction);
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(transition, repeat, duration, look, direction);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() =>
|
|
||||||
'SlideshowConfig(transition: $transition, repeat: $repeat, duration: $duration, look: $look, direction: $direction)';
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import 'package:immich_mobile/domain/models/log.model.dart';
|
||||||
|
|
||||||
|
class SystemConfig {
|
||||||
|
final LogLevel logLevel;
|
||||||
|
|
||||||
|
const SystemConfig({this.logLevel = .info});
|
||||||
|
|
||||||
|
SystemConfig copyWith({LogLevel? logLevel}) => SystemConfig(logLevel: logLevel ?? this.logLevel);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || (other is SystemConfig && other.logLevel == logLevel);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => logLevel.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'SystemConfig(logLevel: $logLevel)';
|
||||||
|
}
|
||||||
@@ -1,105 +1,94 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:immich_mobile/constants/colors.dart';
|
import 'package:immich_mobile/constants/colors.dart';
|
||||||
import 'package:immich_mobile/constants/enums.dart';
|
import 'package:immich_mobile/constants/enums.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/config/app_config.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/config/system_config.dart';
|
||||||
import 'package:immich_mobile/domain/models/log.model.dart';
|
import 'package:immich_mobile/domain/models/log.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
||||||
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
|
||||||
|
|
||||||
enum MetadataScope {
|
enum MetadataDomain<T extends Object> {
|
||||||
user, // keys with this scope are deleted on logout
|
appConfig<AppConfig>('config.app'),
|
||||||
system;
|
systemConfig<SystemConfig>('config.system');
|
||||||
|
|
||||||
const MetadataScope();
|
final String prefix;
|
||||||
|
const MetadataDomain(this.prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum MetadataKey<T extends Object> {
|
enum MetadataKey<T extends Object> {
|
||||||
// Theme
|
// Theme
|
||||||
themePrimaryColor<ImmichColorPreset>(codec: _EnumCodec(ImmichColorPreset.values)),
|
themePrimaryColor<ImmichColorPreset>(.appConfig, 'theme.primaryColor', .indigo, _EnumCodec(ImmichColorPreset.values)),
|
||||||
themeMode<ThemeMode>(codec: _EnumCodec(ThemeMode.values)),
|
themeMode<ThemeMode>(.appConfig, 'theme.mode', .system, _EnumCodec(ThemeMode.values)),
|
||||||
themeDynamic<bool>(),
|
themeDynamic<bool>(.appConfig, 'theme.dynamic', false),
|
||||||
themeColorfulInterface<bool>(),
|
themeColorfulInterface<bool>(.appConfig, 'theme.colorfulInterface', true),
|
||||||
|
|
||||||
// Image
|
// Image
|
||||||
imagePreferRemote<bool>(),
|
imagePreferRemote<bool>(.appConfig, 'image.preferRemote', false),
|
||||||
imageLoadOriginal<bool>(),
|
imageLoadOriginal<bool>(.appConfig, 'image.loadOriginal', false),
|
||||||
|
|
||||||
// Viewer
|
// Viewer
|
||||||
viewerLoopVideo<bool>(),
|
viewerLoopVideo<bool>(.appConfig, 'viewer.loopVideo', true),
|
||||||
viewerLoadOriginalVideo<bool>(),
|
viewerLoadOriginalVideo<bool>(.appConfig, 'viewer.loadOriginalVideo', false),
|
||||||
viewerAutoPlayVideo<bool>(),
|
viewerAutoPlayVideo<bool>(.appConfig, 'viewer.autoPlayVideo', true),
|
||||||
viewerTapToNavigate<bool>(),
|
viewerTapToNavigate<bool>(.appConfig, 'viewer.tapToNavigate', false),
|
||||||
|
|
||||||
// Network
|
|
||||||
networkAutoEndpointSwitching<bool>(scope: .system),
|
|
||||||
networkPreferredWifiName<String>(scope: .system),
|
|
||||||
networkLocalEndpoint<String>(scope: .system),
|
|
||||||
networkExternalEndpointList<List<String>>(scope: .system, codec: _ListCodec(_PrimitiveCodec.string)),
|
|
||||||
networkCustomHeaders<Map<String, String>>(
|
|
||||||
scope: .system,
|
|
||||||
codec: _MapCodec(_PrimitiveCodec.string, _PrimitiveCodec.string),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Album
|
|
||||||
albumSortMode<AlbumSortMode>(codec: _EnumCodec(AlbumSortMode.values)),
|
|
||||||
albumIsReverse<bool>(),
|
|
||||||
albumIsGrid<bool>(),
|
|
||||||
|
|
||||||
// Backup
|
|
||||||
backupEnabled<bool>(),
|
|
||||||
backupUseCellularForVideos<bool>(),
|
|
||||||
backupUseCellularForPhotos<bool>(),
|
|
||||||
backupRequireCharging<bool>(),
|
|
||||||
backupTriggerDelay<int>(),
|
|
||||||
backupSyncAlbums<bool>(),
|
|
||||||
|
|
||||||
// Timeline
|
// Timeline
|
||||||
timelineTilesPerRow<int>(),
|
timelineTilesPerRow<int>(.appConfig, 'timeline.tilesPerRow', 4),
|
||||||
timelineGroupAssetsBy<GroupAssetsBy>(codec: _EnumCodec(GroupAssetsBy.values)),
|
timelineGroupAssetsBy<GroupAssetsBy>(
|
||||||
timelineStorageIndicator<bool>(),
|
.appConfig,
|
||||||
|
'timeline.groupAssetsBy',
|
||||||
|
GroupAssetsBy.day,
|
||||||
|
_EnumCodec(GroupAssetsBy.values),
|
||||||
|
),
|
||||||
|
timelineStorageIndicator<bool>(.appConfig, 'timeline.storageIndicator', true),
|
||||||
|
|
||||||
// Log
|
// Log
|
||||||
logLevel<LogLevel>(scope: .system, codec: _EnumCodec(LogLevel.values)),
|
logLevel<LogLevel>(.systemConfig, 'log.level', .info, _EnumCodec(LogLevel.values)),
|
||||||
|
|
||||||
// Map
|
// Map
|
||||||
mapShowFavoriteOnly<bool>(),
|
mapShowFavoriteOnly<bool>(.appConfig, 'map.showFavoriteOnly', false),
|
||||||
mapRelativeDate<int>(),
|
mapRelativeDate<int>(.appConfig, 'map.relativeDate', 0),
|
||||||
mapIncludeArchived<bool>(),
|
mapIncludeArchived<bool>(.appConfig, 'map.includeArchived', false),
|
||||||
mapThemeMode<ThemeMode>(codec: _EnumCodec(ThemeMode.values)),
|
mapThemeMode<ThemeMode>(.appConfig, 'map.themeMode', .system, _EnumCodec(ThemeMode.values)),
|
||||||
mapWithPartners<bool>(),
|
mapWithPartners<bool>(.appConfig, 'map.withPartners', false),
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
cleanupKeepFavorites<bool>(),
|
cleanupKeepFavorites<bool>(.appConfig, 'cleanup.keepFavorites', true),
|
||||||
cleanupKeepMediaType<AssetKeepType>(codec: _EnumCodec(AssetKeepType.values)),
|
cleanupKeepMediaType<AssetKeepType>(
|
||||||
cleanupKeepAlbumIds<List<String>>(codec: _ListCodec(_PrimitiveCodec.string)),
|
.appConfig,
|
||||||
cleanupCutoffDaysAgo<int>(),
|
'cleanup.keepMediaType',
|
||||||
cleanupDefaultsInitialized<bool>(),
|
AssetKeepType.none,
|
||||||
|
_EnumCodec(AssetKeepType.values),
|
||||||
|
),
|
||||||
|
cleanupKeepAlbumIds<List<String>>(.appConfig, 'cleanup.keepAlbumIds', [], _ListCodec(_PrimitiveCodec.string)),
|
||||||
|
cleanupCutoffDaysAgo<int>(.appConfig, 'cleanup.cutoffDaysAgo', -1),
|
||||||
|
cleanupDefaultsInitialized<bool>(.appConfig, 'cleanup.defaultsInitialized', false);
|
||||||
|
|
||||||
// Slideshow
|
final MetadataDomain domain;
|
||||||
slideshowTransition<bool>(),
|
final String name;
|
||||||
slideshowRepeat<bool>(),
|
final T defaultValue;
|
||||||
slideshowDuration<int>(),
|
|
||||||
slideshowLook<SlideshowLook>(codec: _EnumCodec(SlideshowLook.values)),
|
|
||||||
slideshowDirection<SlideshowDirection>(codec: _EnumCodec(SlideshowDirection.values));
|
|
||||||
|
|
||||||
final MetadataScope scope;
|
|
||||||
final _MetadataCodec<T>? _codecOverride;
|
final _MetadataCodec<T>? _codecOverride;
|
||||||
|
|
||||||
const MetadataKey({this.scope = .user, _MetadataCodec<T>? codec}) : _codecOverride = codec;
|
const MetadataKey(this.domain, this.name, this.defaultValue, [this._codecOverride]);
|
||||||
|
|
||||||
_MetadataCodec<T> get _codec => _codecOverride ?? _MetadataCodec.forType(T);
|
String get key => '${domain.prefix}.$name';
|
||||||
|
|
||||||
|
_MetadataCodec<T> get _codec => _codecOverride ?? _MetadataCodec.forPrimitive(defaultValue);
|
||||||
|
|
||||||
String encode(T value) => _codec.encode(value);
|
String encode(T value) => _codec.encode(value);
|
||||||
|
|
||||||
T decode(String raw) => _codec.decode(raw);
|
T decode(String raw) => _codec.decode(raw) ?? defaultValue;
|
||||||
|
|
||||||
|
static Map<String, MetadataKey<Object>> asKeyMap() => {for (var value in MetadataKey.values) value.key: value};
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class _MetadataCodec<T extends Object> {
|
sealed class _MetadataCodec<T extends Object> {
|
||||||
const _MetadataCodec();
|
const _MetadataCodec();
|
||||||
|
|
||||||
String encode(T value);
|
String encode(T value);
|
||||||
T decode(String raw);
|
T? decode(String raw);
|
||||||
|
|
||||||
static const Map<Type, _MetadataCodec<Object>> _primitives = {
|
static const Map<Type, _MetadataCodec<Object>> _primitives = {
|
||||||
int: _PrimitiveCodec.integer,
|
int: _PrimitiveCodec.integer,
|
||||||
@@ -109,10 +98,12 @@ sealed class _MetadataCodec<T extends Object> {
|
|||||||
DateTime: _DateTimeCodec(),
|
DateTime: _DateTimeCodec(),
|
||||||
};
|
};
|
||||||
|
|
||||||
static _MetadataCodec<T> forType<T extends Object>(Type runtimeType) {
|
static _MetadataCodec<T> forPrimitive<T extends Object>(T sample) {
|
||||||
final codec = _primitives[runtimeType];
|
final codec = _primitives[sample.runtimeType];
|
||||||
if (codec == null) {
|
if (codec == null) {
|
||||||
throw StateError('No primitive codec for $runtimeType. Provide an explicit codec when defining the MetadataKey.');
|
throw StateError(
|
||||||
|
'No primitive codec for ${sample.runtimeType}. Provide an explicit codec when defining the MetadataKey.',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return codec as _MetadataCodec<T>;
|
return codec as _MetadataCodec<T>;
|
||||||
}
|
}
|
||||||
@@ -127,7 +118,7 @@ final class _EnumCodec<T extends Enum> extends _MetadataCodec<T> {
|
|||||||
String encode(T value) => value.name;
|
String encode(T value) => value.name;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
T decode(String raw) => values.firstWhere((v) => v.name == raw);
|
T? decode(String raw) => values.firstWhereOrNull((v) => v.name == raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
final class _DateTimeCodec extends _MetadataCodec<DateTime> {
|
final class _DateTimeCodec extends _MetadataCodec<DateTime> {
|
||||||
@@ -137,45 +128,7 @@ final class _DateTimeCodec extends _MetadataCodec<DateTime> {
|
|||||||
String encode(DateTime value) => value.toIso8601String();
|
String encode(DateTime value) => value.toIso8601String();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DateTime decode(String raw) => DateTime.parse(raw);
|
DateTime? decode(String raw) => DateTime.tryParse(raw);
|
||||||
}
|
|
||||||
|
|
||||||
final class _MapCodec<K extends Object, V extends Object> extends _MetadataCodec<Map<K, V>> {
|
|
||||||
final _MetadataCodec<K> _keyCodec;
|
|
||||||
final _MetadataCodec<V> _valueCodec;
|
|
||||||
|
|
||||||
const _MapCodec(this._keyCodec, this._valueCodec);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String encode(Map<K, V> value) {
|
|
||||||
final entries = <String, String>{};
|
|
||||||
value.forEach((k, v) => entries[_keyCodec.encode(k)] = _valueCodec.encode(v));
|
|
||||||
return jsonEncode(entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<K, V> decode(String raw) {
|
|
||||||
try {
|
|
||||||
final decoded = jsonDecode(raw);
|
|
||||||
if (decoded is! Map) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
final result = <K, V>{};
|
|
||||||
for (final entry in decoded.entries) {
|
|
||||||
final rawKey = entry.key;
|
|
||||||
final rawValue = entry.value;
|
|
||||||
if (rawKey is! String || rawValue is! String) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
final k = _keyCodec.decode(rawKey);
|
|
||||||
final v = _valueCodec.decode(rawValue);
|
|
||||||
result[k] = v;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} on FormatException {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final class _ListCodec<T extends Object> extends _MetadataCodec<List<T>> {
|
final class _ListCodec<T extends Object> extends _MetadataCodec<List<T>> {
|
||||||
@@ -187,29 +140,32 @@ final class _ListCodec<T extends Object> extends _MetadataCodec<List<T>> {
|
|||||||
String encode(List<T> value) => jsonEncode(value.map(_elementCodec.encode).toList());
|
String encode(List<T> value) => jsonEncode(value.map(_elementCodec.encode).toList());
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<T> decode(String raw) {
|
List<T>? decode(String raw) {
|
||||||
try {
|
try {
|
||||||
final decoded = jsonDecode(raw);
|
final decoded = jsonDecode(raw);
|
||||||
if (decoded is! List) {
|
if (decoded is! List) {
|
||||||
return [];
|
return null;
|
||||||
}
|
}
|
||||||
final result = <T>[];
|
final result = <T>[];
|
||||||
for (final item in decoded) {
|
for (final item in decoded) {
|
||||||
if (item is! String) {
|
if (item is! String) {
|
||||||
return [];
|
return null;
|
||||||
}
|
}
|
||||||
final element = _elementCodec.decode(item);
|
final element = _elementCodec.decode(item);
|
||||||
|
if (element == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
result.add(element);
|
result.add(element);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
} on FormatException {
|
} on FormatException {
|
||||||
return [];
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class _PrimitiveCodec<T extends Object> extends _MetadataCodec<T> {
|
final class _PrimitiveCodec<T extends Object> extends _MetadataCodec<T> {
|
||||||
final T Function(String) _parse;
|
final T? Function(String) _parse;
|
||||||
|
|
||||||
const _PrimitiveCodec._(this._parse);
|
const _PrimitiveCodec._(this._parse);
|
||||||
|
|
||||||
@@ -217,12 +173,12 @@ final class _PrimitiveCodec<T extends Object> extends _MetadataCodec<T> {
|
|||||||
String encode(T value) => value.toString();
|
String encode(T value) => value.toString();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
T decode(String raw) => _parse(raw);
|
T? decode(String raw) => _parse(raw);
|
||||||
|
|
||||||
static const integer = _PrimitiveCodec<int>._(int.parse);
|
static const integer = _PrimitiveCodec<int>._(int.tryParse);
|
||||||
static const real = _PrimitiveCodec<double>._(double.parse);
|
static const real = _PrimitiveCodec<double>._(double.tryParse);
|
||||||
static const boolean = _PrimitiveCodec<bool>._(bool.parse);
|
static const boolean = _PrimitiveCodec<bool>._(bool.tryParse);
|
||||||
static const string = _PrimitiveCodec<String>._(_identity);
|
static const string = _PrimitiveCodec<String>._(_identity);
|
||||||
|
|
||||||
static String _identity(String s) => s;
|
static String? _identity(String s) => s;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
|
|
||||||
enum Setting<T> {
|
enum Setting<T> {
|
||||||
advancedTroubleshooting<bool>(StoreKey.advancedTroubleshooting, false);
|
advancedTroubleshooting<bool>(StoreKey.advancedTroubleshooting, false),
|
||||||
|
enableBackup<bool>(StoreKey.enableBackup, false);
|
||||||
|
|
||||||
const Setting(this.storeKey, this.defaultValue);
|
const Setting(this.storeKey, this.defaultValue);
|
||||||
|
|
||||||
|
|||||||
@@ -6,33 +6,36 @@ enum StoreKey<T> {
|
|||||||
version<int>._(0),
|
version<int>._(0),
|
||||||
currentUser<UserDto>._(2),
|
currentUser<UserDto>._(2),
|
||||||
deviceId<String>._(4),
|
deviceId<String>._(4),
|
||||||
|
backupRequireCharging<bool>._(7),
|
||||||
|
backupTriggerDelay<int>._(8),
|
||||||
serverUrl<String>._(10),
|
serverUrl<String>._(10),
|
||||||
accessToken<String>._(11),
|
accessToken<String>._(11),
|
||||||
serverEndpoint<String>._(12),
|
serverEndpoint<String>._(12),
|
||||||
|
selectedAlbumSortOrder<int>._(113),
|
||||||
advancedTroubleshooting<bool>._(114),
|
advancedTroubleshooting<bool>._(114),
|
||||||
|
selectedAlbumSortReverse<bool>._(123),
|
||||||
enableHapticFeedback<bool>._(126),
|
enableHapticFeedback<bool>._(126),
|
||||||
|
customHeaders<String>._(127),
|
||||||
|
syncAlbums<bool>._(131),
|
||||||
|
|
||||||
|
// Auto endpoint switching
|
||||||
|
autoEndpointSwitching<bool>._(132),
|
||||||
|
preferredWifiName<String>._(133),
|
||||||
|
localEndpoint<String>._(134),
|
||||||
|
externalEndpointList<String>._(135),
|
||||||
|
|
||||||
manageLocalMediaAndroid<bool>._(137),
|
manageLocalMediaAndroid<bool>._(137),
|
||||||
// Read-only Mode settings
|
// Read-only Mode settings
|
||||||
readonlyModeEnabled<bool>._(138),
|
readonlyModeEnabled<bool>._(138),
|
||||||
|
albumGridView<bool>._(140),
|
||||||
|
|
||||||
|
// Experimental stuff
|
||||||
|
enableBackup<bool>._(1003),
|
||||||
|
useWifiForUploadVideos<bool>._(1004),
|
||||||
|
useWifiForUploadPhotos<bool>._(1005),
|
||||||
syncMigrationStatus<String>._(1013),
|
syncMigrationStatus<String>._(1013),
|
||||||
|
|
||||||
// Legacy keys that have been migrated to the new metadata store
|
// Legacy keys that have been migrated to the new metadata store
|
||||||
legacyBackupRequireCharging<bool>._(7),
|
|
||||||
legacyBackupTriggerDelay<int>._(8),
|
|
||||||
legacySyncAlbums<bool>._(131),
|
|
||||||
legacyEnableBackup<bool>._(1003),
|
|
||||||
legacyUseWifiForUploadVideos<bool>._(1004),
|
|
||||||
legacyUseWifiForUploadPhotos<bool>._(1005),
|
|
||||||
legacySelectedAlbumSortOrder<int>._(113),
|
|
||||||
legacySelectedAlbumSortReverse<bool>._(123),
|
|
||||||
legacyAlbumGridView<bool>._(140),
|
|
||||||
legacyAutoEndpointSwitching<bool>._(132),
|
|
||||||
legacyPreferredWifiName<String>._(133),
|
|
||||||
legacyLocalEndpoint<String>._(134),
|
|
||||||
legacyExternalEndpointList<String>._(135),
|
|
||||||
legacyCustomHeaders<String>._(127),
|
|
||||||
legacyLoopVideo<bool>._(117),
|
legacyLoopVideo<bool>._(117),
|
||||||
legacyLoadOriginalVideo<bool>._(136),
|
legacyLoadOriginalVideo<bool>._(136),
|
||||||
legacyAutoPlayVideo<bool>._(139),
|
legacyAutoPlayVideo<bool>._(139),
|
||||||
|
|||||||
@@ -8,7 +8,11 @@ class AssetService {
|
|||||||
final RemoteAssetRepository _remoteAssetRepository;
|
final RemoteAssetRepository _remoteAssetRepository;
|
||||||
final DriftLocalAssetRepository _localAssetRepository;
|
final DriftLocalAssetRepository _localAssetRepository;
|
||||||
|
|
||||||
const AssetService({required this._remoteAssetRepository, required this._localAssetRepository});
|
const AssetService({
|
||||||
|
required RemoteAssetRepository remoteAssetRepository,
|
||||||
|
required DriftLocalAssetRepository localAssetRepository,
|
||||||
|
}) : _remoteAssetRepository = remoteAssetRepository,
|
||||||
|
_localAssetRepository = localAssetRepository;
|
||||||
|
|
||||||
Future<BaseAsset?> getAsset(BaseAsset asset) {
|
Future<BaseAsset?> getAsset(BaseAsset asset) {
|
||||||
final id = asset is LocalAsset ? asset.id : (asset as RemoteAsset).id;
|
final id = asset is LocalAsset ? asset.id : (asset as RemoteAsset).id;
|
||||||
|
|||||||
@@ -11,14 +11,15 @@ 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/db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.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/providers/app_settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
import 'package:immich_mobile/providers/backup/drift_backup.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' show nativeSyncApiProvider;
|
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart' show nativeSyncApiProvider;
|
||||||
import 'package:immich_mobile/providers/user.provider.dart';
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
import 'package:immich_mobile/services/auth.service.dart';
|
import 'package:immich_mobile/services/auth.service.dart';
|
||||||
import 'package:immich_mobile/services/foreground_upload.service.dart';
|
import 'package:immich_mobile/services/foreground_upload.service.dart';
|
||||||
import 'package:immich_mobile/services/localization.service.dart';
|
import 'package:immich_mobile/services/localization.service.dart';
|
||||||
@@ -38,15 +39,16 @@ class BackgroundWorkerFgService {
|
|||||||
Future<void> saveNotificationMessage(String title, String body) =>
|
Future<void> saveNotificationMessage(String title, String body) =>
|
||||||
_foregroundHostApi.saveNotificationMessage(title, body);
|
_foregroundHostApi.saveNotificationMessage(title, body);
|
||||||
|
|
||||||
Future<void> configure({int? minimumDelaySeconds, bool? requireCharging}) {
|
Future<void> configure({int? minimumDelaySeconds, bool? requireCharging}) => _foregroundHostApi.configure(
|
||||||
final backup = MetadataRepository.instance.appConfig.backup;
|
BackgroundWorkerSettings(
|
||||||
return _foregroundHostApi.configure(
|
minimumDelaySeconds:
|
||||||
BackgroundWorkerSettings(
|
minimumDelaySeconds ??
|
||||||
minimumDelaySeconds: minimumDelaySeconds ?? backup.triggerDelay,
|
Store.get(AppSettingsEnum.backupTriggerDelay.storeKey, AppSettingsEnum.backupTriggerDelay.defaultValue),
|
||||||
requiresCharging: requireCharging ?? backup.requireCharging,
|
requiresCharging:
|
||||||
),
|
requireCharging ??
|
||||||
);
|
Store.get(AppSettingsEnum.backupRequireCharging.storeKey, AppSettingsEnum.backupRequireCharging.defaultValue),
|
||||||
}
|
),
|
||||||
|
);
|
||||||
|
|
||||||
Future<void> disable() => _foregroundHostApi.disable();
|
Future<void> disable() => _foregroundHostApi.disable();
|
||||||
}
|
}
|
||||||
@@ -61,13 +63,15 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
|||||||
|
|
||||||
bool _isCleanedUp = false;
|
bool _isCleanedUp = false;
|
||||||
|
|
||||||
BackgroundWorkerBgService({required this._drift, required this._driftLogger})
|
BackgroundWorkerBgService({required Drift drift, required DriftLogger driftLogger})
|
||||||
: _backgroundHostApi = BackgroundWorkerBgHostApi() {
|
: _drift = drift,
|
||||||
_ref = ProviderContainer(overrides: [driftProvider.overrideWith(driftOverride(_drift))]);
|
_driftLogger = driftLogger,
|
||||||
|
_backgroundHostApi = BackgroundWorkerBgHostApi() {
|
||||||
|
_ref = ProviderContainer(overrides: [driftProvider.overrideWith(driftOverride(drift))]);
|
||||||
BackgroundWorkerFlutterApi.setUp(this);
|
BackgroundWorkerFlutterApi.setUp(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get _isBackupEnabled => MetadataRepository.instance.appConfig.backup.enabled;
|
bool get _isBackupEnabled => _ref?.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup) ?? false;
|
||||||
|
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -21,13 +21,18 @@ class HashService {
|
|||||||
final _log = Logger('HashService');
|
final _log = Logger('HashService');
|
||||||
|
|
||||||
HashService({
|
HashService({
|
||||||
required this._localAlbumRepository,
|
required DriftLocalAlbumRepository localAlbumRepository,
|
||||||
required this._localAssetRepository,
|
required DriftLocalAssetRepository localAssetRepository,
|
||||||
required this._trashedLocalAssetRepository,
|
required DriftTrashedLocalAssetRepository trashedLocalAssetRepository,
|
||||||
required this._nativeSyncApi,
|
required NativeSyncApi nativeSyncApi,
|
||||||
this._cancelChecker,
|
bool Function()? cancelChecker,
|
||||||
int? batchSize,
|
int? batchSize,
|
||||||
}) : _batchSize = batchSize ?? kBatchHashFileLimit;
|
}) : _localAlbumRepository = localAlbumRepository,
|
||||||
|
_localAssetRepository = localAssetRepository,
|
||||||
|
_trashedLocalAssetRepository = trashedLocalAssetRepository,
|
||||||
|
_cancelChecker = cancelChecker,
|
||||||
|
_nativeSyncApi = nativeSyncApi,
|
||||||
|
_batchSize = batchSize ?? kBatchHashFileLimit;
|
||||||
|
|
||||||
bool get isCancelled => _cancelChecker?.call() ?? false;
|
bool get isCancelled => _cancelChecker?.call() ?? false;
|
||||||
|
|
||||||
|
|||||||
@@ -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,24 +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 this._localAlbumRepository,
|
required DriftLocalAlbumRepository localAlbumRepository,
|
||||||
required this._localAssetRepository,
|
required DriftLocalAssetRepository localAssetRepository,
|
||||||
required this._nativeSyncApi,
|
required DriftTrashedLocalAssetRepository trashedLocalAssetRepository,
|
||||||
required this._trashedLocalAssetRepository,
|
required LocalFilesManagerRepository localFilesManager,
|
||||||
required this._assetMediaRepository,
|
required StorageRepository storageRepository,
|
||||||
required this._permissionRepository,
|
required NativeSyncApi nativeSyncApi,
|
||||||
});
|
}) : _localAlbumRepository = localAlbumRepository,
|
||||||
|
_localAssetRepository = localAssetRepository,
|
||||||
|
_trashedLocalAssetRepository = trashedLocalAssetRepository,
|
||||||
|
_localFilesManager = localFilesManager,
|
||||||
|
_storageRepository = storageRepository,
|
||||||
|
_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 {
|
||||||
@@ -368,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");
|
||||||
@@ -376,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");
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ class LogService {
|
|||||||
}) async {
|
}) async {
|
||||||
final instance = LogService._(logRepository, metadataRepository, shouldBuffer);
|
final instance = LogService._(logRepository, metadataRepository, shouldBuffer);
|
||||||
await logRepository.truncate(limit: kLogTruncateLimit);
|
await logRepository.truncate(limit: kLogTruncateLimit);
|
||||||
final level = instance._metadataRepository.appConfig.logLevel;
|
final level = instance._metadataRepository.systemConfig.logLevel;
|
||||||
Logger.root.level = Level.LEVELS.elementAtOrNull(level.index) ?? Level.INFO;
|
Logger.root.level = Level.LEVELS.elementAtOrNull(level.index) ?? Level.INFO;
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ typedef MapQuery = ({MapMarkerSource markerSource});
|
|||||||
class MapFactory {
|
class MapFactory {
|
||||||
final DriftMapRepository _mapRepository;
|
final DriftMapRepository _mapRepository;
|
||||||
|
|
||||||
const MapFactory({required this._mapRepository});
|
const MapFactory({required DriftMapRepository mapRepository}) : _mapRepository = mapRepository;
|
||||||
|
|
||||||
MapService remote(List<String> ownerIds, TimelineMapOptions options) =>
|
MapService remote(List<String> ownerIds, TimelineMapOptions options) =>
|
||||||
MapService(_mapRepository.remote(ownerIds, options));
|
MapService(_mapRepository.remote(ownerIds, options));
|
||||||
|
|||||||
@@ -9,47 +9,12 @@ import 'package:immich_mobile/infrastructure/repositories/remote_album.repositor
|
|||||||
import 'package:immich_mobile/models/albums/album_search.model.dart';
|
import 'package:immich_mobile/models/albums/album_search.model.dart';
|
||||||
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
||||||
import 'package:immich_mobile/repositories/drift_album_api_repository.dart';
|
import 'package:immich_mobile/repositories/drift_album_api_repository.dart';
|
||||||
import 'package:immich_mobile/services/foreground_upload.service.dart';
|
|
||||||
import 'package:logging/logging.dart';
|
|
||||||
|
|
||||||
/// Categorizes a heterogeneous asset selection into the candidates that can
|
|
||||||
/// be added to an album immediately (already on the server) and the local-only
|
|
||||||
/// candidates that must be uploaded first.
|
|
||||||
class AlbumAssetCandidates {
|
|
||||||
final List<String> remoteAssetIds;
|
|
||||||
final List<LocalAsset> localAssetsToUpload;
|
|
||||||
|
|
||||||
const AlbumAssetCandidates({required this.remoteAssetIds, required this.localAssetsToUpload});
|
|
||||||
}
|
|
||||||
|
|
||||||
class RemoteAlbumService {
|
class RemoteAlbumService {
|
||||||
static final _logger = Logger('RemoteAlbumService');
|
|
||||||
|
|
||||||
final DriftRemoteAlbumRepository _repository;
|
final DriftRemoteAlbumRepository _repository;
|
||||||
final DriftAlbumApiRepository _albumApiRepository;
|
final DriftAlbumApiRepository _albumApiRepository;
|
||||||
final ForegroundUploadService _uploadService;
|
|
||||||
|
|
||||||
const RemoteAlbumService(this._repository, this._albumApiRepository, this._uploadService);
|
const RemoteAlbumService(this._repository, this._albumApiRepository);
|
||||||
|
|
||||||
/// Categorizes a heterogeneous asset selection into already-on-server IDs
|
|
||||||
/// and local assets that still need to be uploaded.
|
|
||||||
static AlbumAssetCandidates categorizeCandidates(Iterable<BaseAsset> assets) {
|
|
||||||
final remoteIds = <String>[];
|
|
||||||
final localToUpload = <LocalAsset>[];
|
|
||||||
for (final asset in assets) {
|
|
||||||
if (asset is RemoteAsset) {
|
|
||||||
remoteIds.add(asset.id);
|
|
||||||
} else if (asset is LocalAsset) {
|
|
||||||
final remoteId = asset.remoteId;
|
|
||||||
if (remoteId != null) {
|
|
||||||
remoteIds.add(remoteId);
|
|
||||||
} else {
|
|
||||||
localToUpload.add(asset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return AlbumAssetCandidates(remoteAssetIds: remoteIds, localAssetsToUpload: localToUpload);
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream<RemoteAlbum?> watchAlbum(String albumId) {
|
Stream<RemoteAlbum?> watchAlbum(String albumId) {
|
||||||
return _repository.watchAlbum(albumId);
|
return _repository.watchAlbum(albumId);
|
||||||
@@ -183,122 +148,6 @@ class RemoteAlbumService {
|
|||||||
return album.added.length;
|
return album.added.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// !TODO The name here is not clear as we have addAssets method above,
|
|
||||||
/// which is only add remote assets to album, for the next PR, we will allow
|
|
||||||
/// adding local assets from album from the timeline as well with this flow.
|
|
||||||
/// So saving that for the next refactor
|
|
||||||
Future<int> addAssetsToAlbum({
|
|
||||||
required String albumId,
|
|
||||||
required UserDto uploader,
|
|
||||||
required AlbumAssetCandidates candidates,
|
|
||||||
UploadCallbacks uploadCallbacks = const UploadCallbacks(),
|
|
||||||
}) async {
|
|
||||||
int addedCount = 0;
|
|
||||||
if (candidates.remoteAssetIds.isNotEmpty) {
|
|
||||||
addedCount += await addAssets(albumId: albumId, assetIds: candidates.remoteAssetIds);
|
|
||||||
}
|
|
||||||
if (candidates.localAssetsToUpload.isNotEmpty) {
|
|
||||||
addedCount += await _uploadAndAddLocals(albumId, uploader, candidates.localAssetsToUpload, uploadCallbacks);
|
|
||||||
}
|
|
||||||
return addedCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates an album, seeding it with already-remote asset IDs, then uploads
|
|
||||||
/// local-only assets and links each one as it finishes.
|
|
||||||
Future<RemoteAlbum> createAlbumWithAssets({
|
|
||||||
required String title,
|
|
||||||
required UserDto owner,
|
|
||||||
String? description,
|
|
||||||
AlbumAssetCandidates candidates = const AlbumAssetCandidates(remoteAssetIds: [], localAssetsToUpload: []),
|
|
||||||
UploadCallbacks uploadCallbacks = const UploadCallbacks(),
|
|
||||||
}) async {
|
|
||||||
final album = await createAlbum(
|
|
||||||
title: title,
|
|
||||||
owner: owner,
|
|
||||||
description: description,
|
|
||||||
assetIds: candidates.remoteAssetIds,
|
|
||||||
);
|
|
||||||
if (candidates.localAssetsToUpload.isNotEmpty) {
|
|
||||||
await _uploadAndAddLocals(album.id, owner, candidates.localAssetsToUpload, uploadCallbacks);
|
|
||||||
}
|
|
||||||
return album;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<int> _uploadAndAddLocals(
|
|
||||||
String albumId,
|
|
||||||
UserDto uploader,
|
|
||||||
List<LocalAsset> localAssets,
|
|
||||||
UploadCallbacks userCallbacks,
|
|
||||||
) async {
|
|
||||||
int addedCount = 0;
|
|
||||||
final pendingAdds = <Future<void>>[];
|
|
||||||
final localById = {for (final a in localAssets) a.id: a};
|
|
||||||
|
|
||||||
final wrappedCallbacks = UploadCallbacks(
|
|
||||||
onProgress: (localId, filename, bytes, totalBytes) => _runUploadCallback(
|
|
||||||
'Upload progress callback failed for $localId',
|
|
||||||
() => userCallbacks.onProgress?.call(localId, filename, bytes, totalBytes),
|
|
||||||
),
|
|
||||||
onICloudProgress: (localId, progress) => _runUploadCallback(
|
|
||||||
'iCloud progress callback failed for $localId',
|
|
||||||
() => userCallbacks.onICloudProgress?.call(localId, progress),
|
|
||||||
),
|
|
||||||
onError: (localId, errorMessage) => _runUploadCallback(
|
|
||||||
'Upload error callback failed for $localId',
|
|
||||||
() => userCallbacks.onError?.call(localId, errorMessage),
|
|
||||||
),
|
|
||||||
onSuccess: (localId, remoteId) {
|
|
||||||
_runUploadCallback(
|
|
||||||
'Upload success callback failed for $localId',
|
|
||||||
() => userCallbacks.onSuccess?.call(localId, remoteId),
|
|
||||||
);
|
|
||||||
final source = localById[localId];
|
|
||||||
if (source == null) {
|
|
||||||
_logger.warning('Upload success for $localId but source LocalAsset missing; skipping album link');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
pendingAdds.add(
|
|
||||||
_linkUploadedAssetToAlbum(albumId, remoteId, uploader, source)
|
|
||||||
.then<void>((added) {
|
|
||||||
addedCount += added;
|
|
||||||
})
|
|
||||||
.catchError((Object error, StackTrace stack) {
|
|
||||||
_logger.warning('Failed to add uploaded asset $remoteId to album $albumId', error, stack);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
await _uploadService.uploadManual(localAssets, callbacks: wrappedCallbacks);
|
|
||||||
await Future.wait(pendingAdds);
|
|
||||||
return addedCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _runUploadCallback(String message, void Function() callback) {
|
|
||||||
try {
|
|
||||||
callback();
|
|
||||||
} catch (error, stack) {
|
|
||||||
_logger.warning(message, error, stack);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Links a freshly-uploaded asset to an album, ensuring the local DB
|
|
||||||
/// reflects the change without waiting for the next sync. We call the API
|
|
||||||
/// (server is the source of truth), then upsert a placeholder
|
|
||||||
/// `remote_asset_entity` row from the local source so the FK-protected
|
|
||||||
/// junction insert succeeds. Sync overwrites the placeholder later with
|
|
||||||
/// the authoritative server data.
|
|
||||||
Future<int> _linkUploadedAssetToAlbum(String albumId, String remoteId, UserDto uploader, LocalAsset source) async {
|
|
||||||
final result = await _albumApiRepository.addAssets(albumId, [remoteId]);
|
|
||||||
if (result.added.isEmpty) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
await _repository.upsertRemoteAssetStub(remoteId: remoteId, ownerId: uploader.id, source: source);
|
|
||||||
await _repository.addAssets(albumId, result.added);
|
|
||||||
return result.added.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> deleteAlbum(String albumId) async {
|
Future<void> deleteAlbum(String albumId) async {
|
||||||
await _albumApiRepository.deleteAlbum(albumId);
|
await _albumApiRepository.deleteAlbum(albumId);
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ final AppSetting = SettingsService(storeService: StoreService.I);
|
|||||||
class SettingsService {
|
class SettingsService {
|
||||||
final StoreService _storeService;
|
final StoreService _storeService;
|
||||||
|
|
||||||
const SettingsService({required this._storeService});
|
const SettingsService({required StoreService storeService}) : _storeService = storeService;
|
||||||
|
|
||||||
T get<T>(Setting<T> setting) => _storeService.get(setting.storeKey, setting.defaultValue);
|
T get<T>(Setting<T> setting) => _storeService.get(setting.storeKey, setting.defaultValue);
|
||||||
|
|
||||||
|
|||||||
@@ -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,23 +34,31 @@ 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;
|
||||||
|
|
||||||
SyncStreamService({
|
SyncStreamService({
|
||||||
required this._syncApiRepository,
|
required SyncApiRepository syncApiRepository,
|
||||||
required this._syncStreamRepository,
|
required SyncStreamRepository syncStreamRepository,
|
||||||
required this._localAssetRepository,
|
required DriftLocalAssetRepository localAssetRepository,
|
||||||
required this._trashedLocalAssetRepository,
|
required DriftTrashedLocalAssetRepository trashedLocalAssetRepository,
|
||||||
required this._assetMediaRepository,
|
required LocalFilesManagerRepository localFilesManager,
|
||||||
required this._permissionRepository,
|
required StorageRepository storageRepository,
|
||||||
required this._syncMigrationRepository,
|
required SyncMigrationRepository syncMigrationRepository,
|
||||||
required this._api,
|
required ApiService api,
|
||||||
this._cancelChecker,
|
bool Function()? cancelChecker,
|
||||||
});
|
}) : _syncApiRepository = syncApiRepository,
|
||||||
|
_syncStreamRepository = syncStreamRepository,
|
||||||
|
_localAssetRepository = localAssetRepository,
|
||||||
|
_trashedLocalAssetRepository = trashedLocalAssetRepository,
|
||||||
|
_localFilesManager = localFilesManager,
|
||||||
|
_storageRepository = storageRepository,
|
||||||
|
_syncMigrationRepository = syncMigrationRepository,
|
||||||
|
_api = api,
|
||||||
|
_cancelChecker = cancelChecker;
|
||||||
|
|
||||||
bool get isCancelled => _cancelChecker?.call() ?? false;
|
bool get isCancelled => _cancelChecker?.call() ?? false;
|
||||||
|
|
||||||
@@ -492,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");
|
||||||
@@ -515,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;
|
||||||
}
|
}
|
||||||
@@ -525,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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/tag.model.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/repositories/tags_api.repository.dart';
|
|
||||||
|
|
||||||
final tagServiceProvider = Provider<TagService>((ref) => TagService(ref.watch(tagsApiRepositoryProvider)));
|
|
||||||
|
|
||||||
class TagService {
|
|
||||||
final TagsApiRepository _repository;
|
|
||||||
|
|
||||||
const TagService(this._repository);
|
|
||||||
|
|
||||||
Future<int> bulkTagAssets(List<String> assetIds, List<String> tagIds) async {
|
|
||||||
return _repository.bulkTagAssets(assetIds, tagIds);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Set<Tag>> getAllTags() async {
|
|
||||||
final dtos = await _repository.getAllTags();
|
|
||||||
if (dtos == null) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
return dtos.map((dto) => Tag.fromDto(dto)).toSet();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<Tag>> upsertTags(List<String> tags) async {
|
|
||||||
final dtos = await _repository.upsertTags(tags);
|
|
||||||
if (dtos == null) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return dtos.map((dto) => Tag.fromDto(dto)).toList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -41,7 +41,11 @@ class TimelineFactory {
|
|||||||
final DriftTimelineRepository _timelineRepository;
|
final DriftTimelineRepository _timelineRepository;
|
||||||
final MetadataRepository _metadataRepository;
|
final MetadataRepository _metadataRepository;
|
||||||
|
|
||||||
const TimelineFactory({required this._timelineRepository, required this._metadataRepository});
|
const TimelineFactory({
|
||||||
|
required DriftTimelineRepository timelineRepository,
|
||||||
|
required MetadataRepository metadataRepository,
|
||||||
|
}) : _timelineRepository = timelineRepository,
|
||||||
|
_metadataRepository = metadataRepository;
|
||||||
|
|
||||||
GroupAssetsBy get groupBy {
|
GroupAssetsBy get groupBy {
|
||||||
final group = _metadataRepository.appConfig.timeline.groupAssetsBy;
|
final group = _metadataRepository.appConfig.timeline.groupAssetsBy;
|
||||||
@@ -104,7 +108,12 @@ class TimelineService {
|
|||||||
TimelineService(TimelineQuery query)
|
TimelineService(TimelineQuery query)
|
||||||
: this._(assetSource: query.assetSource, bucketSource: query.bucketSource, origin: query.origin);
|
: this._(assetSource: query.assetSource, bucketSource: query.bucketSource, origin: query.origin);
|
||||||
|
|
||||||
TimelineService._({required this._assetSource, required this._bucketSource, required this.origin}) {
|
TimelineService._({
|
||||||
|
required TimelineAssetSource assetSource,
|
||||||
|
required TimelineBucketSource bucketSource,
|
||||||
|
required this.origin,
|
||||||
|
}) : _assetSource = assetSource,
|
||||||
|
_bucketSource = bucketSource {
|
||||||
_bucketSubscription = _bucketSource().listen((buckets) {
|
_bucketSubscription = _bucketSource().listen((buckets) {
|
||||||
_mutex.run(() async {
|
_mutex.run(() async {
|
||||||
final totalAssets = buckets.fold<int>(0, (acc, bucket) => acc + bucket.assetCount);
|
final totalAssets = buckets.fold<int>(0, (acc, bucket) => acc + bucket.assetCount);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user