mirror of
https://github.com/immich-app/immich.git
synced 2026-05-27 18:12:31 -04:00
Compare commits
110 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 480b115426 | |||
| 936c28a40b | |||
| 1a837a28ac | |||
| 8d5d12b108 | |||
| dd7a94135f | |||
| 1acc511b5c | |||
| 452e88267a | |||
| b941108cbd | |||
| e46f2843f7 | |||
| cf991e7b1b | |||
| 748a13104a | |||
| 2dd6b47714 | |||
| 8682be4774 | |||
| dc66892ca1 | |||
| 53a24783f5 | |||
| 0546bc900c | |||
| 7c25bcc0a7 | |||
| 7905853639 | |||
| 073dcc1fbe | |||
| ccdaa4223c | |||
| 5386b62dc4 | |||
| 9733fa4872 | |||
| 3b34c53092 | |||
| fd7ddfef54 | |||
| 0975b1599c | |||
| 78ac0ade01 | |||
| 7b9dab872b | |||
| 6413495fb8 | |||
| b414b3d32b | |||
| 20da7c4267 | |||
| 92b6778d2d | |||
| 5a61e589e8 | |||
| 85192bb110 | |||
| c7ae97fa2b | |||
| 8d02f3625d | |||
| a5a7380a26 | |||
| d9ce3d2046 | |||
| 815ff677fc | |||
| 915d865ce2 | |||
| c28e5f90b6 | |||
| 4383473ed6 | |||
| 77701dd5a3 | |||
| d4808fdc4d | |||
| 7fa967a98e | |||
| 9cffcc9f4e | |||
| 40925f0a06 | |||
| 0544d22902 | |||
| 3d075f2bf8 | |||
| 7384799f19 | |||
| 4a7f06e8fd | |||
| 8f662fc459 | |||
| 24b1dae9f2 | |||
| 3a3469a5f9 | |||
| 7993619ed2 | |||
| 4d1f6f869b | |||
| 3eb03f7934 | |||
| 03ed3daa31 | |||
| 02581e81a7 | |||
| 3ab3d5cf43 | |||
| 0ef04d9baa | |||
| df016f9228 | |||
| 17779c1e74 | |||
| 01d6a244d8 | |||
| 21d6755f39 | |||
| e91c017dd0 | |||
| 43687cd8b4 | |||
| 06729ee5a5 | |||
| b0c9743d9a | |||
| 37cc028868 | |||
| 84a2b7a3c8 | |||
| 89b3433346 | |||
| 3ff0d47ee3 | |||
| aeaf846482 | |||
| b031548791 | |||
| fcea617313 | |||
| 024f20ea26 | |||
| 0a4ed6fd71 | |||
| b6e2ce1f35 | |||
| e323e778cd | |||
| 6a87797649 | |||
| f4a4649bbc | |||
| 6ca54ee722 | |||
| 8e3035f783 | |||
| 79801595db | |||
| 3e1c8aacb1 | |||
| 91ac56cef2 | |||
| 58beac8fe0 | |||
| f632d320f5 | |||
| 2ddaf6a611 | |||
| 1932c60e1c | |||
| dc6f8e746e | |||
| ad7aedb843 | |||
| 571e6a8560 | |||
| 4791313def | |||
| f88fdae048 | |||
| bcef7aa6b6 | |||
| ce292bdce9 | |||
| 4eee023648 | |||
| 8f4b0fce49 | |||
| c6b3127b35 | |||
| 4d6a50c2cb | |||
| 15f3947ae6 | |||
| e142e3aca7 | |||
| 38438c8d9a | |||
| a278c10c75 | |||
| 2276443c56 | |||
| bb44773e57 | |||
| 14d9e90a03 | |||
| 03e042213c | |||
| db589455f4 |
@@ -75,7 +75,7 @@
|
||||
{
|
||||
"label": "Build Immich CLI",
|
||||
"type": "shell",
|
||||
"command": "pnpm --filter cli build:dev"
|
||||
"command": "pnpm --filter @immich/cli build:dev"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ services:
|
||||
- ${UPLOAD_LOCATION:-upload-devcontainer-volume}${UPLOAD_LOCATION:+/photos}:/data
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- pnpm_store_server:/buildcache/pnpm-store
|
||||
- ../plugins:/build/corePlugin
|
||||
- ../packages/plugin-core:/build/plugins/immich-plugin-core
|
||||
immich-web:
|
||||
env_file: !reset []
|
||||
immich-machine-learning:
|
||||
|
||||
@@ -8,6 +8,8 @@ log "Preparing Immich Web Frontend"
|
||||
log ""
|
||||
run_cmd pnpm --filter @immich/sdk install
|
||||
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
|
||||
|
||||
log "Starting Immich Web Frontend"
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
cli:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- cli/src/**
|
||||
- packages/cli/src/**
|
||||
|
||||
documentation:
|
||||
- changed-files:
|
||||
|
||||
@@ -90,6 +90,11 @@ jobs:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Create the Keystore
|
||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||
env:
|
||||
@@ -111,16 +116,8 @@ jobs:
|
||||
~/.gradle/wrapper
|
||||
~/.android/sdk
|
||||
mobile/android/.gradle
|
||||
mobile/.dart_tool
|
||||
key: build-mobile-gradle-${{ runner.os }}-main
|
||||
|
||||
- name: Setup Flutter SDK
|
||||
uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0
|
||||
with:
|
||||
channel: 'stable'
|
||||
flutter-version-file: ./mobile/pubspec.yaml
|
||||
cache: true
|
||||
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4.0.1
|
||||
with:
|
||||
@@ -131,11 +128,10 @@ jobs:
|
||||
run: flutter pub get
|
||||
|
||||
- name: Generate translation file
|
||||
run: dart run easy_localization:generate -S ../i18n && dart run bin/generate_keys.dart
|
||||
working-directory: ./mobile
|
||||
run: mise //mobile:codegen:translation
|
||||
|
||||
- name: Generate platform APIs
|
||||
run: make pigeon
|
||||
run: mise //mobile:codegen:pigeon
|
||||
working-directory: ./mobile
|
||||
|
||||
- name: Build Android App Bundle
|
||||
@@ -163,14 +159,14 @@ jobs:
|
||||
|
||||
- name: Comment APK download link on PR
|
||||
if: ${{ github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork }}
|
||||
uses: mshick/add-pr-comment@8e4927817251f1ff60c001f04568532b38e0b4a0 # v3.11.0
|
||||
uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0
|
||||
env:
|
||||
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
APK_URL: ${{ steps.upload-apk.outputs.artifact-url }}
|
||||
with:
|
||||
github-token: ${{ steps.token.outputs.token }}
|
||||
message-id: 'mobile-android-apk'
|
||||
message: |
|
||||
id: mobile-android-apk
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
body: |
|
||||
📱 **Android release APK (universal)** — `${{ env.HEAD_SHA }}`
|
||||
|
||||
Download: ${{ env.APK_URL }}
|
||||
@@ -192,7 +188,6 @@ jobs:
|
||||
~/.gradle/wrapper
|
||||
~/.android/sdk
|
||||
mobile/android/.gradle
|
||||
mobile/.dart_tool
|
||||
key: ${{ steps.cache-gradle-restore.outputs.cache-primary-key }}
|
||||
|
||||
build-sign-ios:
|
||||
@@ -205,6 +200,12 @@ jobs:
|
||||
runs-on: macos-15
|
||||
|
||||
steps:
|
||||
- id: token
|
||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||
with:
|
||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Select Xcode 26
|
||||
run: sudo xcode-select -s /Applications/Xcode_26.2.app/Contents/Developer
|
||||
|
||||
@@ -214,27 +215,27 @@ jobs:
|
||||
ref: ${{ inputs.ref || github.sha }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Flutter SDK
|
||||
uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
||||
with:
|
||||
channel: 'stable'
|
||||
flutter-version-file: ./mobile/pubspec.yaml
|
||||
cache: true
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Install Flutter dependencies
|
||||
working-directory: ./mobile
|
||||
run: flutter pub get
|
||||
|
||||
- name: Generate translation files
|
||||
run: dart run easy_localization:generate -S ../i18n && dart run bin/generate_keys.dart
|
||||
working-directory: ./mobile
|
||||
run: mise //mobile:codegen:translation
|
||||
|
||||
- name: Generate platform APIs
|
||||
run: make 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
|
||||
uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0
|
||||
uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0
|
||||
with:
|
||||
ruby-version: '3.3'
|
||||
bundler-cache: true
|
||||
@@ -291,7 +292,6 @@ jobs:
|
||||
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 }}
|
||||
ENVIRONMENT: ${{ inputs.environment || 'development' }}
|
||||
BUNDLE_ID_SUFFIX: ${{ inputs.environment == 'production' && '' || 'development' }}
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT: 120
|
||||
FASTLANE_XCODEBUILD_SETTINGS_RETRIES: 6
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Check for breaking API changes
|
||||
uses: oasdiff/oasdiff-action/breaking@37bf9ff785c7315df88216660826e71be4cc03da # v0.0.44
|
||||
uses: oasdiff/oasdiff-action/breaking@6147a58e5d1249a12f42fc864ab791d571a30015 # v0.0.47
|
||||
with:
|
||||
base: https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json
|
||||
revision: open-api/immich-openapi-specs.json
|
||||
|
||||
@@ -3,11 +3,11 @@ on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'cli/**'
|
||||
- 'packages/cli/**'
|
||||
- '.github/workflows/cli.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'cli/**'
|
||||
- 'packages/cli/**'
|
||||
- '.github/workflows/cli.yml'
|
||||
release:
|
||||
types: [published]
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
packages: write
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./cli
|
||||
working-directory: ./packages/cli
|
||||
steps:
|
||||
- id: token
|
||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
@@ -89,7 +89,7 @@ jobs:
|
||||
- name: Get package version
|
||||
id: package-version
|
||||
run: |
|
||||
version=$(jq -r '.version' cli/package.json)
|
||||
version=$(jq -r '.version' packages/cli/package.json)
|
||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Generate docker image tags
|
||||
@@ -107,7 +107,7 @@ jobs:
|
||||
- name: Build and push image
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
with:
|
||||
file: cli/Dockerfile
|
||||
file: packages/cli/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: ${{ github.event_name == 'release' }}
|
||||
cache-from: type=gha
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
needs: [get_body, should_run]
|
||||
if: ${{ needs.should_run.outputs.should_run == 'true' }}
|
||||
container:
|
||||
image: ghcr.io/immich-app/mdq:main@sha256:32abe582452b12dff55055e1d6bc24508a8f17164f9d1831db7bb70953c014c6
|
||||
image: ghcr.io/immich-app/mdq:main@sha256:0a8b8867773a0f8368061f47578603f438349f8f1f28b0e16105f481e5c794e0
|
||||
outputs:
|
||||
checked: ${{ steps.get_checkbox.outputs.checked }}
|
||||
steps:
|
||||
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
|
||||
uses: github/codeql-action/init@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# 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).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
|
||||
uses: github/codeql-action/autobuild@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
|
||||
|
||||
# ℹ️ 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
|
||||
@@ -83,6 +83,6 @@ jobs:
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
|
||||
uses: github/codeql-action/analyze@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
|
||||
with:
|
||||
category: '/language:${{matrix.language}}'
|
||||
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ jobs:
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
@@ -213,12 +213,11 @@ jobs:
|
||||
run: 'mise run //deployment:tf apply'
|
||||
|
||||
- name: Comment
|
||||
uses: actions-cool/maintain-one-comment@909842216bc8e8658364c572ec52100f4c2cc50a # v3.3.0
|
||||
uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0
|
||||
if: ${{ steps.parameters.outputs.event == 'pr' }}
|
||||
with:
|
||||
id: docs-pr-url
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
number: ${{ fromJson(needs.checks.outputs.parameters).pr_number }}
|
||||
body: |
|
||||
📖 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 }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
@@ -44,9 +44,8 @@ jobs:
|
||||
run: 'mise run //deployment:tf destroy -- -refresh=false'
|
||||
|
||||
- name: Comment
|
||||
uses: actions-cool/maintain-one-comment@909842216bc8e8658364c572ec52100f4c2cc50a # v3.3.0
|
||||
uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0
|
||||
with:
|
||||
id: docs-pr-url
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
number: ${{ github.event.number }}
|
||||
delete: true
|
||||
body-include: '<!-- Docs PR URL -->'
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
- name: Generate a token
|
||||
id: generate_token
|
||||
if: ${{ inputs.skip != true }}
|
||||
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
|
||||
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
||||
with:
|
||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
@@ -13,3 +13,4 @@ jobs:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
secrets: inherit
|
||||
|
||||
@@ -17,6 +17,6 @@ jobs:
|
||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1
|
||||
- uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 # v6.1.0
|
||||
with:
|
||||
repo-token: ${{ steps.token.outputs.token }}
|
||||
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
ref: main
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
@@ -119,7 +119,7 @@ jobs:
|
||||
steps:
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
|
||||
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
||||
with:
|
||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
@@ -19,11 +19,11 @@ jobs:
|
||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- uses: mshick/add-pr-comment@8e4927817251f1ff60c001f04568532b38e0b4a0 # v3.11.0
|
||||
- uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0
|
||||
with:
|
||||
github-token: ${{ steps.token.outputs.token }}
|
||||
message-id: 'preview-status'
|
||||
message: 'Deploying preview environment to https://pr-${{ github.event.pull_request.number }}.preview.internal.immich.build/'
|
||||
id: preview-status
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
body: 'Deploying preview environment to https://pr-${{ github.event.pull_request.number }}.preview.internal.immich.build/'
|
||||
|
||||
remove-label:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -48,16 +48,16 @@ jobs:
|
||||
name: 'preview'
|
||||
})
|
||||
|
||||
- uses: mshick/add-pr-comment@8e4927817251f1ff60c001f04568532b38e0b4a0 # v3.11.0
|
||||
- uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0
|
||||
if: ${{ github.event.pull_request.head.repo.fork }}
|
||||
with:
|
||||
github-token: ${{ steps.token.outputs.token }}
|
||||
message-id: 'preview-status'
|
||||
message: 'PRs from forks cannot have preview environments.'
|
||||
id: preview-status
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
body: 'PRs from forks cannot have preview environments.'
|
||||
|
||||
- uses: mshick/add-pr-comment@8e4927817251f1ff60c001f04568532b38e0b4a0 # v3.11.0
|
||||
- uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0
|
||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||
with:
|
||||
github-token: ${{ steps.token.outputs.token }}
|
||||
message-id: 'preview-status'
|
||||
message: 'Preview environment has been removed.'
|
||||
id: preview-status
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
body: 'Preview environment has been removed.'
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
|
||||
@@ -60,38 +60,30 @@ jobs:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Flutter SDK
|
||||
uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
||||
with:
|
||||
channel: 'stable'
|
||||
flutter-version-file: ./mobile/pubspec.yaml
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: dart pub get
|
||||
run: flutter pub get
|
||||
|
||||
- name: Install dependencies for UI package
|
||||
run: dart pub get
|
||||
run: flutter pub get
|
||||
working-directory: ./mobile/packages/ui
|
||||
|
||||
- name: Install dependencies for UI Showcase
|
||||
run: dart pub get
|
||||
run: flutter pub get
|
||||
working-directory: ./mobile/packages/ui/showcase
|
||||
|
||||
- name: Install DCM
|
||||
uses: CQLabs/setup-dcm@8697ae0790c0852e964a6ef1d768d62a6675481a # v2.0.1
|
||||
with:
|
||||
github-token: ${{ steps.token.outputs.token }}
|
||||
version: auto
|
||||
working-directory: ./mobile
|
||||
|
||||
- name: Generate translation file
|
||||
run: dart run easy_localization:generate -S ../i18n && dart run bin/generate_keys.dart
|
||||
- name: Generate translation files
|
||||
run: mise //mobile:codegen:translation
|
||||
|
||||
- name: Run Build Runner
|
||||
run: make build
|
||||
run: mise //mobile:codegen:dart
|
||||
|
||||
- name: Generate platform API
|
||||
run: make pigeon
|
||||
run: mise //mobile:codegen:pigeon
|
||||
|
||||
- name: Find file changes
|
||||
uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4
|
||||
@@ -107,20 +99,16 @@ jobs:
|
||||
env:
|
||||
CHANGED_FILES: ${{ steps.verify-changed-files.outputs.changed_files }}
|
||||
run: |
|
||||
echo "ERROR: Generated files not up to date! Run 'make build' and 'make pigeon' inside the mobile directory"
|
||||
echo "ERROR: Generated files not up to date! Run 'mise //mobile:codegen:dart' and 'mise //mobile:codegen:pigeon'"
|
||||
echo "Changed files: ${CHANGED_FILES}"
|
||||
exit 1
|
||||
|
||||
- name: Run dart analyze
|
||||
run: dart analyze --fatal-infos
|
||||
- name: Run analyze
|
||||
run: mise //mobile:analyze
|
||||
|
||||
- name: Run dart format
|
||||
run: make format
|
||||
- name: Run format
|
||||
run: mise //mobile:format
|
||||
|
||||
# TODO: Re-enable after upgrading custom_lint
|
||||
# - name: Run dart custom_lint
|
||||
# run: dart run custom_lint
|
||||
|
||||
# TODO: Use https://github.com/CQLabs/dcm-action
|
||||
- name: Run DCM
|
||||
run: dcm analyze lib --fatal-style --fatal-warnings
|
||||
|
||||
+50
-50
@@ -30,25 +30,32 @@ jobs:
|
||||
filters: |
|
||||
i18n:
|
||||
- 'i18n/**'
|
||||
- 'mise.toml'
|
||||
web:
|
||||
- 'web/**'
|
||||
- 'i18n/**'
|
||||
- 'packages/sdk/**'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'mise.toml'
|
||||
server:
|
||||
- 'server/**'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'mise.toml'
|
||||
cli:
|
||||
- 'cli/**'
|
||||
- 'packages/cli/**'
|
||||
- 'packages/sdk/**'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'mise.toml'
|
||||
e2e:
|
||||
- 'e2e/**'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'mise.toml'
|
||||
mobile:
|
||||
- 'mobile/**'
|
||||
- 'mise.toml'
|
||||
machine-learning:
|
||||
- 'machine-learning/**'
|
||||
- 'mise.toml'
|
||||
.github:
|
||||
- '.github/**'
|
||||
force-filters: |
|
||||
@@ -62,9 +69,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./server
|
||||
steps:
|
||||
- id: token
|
||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||
@@ -79,12 +83,12 @@ jobs:
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Run ci-unit
|
||||
run: mise run ci-unit
|
||||
run: mise run //server:ci-unit
|
||||
|
||||
cli-unit-tests:
|
||||
name: Unit Test CLI
|
||||
@@ -95,7 +99,7 @@ jobs:
|
||||
contents: read
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./cli
|
||||
working-directory: ./packages/cli
|
||||
steps:
|
||||
- id: token
|
||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||
@@ -110,7 +114,7 @@ jobs:
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
@@ -126,7 +130,7 @@ jobs:
|
||||
contents: read
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./cli
|
||||
working-directory: ./packages/cli
|
||||
steps:
|
||||
- id: token
|
||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||
@@ -141,7 +145,7 @@ jobs:
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
@@ -185,7 +189,7 @@ jobs:
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
@@ -223,7 +227,7 @@ jobs:
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
@@ -251,15 +255,15 @@ jobs:
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm --filter=immich-i18n install --frozen-lockfile
|
||||
run: pnpm -w install --frozen-lockfile
|
||||
|
||||
- name: Format
|
||||
run: pnpm --filter=immich-i18n format:fix
|
||||
run: pnpm format:fix
|
||||
|
||||
- name: Find file changes
|
||||
uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4
|
||||
@@ -301,7 +305,7 @@ jobs:
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
@@ -334,7 +338,7 @@ jobs:
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
@@ -379,19 +383,14 @@ jobs:
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||
|
||||
- name: Setup @immich/sdk
|
||||
run: pnpm --filter @immich/sdk install --frozen-lockfile && pnpm --filter @immich/sdk build
|
||||
- name: Setup packages
|
||||
run: pnpm --filter @immich/sdk --filter @immich/cli install --frozen-lockfile && pnpm --filter @immich/sdk --filter @immich/cli build
|
||||
|
||||
- name: Run setup web
|
||||
run: pnpm install --frozen-lockfile && pnpm exec svelte-kit sync
|
||||
working-directory: ./web
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
- name: Run setup cli
|
||||
run: pnpm install --frozen-lockfile && pnpm build
|
||||
working-directory: ./cli
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
if: ${{ !cancelled() }}
|
||||
@@ -556,17 +555,22 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
- name: Setup Flutter SDK
|
||||
uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
||||
with:
|
||||
channel: 'stable'
|
||||
flutter-version-file: ./mobile/pubspec.yaml
|
||||
- name: Generate translation file
|
||||
run: dart run easy_localization:generate -S ../i18n && dart run bin/generate_keys.dart
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: flutter pub get
|
||||
working-directory: ./mobile
|
||||
|
||||
- name: Generate translation files
|
||||
run: mise //mobile:codegen:translation
|
||||
|
||||
- name: Run tests
|
||||
working-directory: ./mobile
|
||||
run: flutter test -j 1
|
||||
run: mise //mobile:test
|
||||
|
||||
ml-unit-tests:
|
||||
name: Unit Test ML
|
||||
needs: pre-job
|
||||
@@ -590,7 +594,7 @@ jobs:
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
@@ -621,7 +625,7 @@ jobs:
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
@@ -672,18 +676,14 @@ jobs:
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Install server dependencies
|
||||
run: SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm --filter immich install --frozen-lockfile
|
||||
|
||||
- name: Build the app
|
||||
run: pnpm --filter immich build
|
||||
|
||||
- name: Run API generation
|
||||
run: ./bin/generate-open-api.sh
|
||||
run: mise //:open-api
|
||||
working-directory: open-api
|
||||
|
||||
- name: Find file changes
|
||||
@@ -720,9 +720,6 @@ jobs:
|
||||
--health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
|
||||
ports:
|
||||
- 5432:5432
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./server
|
||||
steps:
|
||||
- id: token
|
||||
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
|
||||
@@ -737,25 +734,28 @@ jobs:
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@cf6e190bacde3d7bda59372a786b36ac7d01536a # use-mise-action-v2.0.1
|
||||
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Install server dependencies
|
||||
run: SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build plugins
|
||||
run: mise //:plugins
|
||||
|
||||
- name: Build the app
|
||||
run: pnpm build
|
||||
run: mise //server:build
|
||||
|
||||
- name: Run existing migrations
|
||||
run: pnpm migrations:run
|
||||
run: pnpm --filter immich migrations:run
|
||||
|
||||
- name: Test npm run schema:reset command works
|
||||
run: pnpm schema:reset
|
||||
run: pnpm --filter immich schema:reset
|
||||
|
||||
- name: Generate new migrations
|
||||
continue-on-error: true
|
||||
run: pnpm migrations:generate src/TestMigration
|
||||
run: pnpm --filter migrations:generate src/TestMigration
|
||||
|
||||
- name: Find file changes
|
||||
uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4
|
||||
@@ -771,11 +771,11 @@ jobs:
|
||||
run: |
|
||||
echo "ERROR: Generated migration files not up to date!"
|
||||
echo "Changed files: ${CHANGED_FILES}"
|
||||
cat ./src/*-TestMigration.ts
|
||||
cat ./server/src/*-TestMigration.ts
|
||||
exit 1
|
||||
|
||||
- name: Run SQL generation
|
||||
run: pnpm sync:sql
|
||||
run: mise //:sql
|
||||
env:
|
||||
DB_URL: postgres://postgres:postgres@localhost:5432/immich
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
github-token: ${{ steps.token.outputs.token }}
|
||||
filters: |
|
||||
i18n:
|
||||
- modified: 'i18n/!(en|package)**\.json'
|
||||
- modified: 'i18n/!(en)**\.json'
|
||||
skip-force-logic: 'true'
|
||||
|
||||
enforce-lock:
|
||||
|
||||
Vendored
+6
-4
@@ -23,15 +23,17 @@
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Immich CLI",
|
||||
"program": "${workspaceFolder}/cli/dist/index.js",
|
||||
"program": "${workspaceFolder}/packages/cli/dist/index.js",
|
||||
"args": ["upload", "--help"],
|
||||
"runtimeArgs": ["--enable-source-maps"],
|
||||
"console": "integratedTerminal",
|
||||
"resolveSourceMapLocations": ["${workspaceFolder}/cli/dist/**/*.js.map"],
|
||||
"resolveSourceMapLocations": [
|
||||
"${workspaceFolder}/packages/cli/dist/**/*.js.map"
|
||||
],
|
||||
"sourceMaps": true,
|
||||
"outFiles": ["${workspaceFolder}/cli/dist/**/*.js"],
|
||||
"outFiles": ["${workspaceFolder}/packages/cli/dist/**/*.js"],
|
||||
"skipFiles": ["<node_internals>/**"],
|
||||
"preLaunchTask": "Build Immich CLI"
|
||||
"preLaunchTask": "Build @immich/cli"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,141 +1,60 @@
|
||||
dev:
|
||||
@trap 'make dev-down' EXIT; COMPOSE_BAKE=true docker compose -f ./docker/docker-compose.dev.yml up --remove-orphans
|
||||
@printf "This command has been removed. Please use:\n\n mise dev # or mise //:dev from another directory\n\n"\n\n >&2 && exit 1
|
||||
|
||||
dev-down:
|
||||
docker compose -f ./docker/docker-compose.dev.yml down --remove-orphans
|
||||
@printf "This command has been removed. Please use:\n\n mise dev-down # or mise //:dev-down from another directory\n\n"\n\n >&2 && exit 1
|
||||
|
||||
dev-update:
|
||||
@trap 'make dev-down' EXIT; COMPOSE_BAKE=true docker compose -f ./docker/docker-compose.dev.yml up --build -V --remove-orphans
|
||||
@printf "This command has been removed. Please use:\n\n mise dev-update # or mise //:dev-update from another directory\n\n"\n\n >&2 && exit 1
|
||||
|
||||
dev-scale:
|
||||
@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
|
||||
@printf "This command has been removed. Please use:\n\n mise dev-scale # or mise //:dev-scale from another directory\n\n"\n\n >&2 && exit 1
|
||||
|
||||
dev-docs:
|
||||
npm --prefix docs run start
|
||||
|
||||
.PHONY: e2e
|
||||
e2e:
|
||||
@trap 'make e2e-down' EXIT; COMPOSE_BAKE=true docker compose -f ./e2e/docker-compose.yml up --remove-orphans
|
||||
@printf "This command has been removed. Please use:\n\n mise e2e # or mise //:e2e from another directory\n\n"\n\n >&2 && exit 1
|
||||
|
||||
e2e-dev:
|
||||
@trap 'make e2e-down' EXIT; COMPOSE_BAKE=true docker compose -f ./e2e/docker-compose.dev.yml up --remove-orphans
|
||||
@printf "This command has been removed. Please use:\n\n mise e2e-dev # or mise //:e2e-dev from another directory\n\n"\n\n >&2 && exit 1
|
||||
|
||||
e2e-update:
|
||||
@trap 'make e2e-down' EXIT; COMPOSE_BAKE=true docker compose -f ./e2e/docker-compose.yml up --build -V --remove-orphans
|
||||
@printf "This command has been removed. Please use:\n\n mise e2e-update # or mise //:e2e-update from another directory\n\n"\n\n >&2 && exit 1
|
||||
|
||||
e2e-down:
|
||||
docker compose -f ./e2e/docker-compose.yml down --remove-orphans
|
||||
@printf "This command has been removed. Please use:\n\n mise e2e-down # or mise //:e2e-down from another directory\n\n"\n\n >&2 && exit 1
|
||||
|
||||
prod:
|
||||
@trap 'make prod-down' EXIT; COMPOSE_BAKE=true docker compose -f ./docker/docker-compose.prod.yml up --build -V --remove-orphans
|
||||
@printf "This command has been removed. Please use:\n\n mise prod # or mise //:prod from another directory\n\n"\n\n >&2 && exit 1
|
||||
|
||||
prod-down:
|
||||
docker compose -f ./docker/docker-compose.prod.yml down --remove-orphans
|
||||
@printf "This command has been removed. Please use:\n\n mise prod-down # or mise //:prod-down from another directory\n\n"\n\n >&2 && exit 1
|
||||
|
||||
prod-scale:
|
||||
@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
|
||||
@printf "This command has been removed. Please use:\n\n mise prod-scale # or mise //:prod-scale from another directory\n\n"\n\n >&2 && exit 1
|
||||
|
||||
.PHONY: open-api
|
||||
open-api:
|
||||
cd ./open-api && bash ./bin/generate-open-api.sh
|
||||
|
||||
open-api-dart:
|
||||
cd ./open-api && bash ./bin/generate-open-api.sh dart
|
||||
|
||||
open-api-typescript:
|
||||
cd ./open-api && bash ./bin/generate-open-api.sh typescript
|
||||
@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:
|
||||
pnpm --filter immich run sync:sql
|
||||
@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
|
||||
|
||||
attach-server:
|
||||
docker exec -it docker_immich-server_1 sh
|
||||
|
||||
renovate:
|
||||
LOG_LEVEL=debug pnpm exec renovate --platform=local --repository-cache=reset
|
||||
|
||||
# Directories that need to be created for volumes or build output
|
||||
VOLUME_DIRS = \
|
||||
./.pnpm-store \
|
||||
./web/.svelte-kit \
|
||||
./web/node_modules \
|
||||
./web/coverage \
|
||||
./e2e/node_modules \
|
||||
./docs/node_modules \
|
||||
./server/node_modules \
|
||||
./packages/sdk/node_modules \
|
||||
./.github/node_modules \
|
||||
./node_modules \
|
||||
./cli/node_modules
|
||||
|
||||
# Include .env file if it exists
|
||||
-include docker/.env
|
||||
|
||||
MODULES = e2e server web cli sdk docs .github
|
||||
|
||||
# directory to package name mapping function
|
||||
# cli = @immich/cli
|
||||
# docs = documentation
|
||||
# e2e = immich-e2e
|
||||
# packages/sdk = @immich/sdk
|
||||
# server = immich
|
||||
# web = immich-web
|
||||
map-package = $(subst sdk,@immich/sdk,$(subst cli,@immich/cli,$(subst docs,documentation,$(subst e2e,immich-e2e,$(subst server,immich,$(subst web,immich-web,$1))))))
|
||||
|
||||
audit-%:
|
||||
pnpm --filter $(call map-package,$*) audit fix
|
||||
install-%:
|
||||
pnpm --filter $(call map-package,$*) install $(if $(FROZEN),--frozen-lockfile) $(if $(OFFLINE),--offline)
|
||||
build-cli: build-sdk
|
||||
build-web: build-sdk
|
||||
build-%: install-%
|
||||
pnpm --filter $(call map-package,$*) run build
|
||||
format-%:
|
||||
pnpm --filter $(call map-package,$*) run format:fix
|
||||
lint-%:
|
||||
pnpm --filter $(call map-package,$*) run lint:fix
|
||||
check-%:
|
||||
pnpm --filter $(call map-package,$*) run check
|
||||
check-web:
|
||||
pnpm --filter immich-web run check:typescript
|
||||
pnpm --filter immich-web run check:svelte
|
||||
test-%:
|
||||
pnpm --filter $(call map-package,$*) run test
|
||||
test-e2e:
|
||||
docker compose -f ./e2e/docker-compose.yml build
|
||||
pnpm --filter immich-e2e run test
|
||||
pnpm --filter immich-e2e run test:web
|
||||
test-medium:
|
||||
docker run \
|
||||
--rm \
|
||||
-v ./server/src:/usr/src/app/src \
|
||||
-v ./server/test:/usr/src/app/test \
|
||||
-v ./server/vitest.config.medium.mjs:/usr/src/app/vitest.config.medium.mjs \
|
||||
-v ./server/tsconfig.json:/usr/src/app/tsconfig.json \
|
||||
-e NODE_ENV=development \
|
||||
immich-server:latest \
|
||||
-c "pnpm test:medium -- --run"
|
||||
test-medium-dev:
|
||||
docker exec -it immich_server /bin/sh -c "pnpm run test:medium"
|
||||
|
||||
install-all:
|
||||
pnpm -r --filter '!documentation' install
|
||||
|
||||
build-all: $(foreach M,$(filter-out e2e docs .github,$(MODULES)),build-$M) ;
|
||||
|
||||
check-all:
|
||||
pnpm -r --filter '!documentation' run "/^(check|check\:svelte|check\:typescript)$/"
|
||||
lint-all:
|
||||
pnpm -r --filter '!documentation' run lint:fix
|
||||
format-all:
|
||||
pnpm -r --filter '!documentation' run format:fix
|
||||
audit-all:
|
||||
pnpm -r --filter '!documentation' audit fix
|
||||
hygiene-all: audit-all
|
||||
pnpm -r --filter '!documentation' run "/(format:fix|check|check:svelte|check:typescript|sql)/"
|
||||
|
||||
test-all:
|
||||
pnpm -r --filter '!documentation' run "/^test/"
|
||||
|
||||
clean:
|
||||
find . -name "node_modules" -type d -prune -exec rm -rf {} +
|
||||
@@ -146,7 +65,3 @@ clean:
|
||||
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
|
||||
|
||||
|
||||
setup-server-dev: install-server
|
||||
setup-web-dev: install-sdk build-sdk install-web
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
# @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"
|
||||
+30
-30
@@ -2,37 +2,37 @@
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.opentofu.org/cloudflare/cloudflare" {
|
||||
version = "4.52.5"
|
||||
constraints = "4.52.5"
|
||||
version = "4.52.7"
|
||||
constraints = "4.52.7"
|
||||
hashes = [
|
||||
"h1:+rfzF+16ZcWZWnTyW/p1HHTzYbPKX8Zt2nIFtR/+f+E=",
|
||||
"h1:18bXaaOSq8MWKuMxo/4y7EB7/i7G90y5QsKHZRmkoDo=",
|
||||
"h1:4vZVOpKeEQZsF2VrARRZFeL37Ed/gD4rRMtfnvWQres=",
|
||||
"h1:BZOsTF83QPKXTAaYqxPKzdl1KRjk/L2qbPpFjM0w28A=",
|
||||
"h1:CDuC+HXLvc1z6wkCRsSDcc/+QENIHEtssYshiWg3opA=",
|
||||
"h1:DE+YFzLnqSe79pI2R4idRGx5QzLdrA7RXvngTkGfZ30=",
|
||||
"h1:DfaJwH3Ml4yrRbdAY4AcDVy0QTQk5T3A622TXzS/u2E=",
|
||||
"h1:EIDXP0W3kgIv2pecrFmqtK/DnlqkyckzBzhxKaXU+4A=",
|
||||
"h1:EV4kYyaOnwGA0bh/3hU6Ezqnt1PFDxopH7i85e48IzY=",
|
||||
"h1:M0iXabfzamU+MPDi0G9XACpbacFKMakmM+Z9HZ8HrsM=",
|
||||
"h1:YWmCbGF/KbsrUzcYVBLscwLizidbp95TDQa0N2qpmVo=",
|
||||
"h1:cxPcCB5gbrpUO1+IXkQYs1YTY50/0IlApCzGea0cwuQ=",
|
||||
"h1:g6DldikTV2HXUu9uoeNY5FuLufgaYWF4ufgZg7wq62s=",
|
||||
"h1:oi/Hrx9pwoQ+Z52CBC+rrowVH387EIj0qvnxQgDeI+0=",
|
||||
"zh:1a3400cb38863b2585968d1876706bcfc67a148e1318a1d325c6c7704adc999b",
|
||||
"zh:4c5062cb9e9da1676f06ae92b8370186d98976cc4c7030d3cd76df12af54282a",
|
||||
"zh:52110f493b5f0587ef77a1cfd1a67001fd4c617b14c6502d732ab47352bdc2f7",
|
||||
"zh:5aa536f9eaeb43823aaf2aa80e7d39b25ef2b383405ed034aa16a28b446a9238",
|
||||
"zh:5cc39459a1c6be8a918f17054e4fbba573825ed5597dcada588fe99614d98a5b",
|
||||
"zh:629ae6a7ba298815131da826474d199312d21cec53a4d5ded4fa56a692e6f072",
|
||||
"zh:719cc7c75dc1d3eb30c22ff5102a017996d9788b948078c7e1c5b3446aeca661",
|
||||
"zh:8698635a3ca04383c1e93b21d6963346bdae54d27177a48e4b1435b7f731731c",
|
||||
"h1:+O72J3QYiZtYmYYZM/Eh0f4NNfl1BvjX1eju43qTQsQ=",
|
||||
"h1:0oqjYIPXcXh7XiDiKI085cHDYQQ5mh8kDl9dmBtvtog=",
|
||||
"h1:4b4ESb87MGv5bnadgYe7sK5rEkKMZhbkQcwPubQTsR4=",
|
||||
"h1:6mTr3eA1Ddb348lLmJuyvn98z4KF+ejqaUEJ76D1rzQ=",
|
||||
"h1:9/3YH+9k9HqsvFtbmBf7SO2+xqZeZrXNKzLkjNuhUEA=",
|
||||
"h1:Jcq4tBWgyH4/2JsojNBSRaN0mcItVMchO+lynonrlqc=",
|
||||
"h1:Y4Vv/2RdP0Q+uxqhOxzOdKxuuEMjXPDcU0vPc5bCQzI=",
|
||||
"h1:a0gW8FBKsbP9Fi0HEDoy49WIbEWVHk9+BR4/iwuBdDQ=",
|
||||
"h1:gElv6iqJtg8OKN77gbw+MjrkrQmJHPkkMEi1J+0xkpU=",
|
||||
"h1:oslXUugD/NQ+duJgT4BhKQyfGbuFOANknMvR73fiOeM=",
|
||||
"h1:pPItIWii5oymR+geZB219ROSPuSODPLTlM4S/u8xLvM=",
|
||||
"h1:u67GWw8GwD9NDlDzp9Y5VRnSQGcCrE8rSpkGPaBpDl0=",
|
||||
"h1:uUUa9dY0XQOycI8pxg16PFFtL0WCTi9uEJz8trTQ7pU=",
|
||||
"h1:y3rV8KF2q6GEMANNlf5EkKJurlfbKlIKpjGcdxoy7pQ=",
|
||||
"zh:0c904ce31a4c6c4a5b3bf7ff1560e77c0cc7e2450c8553ded8e8c90398e1418b",
|
||||
"zh:36183d310c36373fe4cb936b83c595c6fd3b0a94bc7827f28e5789ccbf59752e",
|
||||
"zh:556a568a6f0235e8f41647de9e4d3a1e7b1d6502df8b19b54ec441f1c653ea10",
|
||||
"zh:633ebbd5b0245e75e500ef9be4d9e62288f97e8da3baaa51323892a786d90285",
|
||||
"zh:6acfe60cf52a65ba8f044f748548d2119e7f4fd7f8ebcb14698960d87c68f529",
|
||||
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
|
||||
"zh:8a9993f1dcadf1dd6ca43b23348abe374605d29945a2fafc07fb3457644e6a54",
|
||||
"zh:b1b9a1e6bcc24d5863a664a411d2dc906373ae7a2399d2d65548ce7377057852",
|
||||
"zh:b270184cdeec277218e84b94cb136fead753da717f9b9dc378e51907f3f00bb0",
|
||||
"zh:dff2bc10071210181726ce270f954995fe42c696e61e2e8f874021fed02521e5",
|
||||
"zh:e8e87b40b6a87dc097b0fdc20d3f725cec0d82abc9cc3755c1f89f8f6e8b0036",
|
||||
"zh:ee964a6573d399a5dd22ce328fb38ca1207797a02248f14b2e4913ee390e7803",
|
||||
"zh:904acc31ebb9d6ef68c792074b30532ee61bf515f19e0a3c75b46f126cca1f13",
|
||||
"zh:a1d0a81246afc8750286d3f6fe7a8fbe6460dd2662407b28dbfbabb612e5fa9d",
|
||||
"zh:a41a36fe253fc365fe2b7ffc749624688b2693b4634862fda161179ab100029f",
|
||||
"zh:a7ef269e77ffa8715c8945a2c14322c7ff159ea44c15f62505f3cbb2cae3b32d",
|
||||
"zh:b01aa3bed30610633b762df64332b26f8844a68c3960cebcb30f04918efc67fe",
|
||||
"zh:b069cc2cd18cae10757df3ae030508eac8d55de7e49eda7a5e3e11f2f7fe6455",
|
||||
"zh:b2d2c6313729ebb7465dceece374049e2d08bda34473901be9ff46a8836d42b2",
|
||||
"zh:db0e114edaf4bc2f3d4769958807c83022bfbc619a00bdf4c4bd17faa4ab2d8b",
|
||||
"zh:ecc0aa8b9044f664fd2aaf8fa992d976578f78478980555b4b8f6148e8d1a5fe",
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ terraform {
|
||||
required_providers {
|
||||
cloudflare = {
|
||||
source = "cloudflare/cloudflare"
|
||||
version = "4.52.5"
|
||||
version = "4.52.7"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+30
-30
@@ -2,37 +2,37 @@
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.opentofu.org/cloudflare/cloudflare" {
|
||||
version = "4.52.5"
|
||||
constraints = "4.52.5"
|
||||
version = "4.52.7"
|
||||
constraints = "4.52.7"
|
||||
hashes = [
|
||||
"h1:+rfzF+16ZcWZWnTyW/p1HHTzYbPKX8Zt2nIFtR/+f+E=",
|
||||
"h1:18bXaaOSq8MWKuMxo/4y7EB7/i7G90y5QsKHZRmkoDo=",
|
||||
"h1:4vZVOpKeEQZsF2VrARRZFeL37Ed/gD4rRMtfnvWQres=",
|
||||
"h1:BZOsTF83QPKXTAaYqxPKzdl1KRjk/L2qbPpFjM0w28A=",
|
||||
"h1:CDuC+HXLvc1z6wkCRsSDcc/+QENIHEtssYshiWg3opA=",
|
||||
"h1:DE+YFzLnqSe79pI2R4idRGx5QzLdrA7RXvngTkGfZ30=",
|
||||
"h1:DfaJwH3Ml4yrRbdAY4AcDVy0QTQk5T3A622TXzS/u2E=",
|
||||
"h1:EIDXP0W3kgIv2pecrFmqtK/DnlqkyckzBzhxKaXU+4A=",
|
||||
"h1:EV4kYyaOnwGA0bh/3hU6Ezqnt1PFDxopH7i85e48IzY=",
|
||||
"h1:M0iXabfzamU+MPDi0G9XACpbacFKMakmM+Z9HZ8HrsM=",
|
||||
"h1:YWmCbGF/KbsrUzcYVBLscwLizidbp95TDQa0N2qpmVo=",
|
||||
"h1:cxPcCB5gbrpUO1+IXkQYs1YTY50/0IlApCzGea0cwuQ=",
|
||||
"h1:g6DldikTV2HXUu9uoeNY5FuLufgaYWF4ufgZg7wq62s=",
|
||||
"h1:oi/Hrx9pwoQ+Z52CBC+rrowVH387EIj0qvnxQgDeI+0=",
|
||||
"zh:1a3400cb38863b2585968d1876706bcfc67a148e1318a1d325c6c7704adc999b",
|
||||
"zh:4c5062cb9e9da1676f06ae92b8370186d98976cc4c7030d3cd76df12af54282a",
|
||||
"zh:52110f493b5f0587ef77a1cfd1a67001fd4c617b14c6502d732ab47352bdc2f7",
|
||||
"zh:5aa536f9eaeb43823aaf2aa80e7d39b25ef2b383405ed034aa16a28b446a9238",
|
||||
"zh:5cc39459a1c6be8a918f17054e4fbba573825ed5597dcada588fe99614d98a5b",
|
||||
"zh:629ae6a7ba298815131da826474d199312d21cec53a4d5ded4fa56a692e6f072",
|
||||
"zh:719cc7c75dc1d3eb30c22ff5102a017996d9788b948078c7e1c5b3446aeca661",
|
||||
"zh:8698635a3ca04383c1e93b21d6963346bdae54d27177a48e4b1435b7f731731c",
|
||||
"h1:+O72J3QYiZtYmYYZM/Eh0f4NNfl1BvjX1eju43qTQsQ=",
|
||||
"h1:0oqjYIPXcXh7XiDiKI085cHDYQQ5mh8kDl9dmBtvtog=",
|
||||
"h1:4b4ESb87MGv5bnadgYe7sK5rEkKMZhbkQcwPubQTsR4=",
|
||||
"h1:6mTr3eA1Ddb348lLmJuyvn98z4KF+ejqaUEJ76D1rzQ=",
|
||||
"h1:9/3YH+9k9HqsvFtbmBf7SO2+xqZeZrXNKzLkjNuhUEA=",
|
||||
"h1:Jcq4tBWgyH4/2JsojNBSRaN0mcItVMchO+lynonrlqc=",
|
||||
"h1:Y4Vv/2RdP0Q+uxqhOxzOdKxuuEMjXPDcU0vPc5bCQzI=",
|
||||
"h1:a0gW8FBKsbP9Fi0HEDoy49WIbEWVHk9+BR4/iwuBdDQ=",
|
||||
"h1:gElv6iqJtg8OKN77gbw+MjrkrQmJHPkkMEi1J+0xkpU=",
|
||||
"h1:oslXUugD/NQ+duJgT4BhKQyfGbuFOANknMvR73fiOeM=",
|
||||
"h1:pPItIWii5oymR+geZB219ROSPuSODPLTlM4S/u8xLvM=",
|
||||
"h1:u67GWw8GwD9NDlDzp9Y5VRnSQGcCrE8rSpkGPaBpDl0=",
|
||||
"h1:uUUa9dY0XQOycI8pxg16PFFtL0WCTi9uEJz8trTQ7pU=",
|
||||
"h1:y3rV8KF2q6GEMANNlf5EkKJurlfbKlIKpjGcdxoy7pQ=",
|
||||
"zh:0c904ce31a4c6c4a5b3bf7ff1560e77c0cc7e2450c8553ded8e8c90398e1418b",
|
||||
"zh:36183d310c36373fe4cb936b83c595c6fd3b0a94bc7827f28e5789ccbf59752e",
|
||||
"zh:556a568a6f0235e8f41647de9e4d3a1e7b1d6502df8b19b54ec441f1c653ea10",
|
||||
"zh:633ebbd5b0245e75e500ef9be4d9e62288f97e8da3baaa51323892a786d90285",
|
||||
"zh:6acfe60cf52a65ba8f044f748548d2119e7f4fd7f8ebcb14698960d87c68f529",
|
||||
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
|
||||
"zh:8a9993f1dcadf1dd6ca43b23348abe374605d29945a2fafc07fb3457644e6a54",
|
||||
"zh:b1b9a1e6bcc24d5863a664a411d2dc906373ae7a2399d2d65548ce7377057852",
|
||||
"zh:b270184cdeec277218e84b94cb136fead753da717f9b9dc378e51907f3f00bb0",
|
||||
"zh:dff2bc10071210181726ce270f954995fe42c696e61e2e8f874021fed02521e5",
|
||||
"zh:e8e87b40b6a87dc097b0fdc20d3f725cec0d82abc9cc3755c1f89f8f6e8b0036",
|
||||
"zh:ee964a6573d399a5dd22ce328fb38ca1207797a02248f14b2e4913ee390e7803",
|
||||
"zh:904acc31ebb9d6ef68c792074b30532ee61bf515f19e0a3c75b46f126cca1f13",
|
||||
"zh:a1d0a81246afc8750286d3f6fe7a8fbe6460dd2662407b28dbfbabb612e5fa9d",
|
||||
"zh:a41a36fe253fc365fe2b7ffc749624688b2693b4634862fda161179ab100029f",
|
||||
"zh:a7ef269e77ffa8715c8945a2c14322c7ff159ea44c15f62505f3cbb2cae3b32d",
|
||||
"zh:b01aa3bed30610633b762df64332b26f8844a68c3960cebcb30f04918efc67fe",
|
||||
"zh:b069cc2cd18cae10757df3ae030508eac8d55de7e49eda7a5e3e11f2f7fe6455",
|
||||
"zh:b2d2c6313729ebb7465dceece374049e2d08bda34473901be9ff46a8836d42b2",
|
||||
"zh:db0e114edaf4bc2f3d4769958807c83022bfbc619a00bdf4c4bd17faa4ab2d8b",
|
||||
"zh:ecc0aa8b9044f664fd2aaf8fa992d976578f78478980555b4b8f6148e8d1a5fe",
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ terraform {
|
||||
required_providers {
|
||||
cloudflare = {
|
||||
source = "cloudflare/cloudflare"
|
||||
version = "4.52.5"
|
||||
version = "4.52.7"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,11 +21,11 @@ services:
|
||||
volumes:
|
||||
- ..:/usr/src/app
|
||||
# - ../../ui:/usr/src/ui
|
||||
- pnpm_cache:/buildcache/pnpm_cache
|
||||
- build_cache:/buildcache
|
||||
- server_node_modules:/usr/src/app/server/node_modules
|
||||
- web_node_modules:/usr/src/app/web/node_modules
|
||||
- github_node_modules:/usr/src/app/.github/node_modules
|
||||
- cli_node_modules:/usr/src/app/cli/node_modules
|
||||
- cli_node_modules:/usr/src/app/packages/cli/node_modules
|
||||
- docs_node_modules:/usr/src/app/docs/node_modules
|
||||
- e2e_node_modules:/usr/src/app/e2e/node_modules
|
||||
- sdk_node_modules:/usr/src/app/packages/sdk/node_modules
|
||||
@@ -45,11 +45,11 @@ services:
|
||||
target: dev
|
||||
command:
|
||||
- |
|
||||
pnpm install
|
||||
mise install
|
||||
touch /tmp/init-complete
|
||||
exec tail -f /dev/null
|
||||
volumes:
|
||||
- pnpm_store_server:/buildcache/pnpm-store
|
||||
- build_cache:/buildcache
|
||||
restart: 'no'
|
||||
healthcheck:
|
||||
test: ['CMD', 'test', '-f', '/tmp/init-complete']
|
||||
@@ -73,8 +73,7 @@ services:
|
||||
volumes:
|
||||
- ${UPLOAD_LOCATION}/photos:/data
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- pnpm_store_server:/buildcache/pnpm-store
|
||||
- ../plugins:/build/corePlugin
|
||||
- ../packages/plugin-core:/build/plugins/immich-plugin-core
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
@@ -122,8 +121,6 @@ services:
|
||||
ports:
|
||||
- 3000:3000
|
||||
- 24678:24678
|
||||
volumes:
|
||||
- pnpm_store_web:/buildcache/pnpm-store
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
immich-init:
|
||||
@@ -157,7 +154,7 @@ services:
|
||||
|
||||
redis:
|
||||
container_name: immich_redis
|
||||
image: docker.io/valkey/valkey:9@sha256:3b55fbaa0cd93cf0d9d961f405e4dfcc70efe325e2d84da207a0a8e6d8fde4f9
|
||||
image: docker.io/valkey/valkey:9@sha256:8436e10bc65c94886a91d4415b6a6dfa9cb5a306fb3b996e5bb67cd2b4854193
|
||||
healthcheck:
|
||||
test: redis-cli ping || exit 1
|
||||
|
||||
@@ -203,9 +200,7 @@ volumes:
|
||||
model_cache:
|
||||
prometheus_data:
|
||||
grafana_data:
|
||||
pnpm_cache:
|
||||
pnpm_store_server:
|
||||
pnpm_store_web:
|
||||
build_cache:
|
||||
server_node_modules:
|
||||
web_node_modules:
|
||||
github_node_modules:
|
||||
|
||||
@@ -56,7 +56,7 @@ services:
|
||||
|
||||
redis:
|
||||
container_name: immich_redis
|
||||
image: docker.io/valkey/valkey:9@sha256:3b55fbaa0cd93cf0d9d961f405e4dfcc70efe325e2d84da207a0a8e6d8fde4f9
|
||||
image: docker.io/valkey/valkey:9@sha256:8436e10bc65c94886a91d4415b6a6dfa9cb5a306fb3b996e5bb67cd2b4854193
|
||||
healthcheck:
|
||||
test: redis-cli ping || exit 1
|
||||
restart: always
|
||||
|
||||
@@ -61,7 +61,7 @@ services:
|
||||
|
||||
redis:
|
||||
container_name: immich_redis
|
||||
image: docker.io/valkey/valkey:9@sha256:3b55fbaa0cd93cf0d9d961f405e4dfcc70efe325e2d84da207a0a8e6d8fde4f9
|
||||
image: docker.io/valkey/valkey:9@sha256:8436e10bc65c94886a91d4415b6a6dfa9cb5a306fb3b996e5bb67cd2b4854193
|
||||
user: '1000:1000'
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
|
||||
@@ -49,7 +49,7 @@ services:
|
||||
|
||||
redis:
|
||||
container_name: immich_redis
|
||||
image: docker.io/valkey/valkey:9@sha256:3b55fbaa0cd93cf0d9d961f405e4dfcc70efe325e2d84da207a0a8e6d8fde4f9
|
||||
image: docker.io/valkey/valkey:9@sha256:8436e10bc65c94886a91d4415b6a6dfa9cb5a306fb3b996e5bb67cd2b4854193
|
||||
healthcheck:
|
||||
test: redis-cli ping || exit 1
|
||||
restart: always
|
||||
|
||||
@@ -26,6 +26,8 @@ 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
|
||||
|
||||
---
|
||||
|
||||
## User
|
||||
|
||||
### How can I reset the admin password?
|
||||
@@ -36,6 +38,10 @@ 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.
|
||||
|
||||
### 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
|
||||
|
||||
@@ -13,8 +13,11 @@ The `immich-server` docker image comes preinstalled with an administrative CLI (
|
||||
| `enable-oauth-login` | Enable OAuth login |
|
||||
| `disable-oauth-login` | Disable OAuth login |
|
||||
| `list-users` | List Immich users |
|
||||
| `grant-admin` | Grant admin privileges to a user (by email) |
|
||||
| `revoke-admin` | Revoke admin privileges from a user (by email) |
|
||||
| `version` | Print Immich version |
|
||||
| `change-media-location` | Change database file paths to align with a new media location |
|
||||
| `schema-check` | Verify database migrations and check for schema drift |
|
||||
|
||||
## How to run a command
|
||||
|
||||
@@ -102,6 +105,22 @@ immich-admin list-users
|
||||
]
|
||||
```
|
||||
|
||||
Grant Admin
|
||||
|
||||
```
|
||||
immich-admin grant-admin
|
||||
? Please enter the user email: user@example.com
|
||||
Admin access has been granted to user@example.com
|
||||
```
|
||||
|
||||
Revoke Admin
|
||||
|
||||
```
|
||||
immich-admin revoke-admin
|
||||
? Please enter the user email: user@example.com
|
||||
Admin access has been revoked from user@example.com
|
||||
```
|
||||
|
||||
Print Immich Version
|
||||
|
||||
```
|
||||
@@ -126,3 +145,12 @@ immich-admin change-media-location
|
||||
Database file paths updated successfully! 🎉
|
||||
...
|
||||
```
|
||||
|
||||
Schema Check
|
||||
|
||||
```
|
||||
immich-admin schema-check
|
||||
Migrations are up to date
|
||||
|
||||
No schema drift detected
|
||||
```
|
||||
|
||||
@@ -5,7 +5,7 @@ After making any changes in the `server/src/schema`, a database migration need t
|
||||
1. Run the command
|
||||
|
||||
```bash
|
||||
pnpm run migrations:generate <migration-name>
|
||||
mise //server:migrations generate <migration-name>
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```bash
|
||||
pnpm run migrations:revert
|
||||
mise //server:migrations revert
|
||||
```
|
||||
|
||||
This command rolls back the latest migration and brings the database schema back to its previous state.
|
||||
|
||||
@@ -243,8 +243,8 @@ To connect the mobile app to your Dev Container:
|
||||
|
||||
- **Server code** (`/server`): Changes trigger automatic restart
|
||||
- **Web code** (`/web`): Changes trigger hot module replacement
|
||||
- **Database migrations**: Run `pnpm run sync:sql` in the server directory
|
||||
- **API changes**: Regenerate TypeScript SDK with `make open-api`
|
||||
- **Database migrations**: Run `mise //:sql`
|
||||
- **API changes**: Regenerate TypeScript SDK with `mise //:open-api`
|
||||
|
||||
## Testing
|
||||
|
||||
@@ -252,85 +252,33 @@ To connect the mobile app to your Dev Container:
|
||||
|
||||
The Dev Container supports multiple ways to run tests:
|
||||
|
||||
#### Using Make Commands (Recommended)
|
||||
|
||||
```bash
|
||||
# Run tests for specific components
|
||||
make test-server # Server unit tests
|
||||
make test-web # Web unit tests
|
||||
make test-e2e # End-to-end tests
|
||||
make test-cli # CLI tests
|
||||
# Server
|
||||
mise //server:test # unit tests
|
||||
mise //server:test-medium # medium / integration tests
|
||||
|
||||
# Run all tests
|
||||
make test-all # Runs tests for all components
|
||||
# Web
|
||||
mise //web:test # unit tests
|
||||
|
||||
# Medium tests (integration tests)
|
||||
make test-medium-dev # End-to-end 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
|
||||
```
|
||||
|
||||
#### Using PNPM Directly
|
||||
### Additional Commands
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
### Code Quality Commands
|
||||
|
||||
```bash
|
||||
# Linting
|
||||
make lint-server # Lint server code
|
||||
make lint-web # Lint web code
|
||||
make lint-all # Lint all components
|
||||
|
||||
# Formatting
|
||||
make format-server # Format server code
|
||||
make format-web # Format web code
|
||||
make format-all # Format all code
|
||||
|
||||
# Type checking
|
||||
make check-server # Type check server
|
||||
make check-web # Type check web
|
||||
make check-all # Check all components
|
||||
|
||||
# Complete hygiene check
|
||||
make hygiene-all # Run lint, format, check, SQL sync, and audit
|
||||
```
|
||||
|
||||
### Additional Make Commands
|
||||
|
||||
```bash
|
||||
# Build commands
|
||||
make build-server # Build server
|
||||
make build-web # Build web app
|
||||
make build-all # Build everything
|
||||
|
||||
# API generation
|
||||
make open-api # Generate OpenAPI specs
|
||||
make open-api-typescript # Generate TypeScript SDK
|
||||
make open-api-dart # Generate Dart SDK
|
||||
mise //:open-api # Generate OpenAPI specs
|
||||
mise //:open-api-typescript # Generate TypeScript SDK
|
||||
mise //:open-api-dart # Generate Dart SDK
|
||||
|
||||
# Database
|
||||
make sql # Sync database schema
|
||||
|
||||
# Dependencies
|
||||
make install-server # Install server dependencies
|
||||
make install-web # Install web dependencies
|
||||
make install-all # Install all dependencies
|
||||
mise //server:sql # Sync database schema
|
||||
```
|
||||
|
||||
### Debugging
|
||||
|
||||
@@ -10,7 +10,8 @@ Our [GitHub Repository](https://github.com/immich-app/immich) is a [monorepo](ht
|
||||
| :------------------ | :------------------------------------------------------------------- |
|
||||
| `.github/` | Github templates and action workflows |
|
||||
| `.vscode/` | VSCode debug launch profiles |
|
||||
| `cli/` | Source code for the work-in-progress CLI rewrite |
|
||||
| `packages/cli` | Source code for the CLI |
|
||||
| `packages/sdk` | Source code for the generated OpenAPI SDK |
|
||||
| `docker/` | Docker compose resources for dev, test, production |
|
||||
| `design/` | Screenshots and logos for the README |
|
||||
| `docs/` | Source code for the [https://immich.app](https://immich.app) website |
|
||||
|
||||
@@ -8,47 +8,68 @@ When contributing code through a pull request, please check the following:
|
||||
|
||||
## Web Checks
|
||||
|
||||
- [ ] `pnpm run lint` (linting via ESLint)
|
||||
- [ ] `pnpm run format` (formatting via Prettier)
|
||||
- [ ] `pnpm run check:svelte` (Type checking via SvelteKit)
|
||||
- [ ] `pnpm run check:typescript` (check typescript)
|
||||
- [ ] `pnpm test` (unit tests)
|
||||
- [ ] `mise //web:lint` (linting via ESLint)
|
||||
- [ ] `mise //web:format` (formatting via Prettier)
|
||||
- [ ] `mise //web:check-svelte` (type checking via SvelteKit)
|
||||
- [ ] `mise //web:check-typescript` (type checking via `tsc`)
|
||||
- [ ] `mise //web:test` (unit tests)
|
||||
|
||||
:::tip AIO
|
||||
Run all web checks with `pnpm run check:all`
|
||||
Run all web checks with `mise //web:checklist`
|
||||
:::
|
||||
|
||||
:::tip Auto Fix
|
||||
Use `mise //web:lint-fix` and `mise //web:format-fix` to automatically correct some issues.
|
||||
:::
|
||||
|
||||
## Documentation
|
||||
|
||||
- [ ] `pnpm run format` (formatting via Prettier)
|
||||
- [ ] `mise //docs:format` (formatting via Prettier)
|
||||
- [ ] 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
|
||||
|
||||
- [ ] `pnpm run lint` (linting via ESLint)
|
||||
- [ ] `pnpm run format` (formatting via Prettier)
|
||||
- [ ] `pnpm run check` (Type checking via `tsc`)
|
||||
- [ ] `pnpm test` (unit tests)
|
||||
- [ ] `mise //server:lint` (linting via ESLint)
|
||||
- [ ] `mise //server:format` (formatting via Prettier)
|
||||
- [ ] `mise //server:check` (type checking via `tsc`)
|
||||
- [ ] `mise //server:test` (unit tests)
|
||||
|
||||
:::tip AIO
|
||||
Run all server checks with `pnpm run check:all`
|
||||
Run all server checks with `mise //server:checklist`
|
||||
:::
|
||||
|
||||
:::info Auto Fix
|
||||
You can use `pnpm run __:fix` to potentially correct some issues automatically for `pnpm run format` and `lint`.
|
||||
:::tip Auto Fix
|
||||
Use `mise //server:lint-fix` and `mise //server:format-fix` to automatically correct some issues.
|
||||
:::
|
||||
|
||||
## Mobile Checks
|
||||
## Mobile Checklist
|
||||
|
||||
The following commands must be executed from within the mobile app directory of the codebase.
|
||||
- [ ] `mise //mobile:codegen` (auto-generate files using build_runner)
|
||||
- [ ] `mise //mobile:lint` (static analysis via Dart Analyzer and DCM)
|
||||
- [ ] `mise //mobile:format` (formatting via Dart Formatter)
|
||||
- [ ] `mise //mobile:test` (unit tests)
|
||||
|
||||
- [ ] `make build` (auto-generate files using build_runner)
|
||||
- [ ] `make analyze` (static analysis via Dart Analyzer and DCM)
|
||||
- [ ] `make format` (formatting via Dart Formatter)
|
||||
- [ ] `make test` (unit tests)
|
||||
:::tip
|
||||
Run all these commands at once with `mise //mobile:checklist`
|
||||
:::
|
||||
|
||||
:::info Auto Fix
|
||||
You can use `dart fix --apply` and `dcm fix lib` to potentially correct some issues automatically for `make analyze`.
|
||||
:::tip Auto Fix
|
||||
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
|
||||
|
||||
@@ -32,6 +32,10 @@ This environment includes the services below. Additional details are available i
|
||||
|
||||
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
|
||||
|
||||
1. Clone the project repo.
|
||||
@@ -56,22 +60,23 @@ You can access the web from `http://your-machine-ip:3000` or `http://localhost:3
|
||||
|
||||
#### Connect web to a remote backend
|
||||
|
||||
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
|
||||
If you only want to do web development connected to an existing, remote backend, run from the repo root:
|
||||
|
||||
```bash
|
||||
IMMICH_SERVER_URL=https://demo.immich.app/ pnpm run dev
|
||||
IMMICH_SERVER_URL=https://demo.immich.app/ mise //web:start
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```powershell
|
||||
$env:IMMICH_SERVER_URL = "https://demo.immich.app/"
|
||||
pnpm run dev
|
||||
mise //web:start
|
||||
```
|
||||
|
||||
#### `@immich/ui`
|
||||
@@ -90,20 +95,16 @@ To see local changes to `@immich/ui` in Immich, do the following:
|
||||
|
||||
#### Setup
|
||||
|
||||
1. [Install mise](https://mise.jdx.dev/installing-mise.html).
|
||||
2. Change to the immich (root) directory and trust the mise config with `mise trust`.
|
||||
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.
|
||||
1. Run `mise //mobile:install` to install Flutter dependencies.
|
||||
2. Run `mise //mobile:translation` to generate the translation file.
|
||||
3. Change to the `mobile/` directory and run `flutter run` to start the app.
|
||||
|
||||
#### 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, from the `mobile/` directory, 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 run:
|
||||
|
||||
```bash
|
||||
make translation
|
||||
mise //mobile: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.
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
### Unit tests
|
||||
|
||||
Unit are run by calling `pnpm run test` from the `server/` directory.
|
||||
You need to run `pnpm install` (in `server/`) before _once_.
|
||||
Unit tests are run with `mise //server:test`.
|
||||
You need to run `mise //server:install` before _once_.
|
||||
|
||||
### End to end tests
|
||||
|
||||
@@ -17,15 +17,13 @@ make e2e
|
||||
|
||||
Before you can run the tests, you need to run the following commands _once_:
|
||||
|
||||
- `pnpm install` (in `e2e/`)
|
||||
- `pnpm run build` (in `cli/`)
|
||||
- `make open-api` (in the project root `/`)
|
||||
- `mise //e2e:ci-setup` (installs e2e, SDK, and CLI dependencies)
|
||||
- `mise //:open-api`
|
||||
|
||||
Once the test environment is running, the e2e tests can be run via:
|
||||
|
||||
```bash
|
||||
cd e2e/
|
||||
pnpm test
|
||||
mise //e2e:test
|
||||
```
|
||||
|
||||
The tests check various things including:
|
||||
|
||||
@@ -154,33 +154,33 @@ Redis (Sentinel) URL example JSON before encoding:
|
||||
|
||||
## Machine Learning
|
||||
|
||||
| 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_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_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_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_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 | `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__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__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__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_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_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__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_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_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 |
|
||||
| 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_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_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_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_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_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__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__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_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_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_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_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_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 |
|
||||
|
||||
\*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.
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ Scroll to the bottom of the "**Details**" section and find the `IP Address` list
|
||||
|
||||
## Step 4 - Configure Firewall Settings
|
||||
|
||||
Once your project completes the build process, your containers will start. In order to be able to access Immich from your browser, you need to configure the firewall settings for your Synology NAS.
|
||||
Once your project completes the build process, your containers will start. In order to be able to access Immich from your browser, you need to configure the firewall settings for your Synology NAS to allow communication between the Immich containers.
|
||||
|
||||
Open "**Control Panel**" on your Synology NAS, and select "**Security**". Navigate to "**Firewall**"
|
||||
|
||||
@@ -74,6 +74,7 @@ Read the [Post Installation](/install/post-install.mdx) steps and [upgrade instr
|
||||
|
||||
<details>
|
||||
<summary>Updating Immich using Container Manager</summary>
|
||||
|
||||
Check the post installation and upgrade instructions at the links above before proceeding with this section.
|
||||
|
||||
## Step 1. Backup
|
||||
@@ -110,7 +111,7 @@ Go to **Project**, select **Action** then **Build**. This will download, unpack,
|
||||
|
||||
## Step 5. Update firewall rule
|
||||
|
||||
The default behavior is to automatically start the containers once installed. If `immich_server` runs for a few seconds and then stops, it may be because the firewall rule no longer matches the server IP address.
|
||||
Without a fixed subnet, the default behavior is to automatically start the containers once installed. If `immich_server` runs for a few seconds and then stops, it may be because the firewall rule no longer matches the server IP address.
|
||||
|
||||
Go to the **Container** section. Click on `immich_server` and scroll down on **General** to find the IP address.
|
||||

|
||||
@@ -123,4 +124,67 @@ In this example, the IP addresses mismatch and the firewall rule needs to be edi
|
||||
|
||||

|
||||
|
||||
To prevent future firewall issues, you may set a fixed subnet. [See Set Fixed Subnet](#set-fixed-subnet) for instructions.
|
||||
|
||||
</details>
|
||||
|
||||
<details id="set-fixed-subnet">
|
||||
<summary>Set Fixed Subnet</summary>
|
||||
|
||||
Docker by default assigns dynamic subnets to bridge networks which can change when rebuilding containers and can cause firewall rules to break. To avoid this, define a fixed subnet in your `docker-compose.yml`:
|
||||
|
||||
## Step 1. Determine current subnet
|
||||
|
||||
Go to the **Container** section. Click on `immich_server` and scroll down on **General** to find the IP address.
|
||||

|
||||
|
||||
## Step 2. Add network configuration
|
||||
|
||||
Add the following network configuration at the end of your `docker-compose.yml` file:
|
||||
|
||||
```yaml
|
||||
networks:
|
||||
immich-network:
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.20.0.0/16
|
||||
gateway: 172.20.0.1
|
||||
```
|
||||
|
||||
If your docker container is running on a different subnet then update accordingly.
|
||||
|
||||
## Step 3. Add network to each service
|
||||
|
||||
Add the network to each service (immich-server, immich-machine-learning, redis, database):
|
||||
|
||||
```yaml
|
||||
services:
|
||||
immich-server:
|
||||
# other config options
|
||||
networks:
|
||||
- immich-network
|
||||
|
||||
immich-machine-learning:
|
||||
# other config options
|
||||
networks:
|
||||
- immich-network
|
||||
|
||||
redis:
|
||||
# other config options
|
||||
networks:
|
||||
- immich-network
|
||||
|
||||
database:
|
||||
# other config options
|
||||
networks:
|
||||
- immich-network
|
||||
```
|
||||
|
||||
Save your changes. Synology will ask if you want to save changes only or rebuild containers. Select rebuild containers.
|
||||
|
||||
## Step 4. Update Firewall Rules, if necessary
|
||||
|
||||
If your firewall rules were not already set for this subnet, the firewall rules will need to be updated. See [Step 4 - Configure Firewall Settings](#step-4---configure-firewall-settings).
|
||||
|
||||
</details>
|
||||
|
||||
@@ -10,7 +10,6 @@ const config = {
|
||||
url: 'https://docs.immich.app',
|
||||
baseUrl: '/',
|
||||
onBrokenLinks: 'throw',
|
||||
onBrokenMarkdownLinks: 'warn',
|
||||
favicon: 'img/favicon.png',
|
||||
|
||||
// GitHub pages deployment config.
|
||||
@@ -29,6 +28,9 @@ const config = {
|
||||
// Mermaid diagrams
|
||||
markdown: {
|
||||
mermaid: true,
|
||||
hooks: {
|
||||
onBrokenMarkdownLinks: 'warn',
|
||||
},
|
||||
},
|
||||
themes: ['@docusaurus/theme-mermaid'],
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# @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]
|
||||
env._.path = "./node_modules/.bin"
|
||||
run = "docusaurus --port 3005"
|
||||
run = "docusaurus start --port 3005"
|
||||
|
||||
[tasks.build]
|
||||
env._.path = "./node_modules/.bin"
|
||||
@@ -28,4 +28,4 @@ run = "prettier --write ."
|
||||
run = "wrangler pages deploy build --project-name=${PROJECT_NAME} --branch=${BRANCH_NAME}"
|
||||
|
||||
[tools]
|
||||
wrangler = "4.66.0"
|
||||
wrangler = "4.91.0"
|
||||
|
||||
@@ -83,9 +83,7 @@ volumes:
|
||||
model_cache:
|
||||
prometheus_data:
|
||||
grafana_data:
|
||||
pnpm_cache:
|
||||
pnpm_store_server:
|
||||
pnpm_store_web:
|
||||
build_cache:
|
||||
server_node_modules:
|
||||
web_node_modules:
|
||||
github_node_modules:
|
||||
|
||||
@@ -4,7 +4,7 @@ services:
|
||||
e2e-auth-server:
|
||||
container_name: immich-e2e-auth-server
|
||||
build:
|
||||
context: ../e2e-auth-server
|
||||
context: ../packages/e2e-auth-server
|
||||
ports:
|
||||
- 2286:2286
|
||||
|
||||
@@ -44,7 +44,7 @@ services:
|
||||
|
||||
redis:
|
||||
container_name: immich-e2e-redis
|
||||
image: docker.io/valkey/valkey:9@sha256:3b55fbaa0cd93cf0d9d961f405e4dfcc70efe325e2d84da207a0a8e6d8fde4f9
|
||||
image: docker.io/valkey/valkey:9@sha256:8436e10bc65c94886a91d4415b6a6dfa9cb5a306fb3b996e5bb67cd2b4854193
|
||||
healthcheck:
|
||||
test: redis-cli ping || exit 1
|
||||
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@
|
||||
"@playwright/test": "^1.44.1",
|
||||
"@socket.io/component-emitter": "^3.1.2",
|
||||
"@types/luxon": "^3.4.2",
|
||||
"@types/node": "^24.12.2",
|
||||
"@types/node": "^24.12.4",
|
||||
"@types/pg": "^8.15.1",
|
||||
"@types/pngjs": "^6.0.4",
|
||||
"@types/supertest": "^7.0.0",
|
||||
|
||||
@@ -2,7 +2,7 @@ import { LoginResponseDto, ManualJobName } from '@immich/sdk';
|
||||
import { errorDto } from 'src/responses';
|
||||
import { app, utils } from 'src/utils';
|
||||
import request from 'supertest';
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
describe('/admin/database-backups', () => {
|
||||
let cookie: string | undefined;
|
||||
@@ -13,6 +13,9 @@ describe('/admin/database-backups', () => {
|
||||
admin = await utils.adminSetup({
|
||||
onboarding: false,
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await utils.resetBackups(admin.accessToken);
|
||||
});
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
getMyUser,
|
||||
LoginResponseDto,
|
||||
SharedLinkType,
|
||||
updateConfig,
|
||||
} from '@immich/sdk';
|
||||
import { exiftool } from 'exiftool-vendored';
|
||||
import { DateTime } from 'luxon';
|
||||
@@ -24,7 +23,6 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
||||
|
||||
const locationAssetFilepath = `${testAssetDir}/metadata/gps-position/thompson-springs.jpg`;
|
||||
const ratingAssetFilepath = `${testAssetDir}/metadata/rating/mongolels.jpg`;
|
||||
const facesAssetDir = `${testAssetDir}/metadata/faces`;
|
||||
|
||||
const readTags = async (bytes: Buffer, filename: string) => {
|
||||
const filepath = join(tempDir, filename);
|
||||
@@ -185,78 +183,6 @@ describe('/asset', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('faces', () => {
|
||||
const metadataFaceTests = [
|
||||
{
|
||||
description: 'without orientation',
|
||||
filename: 'portrait.jpg',
|
||||
},
|
||||
{
|
||||
description: 'adjusting face regions to orientation',
|
||||
filename: 'portrait-orientation-6.jpg',
|
||||
},
|
||||
];
|
||||
// should produce same resulting face region coordinates for any orientation
|
||||
const expectedFaces = [
|
||||
{
|
||||
name: 'Marie Curie',
|
||||
birthDate: null,
|
||||
isHidden: false,
|
||||
faces: [
|
||||
{
|
||||
imageHeight: 700,
|
||||
imageWidth: 840,
|
||||
boundingBoxX1: 261,
|
||||
boundingBoxX2: 356,
|
||||
boundingBoxY1: 146,
|
||||
boundingBoxY2: 284,
|
||||
sourceType: 'exif',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Pierre Curie',
|
||||
birthDate: null,
|
||||
isHidden: false,
|
||||
faces: [
|
||||
{
|
||||
imageHeight: 700,
|
||||
imageWidth: 840,
|
||||
boundingBoxX1: 536,
|
||||
boundingBoxX2: 618,
|
||||
boundingBoxY1: 83,
|
||||
boundingBoxY2: 252,
|
||||
sourceType: 'exif',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
it.each(metadataFaceTests)('should get the asset faces from $filename $description', async ({ filename }) => {
|
||||
const config = await utils.getSystemConfig(admin.accessToken);
|
||||
config.metadata.faces.import = true;
|
||||
await updateConfig({ systemConfigDto: config }, { headers: asBearerAuth(admin.accessToken) });
|
||||
|
||||
const facesAsset = await utils.createAsset(admin.accessToken, {
|
||||
assetData: {
|
||||
filename,
|
||||
bytes: await readFile(`${facesAssetDir}/${filename}`),
|
||||
},
|
||||
});
|
||||
|
||||
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: facesAsset.id });
|
||||
|
||||
const { status, body } = await request(app)
|
||||
.get(`/assets/${facesAsset.id}`)
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||
|
||||
expect(status).toBe(200);
|
||||
expect(body.id).toEqual(facesAsset.id);
|
||||
const sortedPeople = body.people.toSorted((a: any, b: any) => a.name.localeCompare(b.name));
|
||||
expect(sortedPeople).toMatchObject(expectedFaces);
|
||||
});
|
||||
});
|
||||
|
||||
it('should work with a shared link', async () => {
|
||||
const sharedLink = await utils.createSharedLink(user1.accessToken, {
|
||||
type: SharedLinkType.Individual,
|
||||
|
||||
@@ -441,7 +441,18 @@ describe('/search', () => {
|
||||
.get('/search/explore')
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||
expect(status).toBe(200);
|
||||
expect(body).toEqual([{ fieldName: 'exifInfo.city', items: [] }]);
|
||||
expect(Array.isArray(body)).toBe(true);
|
||||
expect(body).toEqual(expect.arrayContaining([{ fieldName: 'exifInfo.city', items: [] }]));
|
||||
expect(body).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
fieldName: 'createdAt',
|
||||
items: expect.arrayContaining([
|
||||
expect.objectContaining({ data: expect.objectContaining({ id: assetLast.id }) }),
|
||||
]),
|
||||
},
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { readFileSync } from 'node:fs';
|
||||
import { immichCli } from 'src/utils';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
const pkg = JSON.parse(readFileSync('../cli/package.json', 'utf8'));
|
||||
const pkg = JSON.parse(readFileSync('../packages/cli/package.json', 'utf8'));
|
||||
|
||||
describe(`immich --version`, () => {
|
||||
describe('immich --version', () => {
|
||||
|
||||
@@ -28,6 +28,7 @@ export function toColumnarFormat(assets: MockTimelineAsset[]): TimeBucketAssetRe
|
||||
ownerId: [],
|
||||
ratio: [],
|
||||
thumbhash: [],
|
||||
createdAt: [],
|
||||
fileCreatedAt: [],
|
||||
localOffsetHours: [],
|
||||
isFavorite: [],
|
||||
@@ -338,7 +339,6 @@ export function toAssetResponseDto(asset: MockTimelineAsset, owner?: UserRespons
|
||||
livePhotoVideoId: asset.livePhotoVideoId,
|
||||
tags: [],
|
||||
people: [],
|
||||
unassignedFaces: [],
|
||||
stack: asset.stack,
|
||||
isOffline: false,
|
||||
hasMetadata: true,
|
||||
|
||||
@@ -66,7 +66,6 @@ export const createMockStackAsset = (ownerId: string): AssetResponseDto => {
|
||||
livePhotoVideoId: null,
|
||||
tags: [],
|
||||
people: [],
|
||||
unassignedFaces: [],
|
||||
stack: undefined,
|
||||
isOffline: false,
|
||||
hasMetadata: true,
|
||||
|
||||
@@ -536,7 +536,7 @@ test.describe('Timeline', () => {
|
||||
force: false,
|
||||
ids: [assetToTrash.id],
|
||||
});
|
||||
await page.locator('#asset-selection-app-bar').getByLabel('Close').click();
|
||||
await page.locator('#control-bar').getByLabel('Close').click();
|
||||
await page.getByText('Trash', { exact: true }).click();
|
||||
await timelineUtils.waitForTimelineLoad(page);
|
||||
await thumbnailUtils.expectInViewport(page, assetToTrash.id);
|
||||
@@ -676,7 +676,7 @@ test.describe('Timeline', () => {
|
||||
ids: [assetToArchive.id],
|
||||
});
|
||||
await thumbnailUtils.expectThumbnailIsArchive(page, assetToArchive.id);
|
||||
await page.locator('#asset-selection-app-bar').getByLabel('Close').click();
|
||||
await page.locator('#control-bar').getByLabel('Close').click();
|
||||
await page.getByRole('link').getByText('Archive').click();
|
||||
await timelineUtils.waitForTimelineLoad(page);
|
||||
await thumbnailUtils.expectInViewport(page, assetToArchive.id);
|
||||
@@ -823,7 +823,7 @@ test.describe('Timeline', () => {
|
||||
});
|
||||
// ensure thumbnail still exists and has favorite icon
|
||||
await thumbnailUtils.expectThumbnailIsFavorite(page, assetToFavorite.id);
|
||||
await page.locator('#asset-selection-app-bar').getByLabel('Close').click();
|
||||
await page.locator('#control-bar').getByLabel('Close').click();
|
||||
await page.getByRole('link').getByText('Favorites').click();
|
||||
await timelineUtils.waitForTimelineLoad(page);
|
||||
await pageUtils.goToAsset(page, assetToFavorite.fileCreatedAt);
|
||||
|
||||
+3
-1
@@ -90,7 +90,7 @@ export const tempDir = tmpdir();
|
||||
export const asBearerAuth = (accessToken: string) => ({ Authorization: `Bearer ${accessToken}` });
|
||||
export const asKeyAuth = (key: string) => ({ 'x-api-key': key });
|
||||
export const immichCli = (args: string[]) =>
|
||||
executeCommand('pnpm', ['exec', 'immich', '-d', `/${tempDir}/immich/`, ...args], { cwd: '../cli' }).promise;
|
||||
executeCommand('pnpm', ['exec', 'immich', '-d', `/${tempDir}/immich/`, ...args], { cwd: '../packages/cli' }).promise;
|
||||
export const dockerExec = (args: string[]) =>
|
||||
executeCommand('docker', ['exec', '-i', 'immich-e2e-server', '/bin/bash', '-c', args.join(' ')]);
|
||||
export const immichAdmin = (args: string[]) => dockerExec([`immich-admin ${args.join(' ')}`]);
|
||||
@@ -568,6 +568,8 @@ export const utils = {
|
||||
name: ManualJobName.BackupDatabase,
|
||||
});
|
||||
|
||||
await utils.waitForQueueFinish(accessToken, 'backupDatabase');
|
||||
|
||||
return utils.poll(
|
||||
() => request(app).get('/admin/database-backups').set('Authorization', `Bearer ${accessToken}`),
|
||||
({ status, body }) => status === 200 && body.backups.length === 1,
|
||||
|
||||
+39
-12
@@ -22,13 +22,12 @@
|
||||
"add_birthday": "Add a birthday",
|
||||
"add_endpoint": "Add endpoint",
|
||||
"add_exclusion_pattern": "Add exclusion pattern",
|
||||
"add_filter": "Add filter",
|
||||
"add_filter_description": "Click to add a filter condition",
|
||||
"add_location": "Add location",
|
||||
"add_more_users": "Add more users",
|
||||
"add_partner": "Add partner",
|
||||
"add_path": "Add path",
|
||||
"add_photos": "Add photos",
|
||||
"add_step": "Add step",
|
||||
"add_tag": "Add tag",
|
||||
"add_to": "Add to…",
|
||||
"add_to_album": "Add to album",
|
||||
@@ -42,7 +41,6 @@
|
||||
"add_to_shared_album": "Add to shared album",
|
||||
"add_upload_to_stack": "Add upload to stack",
|
||||
"add_url": "Add URL",
|
||||
"add_workflow_step": "Add workflow step",
|
||||
"added_to_archive": "Added to archive",
|
||||
"added_to_favorites": "Added to favorites",
|
||||
"added_to_favorites_count": "Added {count, number} to favorites",
|
||||
@@ -700,6 +698,7 @@
|
||||
"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.",
|
||||
"blurred_background": "Blurred background",
|
||||
"browse_templates": "Browse templates",
|
||||
"bugs_and_feature_requests": "Bugs & Feature Requests",
|
||||
"build": "Build",
|
||||
"build_image": "Build Image",
|
||||
@@ -733,6 +732,7 @@
|
||||
"cannot_update_the_description": "Cannot update the description",
|
||||
"cast": "Cast",
|
||||
"cast_description": "Configure available cast destinations",
|
||||
"change": "Change",
|
||||
"change_date": "Change date",
|
||||
"change_description": "Change description",
|
||||
"change_display_order": "Change display order",
|
||||
@@ -761,6 +761,7 @@
|
||||
"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",
|
||||
"checksum": "Checksum",
|
||||
"choose": "Choose",
|
||||
"choose_matching_people_to_merge": "Choose matching people to merge",
|
||||
"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?",
|
||||
@@ -778,6 +779,7 @@
|
||||
"clear": "Clear",
|
||||
"clear_all": "Clear all",
|
||||
"clear_all_recent_searches": "Clear all recent searches",
|
||||
"clear_failed_count": "Clear failed ({count})",
|
||||
"clear_file_cache": "Clear File Cache",
|
||||
"clear_message": "Clear message",
|
||||
"clear_value": "Clear value",
|
||||
@@ -809,6 +811,7 @@
|
||||
"comments_are_disabled": "Comments are disabled",
|
||||
"common_create_new_album": "Create new album",
|
||||
"completed": "Completed",
|
||||
"configuration": "Configuration",
|
||||
"confirm": "Confirm",
|
||||
"confirm_admin_password": "Confirm Admin Password",
|
||||
"confirm_delete_face": "Are you sure you want to delete {name} face from the asset?",
|
||||
@@ -823,6 +826,7 @@
|
||||
"contain": "Contain",
|
||||
"context": "Context",
|
||||
"continue": "Continue",
|
||||
"control_bottom_app_bar_add_tags": "Add Tags",
|
||||
"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_local": "Delete from device",
|
||||
@@ -836,6 +840,7 @@
|
||||
"copy_error": "Copy error",
|
||||
"copy_file_path": "Copy file path",
|
||||
"copy_image": "Copy Image",
|
||||
"copy_json": "Copy JSON",
|
||||
"copy_link": "Copy link",
|
||||
"copy_link_to_clipboard": "Copy link to clipboard",
|
||||
"copy_password": "Copy password",
|
||||
@@ -885,17 +890,16 @@
|
||||
"cutoff_date_description": "Keep photos from the last…",
|
||||
"cutoff_day": "{count, plural, one {day} other {days}}",
|
||||
"cutoff_year": "{count, plural, one {year} other {years}}",
|
||||
"daily_title_text_date": "E, MMM dd",
|
||||
"daily_title_text_date_year": "E, MMM dd, yyyy",
|
||||
"dark": "Dark",
|
||||
"dark_theme": "Switch to dark theme",
|
||||
"date": "Date",
|
||||
"date_after": "Date after",
|
||||
"date_and_time": "Date and Time",
|
||||
"date_before": "Date before",
|
||||
"date_format": "E, LLL d, y • h:mm a",
|
||||
"date_of_birth": "Date of birth",
|
||||
"date_of_birth_saved": "Date of birth saved successfully",
|
||||
"date_range": "Date range",
|
||||
"date_time_original": "Date/Time Original",
|
||||
"day": "Day",
|
||||
"days": "Days",
|
||||
"deduplicate_all": "Deduplicate All",
|
||||
@@ -974,7 +978,10 @@
|
||||
"downloading_asset_filename": "Downloading asset {filename}",
|
||||
"downloading_from_icloud": "Downloading from iCloud",
|
||||
"downloading_media": "Downloading media",
|
||||
"drag_to_reorder": "Drag to reorder",
|
||||
"drop_files_to_upload": "Drop files anywhere to upload",
|
||||
"duplicate": "Duplicate",
|
||||
"duplicate_workflow": "Duplicate workflow",
|
||||
"duplicates": "Duplicates",
|
||||
"duplicates_description": "Resolve each group by indicating which, if any, are duplicates.",
|
||||
"duration": "Duration",
|
||||
@@ -1076,6 +1083,7 @@
|
||||
"failed_to_remove_product_key": "Failed to remove product key",
|
||||
"failed_to_reset_pin_code": "Failed to reset PIN code",
|
||||
"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_update_notification_status": "Failed to update notification status",
|
||||
"incorrect_email_or_password": "Incorrect email or password",
|
||||
@@ -1195,11 +1203,13 @@
|
||||
"export_as_json": "Export as JSON",
|
||||
"export_database": "Export Database",
|
||||
"export_database_description": "Export the SQLite database",
|
||||
"exposure_time": "Exposure Time",
|
||||
"extension": "Extension",
|
||||
"external": "External",
|
||||
"external_libraries": "External Libraries",
|
||||
"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",
|
||||
"f_number": "F-Number",
|
||||
"face_unassigned": "Unassigned",
|
||||
"failed": "Failed",
|
||||
"failed_count": "Failed: {count}",
|
||||
@@ -1217,7 +1227,6 @@
|
||||
"features_setting_description": "Manage the app features",
|
||||
"file_name_or_extension": "File name or extension",
|
||||
"file_name_text": "File name",
|
||||
"file_name_with_value": "File name: {file_name}",
|
||||
"file_size": "File size",
|
||||
"filename": "Filename",
|
||||
"filetype": "Filetype",
|
||||
@@ -1230,6 +1239,7 @@
|
||||
"find_them_fast": "Find them fast by name with search",
|
||||
"first": "First",
|
||||
"fix_incorrect_match": "Fix incorrect match",
|
||||
"focal_length": "Focal Length",
|
||||
"folder": "Folder",
|
||||
"folder_not_found": "Folder not found",
|
||||
"folders": "Folders",
|
||||
@@ -1350,6 +1360,7 @@
|
||||
"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_processing_ran_at": "Processing ran {dateTime}",
|
||||
"iso": "ISO",
|
||||
"items_count": "{count, plural, one {# item} other {# items}}",
|
||||
"jobs": "Jobs",
|
||||
"json_editor": "JSON editor",
|
||||
@@ -1403,6 +1414,7 @@
|
||||
"link_to_oauth": "Link to OAuth",
|
||||
"linked_oauth_account": "Linked OAuth account",
|
||||
"list": "List",
|
||||
"live": "Live",
|
||||
"loading": "Loading",
|
||||
"loading_search_results_failed": "Loading search results failed",
|
||||
"local": "Local",
|
||||
@@ -1581,9 +1593,10 @@
|
||||
"mobile_app": "Mobile App",
|
||||
"mobile_app_download_onboarding_note": "Download the companion mobile app using the following options",
|
||||
"model": "Model",
|
||||
"modify_date": "Modify Date",
|
||||
"month": "Month",
|
||||
"monthly_title_text_date_format": "MMMM y",
|
||||
"more": "More",
|
||||
"motion": "Motion",
|
||||
"move": "Move",
|
||||
"move_down": "Move down",
|
||||
"move_off_locked_folder": "Move out of locked folder",
|
||||
@@ -1629,7 +1642,6 @@
|
||||
"next": "Next",
|
||||
"next_memory": "Next memory",
|
||||
"no": "No",
|
||||
"no_actions_added": "No actions added yet",
|
||||
"no_albums_found": "No albums found",
|
||||
"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.",
|
||||
@@ -1646,7 +1658,6 @@
|
||||
"no_exif_info_available": "No exif info available",
|
||||
"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_filters_added": "No filters added yet",
|
||||
"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_location_set": "No location set",
|
||||
@@ -1659,6 +1670,7 @@
|
||||
"no_results": "No results",
|
||||
"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_steps": "No steps added yet",
|
||||
"no_uploads_in_progress": "No uploads in progress",
|
||||
"none": "None",
|
||||
"not_allowed": "Not allowed",
|
||||
@@ -1704,6 +1716,7 @@
|
||||
"organize_into_albums": "Organize into albums",
|
||||
"organize_into_albums_description": "Put existing photos into albums using current sync settings",
|
||||
"organize_your_library": "Organize your library",
|
||||
"orientation": "Orientation",
|
||||
"original": "original",
|
||||
"other": "Other",
|
||||
"other_devices": "Other devices",
|
||||
@@ -1795,6 +1808,8 @@
|
||||
"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",
|
||||
"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",
|
||||
"preferences_settings_subtitle": "Manage the app's preferences",
|
||||
"preferences_settings_title": "Preferences",
|
||||
@@ -1816,6 +1831,7 @@
|
||||
"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_picture_set": "Profile picture set.",
|
||||
"projection_type": "Projection Type",
|
||||
"public_album": "Public album",
|
||||
"public_share": "Public Share",
|
||||
"purchase_account_info": "Supporter",
|
||||
@@ -1893,6 +1909,7 @@
|
||||
"remove_assets_title": "Remove assets?",
|
||||
"remove_custom_date_range": "Remove custom date range",
|
||||
"remove_deleted_assets": "Remove Deleted Assets",
|
||||
"remove_filter": "Remove filter",
|
||||
"remove_from_album": "Remove from album",
|
||||
"remove_from_album_action_prompt": "{count} removed from the album",
|
||||
"remove_from_favorites": "Remove from favorites",
|
||||
@@ -2184,7 +2201,9 @@
|
||||
"show_in_timeline": "Show in timeline",
|
||||
"show_in_timeline_setting_description": "Show photos and videos from this user in your timeline",
|
||||
"show_keyboard_shortcuts": "Show keyboard shortcuts",
|
||||
"show_less": "Show less",
|
||||
"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_password": "Show password",
|
||||
"show_person_options": "Show person options",
|
||||
@@ -2236,6 +2255,11 @@
|
||||
"start_date_before_end_date": "Start date must be before end date",
|
||||
"state": "State",
|
||||
"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_motion_photo": "Stop Motion Photo",
|
||||
"stop_photo_sharing": "Stop sharing your photos?",
|
||||
@@ -2329,7 +2353,7 @@
|
||||
"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}}.",
|
||||
"trigger": "Trigger",
|
||||
"trigger_asset_uploaded": "Asset Uploaded",
|
||||
"trigger_asset_uploaded": "Asset Upload",
|
||||
"trigger_asset_uploaded_description": "Triggered when a new asset is uploaded",
|
||||
"trigger_description": "An event that kicks off the workflow",
|
||||
"trigger_person_recognized": "Person Recognized",
|
||||
@@ -2369,7 +2393,6 @@
|
||||
"unsupported_field_type": "Unsupported field type",
|
||||
"unsupported_file_type": "File {file} can't be uploaded because its file type {type} is not supported.",
|
||||
"untagged": "Untagged",
|
||||
"untitled_workflow": "Untitled workflow",
|
||||
"up_next": "Up next",
|
||||
"update_location_action_prompt": "Update the location of {count} selected assets with:",
|
||||
"updated_at": "Updated",
|
||||
@@ -2398,6 +2421,7 @@
|
||||
"use_browser_locale_description": "Format dates, times, and numbers based on your browser locale",
|
||||
"use_current_connection": "Use current connection",
|
||||
"use_custom_date_range": "Use custom date range instead",
|
||||
"use_template": "Use template",
|
||||
"user": "User",
|
||||
"user_has_been_deleted": "This user has been deleted.",
|
||||
"user_id": "User ID",
|
||||
@@ -2459,8 +2483,10 @@
|
||||
"week": "Week",
|
||||
"welcome": "Welcome",
|
||||
"welcome_to_immich": "Welcome to Immich",
|
||||
"when": "When",
|
||||
"width": "Width",
|
||||
"wifi_name": "Wi-Fi Name",
|
||||
"workflow": "Workflow",
|
||||
"workflow_delete_prompt": "Are you sure you want to delete this workflow?",
|
||||
"workflow_deleted": "Workflow deleted",
|
||||
"workflow_description": "Workflow description",
|
||||
@@ -2470,6 +2496,7 @@
|
||||
"workflow_name": "Workflow name",
|
||||
"workflow_navigation_prompt": "Are you sure you want to leave without saving your changes?",
|
||||
"workflow_summary": "Workflow summary",
|
||||
"workflow_templates": "Workflow templates",
|
||||
"workflow_update_success": "Workflow updated successfully",
|
||||
"workflow_updated": "Workflow updated",
|
||||
"workflows": "Workflows",
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"name": "immich-i18n",
|
||||
"version": "2.7.5",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"format": "prettier --cache --check .",
|
||||
"format:fix": "prettier --cache --write --list-different ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^3.7.4",
|
||||
"prettier-plugin-sort-json": "^4.1.1"
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ from pathlib import Path
|
||||
from socket import socket
|
||||
|
||||
from gunicorn.arbiter import Arbiter
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
from rich.console import Console
|
||||
from rich.logging import RichHandler
|
||||
@@ -42,6 +42,10 @@ class MaxBatchSize(BaseModel):
|
||||
ocr: int | None = None
|
||||
|
||||
|
||||
def default_worker_timeout() -> int:
|
||||
return 900 if os.environ.get("DEVICE") == "rocm" else 300
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
model_config = SettingsConfigDict(
|
||||
env_prefix="MACHINE_LEARNING_",
|
||||
@@ -54,7 +58,7 @@ class Settings(BaseSettings):
|
||||
model_ttl: int = 300
|
||||
model_ttl_poll_s: int = 10
|
||||
workers: int = 1
|
||||
worker_timeout: int = 300
|
||||
worker_timeout: int = Field(default_factory=default_worker_timeout)
|
||||
http_keepalive_timeout_s: int = 2
|
||||
test_full: bool = False
|
||||
request_threads: int = os.cpu_count() or 4
|
||||
|
||||
@@ -89,4 +89,10 @@ class FaceRecognizer(InferenceModel):
|
||||
@property
|
||||
def _batch_size_default(self) -> int | None:
|
||||
providers = ort.get_available_providers()
|
||||
return None if self.model_format == ModelFormat.ONNX and "OpenVINOExecutionProvider" not in providers else 1
|
||||
if (
|
||||
self.model_format == ModelFormat.ONNX
|
||||
and "MIGraphXExecutionProvider" not in providers
|
||||
and "OpenVINOExecutionProvider" not in providers
|
||||
):
|
||||
return None
|
||||
return 1
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from threading import Lock
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
@@ -12,6 +13,37 @@ from immich_ml.schemas import ModelPrecision, SessionNode
|
||||
|
||||
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:
|
||||
session: ort.InferenceSession
|
||||
@@ -48,7 +80,21 @@ class OrtSession:
|
||||
input_feed: dict[str, NDArray[np.float32]] | dict[str, NDArray[np.int32]],
|
||||
run_options: Any = None,
|
||||
) -> list[NDArray[np.float32]]:
|
||||
outputs: list[NDArray[np.float32]] = self.session.run(output_names, input_feed, run_options)
|
||||
if "MIGraphXExecutionProvider" in self.providers:
|
||||
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
|
||||
|
||||
@property
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
# @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",
|
||||
"gunicorn>=21.1.0",
|
||||
"huggingface-hub>=1.0,<2.0",
|
||||
"insightface>=0.7.3,<1.0",
|
||||
"insightface>=0.7.3,<2.0",
|
||||
"numpy>=2.4.0,<3.0",
|
||||
"opencv-python-headless>=4.7.0.72,<5.0",
|
||||
"orjson>=3.9.5",
|
||||
|
||||
@@ -35,7 +35,37 @@ from immich_ml.sessions.ort import OrtSession
|
||||
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:
|
||||
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:
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai")
|
||||
|
||||
@@ -413,6 +443,52 @@ class TestOrtSession:
|
||||
|
||||
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:
|
||||
def test_creates_ann_session(self, ann_session: mock.Mock, info: mock.Mock) -> None:
|
||||
@@ -883,6 +959,34 @@ class TestFaceRecognition:
|
||||
onnx.load.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:
|
||||
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 = "gunicorn", specifier = ">=21.1.0" },
|
||||
{ name = "huggingface-hub", specifier = ">=1.0,<2.0" },
|
||||
{ name = "insightface", specifier = ">=0.7.3,<1.0" },
|
||||
{ name = "insightface", specifier = ">=0.7.3,<2.0" },
|
||||
{ name = "numpy", specifier = ">=2.4.0,<3.0" },
|
||||
{ name = "onnxruntime", marker = "extra == 'armnn'", specifier = ">=1.23.2,<2" },
|
||||
{ name = "onnxruntime", marker = "extra == 'cpu'", specifier = ">=1.23.2,<2" },
|
||||
|
||||
@@ -64,16 +64,13 @@ if [ "$CURRENT_SERVER" != "$NEXT_SERVER" ]; then
|
||||
|
||||
pnpm version "$NEXT_SERVER" --no-git-tag-version
|
||||
pnpm version "$NEXT_SERVER" --no-git-tag-version --prefix server
|
||||
pnpm version "$NEXT_SERVER" --no-git-tag-version --prefix i18n
|
||||
pnpm version "$NEXT_SERVER" --no-git-tag-version --prefix cli
|
||||
pnpm version "$NEXT_SERVER" --no-git-tag-version --prefix packages/cli
|
||||
pnpm version "$NEXT_SERVER" --no-git-tag-version --prefix web
|
||||
pnpm version "$NEXT_SERVER" --no-git-tag-version --prefix e2e
|
||||
pnpm version "$NEXT_SERVER" --no-git-tag-version --prefix packages/sdk
|
||||
|
||||
# copy version to open-api spec
|
||||
pnpm install --frozen-lockfile --prefix server
|
||||
pnpm --prefix server run build
|
||||
( cd ./open-api && bash ./bin/generate-open-api.sh )
|
||||
mise run //:open-api
|
||||
|
||||
uv version --directory machine-learning "$NEXT_SERVER"
|
||||
|
||||
|
||||
@@ -0,0 +1,354 @@
|
||||
# @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,9 +2,9 @@ experimental_monorepo_root = true
|
||||
|
||||
[monorepo]
|
||||
config_roots = [
|
||||
"plugins",
|
||||
"packages/plugin-core",
|
||||
"server",
|
||||
"cli",
|
||||
"packages/cli",
|
||||
"deployment",
|
||||
"mobile",
|
||||
"e2e",
|
||||
@@ -16,16 +16,27 @@ config_roots = [
|
||||
|
||||
[tools]
|
||||
node = "24.15.0"
|
||||
flutter = "3.41.9"
|
||||
pnpm = "10.33.1"
|
||||
"aqua:flutter/flutter" = "3.44.0"
|
||||
pnpm = "10.33.4"
|
||||
terragrunt = "1.0.3"
|
||||
opentofu = "1.11.6"
|
||||
java = "21.0.2"
|
||||
"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"]
|
||||
version = "1.37.0"
|
||||
bin = "dcm"
|
||||
postinstall = "chmod +x {{ get_env(name='MISE_TOOL_INSTALL_PATH',default='') }}/dcm"
|
||||
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"]
|
||||
version = "7.1.3-6"
|
||||
@@ -39,21 +50,118 @@ macos-arm64 = { asset_pattern = "jellyfin-ffmpeg_*_portable_macarm64-gpl.tar.xz"
|
||||
[settings]
|
||||
experimental = 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]
|
||||
run = [
|
||||
"oazapfts --optimistic --argumentStyle=object --useEnumType --allSchemas open-api/immich-openapi-specs.json packages/sdk/src/fetch-client.ts",
|
||||
{ task = "//:sdk:install" },
|
||||
{ task = "//:sdk:build" },
|
||||
]
|
||||
|
||||
[tasks.open-api-dart]
|
||||
dir = "open-api"
|
||||
run = "bash ./bin/generate-dart-sdk.sh"
|
||||
|
||||
[tasks.open-api]
|
||||
env = { SHARP_IGNORE_GLOBAL_LIBVIPS = true }
|
||||
run = [
|
||||
{ task = "//:plugins" },
|
||||
{ task = "//server:install" },
|
||||
{ task = "//server:build" },
|
||||
{ task = "//server:sync-open-api" },
|
||||
{ task = ":open-api-typescript" },
|
||||
{ task = ":open-api-dart" },
|
||||
]
|
||||
|
||||
[tasks.sql]
|
||||
dir = "server"
|
||||
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
|
||||
[tasks."sdk:install"]
|
||||
dir = "packages/sdk"
|
||||
run = "pnpm install --filter @immich/sdk --frozen-lockfile"
|
||||
run = "pnpm --filter @immich/sdk install --frozen-lockfile"
|
||||
|
||||
[tasks."sdk:build"]
|
||||
dir = "packages/sdk"
|
||||
run = "pnpm run build"
|
||||
run = "pnpm build"
|
||||
|
||||
# i18n tasks
|
||||
[tasks."i18n:format"]
|
||||
dir = "i18n"
|
||||
run = "pnpm run format"
|
||||
run = "pnpm format"
|
||||
|
||||
[tasks."i18n:format-fix"]
|
||||
dir = "i18n"
|
||||
run = "pnpm run format:fix"
|
||||
run = "pnpm format:fix"
|
||||
|
||||
Vendored
-1
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"dart.flutterSdkPath": ".fvm/versions/3.41.9",
|
||||
"dart.lineLength": 120,
|
||||
"[dart]": {
|
||||
"editor.rulers": [
|
||||
|
||||
@@ -89,6 +89,13 @@ flutter {
|
||||
}
|
||||
|
||||
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.cronet.embedded
|
||||
implementation libs.media3.datasource.okhttp
|
||||
|
||||
@@ -17,6 +17,8 @@ import app.alextran.immich.images.LocalImageApi
|
||||
import app.alextran.immich.images.LocalImagesImpl
|
||||
import app.alextran.immich.images.RemoteImageApi
|
||||
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.NativeSyncApiImpl26
|
||||
import app.alextran.immich.sync.NativeSyncApiImpl30
|
||||
@@ -44,7 +46,9 @@ class MainActivity : FlutterFragmentActivity() {
|
||||
} else {
|
||||
NativeSyncApiImpl30(ctx)
|
||||
}
|
||||
val permissionApiImpl = PermissionApiImpl(ctx)
|
||||
NativeSyncApi.setUp(messenger, nativeSyncApiImpl)
|
||||
PermissionApi.setUp(messenger, permissionApiImpl)
|
||||
LocalImageApi.setUp(messenger, LocalImagesImpl(ctx))
|
||||
RemoteImageApi.setUp(messenger, RemoteImagesImpl(ctx))
|
||||
|
||||
@@ -53,6 +57,7 @@ class MainActivity : FlutterFragmentActivity() {
|
||||
|
||||
flutterEngine.plugins.add(backgroundEngineLockImpl)
|
||||
flutterEngine.plugins.add(nativeSyncApiImpl)
|
||||
flutterEngine.plugins.add(permissionApiImpl)
|
||||
}
|
||||
|
||||
fun cancelPlugins(flutterEngine: FlutterEngine) {
|
||||
@@ -60,6 +65,8 @@ class MainActivity : FlutterFragmentActivity() {
|
||||
flutterEngine.plugins.get(NativeSyncApiImpl26::class.java) as ImmichPlugin?
|
||||
?: flutterEngine.plugins.get(NativeSyncApiImpl30::class.java) as ImmichPlugin?
|
||||
nativeApi?.detachFromEngine()
|
||||
val permissionApi = flutterEngine.plugins.get(PermissionApiImpl::class.java) as ImmichPlugin?
|
||||
permissionApi?.detachFromEngine()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -416,12 +416,12 @@ class BackgroundWorkerFlutterApi(private val binaryMessenger: BinaryMessenger, p
|
||||
}
|
||||
}
|
||||
}
|
||||
fun onAndroidUpload(callback: (Result<Unit>) -> Unit)
|
||||
fun onAndroidUpload(maxMinutesArg: Long?, callback: (Result<Unit>) -> Unit)
|
||||
{
|
||||
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
|
||||
val channelName = "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFlutterApi.onAndroidUpload$separatedMessageChannelSuffix"
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
|
||||
channel.send(null) {
|
||||
channel.send(listOf(maxMinutesArg)) {
|
||||
if (it is List<*>) {
|
||||
if (it.size > 1) {
|
||||
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
|
||||
|
||||
+1
-1
@@ -107,7 +107,7 @@ class BackgroundWorker(context: Context, params: WorkerParameters) :
|
||||
* This method acts as a bridge between the native Android background task system and Flutter.
|
||||
*/
|
||||
override fun onInitialized() {
|
||||
flutterApi?.onAndroidUpload { handleHostResult(it) }
|
||||
flutterApi?.onAndroidUpload(maxMinutesArg = 20) { handleHostResult(it) }
|
||||
}
|
||||
|
||||
// TODO: Move this to a separate NotificationManager class
|
||||
|
||||
@@ -315,6 +315,7 @@ interface NetworkApi {
|
||||
fun hasCertificate(): Boolean
|
||||
fun getClientPointer(): Long
|
||||
fun setRequestHeaders(headers: Map<String, String>, serverUrls: List<String>, token: String?)
|
||||
fun getAppGroupId(): String
|
||||
|
||||
companion object {
|
||||
/** The codec used by NetworkApi. */
|
||||
@@ -430,6 +431,21 @@ interface NetworkApi {
|
||||
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
|
||||
|
||||
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
||||
networkApi = NetworkApiImpl()
|
||||
networkApi = NetworkApiImpl(binding.applicationContext)
|
||||
NetworkApi.setUp(binding.binaryMessenger, networkApi)
|
||||
}
|
||||
|
||||
@@ -39,9 +39,11 @@ class NetworkApiPlugin : FlutterPlugin, ActivityAware {
|
||||
}
|
||||
}
|
||||
|
||||
private class NetworkApiImpl : NetworkApi {
|
||||
private class NetworkApiImpl(private val context: Context) : NetworkApi {
|
||||
var activity: Activity? = null
|
||||
|
||||
override fun getAppGroupId(): String = context.packageName
|
||||
|
||||
override fun addCertificate(clientData: ClientCertData, callback: (Result<Unit>) -> Unit) {
|
||||
try {
|
||||
HttpClientManager.setKeyEntry(clientData.data, clientData.password.toCharArray())
|
||||
|
||||
@@ -23,6 +23,8 @@ import java.io.IOException
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
private const val MAX_PREALLOC_BYTES = 128 * 1024 * 1024
|
||||
|
||||
private class RemoteRequest(val cancellationSignal: CancellationSignal)
|
||||
|
||||
class RemoteImagesImpl(context: Context) : RemoteImageApi {
|
||||
@@ -228,7 +230,6 @@ private class CronetImageFetcher : ImageFetcher {
|
||||
private val onComplete: () -> Unit,
|
||||
) : UrlRequest.Callback() {
|
||||
private var buffer: NativeByteBuffer? = null
|
||||
private var wrapped: ByteBuffer? = null
|
||||
private var error: Exception? = null
|
||||
|
||||
override fun onRedirectReceived(request: UrlRequest, info: UrlResponseInfo, newUrl: String) {
|
||||
@@ -242,15 +243,16 @@ private class CronetImageFetcher : ImageFetcher {
|
||||
}
|
||||
|
||||
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
|
||||
if (contentLength > 0) {
|
||||
buffer = NativeByteBuffer(contentLength + 1)
|
||||
wrapped = NativeBuffer.wrap(buffer!!.pointer, contentLength + 1)
|
||||
request.read(wrapped)
|
||||
} else {
|
||||
buffer = NativeByteBuffer(INITIAL_BUFFER_SIZE)
|
||||
request.read(buffer!!.wrapRemaining())
|
||||
}
|
||||
// Cap the up-front alloc: Content-Length is untrusted and can be huge or near
|
||||
// Int.MAX_VALUE (overflowing `+1`). For larger responses the grow path takes over.
|
||||
val initialSize = if (contentLength in 1..MAX_PREALLOC_BYTES) contentLength + 1 else INITIAL_BUFFER_SIZE
|
||||
buffer = NativeByteBuffer(initialSize)
|
||||
request.read(buffer!!.wrapRemaining())
|
||||
} catch (e: Exception) {
|
||||
error = e
|
||||
return request.cancel()
|
||||
@@ -263,14 +265,14 @@ private class CronetImageFetcher : ImageFetcher {
|
||||
byteBuffer: ByteBuffer
|
||||
) {
|
||||
try {
|
||||
val buf = if (wrapped == null) {
|
||||
buffer!!.run {
|
||||
advance(byteBuffer.position())
|
||||
ensureHeadroom()
|
||||
wrapRemaining()
|
||||
}
|
||||
} else {
|
||||
wrapped
|
||||
// Always pass a fresh wrap so byteBuffer.position() represents only the
|
||||
// bytes Cronet wrote in this iteration. Reusing the caller-supplied
|
||||
// ByteBuffer breaks advance(): Cronet's position keeps accumulating
|
||||
// across reads, which would double-count previous iterations' bytes.
|
||||
val buf = buffer!!.run {
|
||||
advance(byteBuffer.position())
|
||||
ensureHeadroom()
|
||||
wrapRemaining()
|
||||
}
|
||||
request.read(buf)
|
||||
} catch (e: Exception) {
|
||||
@@ -280,7 +282,6 @@ private class CronetImageFetcher : ImageFetcher {
|
||||
}
|
||||
|
||||
override fun onSucceeded(request: UrlRequest, info: UrlResponseInfo) {
|
||||
wrapped?.let { buffer!!.advance(it.position()) }
|
||||
onSuccess(buffer!!)
|
||||
onComplete()
|
||||
}
|
||||
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
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
@@ -0,0 +1,128 @@
|
||||
// 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
@@ -0,0 +1,37 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
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,6 +553,7 @@ interface NativeSyncApi {
|
||||
fun hashAssets(assetIds: List<String>, allowNetworkAccess: Boolean, callback: (Result<List<HashResult>>) -> Unit)
|
||||
fun cancelHashing()
|
||||
fun getTrashedAssets(): Map<String, List<PlatformAsset>>
|
||||
fun restoreFromTrashById(mediaId: String, type: Long, callback: (Result<Boolean>) -> Unit)
|
||||
fun getCloudIdForAssetIds(assetIds: List<String>): List<CloudIdResult>
|
||||
|
||||
companion object {
|
||||
@@ -747,6 +748,27 @@ interface NativeSyncApi {
|
||||
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 {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getCloudIdForAssetIds$separatedMessageChannelSuffix", codec, taskQueue)
|
||||
if (api != null) {
|
||||
|
||||
@@ -17,6 +17,8 @@ import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.ImageHeaderParser
|
||||
import com.bumptech.glide.load.ImageHeaderParserUtils
|
||||
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.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
@@ -39,10 +41,11 @@ sealed class AssetResult {
|
||||
private const val TAG = "NativeSyncApiImplBase"
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() {
|
||||
open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAware {
|
||||
private val ctx: Context = context.applicationContext
|
||||
|
||||
private var hashTask: Job? = null
|
||||
private val mediaTrashDelegate = MediaTrashDelegate(ctx)
|
||||
|
||||
companion object {
|
||||
private const val MAX_CONCURRENT_HASH_OPERATIONS = 16
|
||||
@@ -448,6 +451,26 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() {
|
||||
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
|
||||
@Suppress("unused", "UNUSED_PARAMETER")
|
||||
fun getCloudIdForAssetIds(assetIds: List<String>): List<CloudIdResult> {
|
||||
|
||||
@@ -5,3 +5,7 @@ android.nonTransitiveRClass=false
|
||||
android.nonFinalResIds=false
|
||||
org.gradle.caching=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
|
||||
|
||||
+117
-117
@@ -1003,20 +1003,6 @@
|
||||
1
|
||||
],
|
||||
"type": "index",
|
||||
"data": {
|
||||
"on": 1,
|
||||
"name": "idx_remote_asset_owner_checksum",
|
||||
"sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)",
|
||||
"unique": false,
|
||||
"columns": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"references": [
|
||||
1
|
||||
],
|
||||
"type": "index",
|
||||
"data": {
|
||||
"on": 1,
|
||||
"name": "UQ_remote_assets_owner_checksum",
|
||||
@@ -1026,7 +1012,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"id": 12,
|
||||
"references": [
|
||||
1
|
||||
],
|
||||
@@ -1040,7 +1026,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"id": 13,
|
||||
"references": [
|
||||
1
|
||||
],
|
||||
@@ -1054,7 +1040,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"id": 14,
|
||||
"references": [
|
||||
1
|
||||
],
|
||||
@@ -1067,36 +1053,22 @@
|
||||
"columns": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"references": [
|
||||
1
|
||||
],
|
||||
"type": "index",
|
||||
"data": {
|
||||
"on": 1,
|
||||
"name": "idx_remote_asset_owner_visibility_deleted_created",
|
||||
"sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created\nON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)\n",
|
||||
"unique": false,
|
||||
"columns": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"references": [
|
||||
1
|
||||
],
|
||||
"type": "index",
|
||||
"data": {
|
||||
"on": 1,
|
||||
"name": "idx_remote_asset_local_date_time_day",
|
||||
"sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_day ON remote_asset_entity (STRFTIME('%Y-%m-%d', local_date_time))",
|
||||
"unique": false,
|
||||
"columns": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"references": [
|
||||
1
|
||||
],
|
||||
"type": "index",
|
||||
"data": {
|
||||
"on": 1,
|
||||
"name": "idx_remote_asset_local_date_time_month",
|
||||
"sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_month ON remote_asset_entity (STRFTIME('%Y-%m', local_date_time))",
|
||||
"unique": false,
|
||||
"columns": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"references": [],
|
||||
"type": "table",
|
||||
"data": {
|
||||
@@ -1226,7 +1198,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"id": 17,
|
||||
"references": [
|
||||
0
|
||||
],
|
||||
@@ -1301,7 +1273,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"id": 18,
|
||||
"references": [
|
||||
0
|
||||
],
|
||||
@@ -1388,7 +1360,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"id": 19,
|
||||
"references": [
|
||||
1
|
||||
],
|
||||
@@ -1644,7 +1616,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"id": 20,
|
||||
"references": [
|
||||
1,
|
||||
4
|
||||
@@ -1718,7 +1690,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"id": 21,
|
||||
"references": [
|
||||
4,
|
||||
0
|
||||
@@ -1806,7 +1778,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 24,
|
||||
"id": 22,
|
||||
"references": [
|
||||
1
|
||||
],
|
||||
@@ -1902,7 +1874,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 25,
|
||||
"id": 23,
|
||||
"references": [
|
||||
0
|
||||
],
|
||||
@@ -2066,10 +2038,10 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 26,
|
||||
"id": 24,
|
||||
"references": [
|
||||
1,
|
||||
25
|
||||
23
|
||||
],
|
||||
"type": "table",
|
||||
"data": {
|
||||
@@ -2140,7 +2112,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 27,
|
||||
"id": 25,
|
||||
"references": [
|
||||
0
|
||||
],
|
||||
@@ -2284,10 +2256,10 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 28,
|
||||
"id": 26,
|
||||
"references": [
|
||||
1,
|
||||
27
|
||||
25
|
||||
],
|
||||
"type": "table",
|
||||
"data": {
|
||||
@@ -2461,7 +2433,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 29,
|
||||
"id": 27,
|
||||
"references": [],
|
||||
"type": "table",
|
||||
"data": {
|
||||
@@ -2509,7 +2481,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 30,
|
||||
"id": 28,
|
||||
"references": [],
|
||||
"type": "table",
|
||||
"data": {
|
||||
@@ -2684,7 +2656,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 31,
|
||||
"id": 29,
|
||||
"references": [
|
||||
1
|
||||
],
|
||||
@@ -2778,7 +2750,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 32,
|
||||
"id": 30,
|
||||
"references": [],
|
||||
"type": "table",
|
||||
"data": {
|
||||
@@ -2826,13 +2798,13 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 33,
|
||||
"id": 31,
|
||||
"references": [
|
||||
20
|
||||
18
|
||||
],
|
||||
"type": "index",
|
||||
"data": {
|
||||
"on": 20,
|
||||
"on": 18,
|
||||
"name": "idx_partner_shared_with_id",
|
||||
"sql": "CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)",
|
||||
"unique": false,
|
||||
@@ -2840,19 +2812,47 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 34,
|
||||
"id": 32,
|
||||
"references": [
|
||||
21
|
||||
19
|
||||
],
|
||||
"type": "index",
|
||||
"data": {
|
||||
"on": 21,
|
||||
"on": 19,
|
||||
"name": "idx_lat_lng",
|
||||
"sql": "CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)",
|
||||
"unique": false,
|
||||
"columns": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 33,
|
||||
"references": [
|
||||
19
|
||||
],
|
||||
"type": "index",
|
||||
"data": {
|
||||
"on": 19,
|
||||
"name": "idx_remote_exif_city",
|
||||
"sql": "CREATE INDEX IF NOT EXISTS idx_remote_exif_city\nON remote_exif_entity (city) WHERE city IS NOT NULL\n",
|
||||
"unique": false,
|
||||
"columns": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 34,
|
||||
"references": [
|
||||
20
|
||||
],
|
||||
"type": "index",
|
||||
"data": {
|
||||
"on": 20,
|
||||
"name": "idx_remote_album_asset_album_asset",
|
||||
"sql": "CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)",
|
||||
"unique": false,
|
||||
"columns": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 35,
|
||||
"references": [
|
||||
@@ -2861,20 +2861,6 @@
|
||||
"type": "index",
|
||||
"data": {
|
||||
"on": 22,
|
||||
"name": "idx_remote_album_asset_album_asset",
|
||||
"sql": "CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)",
|
||||
"unique": false,
|
||||
"columns": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 36,
|
||||
"references": [
|
||||
24
|
||||
],
|
||||
"type": "index",
|
||||
"data": {
|
||||
"on": 24,
|
||||
"name": "idx_remote_asset_cloud_id",
|
||||
"sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)",
|
||||
"unique": false,
|
||||
@@ -2882,13 +2868,13 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 37,
|
||||
"id": 36,
|
||||
"references": [
|
||||
27
|
||||
25
|
||||
],
|
||||
"type": "index",
|
||||
"data": {
|
||||
"on": 27,
|
||||
"on": 25,
|
||||
"name": "idx_person_owner_id",
|
||||
"sql": "CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)",
|
||||
"unique": false,
|
||||
@@ -2896,13 +2882,13 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 38,
|
||||
"id": 37,
|
||||
"references": [
|
||||
28
|
||||
26
|
||||
],
|
||||
"type": "index",
|
||||
"data": {
|
||||
"on": 28,
|
||||
"on": 26,
|
||||
"name": "idx_asset_face_person_id",
|
||||
"sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)",
|
||||
"unique": false,
|
||||
@@ -2910,13 +2896,13 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 39,
|
||||
"id": 38,
|
||||
"references": [
|
||||
28
|
||||
26
|
||||
],
|
||||
"type": "index",
|
||||
"data": {
|
||||
"on": 28,
|
||||
"on": 26,
|
||||
"name": "idx_asset_face_asset_id",
|
||||
"sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)",
|
||||
"unique": false,
|
||||
@@ -2924,13 +2910,27 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 40,
|
||||
"id": 39,
|
||||
"references": [
|
||||
30
|
||||
26
|
||||
],
|
||||
"type": "index",
|
||||
"data": {
|
||||
"on": 30,
|
||||
"on": 26,
|
||||
"name": "idx_asset_face_visible_person",
|
||||
"sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person\nON asset_face_entity (person_id, asset_id)\nWHERE is_visible = 1 AND deleted_at IS NULL\n",
|
||||
"unique": false,
|
||||
"columns": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 40,
|
||||
"references": [
|
||||
28
|
||||
],
|
||||
"type": "index",
|
||||
"data": {
|
||||
"on": 28,
|
||||
"name": "idx_trashed_local_asset_checksum",
|
||||
"sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)",
|
||||
"unique": false,
|
||||
@@ -2940,11 +2940,11 @@
|
||||
{
|
||||
"id": 41,
|
||||
"references": [
|
||||
30
|
||||
28
|
||||
],
|
||||
"type": "index",
|
||||
"data": {
|
||||
"on": 30,
|
||||
"on": 28,
|
||||
"name": "idx_trashed_local_asset_album",
|
||||
"sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)",
|
||||
"unique": false,
|
||||
@@ -2954,11 +2954,11 @@
|
||||
{
|
||||
"id": 42,
|
||||
"references": [
|
||||
31
|
||||
29
|
||||
],
|
||||
"type": "index",
|
||||
"data": {
|
||||
"on": 31,
|
||||
"on": 29,
|
||||
"name": "idx_asset_edit_asset_id",
|
||||
"sql": "CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)",
|
||||
"unique": false,
|
||||
@@ -3066,15 +3066,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "idx_remote_asset_owner_checksum",
|
||||
"sql": [
|
||||
{
|
||||
"dialect": "sqlite",
|
||||
"sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "UQ_remote_assets_owner_checksum",
|
||||
"sql": [
|
||||
@@ -3112,20 +3103,11 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "idx_remote_asset_local_date_time_day",
|
||||
"name": "idx_remote_asset_owner_visibility_deleted_created",
|
||||
"sql": [
|
||||
{
|
||||
"dialect": "sqlite",
|
||||
"sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_day ON remote_asset_entity (STRFTIME('%Y-%m-%d', local_date_time))"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "idx_remote_asset_local_date_time_month",
|
||||
"sql": [
|
||||
{
|
||||
"dialect": "sqlite",
|
||||
"sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_month ON remote_asset_entity (STRFTIME('%Y-%m', local_date_time))"
|
||||
"sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -3282,6 +3264,15 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "idx_remote_exif_city",
|
||||
"sql": [
|
||||
{
|
||||
"dialect": "sqlite",
|
||||
"sql": "CREATE INDEX IF NOT EXISTS idx_remote_exif_city ON remote_exif_entity (city) WHERE city IS NOT NULL"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "idx_remote_album_asset_album_asset",
|
||||
"sql": [
|
||||
@@ -3327,6 +3318,15 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "idx_asset_face_visible_person",
|
||||
"sql": [
|
||||
{
|
||||
"dialect": "sqlite",
|
||||
"sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person ON asset_face_entity (person_id, asset_id) WHERE is_visible = 1 AND deleted_at IS NULL"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "idx_trashed_local_asset_checksum",
|
||||
"sql": [
|
||||
|
||||
+3368
File diff suppressed because it is too large
Load Diff
@@ -1,58 +1,23 @@
|
||||
PODS:
|
||||
- background_downloader (0.0.1):
|
||||
- Flutter
|
||||
- bonsoir_darwin (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- connectivity_plus (0.0.1):
|
||||
- Flutter
|
||||
- cupertino_http (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- device_info_plus (0.0.1):
|
||||
- Flutter
|
||||
- Flutter (1.0.0)
|
||||
- flutter_local_notifications (0.0.1):
|
||||
- Flutter
|
||||
- flutter_native_splash (2.4.3):
|
||||
- Flutter
|
||||
- flutter_secure_storage (6.0.0):
|
||||
- Flutter
|
||||
- flutter_udid (0.0.1):
|
||||
- Flutter
|
||||
- KeychainAccess
|
||||
- flutter_web_auth_2 (5.0.0):
|
||||
- Flutter
|
||||
- fluttertoast (0.0.2):
|
||||
- Flutter
|
||||
- geolocator_apple (1.2.0):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- home_widget (0.0.1):
|
||||
- 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):
|
||||
- Flutter
|
||||
- network_info_plus (0.0.1):
|
||||
- Flutter
|
||||
- package_info_plus (0.4.5):
|
||||
- Flutter
|
||||
- permission_handler_apple (9.3.0):
|
||||
- Flutter
|
||||
- photo_manager (3.9.0):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- share_handler_ios (0.0.14):
|
||||
- Flutter
|
||||
- share_handler_ios/share_handler_ios_models (= 0.0.14)
|
||||
@@ -61,144 +26,56 @@ PODS:
|
||||
- Flutter
|
||||
- share_handler_ios_models
|
||||
- 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:
|
||||
- background_downloader (from `.symlinks/plugins/background_downloader/ios`)
|
||||
- bonsoir_darwin (from `.symlinks/plugins/bonsoir_darwin/darwin`)
|
||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||
- cupertino_http (from `.symlinks/plugins/cupertino_http/darwin`)
|
||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||
- Flutter (from `Flutter`)
|
||||
- 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_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`)
|
||||
- geolocator_apple (from `.symlinks/plugins/geolocator_apple/darwin`)
|
||||
- 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`)
|
||||
- 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`)
|
||||
- photo_manager (from `.symlinks/plugins/photo_manager/darwin`)
|
||||
- share_handler_ios (from `.symlinks/plugins/share_handler_ios/ios`)
|
||||
- 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:
|
||||
background_downloader:
|
||||
:path: ".symlinks/plugins/background_downloader/ios"
|
||||
bonsoir_darwin:
|
||||
:path: ".symlinks/plugins/bonsoir_darwin/darwin"
|
||||
connectivity_plus:
|
||||
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||
cupertino_http:
|
||||
:path: ".symlinks/plugins/cupertino_http/darwin"
|
||||
device_info_plus:
|
||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||
Flutter:
|
||||
:path: Flutter
|
||||
flutter_local_notifications:
|
||||
:path: ".symlinks/plugins/flutter_local_notifications/ios"
|
||||
flutter_native_splash:
|
||||
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
||||
flutter_secure_storage:
|
||||
: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:
|
||||
:path: ".symlinks/plugins/fluttertoast/ios"
|
||||
geolocator_apple:
|
||||
:path: ".symlinks/plugins/geolocator_apple/darwin"
|
||||
home_widget:
|
||||
: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:
|
||||
: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:
|
||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||
photo_manager:
|
||||
:path: ".symlinks/plugins/photo_manager/darwin"
|
||||
share_handler_ios:
|
||||
:path: ".symlinks/plugins/share_handler_ios/ios"
|
||||
share_handler_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:
|
||||
background_downloader: 50e91d979067b82081aba359d7d916b3ba5fadad
|
||||
bonsoir_darwin: 29c7ccf356646118844721f36e1de4b61f6cbd0e
|
||||
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
|
||||
cupertino_http: 94ac07f5ff090b8effa6c5e2c47871d48ab7c86c
|
||||
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
|
||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||
flutter_local_notifications: ad39620c743ea4c15127860f4b5641649a988100
|
||||
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
|
||||
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
|
||||
flutter_udid: 92a5d31fe0526b7b6002a2318df702e12e7eb300
|
||||
flutter_web_auth_2: 646fc9df97a01c59e5eea99b237da2c6360f8439
|
||||
fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1
|
||||
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
|
||||
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
|
||||
network_info_plus: cf61925ab5205dce05a4f0895989afdb6aade5fc
|
||||
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
||||
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
||||
photo_manager: 25fd77df14f4f0ba5ef99e2c61814dde77e2bceb
|
||||
share_handler_ios: e2244e990f826b2c8eaa291ac3831569438ba0fb
|
||||
share_handler_ios_models: fc638c9b4330dc7f082586c92aee9dfa0b87b871
|
||||
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
||||
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
|
||||
|
||||
PODFILE CHECKSUM: 938abbae4114b9c2140c550a2a0d8f7c674f5dfe
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
B21E34AC2E5B09190031FDB9 /* BackgroundWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B21E34AB2E5B09100031FDB9 /* BackgroundWorker.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 */; };
|
||||
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 */; };
|
||||
D218389C4A4C4693F141F7D1 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 886774DBDDE6B35BF2B4F2CD /* Pods_Runner.framework */; };
|
||||
F02538E92DFBCBDD008C3FA3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
@@ -37,6 +39,7 @@
|
||||
FEE084F82EC172460045228E /* SQLiteData in Frameworks */ = {isa = PBXBuildFile; productRef = FEE084F72EC172460045228E /* SQLiteData */; };
|
||||
FEE084FB2EC1725A0045228E /* RawStructuredFieldValues in Frameworks */ = {isa = PBXBuildFile; productRef = FEE084FA2EC1725A0045228E /* RawStructuredFieldValues */; };
|
||||
FEE084FD2EC1725A0045228E /* StructuredFieldValues in Frameworks */ = {isa = PBXBuildFile; productRef = FEE084FC2EC1725A0045228E /* StructuredFieldValues */; };
|
||||
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -105,6 +108,8 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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; };
|
||||
@@ -125,6 +130,7 @@
|
||||
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>"; };
|
||||
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 */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
@@ -189,6 +195,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
|
||||
FEE084F82EC172460045228E /* SQLiteData in Frameworks */,
|
||||
FEE084FB2EC1725A0045228E /* RawStructuredFieldValues in Frameworks */,
|
||||
FEE084FD2EC1725A0045228E /* StructuredFieldValues in Frameworks */,
|
||||
@@ -243,6 +250,7 @@
|
||||
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||
@@ -283,6 +291,7 @@
|
||||
B25D37792E72CA15008B6CA7 /* Connectivity */,
|
||||
B21E34A62E5AF9760031FDB9 /* Background */,
|
||||
B2CF7F8C2DDE4EBB00744BF6 /* Sync */,
|
||||
B2EE00052E72CA15008B6CA7 /* Permission */,
|
||||
FA9973382CF6DF4B000EF859 /* Runner.entitlements */,
|
||||
FAC7416727DB9F5500C668D8 /* RunnerProfile.entitlements */,
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||
@@ -317,6 +326,15 @@
|
||||
path = Connectivity;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B2EE00052E72CA15008B6CA7 /* Permission */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B2EE00032E72CA15008B6CA7 /* PermissionApiImpl.swift */,
|
||||
B2EE00012E72CA15008B6CA7 /* PermissionApi.g.swift */,
|
||||
);
|
||||
path = Permission;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FAC6F8B62D287F120078CB2F /* ShareExtension */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -346,6 +364,9 @@
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||
packageProductDependencies = (
|
||||
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
|
||||
);
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
@@ -449,6 +470,7 @@
|
||||
);
|
||||
mainGroup = 97C146E51CF9000F007C117D;
|
||||
packageReferences = (
|
||||
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
|
||||
FEE084F62EC172460045228E /* XCRemoteSwiftPackageReference "sqlite-data" */,
|
||||
FEE084F92EC1725A0045228E /* XCRemoteSwiftPackageReference "swift-http-structured-headers" */,
|
||||
);
|
||||
@@ -619,6 +641,8 @@
|
||||
FE5499F42F1197D8006016CB /* RemoteImages.g.swift in Sources */,
|
||||
FE5FE4AE2F30FBC000A71243 /* ImageProcessing.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 */,
|
||||
FEAFA8732E4D42F4001E47FE /* Thumbhash.swift in Sources */,
|
||||
B25D377C2E72CA26008B6CA7 /* ConnectivityApiImpl.swift in Sources */,
|
||||
@@ -718,6 +742,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CUSTOM_GROUP_ID = group.app.immich.share.profile;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
@@ -750,7 +775,6 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 240;
|
||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||
DEVELOPMENT_TEAM = 2W7AC6T8T5;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -801,6 +825,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CUSTOM_GROUP_ID = group.app.immich.share.debug;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
@@ -860,6 +885,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
@@ -894,7 +920,6 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 240;
|
||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||
DEVELOPMENT_TEAM = 2W7AC6T8T5;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -924,7 +949,6 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 240;
|
||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||
DEVELOPMENT_TEAM = 2W7AC6T8T5;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -1080,7 +1104,6 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 240;
|
||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||
DEVELOPMENT_TEAM = 2W7AC6T8T5;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
@@ -1124,7 +1147,6 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 240;
|
||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||
DEVELOPMENT_TEAM = 2W7AC6T8T5;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
@@ -1165,7 +1187,6 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 240;
|
||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||
DEVELOPMENT_TEAM = 2W7AC6T8T5;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
@@ -1272,7 +1293,17 @@
|
||||
package = FEE084F92EC1725A0045228E /* XCRemoteSwiftPackageReference "swift-http-structured-headers" */;
|
||||
productName = StructuredFieldValues;
|
||||
};
|
||||
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = FlutterGeneratedPluginSwiftPackage;
|
||||
};
|
||||
/* 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 */;
|
||||
}
|
||||
|
||||
+19
-2
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"originHash" : "9be33bfaa68721646604aefff3cabbdaf9a193da192aae024c265065671f6c49",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "combine-schedulers",
|
||||
@@ -19,6 +18,24 @@
|
||||
"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",
|
||||
"kind" : "remoteSourceControl",
|
||||
@@ -146,5 +163,5 @@
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 3
|
||||
"version" : 2
|
||||
}
|
||||
|
||||
@@ -5,6 +5,24 @@
|
||||
<BuildAction
|
||||
parallelizeBuildables = "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>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"originHash" : "9be33bfaa68721646604aefff3cabbdaf9a193da192aae024c265065671f6c49",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "combine-schedulers",
|
||||
@@ -19,6 +18,24 @@
|
||||
"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",
|
||||
"kind" : "remoteSourceControl",
|
||||
@@ -146,5 +163,5 @@
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 3
|
||||
"version" : 2
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import native_video_player
|
||||
|
||||
public static func registerPlugins(with registry: FlutterPluginRegistry, messenger: FlutterBinaryMessenger) {
|
||||
NativeSyncApiImpl.register(with: registry.registrar(forPlugin: NativeSyncApiImpl.name)!)
|
||||
PermissionApiSetup.setUp(binaryMessenger: messenger, api: PermissionApiImpl())
|
||||
LocalImageApiSetup.setUp(binaryMessenger: messenger, api: LocalImageApiImpl())
|
||||
RemoteImageApiSetup.setUp(binaryMessenger: messenger, api: RemoteImageApiImpl())
|
||||
BackgroundWorkerFgHostApiSetup.setUp(binaryMessenger: messenger, api: BackgroundWorkerApiImpl())
|
||||
|
||||
+3
-3
@@ -348,7 +348,7 @@ class BackgroundWorkerBgHostApiSetup {
|
||||
/// Generated protocol from Pigeon that represents Flutter messages that can be called from Swift.
|
||||
protocol BackgroundWorkerFlutterApiProtocol {
|
||||
func onIosUpload(isRefresh isRefreshArg: Bool, maxSeconds maxSecondsArg: Int64?, completion: @escaping (Result<Void, PigeonError>) -> Void)
|
||||
func onAndroidUpload(completion: @escaping (Result<Void, PigeonError>) -> Void)
|
||||
func onAndroidUpload(maxMinutes maxMinutesArg: Int64?, completion: @escaping (Result<Void, PigeonError>) -> Void)
|
||||
func cancel(completion: @escaping (Result<Void, PigeonError>) -> Void)
|
||||
}
|
||||
class BackgroundWorkerFlutterApi: BackgroundWorkerFlutterApiProtocol {
|
||||
@@ -379,10 +379,10 @@ class BackgroundWorkerFlutterApi: BackgroundWorkerFlutterApiProtocol {
|
||||
}
|
||||
}
|
||||
}
|
||||
func onAndroidUpload(completion: @escaping (Result<Void, PigeonError>) -> Void) {
|
||||
func onAndroidUpload(maxMinutes maxMinutesArg: Int64?, completion: @escaping (Result<Void, PigeonError>) -> Void) {
|
||||
let channelName: String = "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFlutterApi.onAndroidUpload\(messageChannelSuffix)"
|
||||
let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec)
|
||||
channel.sendMessage(nil) { response in
|
||||
channel.sendMessage([maxMinutesArg] as [Any?]) { response in
|
||||
guard let listResponse = response as? [Any?] else {
|
||||
completion(.failure(createConnectionError(withChannelName: channelName)))
|
||||
return
|
||||
|
||||
Generated
+14
@@ -288,6 +288,7 @@ protocol NetworkApi {
|
||||
func hasCertificate() throws -> Bool
|
||||
func getClientPointer() throws -> Int64
|
||||
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`.
|
||||
@@ -388,5 +389,18 @@ class NetworkApiSetup {
|
||||
} else {
|
||||
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,6 +61,10 @@ class NetworkApiImpl: NetworkApi {
|
||||
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 {
|
||||
URLSessionManager.setServerUrls(serverUrls)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import native_video_player
|
||||
let CLIENT_CERT_LABEL = "app.alextran.immich.client_identity"
|
||||
let HEADERS_KEY = "immich.request_headers"
|
||||
let SERVER_URLS_KEY = "immich.server_urls"
|
||||
let APP_GROUP = "group.app.immich.share"
|
||||
let APP_GROUP = Bundle.main.object(forInfoDictionaryKey: "AppGroupId") as! String
|
||||
let COOKIE_EXPIRY_DAYS: TimeInterval = 400
|
||||
|
||||
enum AuthCookie: CaseIterable {
|
||||
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
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/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.app.immich.share</string>
|
||||
<string>$(CUSTOM_GROUP_ID)</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user