Compare commits

..

1 Commits

Author SHA1 Message Date
Thomas Way c087b7c063 chore(mobile): replace maplibre_gl with maplibre
maplibre is a ground-up rewrite of maplibre_gl with a more modern and
ergonomic API. It should fix a few bugs we've seen with maps, and
perform better.
2026-02-21 01:17:06 +00:00
746 changed files with 18505 additions and 34553 deletions
+2 -2
View File
@@ -1,7 +1,7 @@
{
"scripts": {
"format": "prettier --cache --check .",
"format:fix": "prettier --cache --write --list-different ."
"format": "prettier --check .",
"format:fix": "prettier --write ."
},
"devDependencies": {
"prettier": "^3.7.4"
+3 -2
View File
@@ -19,12 +19,13 @@ jobs:
contents: read
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- name: Check for breaking API changes
uses: oasdiff/oasdiff-action/breaking@65fef71494258f00f911d7a71edb0482c5378899 # v0.0.30
# sha is pinning to a commit instead of a tag since the action does not tag versions
uses: oasdiff/oasdiff-action/breaking@ccb863950ce437a50f8f1a40d2a1112117e06ce4
with:
base: https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json
revision: open-api/immich-openapi-specs.json
+3 -3
View File
@@ -57,7 +57,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
uses: github/codeql-action/init@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
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@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
uses: github/codeql-action/autobuild@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
# ️ 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@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
uses: github/codeql-action/analyze@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
with:
category: '/language:${{matrix.language}}'
+1 -1
View File
@@ -511,7 +511,7 @@ jobs:
run: pnpm install --frozen-lockfile
if: ${{ !cancelled() }}
- name: Install Playwright Browsers
run: pnpm exec playwright install chromium --only-shell
run: npx playwright install chromium --only-shell
if: ${{ !cancelled() }}
- name: Docker build
run: docker compose up -d --build --renew-anon-volumes --force-recreate --remove-orphans --wait --wait-timeout 300
+1 -8
View File
@@ -5,13 +5,6 @@
"dbaeumer.vscode-eslint",
"dart-code.flutter",
"dart-code.dart-code",
"dcmdev.dcm-vscode-extension",
"bradlc.vscode-tailwindcss",
"ms-playwright.playwright",
"vitest.explorer",
"editorconfig.editorconfig",
"foxundermoon.shell-format",
"timonwong.shellcheck",
"bluebrown.yamlfmt"
"dcmdev.dcm-vscode-extension"
]
}
+1 -33
View File
@@ -15,7 +15,6 @@
},
"[javascript]": {
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports": "explicit",
"source.removeUnusedImports": "explicit"
},
@@ -35,7 +34,6 @@
},
"[svelte]": {
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports": "explicit",
"source.removeUnusedImports": "explicit"
},
@@ -45,7 +43,6 @@
},
"[typescript]": {
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports": "explicit",
"source.removeUnusedImports": "explicit"
},
@@ -54,43 +51,14 @@
"editor.tabSize": 2
},
"cSpell.words": ["immich"],
"css.lint.unknownAtRules": "ignore",
"editor.bracketPairColorization.enabled": true,
"editor.formatOnSave": true,
"editor.guides.bracketPairs": "active",
"eslint.useFlatConfig": true,
"eslint.validate": ["javascript", "typescript", "svelte"],
"eslint.workingDirectories": [
{ "directory": "cli", "changeProcessCWD": true },
{ "directory": "e2e", "changeProcessCWD": true },
{ "directory": "server", "changeProcessCWD": true },
{ "directory": "web", "changeProcessCWD": true }
],
"files.watcherExclude": {
"**/.jj/**": true,
"**/.git/**": true,
"**/node_modules/**": true,
"**/build/**": true,
"**/dist/**": true,
"**/.svelte-kit/**": true
},
"explorer.fileNesting.enabled": true,
"explorer.fileNesting.patterns": {
"*.dart": "${capture}.g.dart,${capture}.gr.dart,${capture}.drift.dart",
"*.ts": "${capture}.spec.ts,${capture}.mock.ts",
"package.json": "package-lock.json, yarn.lock, pnpm-lock.yaml, bun.lockb, bun.lock, pnpm-workspace.yaml, .pnpmfile.cjs"
},
"search.exclude": {
"**/node_modules": true,
"**/build": true,
"**/dist": true,
"**/.svelte-kit": true,
"**/open-api/typescript-sdk/src": true
},
"svelte.enable-ts-plugin": true,
"tailwindCSS.experimental.configFile": {
"web/src/app.css": "web/src/**"
},
"typescript.preferences.importModuleSpecifier": "non-relative",
"vitest.maximumConfigs": 10
"typescript.preferences.importModuleSpecifier": "non-relative"
}
-2
View File
@@ -15,8 +15,6 @@ Please try to keep pull requests as focused as possible. A PR should do exactly
If you are looking for something to work on, there are discussions and issues with a `good-first-issue` label on them. These are always a good starting point. If none of them sound interesting or fit your skill set, feel free to reach out on our Discord. We're happy to help you find something to work on!
We usually do not assign issues to new contributors, since it happens often that a PR is never even opened. Again, reach out on Discord if you fear putting a lot of time into fixing an issue, but ending up with a duplicate PR.
## Use of generative AI
We ask you not to open PRs generated with an LLM. We find that code generated like this tends to need a large amount of back-and-forth, which is a very inefficient use of our time. If we want LLM-generated code, it's much faster for us to use an LLM ourselves than to go through an intermediary via a pull request.
+1 -1
View File
@@ -52,7 +52,7 @@ attach-server:
docker exec -it docker_immich-server_1 sh
renovate:
LOG_LEVEL=debug pnpm exec renovate --platform=local --repository-cache=reset
LOG_LEVEL=debug npx renovate --platform=local --repository-cache=reset
# Directories that need to be created for volumes or build output
VOLUME_DIRS = \
+11 -11
View File
@@ -13,23 +13,23 @@
"cli"
],
"devDependencies": {
"@eslint/js": "^10.0.0",
"@eslint/js": "^9.8.0",
"@immich/sdk": "workspace:*",
"@types/byte-size": "^8.1.0",
"@types/cli-progress": "^3.11.0",
"@types/lodash-es": "^4.17.12",
"@types/micromatch": "^4.0.9",
"@types/mock-fs": "^4.13.1",
"@types/node": "^24.10.14",
"@vitest/coverage-v8": "^4.0.0",
"@types/node": "^24.10.13",
"@vitest/coverage-v8": "^3.0.0",
"byte-size": "^9.0.0",
"cli-progress": "^3.12.0",
"commander": "^12.0.0",
"eslint": "^10.0.0",
"eslint": "^9.14.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^63.0.0",
"globals": "^17.0.0",
"eslint-plugin-unicorn": "^62.0.0",
"globals": "^16.0.0",
"mock-fs": "^5.2.0",
"prettier": "^3.7.4",
"prettier-plugin-organize-imports": "^4.0.0",
@@ -37,7 +37,7 @@
"typescript-eslint": "^8.28.0",
"vite": "^7.0.0",
"vite-tsconfig-paths": "^6.0.0",
"vitest": "^4.0.0",
"vitest": "^3.0.0",
"vitest-fetch-mock": "^0.4.0",
"yaml": "^2.3.1"
},
@@ -45,12 +45,12 @@
"build": "vite build",
"build:dev": "vite build --sourcemap true",
"lint": "eslint \"src/**/*.ts\" --max-warnings 0",
"lint:fix": "pnpm run lint --fix",
"prepack": "pnpm run build",
"lint:fix": "npm run lint -- --fix",
"prepack": "npm run build",
"test": "vitest",
"test:cov": "vitest --coverage",
"format": "prettier --cache --check .",
"format:fix": "prettier --cache --write --list-different .",
"format": "prettier --check .",
"format:fix": "prettier --write .",
"check": "tsc --noEmit"
},
"repository": {
+36 -45
View File
@@ -1,6 +1,6 @@
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { setTimeout as sleep } from 'node:timers/promises';
import { describe, expect, it, MockedFunction, vi } from 'vitest';
@@ -58,7 +58,7 @@ describe('uploadFiles', () => {
});
it('returns new assets when upload file is successful', async () => {
fetchMocker.doMockIf(new RegExp(`${baseUrl}/assets$`), function () {
fetchMocker.doMockIf(new RegExp(`${baseUrl}/assets$`), () => {
return {
status: 200,
body: JSON.stringify({ id: 'fc5621b1-86f6-44a1-9905-403e607df9f5', status: 'created' }),
@@ -75,7 +75,7 @@ describe('uploadFiles', () => {
it('returns new assets when upload file retry is successful', async () => {
let counter = 0;
fetchMocker.doMockIf(new RegExp(`${baseUrl}/assets$`), function () {
fetchMocker.doMockIf(new RegExp(`${baseUrl}/assets$`), () => {
counter++;
if (counter < retry) {
throw new Error('Network error');
@@ -96,7 +96,7 @@ describe('uploadFiles', () => {
});
it('returns new assets when upload file retry is failed', async () => {
fetchMocker.doMockIf(new RegExp(`${baseUrl}/assets$`), function () {
fetchMocker.doMockIf(new RegExp(`${baseUrl}/assets$`), () => {
throw new Error('Network error');
});
@@ -236,19 +236,16 @@ describe('startWatch', () => {
await sleep(100); // to debounce the watcher from considering the test file as a existing file
await fs.promises.writeFile(testFilePath, 'testjpg');
await vi.waitFor(
() =>
expect(checkBulkUpload).toHaveBeenCalledWith({
assetBulkUploadCheckDto: {
assets: [
expect.objectContaining({
id: testFilePath,
}),
],
},
}),
{ timeout: 5000 },
);
await vi.waitUntil(() => checkBulkUploadMocked.mock.calls.length > 0, 3000);
expect(checkBulkUpload).toHaveBeenCalledWith({
assetBulkUploadCheckDto: {
assets: [
expect.objectContaining({
id: testFilePath,
}),
],
},
});
});
it('should filter out unsupported files', async () => {
@@ -260,19 +257,16 @@ describe('startWatch', () => {
await fs.promises.writeFile(testFilePath, 'testjpg');
await fs.promises.writeFile(unsupportedFilePath, 'testtxt');
await vi.waitFor(
() =>
expect(checkBulkUpload).toHaveBeenCalledWith({
assetBulkUploadCheckDto: {
assets: expect.arrayContaining([
expect.objectContaining({
id: testFilePath,
}),
]),
},
}),
{ timeout: 5000 },
);
await vi.waitUntil(() => checkBulkUploadMocked.mock.calls.length > 0, 3000);
expect(checkBulkUpload).toHaveBeenCalledWith({
assetBulkUploadCheckDto: {
assets: expect.arrayContaining([
expect.objectContaining({
id: testFilePath,
}),
]),
},
});
expect(checkBulkUpload).not.toHaveBeenCalledWith({
assetBulkUploadCheckDto: {
@@ -297,19 +291,16 @@ describe('startWatch', () => {
await fs.promises.writeFile(testFilePath, 'testjpg');
await fs.promises.writeFile(ignoredFilePath, 'ignoredjpg');
await vi.waitFor(
() =>
expect(checkBulkUpload).toHaveBeenCalledWith({
assetBulkUploadCheckDto: {
assets: expect.arrayContaining([
expect.objectContaining({
id: testFilePath,
}),
]),
},
}),
{ timeout: 5000 },
);
await vi.waitUntil(() => checkBulkUploadMocked.mock.calls.length > 0, 3000);
expect(checkBulkUpload).toHaveBeenCalledWith({
assetBulkUploadCheckDto: {
assets: expect.arrayContaining([
expect.objectContaining({
id: testFilePath,
}),
]),
},
});
expect(checkBulkUpload).not.toHaveBeenCalledWith({
assetBulkUploadCheckDto: {
+2 -6
View File
@@ -1,4 +1,4 @@
import { defineConfig, UserConfig } from 'vite';
import { defineConfig } from 'vite';
import tsconfigPaths from 'vite-tsconfig-paths';
export default defineConfig({
@@ -17,8 +17,4 @@ export default defineConfig({
noExternal: /^(?!node:).*$/,
},
plugins: [tsconfigPaths()],
test: {
name: 'cli:unit',
globals: true,
},
} as UserConfig);
});
+7
View File
@@ -0,0 +1,7 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
},
});
+2 -2
View File
@@ -1,6 +1,6 @@
[tools]
terragrunt = "0.99.4"
opentofu = "1.11.5"
terragrunt = "0.98.0"
opentofu = "1.11.4"
[tasks."tg:fmt"]
run = "terragrunt hclfmt"
+1 -1
View File
@@ -155,7 +155,7 @@ services:
redis:
container_name: immich_redis
image: docker.io/valkey/valkey:9@sha256:2bce660b767cb62c8c0ea020e94a230093be63dbd6af4f21b044960517a5842d
image: docker.io/valkey/valkey:9@sha256:930b41430fb727f533c5982fe509b6f04233e26d0f7354e04de4b0d5c706e44e
healthcheck:
test: redis-cli ping || exit 1
+2 -2
View File
@@ -56,7 +56,7 @@ services:
redis:
container_name: immich_redis
image: docker.io/valkey/valkey:9@sha256:2bce660b767cb62c8c0ea020e94a230093be63dbd6af4f21b044960517a5842d
image: docker.io/valkey/valkey:9@sha256:930b41430fb727f533c5982fe509b6f04233e26d0f7354e04de4b0d5c706e44e
healthcheck:
test: redis-cli ping || exit 1
restart: always
@@ -85,7 +85,7 @@ services:
container_name: immich_prometheus
ports:
- 9090:9090
image: prom/prometheus@sha256:4a61322ac1103a0e3aea2a61ef1718422a48fa046441f299d71e660a3bc71ae9
image: prom/prometheus@sha256:1f0f50f06acaceb0f5670d2c8a658a599affe7b0d8e78b898c1035653849a702
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus
+1 -1
View File
@@ -61,7 +61,7 @@ services:
redis:
container_name: immich_redis
image: docker.io/valkey/valkey:9@sha256:2bce660b767cb62c8c0ea020e94a230093be63dbd6af4f21b044960517a5842d
image: docker.io/valkey/valkey:9@sha256:930b41430fb727f533c5982fe509b6f04233e26d0f7354e04de4b0d5c706e44e
user: '1000:1000'
security_opt:
- no-new-privileges:true
+1 -1
View File
@@ -49,7 +49,7 @@ services:
redis:
container_name: immich_redis
image: docker.io/valkey/valkey:9@sha256:2bce660b767cb62c8c0ea020e94a230093be63dbd6af4f21b044960517a5842d
image: docker.io/valkey/valkey:9@sha256:930b41430fb727f533c5982fe509b6f04233e26d0f7354e04de4b0d5c706e44e
healthcheck:
test: redis-cli ping || exit 1
restart: always
+3 -4
View File
@@ -67,8 +67,7 @@ graph TD
C --> D["Thumbnail Generation (Large, small, blurred and person)"]
D --> E[Smart Search]
D --> F[Face Detection]
D --> G[OCR]
D --> H[Video Transcoding]
E --> I[Duplicate Detection]
F --> J[Facial Recognition]
D --> G[Video Transcoding]
E --> H[Duplicate Detection]
F --> I[Facial Recognition]
```
+2 -2
View File
@@ -24,7 +24,7 @@ Immich has three main clients:
3. CLI - Command-line utility for bulk upload
:::info
All three clients use [OpenAPI](/api.md) to auto-generate rest clients for easy integration. For more information about this process, see [OpenAPI](/api.md).
All three clients use [OpenAPI](./open-api.md) to auto-generate rest clients for easy integration. For more information about this process, see [OpenAPI](./open-api.md).
:::
### Mobile App
@@ -71,7 +71,7 @@ An incoming HTTP request is mapped to a controller (`src/controllers`). Controll
### Domain Transfer Objects (DTOs)
The server uses [Domain Transfer Objects](https://en.wikipedia.org/wiki/Data_transfer_object) as public interfaces for the inputs (query, params, and body) and outputs (response) for each endpoint. DTOs translate to [OpenAPI](/api.md) schemas and control the generated code used by each client.
The server uses [Domain Transfer Objects](https://en.wikipedia.org/wiki/Data_transfer_object) as public interfaces for the inputs (query, params, and body) and outputs (response) for each endpoint. DTOs translate to [OpenAPI](./open-api.md) schemas and control the generated code used by each client.
### Background Jobs
@@ -1,4 +1,4 @@
# API
# OpenAPI
Immich uses the [OpenAPI](https://swagger.io/specification/) standard to generate API documentation. To view the published docs see [here](https://api.immich.app/).
+1 -1
View File
@@ -53,7 +53,7 @@ You can use `dart fix --apply` and `dcm fix lib` to potentially correct some iss
## OpenAPI
The OpenAPI client libraries need to be regenerated whenever there are changes to the `immich-openapi-specs.json` file. Note that you should not modify this file directly as it is auto-generated. See [OpenAPI](/api.md) for more details.
The OpenAPI client libraries need to be regenerated whenever there are changes to the `immich-openapi-specs.json` file. Note that you should not modify this file directly as it is auto-generated. See [OpenAPI](/developer/open-api.md) for more details.
## Database Migrations
-4
View File
@@ -80,10 +80,6 @@ There is an automatic scan job that is scheduled to run once a day. Its schedule
This job also cleans up any libraries stuck in deletion. It is possible to trigger the cleanup by clicking "Scan all libraries" in the library management page.
### Deleting a Library
When deleting an external library, all assets inside are immediately deleted along with the library. Note that while a library can take a long time to fully delete in the background, it is immediately removed from the library list. If the deletion process is interrupted (for example, due to server restart), it will be cleaned up in the next nightly cron job. The cleanup process can also be manually initiated by clicking the "Scan All Libraries" button in the library list.
## Usage
Let's show a concrete example where we add an existing gallery to Immich. Here, we have the following folders we want to add:
@@ -50,7 +50,6 @@ You do not need to redo any machine learning jobs after enabling hardware accele
- The GPU must be supported by ROCm. If it isn't officially supported, you can attempt to use the `HSA_OVERRIDE_GFX_VERSION` environmental variable: `HSA_OVERRIDE_GFX_VERSION=<a supported version, e.g. 10.3.0>`. If this doesn't work, you might need to also set `HSA_USE_SVM=0`.
- The ROCm image is quite large and requires at least 35GiB of free disk space. However, pulling later updates to the service through Docker will generally only amount to a few hundred megabytes as the rest will be cached.
- This backend is new and may experience some issues. For example, GPU power consumption can be higher than usual after running inference, even if the machine learning service is idle. In this case, it will only go back to normal after being idle for 5 minutes (configurable with the [MACHINE_LEARNING_MODEL_TTL](/install/environment-variables) setting).
- MIGraphX is a new backend for AMD cards, which compiles models at runtime. As such, the first few inferences will be slow.
#### OpenVINO
+48 -70
View File
@@ -6,7 +6,7 @@ const prism = require('prism-react-renderer');
/** @type {import('@docusaurus/types').Config} */
const config = {
title: 'Immich',
tagline: 'Self-hosted photo and video management solution',
tagline: 'High performance self-hosted photo and video backup solution directly from your mobile phone',
url: 'https://docs.immich.app',
baseUrl: '/',
onBrokenLinks: 'throw',
@@ -93,15 +93,35 @@ const config = {
position: 'right',
},
{
href: 'https://immich.app/',
to: '/overview/quick-start',
position: 'right',
label: 'Home',
label: 'Docs',
},
{
href: 'https://immich.app/roadmap',
position: 'right',
label: 'Roadmap',
},
{
href: 'https://api.immich.app/',
position: 'right',
label: 'API',
},
{
href: 'https://immich.store',
position: 'right',
label: 'Merch',
},
{
href: 'https://github.com/immich-app/immich',
label: 'GitHub',
position: 'right',
},
{
href: 'https://discord.immich.app',
label: 'Discord',
position: 'right',
},
{
type: 'html',
position: 'right',
@@ -114,78 +134,19 @@ const config = {
style: 'light',
links: [
{
title: 'Download',
title: 'Overview',
items: [
{
label: 'Android',
href: 'https://get.immich.app/android',
label: 'Quick start',
to: '/overview/quick-start',
},
{
label: 'iOS',
href: 'https://get.immich.app/ios',
label: 'Installation',
to: '/install/requirements',
},
{
label: 'Server',
href: 'https://immich.app/download',
},
],
},
{
title: 'Company',
items: [
{
label: 'FUTO',
href: 'https://futo.tech/',
},
{
label: 'Purchase',
href: 'https://buy.immich.app/',
},
{
label: 'Merch',
href: 'https://immich.store/',
},
],
},
{
title: 'Sites',
items: [
{
label: 'Home',
href: 'https://immich.app',
},
{
label: 'My Immich',
href: 'https://my.immich.app/',
},
{
label: 'Awesome Immich',
href: 'https://awesome.immich.app/',
},
{
label: 'Immich API',
href: 'https://api.immich.app/',
},
{
label: 'Immich Data',
href: 'https://data.immich.app/',
},
{
label: 'Immich Datasets',
href: 'https://datasets.immich.app/',
},
],
},
{
title: 'Miscellaneous',
items: [
{
label: 'Roadmap',
href: 'https://immich.app/roadmap',
},
{
label: 'Cursed Knowledge',
href: 'https://immich.app/cursed-knowledge',
label: 'Contributing',
to: '/overview/support-the-project',
},
{
label: 'Privacy Policy',
@@ -194,7 +155,24 @@ const config = {
],
},
{
title: 'Social',
title: 'Documentation',
items: [
{
label: 'Roadmap',
href: 'https://immich.app/roadmap',
},
{
label: 'API',
href: 'https://api.immich.app/',
},
{
label: 'Cursed Knowledge',
href: 'https://immich.app/cursed-knowledge',
},
],
},
{
title: 'Links',
items: [
{
label: 'GitHub',
+3 -3
View File
@@ -4,11 +4,11 @@
"private": true,
"scripts": {
"docusaurus": "docusaurus",
"format": "prettier --cache --check .",
"format:fix": "prettier --cache --write --list-different .",
"format": "prettier --check .",
"format:fix": "prettier --write .",
"start": "docusaurus start --port 3005",
"copy:openapi": "jq -c < ../open-api/immich-openapi-specs.json > ./static/openapi.json || exit 0",
"build": "pnpm run copy:openapi && docusaurus build",
"build": "npm run copy:openapi && docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
-1
View File
@@ -23,7 +23,6 @@
/features/storage-template /administration/storage-template 307
/features/user-management /administration/user-management 307
/developer/contributing /developer/pr-checklist 307
/developer/open-api /api 307
/guides/machine-learning /guides/remote-machine-learning 307
/administration/password-login /administration/system-settings 307
/features/search /features/searching 307
+1 -1
View File
@@ -44,7 +44,7 @@ services:
redis:
container_name: immich-e2e-redis
image: docker.io/valkey/valkey:9@sha256:2bce660b767cb62c8c0ea020e94a230093be63dbd6af4f21b044960517a5842d
image: docker.io/valkey/valkey:9@sha256:930b41430fb727f533c5982fe509b6f04233e26d0f7354e04de4b0d5c706e44e
healthcheck:
test: redis-cli ping || exit 1
+17 -18
View File
@@ -8,41 +8,41 @@
"test": "vitest --run",
"test:watch": "vitest",
"test:maintenance": "vitest --run --config vitest.maintenance.config.ts",
"test:web": "pnpm exec playwright test --project=web",
"test:web:maintenance": "pnpm exec playwright test --project=maintenance",
"test:web:ui": "pnpm exec playwright test --project=ui",
"start:web": "pnpm exec playwright test --ui --project=web",
"start:web:maintenance": "pnpm exec playwright test --ui --project=maintenance",
"start:web:ui": "pnpm exec playwright test --ui --project=ui",
"format": "prettier --cache --check .",
"format:fix": "prettier --cache --write --list-different .",
"test:web": "npx playwright test --project=web",
"test:web:maintenance": "npx playwright test --project=maintenance",
"test:web:ui": "npx playwright test --project=ui",
"start:web": "npx playwright test --ui --project=web",
"start:web:maintenance": "npx playwright test --ui --project=maintenance",
"start:web:ui": "npx playwright test --ui --project=ui",
"format": "prettier --check .",
"format:fix": "prettier --write .",
"lint": "eslint \"src/**/*.ts\" --max-warnings 0",
"lint:fix": "pnpm run lint --fix",
"lint:fix": "npm run lint -- --fix",
"check": "tsc --noEmit"
},
"keywords": [],
"author": "",
"license": "GNU Affero General Public License version 3",
"devDependencies": {
"@eslint/js": "^10.0.0",
"@eslint/js": "^9.8.0",
"@faker-js/faker": "^10.1.0",
"@immich/cli": "workspace:*",
"@immich/e2e-auth-server": "workspace:*",
"@immich/e2e-auth-server": "workspace:*",
"@immich/sdk": "workspace:*",
"@playwright/test": "^1.44.1",
"@socket.io/component-emitter": "^3.1.2",
"@types/luxon": "^3.4.2",
"@types/node": "^24.10.14",
"@types/node": "^24.10.13",
"@types/pg": "^8.15.1",
"@types/pngjs": "^6.0.4",
"@types/supertest": "^6.0.2",
"dotenv": "^17.2.3",
"eslint": "^10.0.0",
"eslint": "^9.14.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^63.0.0",
"exiftool-vendored": "^35.0.0",
"globals": "^17.0.0",
"eslint-plugin-unicorn": "^62.0.0",
"exiftool-vendored": "^34.3.0",
"globals": "^16.0.0",
"luxon": "^3.4.4",
"pg": "^8.11.3",
"pngjs": "^7.0.0",
@@ -54,8 +54,7 @@
"typescript": "^5.3.3",
"typescript-eslint": "^8.28.0",
"utimes": "^5.2.1",
"vite-tsconfig-paths": "^6.1.1",
"vitest": "^4.0.0"
"vitest": "^3.0.0"
},
"volta": {
"node": "24.13.1"
@@ -0,0 +1,66 @@
import { AssetMediaResponseDto, LoginResponseDto } from '@immich/sdk';
import { expect, Page, test } from '@playwright/test';
import { utils } from 'src/utils';
async function ensureDetailPanelVisible(page: Page) {
await page.waitForSelector('#immich-asset-viewer');
const isVisible = await page.locator('#detail-panel').isVisible();
if (!isVisible) {
await page.keyboard.press('i');
await page.waitForSelector('#detail-panel');
}
}
test.describe('Asset Viewer stack', () => {
let admin: LoginResponseDto;
let assetOne: AssetMediaResponseDto;
let assetTwo: AssetMediaResponseDto;
test.beforeAll(async () => {
utils.initSdk();
await utils.resetDatabase();
admin = await utils.adminSetup();
await utils.updateMyPreferences(admin.accessToken, { tags: { enabled: true } });
assetOne = await utils.createAsset(admin.accessToken);
assetTwo = await utils.createAsset(admin.accessToken);
await utils.createStack(admin.accessToken, [assetOne.id, assetTwo.id]);
const tags = await utils.upsertTags(admin.accessToken, ['test/1', 'test/2']);
const tagOne = tags.find((tag) => tag.value === 'test/1')!;
const tagTwo = tags.find((tag) => tag.value === 'test/2')!;
await utils.tagAssets(admin.accessToken, tagOne.id, [assetOne.id]);
await utils.tagAssets(admin.accessToken, tagTwo.id, [assetTwo.id]);
});
test('stack slideshow is visible', async ({ page, context }) => {
await utils.setAuthCookies(context, admin.accessToken);
await page.goto(`/photos/${assetOne.id}`);
const stackAssets = page.locator('#stack-slideshow [data-asset]');
await expect(stackAssets.first()).toBeVisible();
await expect(stackAssets.nth(1)).toBeVisible();
});
test('tags of primary asset are visible', async ({ page, context }) => {
await utils.setAuthCookies(context, admin.accessToken);
await page.goto(`/photos/${assetOne.id}`);
await ensureDetailPanelVisible(page);
const tags = page.getByTestId('detail-panel-tags').getByRole('link');
await expect(tags.first()).toHaveText('test/1');
});
test('tags of second asset are visible', async ({ page, context }) => {
await utils.setAuthCookies(context, admin.accessToken);
await page.goto(`/photos/${assetOne.id}`);
await ensureDetailPanelVisible(page);
const stackAssets = page.locator('#stack-slideshow [data-asset]');
await stackAssets.nth(1).click();
const tags = page.getByTestId('detail-panel-tags').getByRole('link');
await expect(tags.first()).toHaveText('test/2');
});
});
+19 -27
View File
@@ -1,7 +1,10 @@
import { AssetMediaResponseDto, LoginResponseDto } from '@immich/sdk';
import { expect, test } from '@playwright/test';
import { Page, expect, test } from '@playwright/test';
import { utils } from 'src/utils';
function imageLocator(page: Page) {
return page.getByAltText('Image taken').locator('visible=true');
}
test.describe('Photo Viewer', () => {
let admin: LoginResponseDto;
let asset: AssetMediaResponseDto;
@@ -23,42 +26,31 @@ test.describe('Photo Viewer', () => {
test('loads original photo when zoomed', async ({ page }) => {
await page.goto(`/photos/${asset.id}`);
const thumbnail = page.getByTestId('thumbnail').filter({ visible: true });
const original = page.getByTestId('original').filter({ visible: true });
await expect(thumbnail).toHaveAttribute('src', /thumbnail/);
const { width, height } = page.viewportSize()!;
await page.mouse.move(width / 2, height / 2);
await expect.poll(async () => await imageLocator(page).getAttribute('src')).toContain('thumbnail');
const box = await imageLocator(page).boundingBox();
expect(box).toBeTruthy();
const { x, y, width, height } = box!;
await page.mouse.move(x + width / 2, y + height / 2);
await page.mouse.wheel(0, -1);
await expect(original).toBeInViewport();
await expect(original).toHaveAttribute('src', /original/);
await expect.poll(async () => await imageLocator(page).getAttribute('src')).toContain('original');
});
test('loads fullsize image when zoomed and original is web-incompatible', async ({ page }) => {
await page.goto(`/photos/${rawAsset.id}`);
const thumbnail = page.getByTestId('thumbnail').filter({ visible: true });
const original = page.getByTestId('original').filter({ visible: true });
await expect(thumbnail).toHaveAttribute('src', /thumbnail/);
const { width, height } = page.viewportSize()!;
await page.mouse.move(width / 2, height / 2);
await expect.poll(async () => await imageLocator(page).getAttribute('src')).toContain('thumbnail');
const box = await imageLocator(page).boundingBox();
expect(box).toBeTruthy();
const { x, y, width, height } = box!;
await page.mouse.move(x + width / 2, y + height / 2);
await page.mouse.wheel(0, -1);
await expect(original).toHaveAttribute('src', /fullsize/);
await expect.poll(async () => await imageLocator(page).getAttribute('src')).toContain('fullsize');
});
test('reloads photo when checksum changes', async ({ page }) => {
await page.goto(`/photos/${asset.id}`);
const thumbnail = page.getByTestId('thumbnail').filter({ visible: true });
const preview = page.getByTestId('preview').filter({ visible: true });
await expect(thumbnail).toHaveAttribute('src', /thumbnail/);
const initialSrc = await thumbnail.getAttribute('src');
await expect.poll(async () => await imageLocator(page).getAttribute('src')).toContain('thumbnail');
const initialSrc = await imageLocator(page).getAttribute('src');
await utils.replaceAsset(admin.accessToken, asset.id);
await expect(preview).not.toHaveAttribute('src', initialSrc!);
await expect.poll(async () => await imageLocator(page).getAttribute('src')).not.toBe(initialSrc);
});
});
+2 -1
View File
@@ -45,7 +45,8 @@ test.describe('Shared Links', () => {
await page.goto(`/share/${sharedLink.key}`);
await page.getByRole('heading', { name: 'Test Album' }).waitFor();
await page.locator(`[data-asset-id="${asset.id}"]`).hover();
await page.waitForSelector(`[data-asset-id="${asset.id}"] [role="checkbox"]`);
await page.waitForSelector('[data-group] svg');
await page.getByRole('checkbox').click();
await Promise.all([page.waitForEvent('download'), page.getByRole('button', { name: 'Download' }).click()]);
});
@@ -1,167 +0,0 @@
import { faker } from '@faker-js/faker';
import { AssetTypeEnum, AssetVisibility, type AssetResponseDto, type StackResponseDto } from '@immich/sdk';
import { BrowserContext } from '@playwright/test';
import { randomPreview, randomThumbnail } from 'src/ui/generators/timeline';
export type MockStack = {
id: string;
primaryAssetId: string;
assets: AssetResponseDto[];
brokenAssetIds: Set<string>;
assetMap: Map<string, AssetResponseDto>;
};
export const createMockStackAsset = (ownerId: string): AssetResponseDto => {
const assetId = faker.string.uuid();
const now = new Date().toISOString();
return {
id: assetId,
deviceAssetId: `device-${assetId}`,
ownerId,
owner: {
id: ownerId,
email: 'admin@immich.cloud',
name: 'Admin',
profileImagePath: '',
profileChangedAt: now,
avatarColor: 'blue' as never,
},
libraryId: `library-${ownerId}`,
deviceId: `device-${ownerId}`,
type: AssetTypeEnum.Image,
originalPath: `/original/${assetId}.jpg`,
originalFileName: `${assetId}.jpg`,
originalMimeType: 'image/jpeg',
thumbhash: null,
fileCreatedAt: now,
fileModifiedAt: now,
localDateTime: now,
updatedAt: now,
createdAt: now,
isFavorite: false,
isArchived: false,
isTrashed: false,
visibility: AssetVisibility.Timeline,
duration: '0:00:00.00000',
exifInfo: {
make: null,
model: null,
exifImageWidth: 3000,
exifImageHeight: 4000,
fileSizeInByte: null,
orientation: null,
dateTimeOriginal: now,
modifyDate: null,
timeZone: null,
lensModel: null,
fNumber: null,
focalLength: null,
iso: null,
exposureTime: null,
latitude: null,
longitude: null,
city: null,
country: null,
state: null,
description: null,
},
livePhotoVideoId: null,
tags: [],
people: [],
unassignedFaces: [],
stack: null,
isOffline: false,
hasMetadata: true,
duplicateId: null,
resized: true,
checksum: faker.string.alphanumeric({ length: 28 }),
width: 3000,
height: 4000,
isEdited: false,
};
};
export const createMockStack = (
primaryAssetDto: AssetResponseDto,
additionalAssets: AssetResponseDto[],
brokenAssetIds?: Set<string>,
): MockStack => {
const stackId = faker.string.uuid();
const allAssets = [primaryAssetDto, ...additionalAssets];
const resolvedBrokenIds = brokenAssetIds ?? new Set(additionalAssets.map((a) => a.id));
const assetMap = new Map(allAssets.map((a) => [a.id, a]));
primaryAssetDto.stack = {
id: stackId,
assetCount: allAssets.length,
primaryAssetId: primaryAssetDto.id,
};
return {
id: stackId,
primaryAssetId: primaryAssetDto.id,
assets: allAssets,
brokenAssetIds: resolvedBrokenIds,
assetMap,
};
};
export const setupBrokenAssetMockApiRoutes = async (context: BrowserContext, mockStack: MockStack) => {
await context.route('**/api/stacks/*', async (route, request) => {
if (request.method() !== 'GET') {
return route.fallback();
}
const stackResponse: StackResponseDto = {
id: mockStack.id,
primaryAssetId: mockStack.primaryAssetId,
assets: mockStack.assets,
};
return route.fulfill({
status: 200,
contentType: 'application/json',
json: stackResponse,
});
});
await context.route('**/api/assets/*', async (route, request) => {
if (request.method() !== 'GET') {
return route.fallback();
}
const url = new URL(request.url());
const segments = url.pathname.split('/');
const assetId = segments.at(-1);
if (assetId && mockStack.assetMap.has(assetId)) {
return route.fulfill({
status: 200,
contentType: 'application/json',
json: mockStack.assetMap.get(assetId),
});
}
return route.fallback();
});
await context.route('**/api/assets/*/thumbnail?size=*', async (route, request) => {
if (!route.request().serviceWorker()) {
return route.continue();
}
const pattern = /\/api\/assets\/(?<assetId>[^/]+)\/thumbnail\?size=(?<size>preview|thumbnail)/;
const match = request.url().match(pattern);
if (!match?.groups || !mockStack.assetMap.has(match.groups.assetId)) {
return route.fallback();
}
if (mockStack.brokenAssetIds.has(match.groups.assetId)) {
return route.fulfill({ status: 404 });
}
const asset = mockStack.assetMap.get(match.groups.assetId)!;
const ratio = (asset.exifInfo?.exifImageWidth ?? 3000) / (asset.exifInfo?.exifImageHeight ?? 4000);
const body =
match.groups.size === 'preview'
? await randomPreview(match.groups.assetId, ratio)
: await randomThumbnail(match.groups.assetId, ratio);
return route.fulfill({
status: 200,
headers: { 'content-type': 'image/jpeg' },
body,
});
});
};
@@ -1,127 +0,0 @@
import { BrowserContext } from '@playwright/test';
import { randomThumbnail } from 'src/ui/generators/timeline';
// Minimal valid H.264 MP4 (8x8px, 1 frame) that browsers can decode to get videoWidth/videoHeight
const MINIMAL_MP4_BASE64 =
'AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAAAr9tZGF0AAACoAYF//+c' +
'3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDEyNSAtIEguMjY0L01QRUctNCBBVkMgY29kZWMg' +
'LSBDb3B5bGVmdCAyMDAzLTIwMTIgLSBodHRwOi8vd3d3LnZpZGVvbGFuLm9yZy94MjY0Lmh0bWwg' +
'LSBvcHRpb25zOiBjYWJhYz0xIHJlZj0zIGRlYmxvY2s9MTowOjAgYW5hbHlzZT0weDM6MHgxMTMg' +
'bWU9aGV4IHN1Ym1lPTcgcHN5PTEgcHN5X3JkPTEuMDA6MC4wMCBtaXhlZF9yZWY9MSBtZV9yYW5n' +
'ZT0xNiBjaHJvbWFfbWU9MSB0cmVsbGlzPTEgOHg4ZGN0PTEgY3FtPTAgZGVhZHpvbmU9MjEsMTEg' +
'ZmFzdF9wc2tpcD0xIGNocm9tYV9xcF9vZmZzZXQ9LTIgdGhyZWFkcz02IGxvb2thaGVhZF90aHJl' +
'YWRzPTEgc2xpY2VkX3RocmVhZHM9MCBucj0wIGRlY2ltYXRlPTEgaW50ZXJsYWNlZD0wIGJsdXJh' +
'eV9jb21wYXQ9MCBjb25zdHJhaW5lZF9pbnRyYT0wIGJmcmFtZXM9MyBiX3B5cmFtaWQ9MiBiX2Fk' +
'YXB0PTEgYl9iaWFzPTAgZGlyZWN0PTEgd2VpZ2h0Yj0xIG9wZW5fZ29wPTAgd2VpZ2h0cD0yIGtl' +
'eWludD0yNTAga2V5aW50X21pbj0yNCBzY2VuZWN1dD00MCBpbnRyYV9yZWZyZXNoPTAgcmNfbG9v' +
'a2FoZWFkPTQwIHJjPWNyZiBtYnRyZWU9MSBjcmY9MjMuMCBxY29tcD0wLjYwIHFwbWluPTAgcXBt' +
'YXg9NjkgcXBzdGVwPTQgaXBfcmF0aW89MS40MCBhcT0xOjEuMDAAgAAAAA9liIQAV/0TAAYdeBTX' +
'zg8AAALvbW9vdgAAAGxtdmhkAAAAAAAAAAAAAAAAAAAD6AAAACoAAQAAAQAAAAAAAAAAAAAAAAEAAAAA' +
'AAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAA' +
'Ahl0cmFrAAAAXHRraGQAAAAPAAAAAAAAAAAAAAABAAAAAAAAACoAAAAAAAAAAAAAAAAAAAAAAAEAAAAA' +
'AAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAAgAAAAIAAAAAAAkZWR0cwAAABxlbHN0AAAAAAAA' +
'AAEAAAAqAAAAAAABAAAAAAGRbWRpYQAAACBtZGhkAAAAAAAAAAAAAAAAAAAwAAAAAgBVxAAAAAAA' +
'LWhkbHIAAAAAAAAAAHZpZGUAAAAAAAAAAAAAAABWaWRlb0hhbmRsZXIAAAABPG1pbmYAAAAUdm1oZAAA' +
'AAEAAAAAAAAAAAAAACRkaW5mAAAAHGRyZWYAAAAAAAAAAQAAAAx1cmwgAAAAAQAAAPxzdGJsAAAAmHN0' +
'c2QAAAAAAAAAAQAAAIhhdmMxAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAgACABIAAAASAAAAAAAAAAB' +
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGP//AAAAMmF2Y0MBZAAK/+EAGWdkAAqs' +
'2V+WXAWyAAADAAIAAAMAYB4kSywBAAZo6+PLIsAAAAAYc3R0cwAAAAAAAAABAAAAAQAAAgAAAAAcc3Rz' +
'YwAAAAAAAAABAAAAAQAAAAEAAAABAAAAFHN0c3oAAAAAAAACtwAAAAEAAAAUc3RjbwAAAAAAAAABAAAA' +
'MAAAAGJ1ZHRhAAAAWm1ldGEAAAAAAAAAIWhkbHIAAAAAAAAAAG1kaXJhcHBsAAAAAAAAAAAAAAAALWls' +
'c3QAAAAlqXRvbwAAAB1kYXRhAAAAAQAAAABMYXZmNTQuNjMuMTA0';
export const MINIMAL_MP4_BUFFER = Buffer.from(MINIMAL_MP4_BASE64, 'base64');
export type MockPerson = {
id: string;
name: string;
birthDate: string | null;
isHidden: boolean;
thumbnailPath: string;
updatedAt: string;
};
export const createMockPeople = (count: number): MockPerson[] => {
const names = [
'Alice Johnson',
'Bob Smith',
'Charlie Brown',
'Diana Prince',
'Eve Adams',
'Frank Castle',
'Grace Lee',
'Hank Pym',
'Iris West',
'Jack Ryan',
];
return Array.from({ length: count }, (_, index) => ({
id: `person-${index}`,
name: names[index % names.length],
birthDate: null,
isHidden: false,
thumbnailPath: `/upload/thumbs/person-${index}.jpeg`,
updatedAt: '2025-01-01T00:00:00.000Z',
}));
};
export type FaceCreateCapture = {
requests: Array<{
assetId: string;
personId: string;
x: number;
y: number;
width: number;
height: number;
imageWidth: number;
imageHeight: number;
}>;
};
export const setupFaceEditorMockApiRoutes = async (
context: BrowserContext,
mockPeople: MockPerson[],
faceCreateCapture: FaceCreateCapture,
) => {
await context.route('**/api/people?*', async (route, request) => {
if (request.method() !== 'GET') {
return route.fallback();
}
return route.fulfill({
status: 200,
contentType: 'application/json',
json: {
hasNextPage: false,
hidden: 0,
people: mockPeople,
total: mockPeople.length,
},
});
});
await context.route('**/api/faces', async (route, request) => {
if (request.method() !== 'POST') {
return route.fallback();
}
const body = request.postDataJSON();
faceCreateCapture.requests.push(body);
return route.fulfill({
status: 201,
contentType: 'text/plain',
body: 'OK',
});
});
await context.route('**/api/people/*/thumbnail', async (route) => {
if (!route.request().serviceWorker()) {
return route.continue();
}
return route.fulfill({
status: 200,
headers: { 'content-type': 'image/jpeg' },
body: await randomThumbnail('person-thumb', 1),
});
});
};
+2 -11
View File
@@ -12,7 +12,6 @@ import {
TimelineData,
} from 'src/ui/generators/timeline';
import { sleep } from 'src/ui/specs/timeline/utils';
import { MINIMAL_MP4_BUFFER } from './face-editor-network';
export class TimelineTestContext {
slowBucket = false;
@@ -99,13 +98,13 @@ export const setupTimelineMockApiRoutes = async (
});
await context.route('**/api/assets/*/thumbnail?size=*', async (route, request) => {
const pattern = /\/api\/assets\/(?<assetId>[^/]+)\/thumbnail\?size=(?<size>preview|thumbnail|fullsize)/;
const pattern = /\/api\/assets\/(?<assetId>[^/]+)\/thumbnail\?size=(?<size>preview|thumbnail)/;
const match = request.url().match(pattern);
if (!match?.groups) {
throw new Error(`Invalid URL for thumbnail endpoint: ${request.url()}`);
}
if (match.groups.size === 'preview' || match.groups.size === 'fullsize') {
if (match.groups.size === 'preview') {
if (!route.request().serviceWorker()) {
return route.continue();
}
@@ -136,14 +135,6 @@ export const setupTimelineMockApiRoutes = async (
return route.continue();
});
await context.route('**/api/assets/*/video/playback*', async (route) => {
return route.fulfill({
status: 200,
headers: { 'content-type': 'video/mp4' },
body: MINIMAL_MP4_BUFFER,
});
});
await context.route('**/api/albums/**', async (route, request) => {
const albumsMatch = request.url().match(/\/api\/albums\/(?<albumId>[^/?]+)/);
if (albumsMatch) {
@@ -1,84 +0,0 @@
import { expect, test } from '@playwright/test';
import { toAssetResponseDto } from 'src/ui/generators/timeline';
import {
createMockStack,
createMockStackAsset,
MockStack,
setupBrokenAssetMockApiRoutes,
} from 'src/ui/mock-network/broken-asset-network';
import { assetViewerUtils } from '../timeline/utils';
import { setupAssetViewerFixture } from './utils';
test.describe.configure({ mode: 'parallel' });
test.describe('broken-asset responsiveness', () => {
const fixture = setupAssetViewerFixture(889);
let mockStack: MockStack;
test.beforeAll(async () => {
const primaryAssetDto = toAssetResponseDto(fixture.primaryAsset);
const brokenAssets = [
createMockStackAsset(fixture.adminUserId),
createMockStackAsset(fixture.adminUserId),
createMockStackAsset(fixture.adminUserId),
];
mockStack = createMockStack(primaryAssetDto, brokenAssets);
});
test.beforeEach(async ({ context }) => {
await setupBrokenAssetMockApiRoutes(context, mockStack);
});
test('broken asset in stack strip hides icon at small size', async ({ page }) => {
await page.goto(`/photos/${fixture.primaryAsset.id}`);
await assetViewerUtils.waitForViewerLoad(page, fixture.primaryAsset);
const stackSlideshow = page.locator('#stack-slideshow');
await expect(stackSlideshow).toBeVisible();
const brokenAssets = stackSlideshow.locator('[data-broken-asset]');
await expect(brokenAssets.first()).toBeVisible();
await expect(brokenAssets).toHaveCount(mockStack.brokenAssetIds.size);
for (const brokenAsset of await brokenAssets.all()) {
await expect(brokenAsset.locator('svg')).not.toBeVisible();
}
});
test('broken asset in stack strip uses text-xs class', async ({ page }) => {
await page.goto(`/photos/${fixture.primaryAsset.id}`);
await assetViewerUtils.waitForViewerLoad(page, fixture.primaryAsset);
const stackSlideshow = page.locator('#stack-slideshow');
await expect(stackSlideshow).toBeVisible();
const brokenAssets = stackSlideshow.locator('[data-broken-asset]');
await expect(brokenAssets.first()).toBeVisible();
for (const brokenAsset of await brokenAssets.all()) {
const messageSpan = brokenAsset.locator('span');
await expect(messageSpan).toHaveClass(/text-xs/);
}
});
test('broken asset in main viewer shows icon and uses text-base', async ({ context, page }) => {
await context.route(
(url) => url.pathname.includes(`/api/assets/${fixture.primaryAsset.id}/thumbnail`),
async (route) => {
return route.fulfill({ status: 404 });
},
);
await page.goto(`/photos/${fixture.primaryAsset.id}`);
await page.waitForSelector('#immich-asset-viewer');
const viewerBrokenAsset = page.locator('[data-viewer-content] [data-broken-asset]');
await expect(viewerBrokenAsset).toBeVisible();
await expect(viewerBrokenAsset.locator('svg')).toBeVisible();
const messageSpan = viewerBrokenAsset.locator('span');
await expect(messageSpan).toHaveClass(/text-base/);
});
});
@@ -1,285 +0,0 @@
import { expect, Page, test } from '@playwright/test';
import { SeededRandom, selectRandom, TimelineAssetConfig } from 'src/ui/generators/timeline';
import {
createMockPeople,
FaceCreateCapture,
MockPerson,
setupFaceEditorMockApiRoutes,
} from 'src/ui/mock-network/face-editor-network';
import { assetViewerUtils } from '../timeline/utils';
import { setupAssetViewerFixture } from './utils';
const waitForSelectorTransition = async (page: Page) => {
await page.waitForFunction(
() => {
const selector = document.querySelector('#face-selector') as HTMLElement | null;
if (!selector) {
return false;
}
return selector.getAnimations({ subtree: false }).every((animation) => animation.playState === 'finished');
},
undefined,
{ timeout: 1000, polling: 50 },
);
};
const openFaceEditor = async (page: Page, asset: TimelineAssetConfig) => {
await page.goto(`/photos/${asset.id}`);
await assetViewerUtils.waitForViewerLoad(page, asset);
await page.keyboard.press('i');
await page.locator('#detail-panel').waitFor({ state: 'visible' });
await page.getByLabel('Tag people').click();
await page.locator('#face-selector').waitFor({ state: 'visible' });
await waitForSelectorTransition(page);
};
test.describe.configure({ mode: 'parallel' });
test.describe('face-editor', () => {
const fixture = setupAssetViewerFixture(777);
const rng = new SeededRandom(777);
let mockPeople: MockPerson[];
let faceCreateCapture: FaceCreateCapture;
test.beforeAll(async () => {
mockPeople = createMockPeople(8);
});
test.beforeEach(async ({ context }) => {
faceCreateCapture = { requests: [] };
await setupFaceEditorMockApiRoutes(context, mockPeople, faceCreateCapture);
});
type ScreenRect = { top: number; left: number; width: number; height: number };
const getFaceBoxRect = async (page: Page): Promise<ScreenRect> => {
const dataEl = page.locator('#face-editor-data');
await expect(dataEl).toHaveAttribute('data-face-left', /^-?\d+/);
await expect(dataEl).toHaveAttribute('data-face-top', /^-?\d+/);
await expect(dataEl).toHaveAttribute('data-face-width', /^[1-9]/);
await expect(dataEl).toHaveAttribute('data-face-height', /^[1-9]/);
const canvasBox = await page.locator('#face-editor').boundingBox();
if (!canvasBox) {
throw new Error('Canvas element not found');
}
const left = Number(await dataEl.getAttribute('data-face-left'));
const top = Number(await dataEl.getAttribute('data-face-top'));
const width = Number(await dataEl.getAttribute('data-face-width'));
const height = Number(await dataEl.getAttribute('data-face-height'));
return {
top: canvasBox.y + top,
left: canvasBox.x + left,
width,
height,
};
};
const getSelectorRect = async (page: Page): Promise<ScreenRect> => {
const box = await page.locator('#face-selector').boundingBox();
if (!box) {
throw new Error('Face selector element not found');
}
return { top: box.y, left: box.x, width: box.width, height: box.height };
};
const computeOverlapArea = (a: ScreenRect, b: ScreenRect): number => {
const overlapX = Math.max(0, Math.min(a.left + a.width, b.left + b.width) - Math.max(a.left, b.left));
const overlapY = Math.max(0, Math.min(a.top + a.height, b.top + b.height) - Math.max(a.top, b.top));
return overlapX * overlapY;
};
const dragFaceBox = async (page: Page, deltaX: number, deltaY: number) => {
const faceBox = await getFaceBoxRect(page);
const centerX = faceBox.left + faceBox.width / 2;
const centerY = faceBox.top + faceBox.height / 2;
await page.mouse.move(centerX, centerY);
await page.mouse.down();
await page.mouse.move(centerX + deltaX, centerY + deltaY, { steps: 5 });
await page.mouse.up();
await page.waitForTimeout(300);
};
test('Face editor opens with person list', async ({ page }) => {
const asset = selectRandom(fixture.assets, rng);
await openFaceEditor(page, asset);
await expect(page.locator('#face-selector')).toBeVisible();
await expect(page.locator('#face-editor')).toBeVisible();
for (const person of mockPeople) {
await expect(page.locator('#face-selector').getByText(person.name)).toBeVisible();
}
});
test('Search filters people by name', async ({ page }) => {
const asset = selectRandom(fixture.assets, rng);
await openFaceEditor(page, asset);
const searchInput = page.locator('#face-selector input');
await searchInput.fill('Alice');
await expect(page.locator('#face-selector').getByText('Alice Johnson')).toBeVisible();
await expect(page.locator('#face-selector').getByText('Bob Smith')).toBeHidden();
await searchInput.clear();
for (const person of mockPeople) {
await expect(page.locator('#face-selector').getByText(person.name)).toBeVisible();
}
});
test('Search with no results shows empty message', async ({ page }) => {
const asset = selectRandom(fixture.assets, rng);
await openFaceEditor(page, asset);
const searchInput = page.locator('#face-selector input');
await searchInput.fill('Nonexistent Person XYZ');
for (const person of mockPeople) {
await expect(page.locator('#face-selector').getByText(person.name)).toBeHidden();
}
});
test('Selecting a person shows confirmation dialog', async ({ page }) => {
const asset = selectRandom(fixture.assets, rng);
await openFaceEditor(page, asset);
const personToTag = mockPeople[0];
await page.locator('#face-selector').getByText(personToTag.name).click();
await expect(page.getByRole('dialog')).toBeVisible();
});
test('Confirming tag calls createFace API and closes editor', async ({ page }) => {
const asset = selectRandom(fixture.assets, rng);
await openFaceEditor(page, asset);
const personToTag = mockPeople[0];
await page.locator('#face-selector').getByText(personToTag.name).click();
await expect(page.getByRole('dialog')).toBeVisible();
await page.getByRole('button', { name: /confirm/i }).click();
await expect(page.locator('#face-selector')).toBeHidden();
await expect(page.locator('#face-editor')).toBeHidden();
expect(faceCreateCapture.requests).toHaveLength(1);
expect(faceCreateCapture.requests[0].assetId).toBe(asset.id);
expect(faceCreateCapture.requests[0].personId).toBe(personToTag.id);
});
test('Cancel button closes face editor', async ({ page }) => {
const asset = selectRandom(fixture.assets, rng);
await openFaceEditor(page, asset);
await expect(page.locator('#face-selector')).toBeVisible();
await expect(page.locator('#face-editor')).toBeVisible();
await page.getByRole('button', { name: /cancel/i }).click();
await expect(page.locator('#face-selector')).toBeHidden();
await expect(page.locator('#face-editor')).toBeHidden();
});
test('Selector does not overlap face box on initial open', async ({ page }) => {
const asset = selectRandom(fixture.assets, rng);
await openFaceEditor(page, asset);
const faceBox = await getFaceBoxRect(page);
const selectorBox = await getSelectorRect(page);
const overlap = computeOverlapArea(faceBox, selectorBox);
expect(overlap).toBe(0);
});
test('Selector repositions without overlap after dragging face box down', async ({ page }) => {
const asset = selectRandom(fixture.assets, rng);
await openFaceEditor(page, asset);
await dragFaceBox(page, 0, 150);
const faceBox = await getFaceBoxRect(page);
const selectorBox = await getSelectorRect(page);
const overlap = computeOverlapArea(faceBox, selectorBox);
expect(overlap).toBe(0);
});
test('Selector repositions without overlap after dragging face box right', async ({ page }) => {
const asset = selectRandom(fixture.assets, rng);
await openFaceEditor(page, asset);
await dragFaceBox(page, 200, 0);
const faceBox = await getFaceBoxRect(page);
const selectorBox = await getSelectorRect(page);
const overlap = computeOverlapArea(faceBox, selectorBox);
expect(overlap).toBe(0);
});
test('Selector repositions without overlap after dragging face box to top-left corner', async ({ page }) => {
const asset = selectRandom(fixture.assets, rng);
await openFaceEditor(page, asset);
await dragFaceBox(page, -300, -300);
const faceBox = await getFaceBoxRect(page);
const selectorBox = await getSelectorRect(page);
const overlap = computeOverlapArea(faceBox, selectorBox);
expect(overlap).toBe(0);
});
test('Selector repositions without overlap after dragging face box to bottom-right', async ({ page }) => {
const asset = selectRandom(fixture.assets, rng);
await openFaceEditor(page, asset);
await dragFaceBox(page, 300, 300);
const faceBox = await getFaceBoxRect(page);
const selectorBox = await getSelectorRect(page);
const overlap = computeOverlapArea(faceBox, selectorBox);
expect(overlap).toBe(0);
});
test('Selector stays within viewport bounds', async ({ page }) => {
const asset = selectRandom(fixture.assets, rng);
await openFaceEditor(page, asset);
const viewportSize = page.viewportSize()!;
const selectorBox = await getSelectorRect(page);
expect(selectorBox.top).toBeGreaterThanOrEqual(0);
expect(selectorBox.left).toBeGreaterThanOrEqual(0);
expect(selectorBox.top + selectorBox.height).toBeLessThanOrEqual(viewportSize.height);
expect(selectorBox.left + selectorBox.width).toBeLessThanOrEqual(viewportSize.width);
});
test('Selector stays within viewport after dragging to edge', async ({ page }) => {
const asset = selectRandom(fixture.assets, rng);
await openFaceEditor(page, asset);
await dragFaceBox(page, -400, -400);
const viewportSize = page.viewportSize()!;
const selectorBox = await getSelectorRect(page);
expect(selectorBox.top).toBeGreaterThanOrEqual(0);
expect(selectorBox.left).toBeGreaterThanOrEqual(0);
expect(selectorBox.top + selectorBox.height).toBeLessThanOrEqual(viewportSize.height);
expect(selectorBox.left + selectorBox.width).toBeLessThanOrEqual(viewportSize.width);
});
test('Face box is draggable on the canvas', async ({ page }) => {
const asset = selectRandom(fixture.assets, rng);
await openFaceEditor(page, asset);
const beforeDrag = await getFaceBoxRect(page);
await dragFaceBox(page, 100, 50);
const afterDrag = await getFaceBoxRect(page);
expect(afterDrag.left).toBeGreaterThan(beforeDrag.left + 50);
expect(afterDrag.top).toBeGreaterThan(beforeDrag.top + 20);
});
});
@@ -1,84 +0,0 @@
import { faker } from '@faker-js/faker';
import type { AssetResponseDto } from '@immich/sdk';
import { expect, test } from '@playwright/test';
import { toAssetResponseDto } from 'src/ui/generators/timeline';
import {
createMockStack,
createMockStackAsset,
MockStack,
setupBrokenAssetMockApiRoutes,
} from 'src/ui/mock-network/broken-asset-network';
import { assetViewerUtils } from '../timeline/utils';
import { enableTagsPreference, ensureDetailPanelVisible, setupAssetViewerFixture } from './utils';
test.describe.configure({ mode: 'parallel' });
test.describe('asset-viewer stack', () => {
const fixture = setupAssetViewerFixture(888);
let mockStack: MockStack;
let primaryAssetDto: AssetResponseDto;
let secondAssetDto: AssetResponseDto;
test.beforeAll(async () => {
primaryAssetDto = toAssetResponseDto(fixture.primaryAsset);
primaryAssetDto.tags = [
{
id: faker.string.uuid(),
name: '1',
value: 'test/1',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
},
];
secondAssetDto = createMockStackAsset(fixture.adminUserId);
secondAssetDto.tags = [
{
id: faker.string.uuid(),
name: '2',
value: 'test/2',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
},
];
mockStack = createMockStack(primaryAssetDto, [secondAssetDto], new Set());
});
test.beforeEach(async ({ context }) => {
await setupBrokenAssetMockApiRoutes(context, mockStack);
});
test('stack slideshow is visible', async ({ page }) => {
await page.goto(`/photos/${fixture.primaryAsset.id}`);
await assetViewerUtils.waitForViewerLoad(page, fixture.primaryAsset);
const stackSlideshow = page.locator('#stack-slideshow');
await expect(stackSlideshow).toBeVisible();
const stackAssets = stackSlideshow.locator('[data-asset]');
await expect(stackAssets).toHaveCount(mockStack.assets.length);
});
test('tags of primary asset are visible', async ({ context, page }) => {
await enableTagsPreference(context);
await page.goto(`/photos/${fixture.primaryAsset.id}`);
await ensureDetailPanelVisible(page);
const tags = page.getByTestId('detail-panel-tags').getByRole('link');
await expect(tags.first()).toHaveText('test/1');
});
test('tags of second asset are visible', async ({ context, page }) => {
await enableTagsPreference(context);
await page.goto(`/photos/${fixture.primaryAsset.id}`);
await ensureDetailPanelVisible(page);
const stackAssets = page.locator('#stack-slideshow [data-asset]');
await stackAssets.nth(1).click();
const tags = page.getByTestId('detail-panel-tags').getByRole('link');
await expect(tags.first()).toHaveText('test/2');
});
});
-116
View File
@@ -1,116 +0,0 @@
import { faker } from '@faker-js/faker';
import type { AssetResponseDto } from '@immich/sdk';
import { BrowserContext, Page, test } from '@playwright/test';
import {
Changes,
createDefaultTimelineConfig,
generateTimelineData,
SeededRandom,
selectRandom,
TimelineAssetConfig,
TimelineData,
toAssetResponseDto,
} from 'src/ui/generators/timeline';
import { setupBaseMockApiRoutes } from 'src/ui/mock-network/base-network';
import { setupTimelineMockApiRoutes, TimelineTestContext } from 'src/ui/mock-network/timeline-network';
import { utils } from 'src/utils';
export type AssetViewerTestFixture = {
adminUserId: string;
timelineRestData: TimelineData;
assets: TimelineAssetConfig[];
testContext: TimelineTestContext;
changes: Changes;
primaryAsset: TimelineAssetConfig;
primaryAssetDto: AssetResponseDto;
};
export function setupAssetViewerFixture(seed: number): AssetViewerTestFixture {
const rng = new SeededRandom(seed);
const testContext = new TimelineTestContext();
const fixture: AssetViewerTestFixture = {
adminUserId: undefined!,
timelineRestData: undefined!,
assets: [],
testContext,
changes: {
albumAdditions: [],
assetDeletions: [],
assetArchivals: [],
assetFavorites: [],
},
primaryAsset: undefined!,
primaryAssetDto: undefined!,
};
test.beforeAll(async () => {
test.fail(
process.env.PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS !== '1',
'This test requires env var: PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS=1',
);
utils.initSdk();
fixture.adminUserId = faker.string.uuid();
testContext.adminId = fixture.adminUserId;
fixture.timelineRestData = generateTimelineData({
...createDefaultTimelineConfig(),
ownerId: fixture.adminUserId,
});
for (const timeBucket of fixture.timelineRestData.buckets.values()) {
fixture.assets.push(...timeBucket);
}
fixture.primaryAsset = selectRandom(
fixture.assets.filter((a) => a.isImage),
rng,
);
fixture.primaryAssetDto = toAssetResponseDto(fixture.primaryAsset);
});
test.beforeEach(async ({ context }) => {
await setupBaseMockApiRoutes(context, fixture.adminUserId);
await setupTimelineMockApiRoutes(context, fixture.timelineRestData, fixture.changes, fixture.testContext);
});
test.afterEach(() => {
fixture.testContext.slowBucket = false;
fixture.changes.albumAdditions = [];
fixture.changes.assetDeletions = [];
fixture.changes.assetArchivals = [];
fixture.changes.assetFavorites = [];
});
return fixture;
}
export async function ensureDetailPanelVisible(page: Page) {
await page.waitForSelector('#immich-asset-viewer');
const isVisible = await page.locator('#detail-panel').isVisible();
if (!isVisible) {
await page.keyboard.press('i');
await page.waitForSelector('#detail-panel');
}
}
export async function enableTagsPreference(context: BrowserContext) {
await context.route('**/users/me/preferences', async (route) => {
return route.fulfill({
status: 200,
contentType: 'application/json',
json: {
albums: { defaultAssetOrder: 'desc' },
folders: { enabled: false, sidebarWeb: false },
memories: { enabled: true, duration: 5 },
people: { enabled: true, sidebarWeb: false },
sharedLinks: { enabled: true, sidebarWeb: false },
ratings: { enabled: false },
tags: { enabled: true, sidebarWeb: false },
emailNotifications: { enabled: true, albumInvite: true, albumUpdate: true },
download: { archiveSize: 4_294_967_296, includeEmbeddedVideos: false },
purchase: { showSupportBadge: true, hideBuyButtonUntil: '2100-02-12T00:00:00.000Z' },
cast: { gCastEnabled: false },
},
});
});
}
@@ -6,7 +6,6 @@ import {
generateTimelineData,
TimelineAssetConfig,
TimelineData,
toAssetResponseDto,
} from 'src/ui/generators/timeline';
import { setupBaseMockApiRoutes } from 'src/ui/mock-network/base-network';
import { setupTimelineMockApiRoutes, TimelineTestContext } from 'src/ui/mock-network/timeline-network';
@@ -54,7 +53,7 @@ test.describe('search gallery-viewer', () => {
assets: {
total: searchAssets.length,
count: searchAssets.length,
items: searchAssets.map((asset) => toAssetResponseDto(asset)),
items: searchAssets,
facets: [],
nextPage: null,
},
@@ -438,7 +438,7 @@ test.describe('Timeline', () => {
const asset = getAsset(timelineRestData, album.assetIds[0])!;
await pageUtils.goToAsset(page, asset.fileCreatedAt);
await thumbnailUtils.expectInViewport(page, asset.id);
await thumbnailUtils.expectSelectedDisabled(page, asset.id);
await thumbnailUtils.expectSelectedReadonly(page, asset.id);
});
test('Add photos to album', async ({ page }) => {
const album = timelineRestData.album;
@@ -447,7 +447,7 @@ test.describe('Timeline', () => {
const asset = getAsset(timelineRestData, album.assetIds[0])!;
await pageUtils.goToAsset(page, asset.fileCreatedAt);
await thumbnailUtils.expectInViewport(page, asset.id);
await thumbnailUtils.expectSelectedDisabled(page, asset.id);
await thumbnailUtils.expectSelectedReadonly(page, asset.id);
await pageUtils.selectDay(page, 'Tue, Feb 27, 2024');
const put = pageRoutePromise(page, `**/api/albums/${album.id}/assets`, async (route, request) => {
const requestJson = request.postDataJSON();
+8 -6
View File
@@ -102,9 +102,9 @@ export const thumbnailUtils = {
async expectThumbnailIsNotArchive(page: Page, assetId: string) {
await expect(thumbnailUtils.withAssetId(page, assetId).locator('[data-icon-archive]')).toHaveCount(0);
},
async expectSelectedDisabled(page: Page, assetId: string) {
async expectSelectedReadonly(page: Page, assetId: string) {
await expect(
page.locator(`[data-thumbnail-focus-container][data-asset="${assetId}"][data-selected][data-disabled]`),
page.locator(`[data-thumbnail-focus-container][data-asset="${assetId}"][data-selected]`),
).toBeVisible();
},
async expectTimelineHasOnScreenAssets(page: Page) {
@@ -163,11 +163,13 @@ export const assetViewerUtils = {
return page.locator('#immich-asset-viewer');
},
async waitForViewerLoad(page: Page, asset: TimelineAssetConfig) {
const previewUrl = `/api/assets/${asset.id}/thumbnail?size=preview&c=${asset.thumbhash}&edited=true`;
await page
.getByTestId('preview')
.and(page.locator(`[src="${previewUrl}"]`))
.or(page.locator(`video[poster="${previewUrl}"]`))
.locator(
`img[draggable="false"][src="/api/assets/${asset.id}/thumbnail?size=preview&c=${asset.thumbhash}&edited=true"]`,
)
.or(
page.locator(`video[poster="/api/assets/${asset.id}/thumbnail?size=preview&c=${asset.thumbhash}&edited=true"]`),
)
.waitFor();
},
async expectActiveAssetToBe(page: Page, assetId: string) {
+1 -1
View File
@@ -17,6 +17,6 @@
"esModuleInterop": true,
"baseUrl": "./"
},
"include": ["src/**/*.ts", "vitest*.config.ts"],
"include": ["src/**/*.ts"],
"exclude": ["dist", "node_modules"]
}
+5 -5
View File
@@ -1,4 +1,3 @@
import tsconfigPaths from 'vite-tsconfig-paths';
import { defineConfig } from 'vitest/config';
const skipDockerSetup = process.env.VITEST_DISABLE_DOCKER_SETUP === 'true';
@@ -15,14 +14,15 @@ if (!skipDockerSetup) {
export default defineConfig({
test: {
name: 'e2e:server',
retry: process.env.CI ? 4 : 0,
include: ['src/specs/server/**/*.e2e-spec.ts'],
globalSetup,
testTimeout: 15_000,
pool: 'threads',
maxWorkers: 1,
isolate: false,
poolOptions: {
threads: {
singleThread: true,
},
},
},
plugins: [tsconfigPaths()],
});
+5 -5
View File
@@ -1,4 +1,3 @@
import tsconfigPaths from 'vite-tsconfig-paths';
import { defineConfig } from 'vitest/config';
const skipDockerSetup = process.env.VITEST_DISABLE_DOCKER_SETUP === 'true';
@@ -15,14 +14,15 @@ if (!skipDockerSetup) {
export default defineConfig({
test: {
name: 'e2e:maintenance',
retry: process.env.CI ? 4 : 0,
include: ['src/specs/maintenance/server/**/*.e2e-spec.ts'],
globalSetup,
testTimeout: 15_000,
pool: 'threads',
maxWorkers: 1,
isolate: false,
poolOptions: {
threads: {
singleThread: true,
},
},
},
plugins: [tsconfigPaths()],
});
+7 -15
View File
@@ -871,8 +871,8 @@
"current_pin_code": "Current PIN code",
"current_server_address": "Current server address",
"custom_date": "Custom date",
"custom_locale": "Custom locale",
"custom_locale_description": "Format dates, times, and numbers based on the selected language and region",
"custom_locale": "Custom Locale",
"custom_locale_description": "Format dates and numbers based on the language and the region",
"custom_url": "Custom URL",
"cutoff_date_description": "Keep photos from the last…",
"cutoff_day": "{count, plural, one {day} other {days}}",
@@ -895,6 +895,8 @@
"deduplication_criteria_2": "Count of EXIF data",
"deduplication_info": "Deduplication Info",
"deduplication_info_description": "To automatically preselect assets and remove duplicates in bulk, we look at:",
"default_locale": "Default Locale",
"default_locale_description": "Format dates and numbers based on your browser locale",
"delete": "Delete",
"delete_action_confirmation_message": "Are you sure you want to delete this asset? This action will move the asset to the server's trash and will prompt if you want to delete it locally",
"delete_action_prompt": "{count} deleted",
@@ -1072,7 +1074,6 @@
"failed_to_update_notification_status": "Failed to update notification status",
"incorrect_email_or_password": "Incorrect email or password",
"library_folder_already_exists": "This import path already exists.",
"page_not_found": "Page not found :/",
"paths_validation_failed": "{paths, plural, one {# path} other {# paths}} failed validation",
"profile_picture_transparent_pixels": "Profile pictures cannot have transparent pixels. Please zoom in and/or move the image.",
"quota_higher_than_disk_size": "You set a quota higher than the disk size",
@@ -1808,8 +1809,9 @@
"rate_asset": "Rate Asset",
"rating": "Star rating",
"rating_clear": "Clear rating",
"rating_count": "{count, plural, =0 {Unrated} one {# star} other {# stars}}",
"rating_count": "{count, plural, one {# star} other {# stars}}",
"rating_description": "Display the EXIF rating in the info panel",
"rating_set": "Rating set to {rating, plural, one {# star} other {# stars}}",
"reaction_options": "Reaction options",
"read_changelog": "Read Changelog",
"readonly_mode_disabled": "Read-only mode disabled",
@@ -1881,10 +1883,7 @@
"reset_pin_code_success": "Successfully reset PIN code",
"reset_pin_code_with_password": "You can always reset your PIN code with your password",
"reset_sqlite": "Reset SQLite Database",
"reset_sqlite_clear_app_data": "Clear Data",
"reset_sqlite_confirmation": "Are you sure you want to clear the app data? This will remove all settings and sign you out.",
"reset_sqlite_confirmation_note": "Note: You will need to restart the app after clearing.",
"reset_sqlite_done": "App data has been cleared. Please restart Immich and log in again.",
"reset_sqlite_confirmation": "Are you sure you want to reset the SQLite database? You will need to log out and log in again to resync the data",
"reset_sqlite_success": "Successfully reset the SQLite database",
"reset_to_default": "Reset to default",
"resolution": "Resolution",
@@ -1912,7 +1911,6 @@
"saved_settings": "Saved settings",
"say_something": "Say something",
"scaffold_body_error_occurred": "Error occurred",
"scaffold_body_error_unrecoverable": "An unrecoverable error has occurred. Please share the error and stack trace on Discord or GitHub so we can help. If advised, you can clear the app data below.",
"scan": "Scan",
"scan_all_libraries": "Scan All Libraries",
"scan_library": "Scan",
@@ -2028,9 +2026,6 @@
"set_profile_picture": "Set profile picture",
"set_slideshow_to_fullscreen": "Set Slideshow to fullscreen",
"set_stack_primary_asset": "Set as primary asset",
"setting_image_navigation_enable_subtitle": "If enabled, you can navigate to the previous/next image by tapping the leftmost/rightmost quarter of the screen.",
"setting_image_navigation_enable_title": "Tap to Navigate",
"setting_image_navigation_title": "Image Navigation",
"setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).",
"setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).",
"setting_image_viewer_original_title": "Load original image",
@@ -2309,7 +2304,6 @@
"unstack_action_prompt": "{count} unstacked",
"unstacked_assets_count": "Un-stacked {count, plural, one {# asset} other {# assets}}",
"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",
@@ -2336,8 +2330,6 @@
"url": "URL",
"usage": "Usage",
"use_biometric": "Use biometric",
"use_browser_locale": "Use browser locale",
"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",
"user": "User",
+2 -2
View File
@@ -3,8 +3,8 @@
"version": "2.5.6",
"private": true,
"scripts": {
"format": "prettier --cache --check .",
"format:fix": "prettier --cache --write --list-different ."
"format": "prettier --check .",
"format:fix": "prettier --write ."
},
"devDependencies": {
"prettier": "^3.7.4",
-1
View File
@@ -1 +0,0 @@
3.13
+50 -10
View File
@@ -22,7 +22,48 @@ FROM builder-cpu AS builder-rknn
# Warning: 25GiB+ disk space required to pull this image
# TODO: find a way to reduce the image size
FROM rocm/dev-ubuntu-24.04:7.2-complete@sha256:86e11093b4a7ec2a79b1b6701d10e840a6994f21c7e05929b51eb9be361c683a AS builder-rocm
FROM rocm/dev-ubuntu-24.04:6.4.4-complete@sha256:31418ac10a3769a71eaef330c07280d1d999d7074621339b8f93c484c35f6078 AS builder-rocm
# renovate: datasource=github-releases depName=Microsoft/onnxruntime
ARG ONNXRUNTIME_VERSION="v1.22.1"
WORKDIR /code
RUN apt-get update && apt-get install -y --no-install-recommends wget git
RUN wget -nv https://github.com/Kitware/CMake/releases/download/v3.31.9/cmake-3.31.9-linux-x86_64.sh && \
chmod +x cmake-3.31.9-linux-x86_64.sh && \
mkdir -p /code/cmake-3.31.9-linux-x86_64 && \
./cmake-3.31.9-linux-x86_64.sh --skip-license --prefix=/code/cmake-3.31.9-linux-x86_64 && \
rm cmake-3.31.9-linux-x86_64.sh
RUN git clone --single-branch --branch "${ONNXRUNTIME_VERSION}" --recursive "https://github.com/Microsoft/onnxruntime" onnxruntime
WORKDIR /code/onnxruntime
# Fix for multi-threading based on comments in https://github.com/microsoft/onnxruntime/pull/19567
# TODO: find a way to fix this without disabling algo caching
COPY ./patches/* /tmp/
RUN git apply /tmp/*.patch
RUN /bin/sh ./dockerfiles/scripts/install_common_deps.sh
ENV PATH=/opt/rocm-venv/bin:/code/cmake-3.31.9-linux-x86_64/bin:${PATH}
ENV CCACHE_DIR="/ccache"
# Note: the `parallel` setting uses a substantial amount of RAM
RUN --mount=type=cache,target=/ccache \
./build.sh \
--allow_running_as_root \
--config Release \
--build_wheel \
--update \
--build \
--parallel 48 \
--cmake_extra_defines \
ONNXRUNTIME_VERSION="${ONNXRUNTIME_VERSION}" \
CMAKE_HIP_ARCHITECTURES="gfx900;gfx906;gfx908;gfx90a;gfx940;gfx941;gfx942;gfx1030;gfx1100;gfx1101;gfx1102;gfx1200;gfx1201" \
--skip_tests \
--use_rocm \
--rocm_home=/opt/rocm \
--use_cache \
--compile_no_warning_as_error
RUN mv /code/onnxruntime/build/Linux/Release/dist/*.whl /opt/
FROM builder-${DEVICE} AS builder
@@ -38,6 +79,9 @@ RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv sync --frozen --extra ${DEVICE} --no-dev --no-editable --no-install-project --compile-bytecode --no-progress --active --link-mode copy
RUN if [ "$DEVICE" = "rocm" ]; then \
uv pip install /opt/onnxruntime_rocm-*.whl; \
fi
FROM python:3.11-slim-bookworm@sha256:04cd27899595a99dfe77709d96f08876bf2ee99139ee2f0fe9ac948005034e5b AS prod-cpu
@@ -48,14 +92,14 @@ FROM python:3.13-slim-trixie@sha256:3de9a8d7aedbb7984dc18f2dff178a7850f16c1ae7c3
RUN apt-get update && \
apt-get install --no-install-recommends -yqq ocl-icd-libopencl1 wget && \
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.28.4/intel-igc-core-2_2.28.4+20760_amd64.deb && \
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.28.4/intel-igc-opencl-2_2.28.4+20760_amd64.deb && \
wget -nv https://github.com/intel/compute-runtime/releases/download/26.05.37020.3/intel-opencl-icd_26.05.37020.3-0_amd64.deb && \
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.27.10/intel-igc-core-2_2.27.10+20617_amd64.deb && \
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.27.10/intel-igc-opencl-2_2.27.10+20617_amd64.deb && \
wget -nv https://github.com/intel/compute-runtime/releases/download/26.01.36711.4/intel-opencl-icd_26.01.36711.4-0_amd64.deb && \
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.24/intel-igc-core_1.0.17537.24_amd64.deb && \
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.24/intel-igc-opencl_1.0.17537.24_amd64.deb && \
wget -nv https://github.com/intel/compute-runtime/releases/download/24.35.30872.36/intel-opencl-icd-legacy1_24.35.30872.36_amd64.deb && \
# TODO: Figure out how to get renovate to manage this differently versioned libigdgmm file
wget -nv https://github.com/intel/compute-runtime/releases/download/26.05.37020.3/libigdgmm12_22.9.0_amd64.deb && \
wget -nv https://github.com/intel/compute-runtime/releases/download/26.01.36711.4/libigdgmm12_22.9.0_amd64.deb && \
dpkg -i *.deb && \
rm *.deb && \
apt-get remove wget -yqq && \
@@ -76,11 +120,7 @@ COPY --from=builder-cuda /usr/local/bin/python3 /usr/local/bin/python3
COPY --from=builder-cuda /usr/local/lib/python3.11 /usr/local/lib/python3.11
COPY --from=builder-cuda /usr/local/lib/libpython3.11.so /usr/local/lib/libpython3.11.so
FROM rocm/dev-ubuntu-24.04:7.2-complete@sha256:86e11093b4a7ec2a79b1b6701d10e840a6994f21c7e05929b51eb9be361c683a AS prod-rocm
RUN apt-get update && apt-get install --no-install-recommends -yqq migraphx miopen-hip && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
FROM rocm/dev-ubuntu-24.04:6.4.4-complete@sha256:31418ac10a3769a71eaef330c07280d1d999d7074621339b8f93c484c35f6078 AS prod-rocm
FROM prod-cpu AS prod-armnn
-1
View File
@@ -79,7 +79,6 @@ class Settings(BaseSettings):
preload: PreloadModelData | None = None
max_batch_size: MaxBatchSize | None = None
openvino_precision: ModelPrecision = ModelPrecision.FP32
rocm_precision: ModelPrecision = ModelPrecision.FP32
@property
def device_id(self) -> str:
@@ -90,7 +90,7 @@ _PADDLE_MODELS = {
SUPPORTED_PROVIDERS = [
"CUDAExecutionProvider",
"MIGraphXExecutionProvider",
"ROCMExecutionProvider",
"OpenVINOExecutionProvider",
"CoreMLExecutionProvider",
"CPUExecutionProvider",
+2 -11
View File
@@ -8,7 +8,7 @@ import onnxruntime as ort
from numpy.typing import NDArray
from immich_ml.models.constants import SUPPORTED_PROVIDERS
from immich_ml.schemas import ModelPrecision, SessionNode
from immich_ml.schemas import SessionNode
from ..config import log, settings
@@ -90,17 +90,8 @@ class OrtSession:
match provider:
case "CPUExecutionProvider":
options = {"arena_extend_strategy": "kSameAsRequested"}
case "CUDAExecutionProvider":
case "CUDAExecutionProvider" | "ROCMExecutionProvider":
options = {"arena_extend_strategy": "kSameAsRequested", "device_id": settings.device_id}
case "MIGraphXExecutionProvider":
migraphx_dir = self.model_path.parent / "migraphx"
# MIGraphX does not create the underlying folder and will crash if it does not exist
migraphx_dir.mkdir(parents=True, exist_ok=True)
options = {
"device_id": settings.device_id,
"migraphx_model_cache_dir": migraphx_dir.as_posix(),
"migraphx_fp16_enable": "1" if settings.rocm_precision == ModelPrecision.FP16 else "0",
}
case "OpenVINOExecutionProvider":
openvino_dir = self.model_path.parent / "openvino"
device = f"GPU.{settings.device_id}"
@@ -0,0 +1,179 @@
commit 16839b58d9b3c3162a67ce5d776b36d4d24e801f
Author: mertalev <101130780+mertalev@users.noreply.github.com>
Date: Wed Mar 5 11:25:38 2025 -0500
disable algo caching (attributed to @dmnieto in https://github.com/microsoft/onnxruntime/pull/19567)
diff --git a/onnxruntime/core/providers/rocm/nn/conv.cc b/onnxruntime/core/providers/rocm/nn/conv.cc
index d7f47d07a8..4060a2af52 100644
--- a/onnxruntime/core/providers/rocm/nn/conv.cc
+++ b/onnxruntime/core/providers/rocm/nn/conv.cc
@@ -127,7 +127,6 @@ Status Conv<T, NHWC>::UpdateState(OpKernelContext* context, bool bias_expected)
if (w_dims_changed) {
s_.last_w_dims = gsl::make_span(w_dims);
- s_.cached_benchmark_fwd_results.clear();
}
ORT_RETURN_IF_ERROR(conv_attrs_.ValidateInputShape(X->Shape(), W->Shape(), channels_last, channels_last));
@@ -277,35 +276,6 @@ Status Conv<T, NHWC>::UpdateState(OpKernelContext* context, bool bias_expected)
HIP_CALL_THROW(hipMalloc(&s_.b_zero, malloc_size));
HIP_CALL_THROW(hipMemsetAsync(s_.b_zero, 0, malloc_size, Stream(context)));
}
-
- if (!s_.cached_benchmark_fwd_results.contains(x_dims_miopen)) {
- miopenConvAlgoPerf_t perf;
- int algo_count = 1;
- const ROCMExecutionProvider* rocm_ep = static_cast<const ROCMExecutionProvider*>(this->Info().GetExecutionProvider());
- static constexpr int num_algos = MIOPEN_CONVOLUTION_FWD_ALGO_COUNT;
- size_t max_ws_size = rocm_ep->GetMiopenConvUseMaxWorkspace() ? GetMaxWorkspaceSize(GetMiopenHandle(context), s_, kAllAlgos, num_algos, rocm_ep->GetDeviceId())
- : AlgoSearchWorkspaceSize;
- IAllocatorUniquePtr<void> algo_search_workspace = GetTransientScratchBuffer<void>(max_ws_size);
- MIOPEN_RETURN_IF_ERROR(miopenFindConvolutionForwardAlgorithm(
- GetMiopenHandle(context),
- s_.x_tensor,
- s_.x_data,
- s_.w_desc,
- s_.w_data,
- s_.conv_desc,
- s_.y_tensor,
- s_.y_data,
- 1, // requestedAlgoCount
- &algo_count, // returnedAlgoCount
- &perf,
- algo_search_workspace.get(),
- max_ws_size,
- false)); // Do not do exhaustive algo search.
- s_.cached_benchmark_fwd_results.insert(x_dims_miopen, {perf.fwd_algo, perf.memory});
- }
- const auto& perf = s_.cached_benchmark_fwd_results.at(x_dims_miopen);
- s_.fwd_algo = perf.fwd_algo;
- s_.workspace_bytes = perf.memory;
} else {
// set Y
s_.Y = context->Output(0, TensorShape(s_.y_dims));
@@ -319,6 +289,31 @@ Status Conv<T, NHWC>::UpdateState(OpKernelContext* context, bool bias_expected)
s_.y_data = reinterpret_cast<HipT*>(s_.Y->MutableData<T>());
}
}
+
+ miopenConvAlgoPerf_t perf;
+ int algo_count = 1;
+ const ROCMExecutionProvider* rocm_ep = static_cast<const ROCMExecutionProvider*>(this->Info().GetExecutionProvider());
+ static constexpr int num_algos = MIOPEN_CONVOLUTION_FWD_ALGO_COUNT;
+ size_t max_ws_size = rocm_ep->GetMiopenConvUseMaxWorkspace() ? GetMaxWorkspaceSize(GetMiopenHandle(context), s_, kAllAlgos, num_algos, rocm_ep->GetDeviceId())
+ : AlgoSearchWorkspaceSize;
+ IAllocatorUniquePtr<void> algo_search_workspace = GetTransientScratchBuffer<void>(max_ws_size);
+ MIOPEN_RETURN_IF_ERROR(miopenFindConvolutionForwardAlgorithm(
+ GetMiopenHandle(context),
+ s_.x_tensor,
+ s_.x_data,
+ s_.w_desc,
+ s_.w_data,
+ s_.conv_desc,
+ s_.y_tensor,
+ s_.y_data,
+ 1, // requestedAlgoCount
+ &algo_count, // returnedAlgoCount
+ &perf,
+ algo_search_workspace.get(),
+ max_ws_size,
+ false)); // Do not do exhaustive algo search.
+ s_.fwd_algo = perf.fwd_algo;
+ s_.workspace_bytes = perf.memory;
return Status::OK();
}
diff --git a/onnxruntime/core/providers/rocm/nn/conv.h b/onnxruntime/core/providers/rocm/nn/conv.h
index bc9846203e..d54218f258 100644
--- a/onnxruntime/core/providers/rocm/nn/conv.h
+++ b/onnxruntime/core/providers/rocm/nn/conv.h
@@ -108,9 +108,6 @@ class lru_unordered_map {
list_type lru_list_;
};
-// cached miopen descriptors
-constexpr size_t MAX_CACHED_ALGO_PERF_RESULTS = 10000;
-
template <typename AlgoPerfType>
struct MiopenConvState {
// if x/w dims changed, update algo and miopenTensors
@@ -148,9 +145,6 @@ struct MiopenConvState {
decltype(AlgoPerfType().memory) memory;
};
- lru_unordered_map<TensorShapeVector, PerfFwdResultParams, vector_hash> cached_benchmark_fwd_results{MAX_CACHED_ALGO_PERF_RESULTS};
- lru_unordered_map<TensorShapeVector, PerfBwdResultParams, vector_hash> cached_benchmark_bwd_results{MAX_CACHED_ALGO_PERF_RESULTS};
-
// Some properties needed to support asymmetric padded Conv nodes
bool post_slicing_required;
TensorShapeVector slice_starts;
diff --git a/onnxruntime/core/providers/rocm/nn/conv_transpose.cc b/onnxruntime/core/providers/rocm/nn/conv_transpose.cc
index 7447113fdf..a662e35b2e 100644
--- a/onnxruntime/core/providers/rocm/nn/conv_transpose.cc
+++ b/onnxruntime/core/providers/rocm/nn/conv_transpose.cc
@@ -76,7 +76,6 @@ Status ConvTranspose<T, NHWC>::DoConvTranspose(OpKernelContext* context, bool dy
if (w_dims_changed) {
s_.last_w_dims = gsl::make_span(w_dims);
- s_.cached_benchmark_bwd_results.clear();
}
ConvTransposeAttributes::Prepare p;
@@ -126,35 +125,29 @@ Status ConvTranspose<T, NHWC>::DoConvTranspose(OpKernelContext* context, bool dy
}
y_data = reinterpret_cast<HipT*>(p.Y->MutableData<T>());
-
- if (!s_.cached_benchmark_bwd_results.contains(x_dims)) {
- IAllocatorUniquePtr<void> algo_search_workspace = GetScratchBuffer<void>(AlgoSearchWorkspaceSize, context->GetComputeStream());
-
- miopenConvAlgoPerf_t perf;
- int algo_count = 1;
- MIOPEN_RETURN_IF_ERROR(miopenFindConvolutionBackwardDataAlgorithm(
- GetMiopenHandle(context),
- s_.x_tensor,
- x_data,
- s_.w_desc,
- w_data,
- s_.conv_desc,
- s_.y_tensor,
- y_data,
- 1,
- &algo_count,
- &perf,
- algo_search_workspace.get(),
- AlgoSearchWorkspaceSize,
- false));
- s_.cached_benchmark_bwd_results.insert(x_dims, {perf.bwd_data_algo, perf.memory});
- }
-
- const auto& perf = s_.cached_benchmark_bwd_results.at(x_dims);
- s_.bwd_data_algo = perf.bwd_data_algo;
- s_.workspace_bytes = perf.memory;
}
+ IAllocatorUniquePtr<void> algo_search_workspace = GetScratchBuffer<void>(AlgoSearchWorkspaceSize, context->GetComputeStream());
+ miopenConvAlgoPerf_t perf;
+ int algo_count = 1;
+ MIOPEN_RETURN_IF_ERROR(miopenFindConvolutionBackwardDataAlgorithm(
+ GetMiopenHandle(context),
+ s_.x_tensor,
+ x_data,
+ s_.w_desc,
+ w_data,
+ s_.conv_desc,
+ s_.y_tensor,
+ y_data,
+ 1,
+ &algo_count,
+ &perf,
+ algo_search_workspace.get(),
+ AlgoSearchWorkspaceSize,
+ false));
+ s_.bwd_data_algo = perf.bwd_data_algo;
+ s_.workspace_bytes = perf.memory;
+
// The following block will be executed in case there has been no change in the shapes of the
// input and the filter compared to the previous run
if (!y_data) {
@@ -0,0 +1,33 @@
diff --git a/dockerfiles/scripts/install_common_deps.sh b/dockerfiles/scripts/install_common_deps.sh
index bbb672a99e..0dc652fbda 100644
--- a/dockerfiles/scripts/install_common_deps.sh
+++ b/dockerfiles/scripts/install_common_deps.sh
@@ -8,16 +8,23 @@ apt-get update && apt-get install -y --no-install-recommends \
curl \
libcurl4-openssl-dev \
libssl-dev \
- python3-dev
+ python3-dev \
+ ccache
# Dependencies: conda
-wget --quiet https://repo.anaconda.com/miniconda/Miniconda3-4.5.11-Linux-x86_64.sh -O ~/miniconda.sh --no-check-certificate && /bin/bash ~/miniconda.sh -b -p /opt/miniconda
+wget --quiet https://repo.anaconda.com/miniconda/Miniconda3-py312_25.9.1-1-Linux-x86_64.sh -O ~/miniconda.sh && /bin/bash ~/miniconda.sh -b -p /opt/miniconda
rm ~/miniconda.sh
/opt/miniconda/bin/conda clean -ya
-pip install numpy
-pip install packaging
-pip install "wheel>=0.35.1"
+# Dependencies: venv and packages
+/opt/miniconda/bin/python3 -m venv /opt/rocm-venv
+/opt/rocm-venv/bin/pip install --no-cache-dir --upgrade pip
+/opt/rocm-venv/bin/pip install --no-cache-dir \
+ "numpy==2.3.4" \
+ "packaging==25.0" \
+ "wheel==0.45.1" \
+ "setuptools==80.9.0"
+
rm -rf /opt/miniconda/pkgs
# Dependencies: cmake
+2 -2
View File
@@ -49,10 +49,10 @@ dev = ["locust>=2.15.1", { include-group = "test" }, { include-group = "lint" }]
[project.optional-dependencies]
cpu = ["onnxruntime>=1.23.2,<2"]
cuda = ["onnxruntime-gpu>=1.23.2,<2"]
openvino = ["onnxruntime-openvino>=1.24.1,<2"]
openvino = ["onnxruntime-openvino>=1.23.0,<2"]
armnn = ["onnxruntime>=1.23.2,<2"]
rknn = ["onnxruntime>=1.23.2,<2", "rknn-toolkit-lite2>=2.3.0,<3"]
rocm = ["onnxruntime-migraphx>=1.23.2,<2"]
rocm = []
[tool.uv]
compile-bytecode = true
+4 -30
View File
@@ -179,7 +179,7 @@ class TestOrtSession:
OV_EP = ["OpenVINOExecutionProvider", "CPUExecutionProvider"]
CUDA_EP_OUT_OF_ORDER = ["CPUExecutionProvider", "CUDAExecutionProvider"]
TRT_EP = ["TensorrtExecutionProvider", "CUDAExecutionProvider", "CPUExecutionProvider"]
ROCM_EP = ["MIGraphXExecutionProvider", "CPUExecutionProvider"]
ROCM_EP = ["ROCMExecutionProvider", "CPUExecutionProvider"]
COREML_EP = ["CoreMLExecutionProvider", "CPUExecutionProvider"]
@pytest.mark.providers(CPU_EP)
@@ -289,38 +289,12 @@ class TestOrtSession:
assert session.provider_options == [{"arena_extend_strategy": "kSameAsRequested", "device_id": "1"}]
def test_sets_provider_options_for_rocm(self, mocker: MockerFixture) -> None:
model_path = "/cache/ViT-B-32__openai/textual/model.onnx"
def test_sets_provider_options_for_rocm(self) -> None:
os.environ["MACHINE_LEARNING_DEVICE_ID"] = "1"
mkdir = mocker.patch("immich_ml.sessions.ort.Path.mkdir")
session = OrtSession(model_path, providers=["MIGraphXExecutionProvider"])
session = OrtSession("ViT-B-32__openai", providers=["ROCMExecutionProvider"])
assert session.provider_options == [
{
"device_id": "1",
"migraphx_model_cache_dir": "/cache/ViT-B-32__openai/textual/migraphx",
"migraphx_fp16_enable": "0",
}
]
mkdir.assert_called_once_with(parents=True, exist_ok=True)
def test_sets_rocm_to_fp16_if_enabled(self, path: mock.Mock, mocker: MockerFixture) -> None:
model_path = "/cache/ViT-B-32__openai/textual/model.onnx"
os.environ["MACHINE_LEARNING_DEVICE_ID"] = "1"
mocker.patch.object(settings, "rocm_precision", ModelPrecision.FP16)
mkdir = mocker.patch("immich_ml.sessions.ort.Path.mkdir")
session = OrtSession(model_path, providers=["MIGraphXExecutionProvider"])
assert session.provider_options == [
{
"device_id": "1",
"migraphx_model_cache_dir": "/cache/ViT-B-32__openai/textual/migraphx",
"migraphx_fp16_enable": "1",
}
]
mkdir.assert_called_once_with(parents=True, exist_ok=True)
assert session.provider_options == [{"arena_extend_strategy": "kSameAsRequested", "device_id": "1"}]
def test_sets_provider_options_kwarg(self) -> None:
session = OrtSession(
+83 -80
View File
@@ -262,6 +262,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
]
[[package]]
name = "coloredlogs"
version = "15.0.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "humanfriendly" },
]
sdist = { url = "https://files.pythonhosted.org/packages/cc/c7/eed8f27100517e8c0e6b923d5f0845d0cb99763da6fdee00478f91db7325/coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0", size = 278520, upload-time = "2021-06-11T10:22:45.202Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a7/06/3d6badcf13db419e25b07041d9c7b4a2c331d3f4e7134445ec5df57714cd/coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934", size = 46018, upload-time = "2021-06-11T10:22:42.561Z" },
]
[[package]]
name = "colorlog"
version = "6.9.0"
@@ -642,6 +654,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/70/25/fab23259a52ece5670dcb8452e1af34b89e6135ecc17cd4b54b4b479eac6/fsspec-2023.12.2-py3-none-any.whl", hash = "sha256:d800d87f72189a745fa3d6b033b9dc4a34ad069f60ca60b943a63599f5501960", size = 168979, upload-time = "2023-12-11T21:19:52.446Z" },
]
[[package]]
name = "ftfy"
version = "6.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "wcwidth" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a5/d3/8650919bc3c7c6e90ee3fa7fd618bf373cbbe55dff043bd67353dbb20cd8/ftfy-6.3.1.tar.gz", hash = "sha256:9b3c3d90f84fb267fe64d375a07b7f8912d817cf86009ae134aa03e1819506ec", size = 308927, upload-time = "2024-10-26T00:50:35.149Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ab/6e/81d47999aebc1b155f81eca4477a616a70f238a2549848c38983f3c22a82/ftfy-6.3.1-py3-none-any.whl", hash = "sha256:7c70eb532015cd2f9adb53f101fb6c7945988d023a085d127d1573dc49dd0083", size = 44821, upload-time = "2024-10-26T00:50:33.425Z" },
]
[[package]]
name = "gevent"
version = "24.10.3"
@@ -764,14 +788,14 @@ wheels = [
[[package]]
name = "gunicorn"
version = "25.1.0"
version = "23.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "packaging" },
]
sdist = { url = "https://files.pythonhosted.org/packages/66/13/ef67f59f6a7896fdc2c1d62b5665c5219d6b0a9a1784938eb9a28e55e128/gunicorn-25.1.0.tar.gz", hash = "sha256:1426611d959fa77e7de89f8c0f32eed6aa03ee735f98c01efba3e281b1c47616", size = 594377, upload-time = "2026-02-13T11:09:58.989Z" }
sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031, upload-time = "2024-08-10T20:25:27.378Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/da/73/4ad5b1f6a2e21cf1e85afdaad2b7b1a933985e2f5d679147a1953aaa192c/gunicorn-25.1.0-py3-none-any.whl", hash = "sha256:d0b1236ccf27f72cfe14bce7caadf467186f19e865094ca84221424e839b8b8b", size = 197067, upload-time = "2026-02-13T11:09:57.146Z" },
{ url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029, upload-time = "2024-08-10T20:25:24.996Z" },
]
[[package]]
@@ -874,6 +898,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/a8/af/48ac8483240de756d2438c380746e7130d1c6f75802ef22f3c6d49982787/huggingface_hub-0.36.2-py3-none-any.whl", hash = "sha256:48f0c8eac16145dfce371e9d2d7772854a4f591bcb56c9cf548accf531d54270", size = 566395, upload-time = "2026-02-06T09:24:11.133Z" },
]
[[package]]
name = "humanfriendly"
version = "10.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyreadline3", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/cc/3f/2c29224acb2e2df4d2046e4c73ee2662023c58ff5b113c4c1adac0886c43/humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc", size = 360702, upload-time = "2021-09-17T21:40:43.31Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", size = 86794, upload-time = "2021-09-17T21:40:39.897Z" },
]
[[package]]
name = "idna"
version = "3.11"
@@ -903,6 +939,7 @@ source = { editable = "." }
dependencies = [
{ name = "aiocache" },
{ name = "fastapi" },
{ name = "ftfy" },
{ name = "gunicorn" },
{ name = "huggingface-hub" },
{ name = "insightface" },
@@ -936,9 +973,6 @@ rknn = [
{ name = "onnxruntime" },
{ name = "rknn-toolkit-lite2" },
]
rocm = [
{ name = "onnxruntime-migraphx" },
]
[package.dev-dependencies]
dev = [
@@ -984,6 +1018,7 @@ types = [
requires-dist = [
{ name = "aiocache", specifier = ">=0.12.1,<1.0" },
{ name = "fastapi", specifier = ">=0.95.2,<1.0" },
{ name = "ftfy", specifier = ">=6.1.1" },
{ name = "gunicorn", specifier = ">=21.1.0" },
{ name = "huggingface-hub", specifier = ">=0.20.1,<1.0" },
{ name = "insightface", specifier = ">=0.7.3,<1.0" },
@@ -992,8 +1027,7 @@ requires-dist = [
{ name = "onnxruntime", marker = "extra == 'cpu'", specifier = ">=1.23.2,<2" },
{ name = "onnxruntime", marker = "extra == 'rknn'", specifier = ">=1.23.2,<2" },
{ name = "onnxruntime-gpu", marker = "extra == 'cuda'", specifier = ">=1.23.2,<2" },
{ name = "onnxruntime-migraphx", marker = "extra == 'rocm'", specifier = ">=1.23.2,<2" },
{ name = "onnxruntime-openvino", marker = "extra == 'openvino'", specifier = ">=1.24.1,<2" },
{ name = "onnxruntime-openvino", marker = "extra == 'openvino'", specifier = ">=1.23.0,<2" },
{ name = "opencv-python-headless", specifier = ">=4.7.0.72,<5.0" },
{ name = "orjson", specifier = ">=3.9.5" },
{ name = "pillow", specifier = ">=12.1.1,<12.2" },
@@ -1409,55 +1443,32 @@ wheels = [
[[package]]
name = "msgpack"
version = "1.1.2"
version = "1.0.7"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/4d/f2/bfb55a6236ed8725a96b0aa3acbd0ec17588e6a2c3b62a93eb513ed8783f/msgpack-1.1.2.tar.gz", hash = "sha256:3b60763c1373dd60f398488069bcdc703cd08a711477b5d480eecc9f9626f47e", size = 173581, upload-time = "2025-10-08T09:15:56.596Z" }
sdist = { url = "https://files.pythonhosted.org/packages/c2/d5/5662032db1571110b5b51647aed4b56dfbd01bfae789fa566a2be1f385d1/msgpack-1.0.7.tar.gz", hash = "sha256:572efc93db7a4d27e404501975ca6d2d9775705c2d922390d878fcf768d92c87", size = 166311, upload-time = "2023-09-28T13:20:36.726Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2c/97/560d11202bcd537abca693fd85d81cebe2107ba17301de42b01ac1677b69/msgpack-1.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2e86a607e558d22985d856948c12a3fa7b42efad264dca8a3ebbcfa2735d786c", size = 82271, upload-time = "2025-10-08T09:14:49.967Z" },
{ url = "https://files.pythonhosted.org/packages/83/04/28a41024ccbd67467380b6fb440ae916c1e4f25e2cd4c63abe6835ac566e/msgpack-1.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:283ae72fc89da59aa004ba147e8fc2f766647b1251500182fac0350d8af299c0", size = 84914, upload-time = "2025-10-08T09:14:50.958Z" },
{ url = "https://files.pythonhosted.org/packages/71/46/b817349db6886d79e57a966346cf0902a426375aadc1e8e7a86a75e22f19/msgpack-1.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:61c8aa3bd513d87c72ed0b37b53dd5c5a0f58f2ff9f26e1555d3bd7948fb7296", size = 416962, upload-time = "2025-10-08T09:14:51.997Z" },
{ url = "https://files.pythonhosted.org/packages/da/e0/6cc2e852837cd6086fe7d8406af4294e66827a60a4cf60b86575a4a65ca8/msgpack-1.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:454e29e186285d2ebe65be34629fa0e8605202c60fbc7c4c650ccd41870896ef", size = 426183, upload-time = "2025-10-08T09:14:53.477Z" },
{ url = "https://files.pythonhosted.org/packages/25/98/6a19f030b3d2ea906696cedd1eb251708e50a5891d0978b012cb6107234c/msgpack-1.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7bc8813f88417599564fafa59fd6f95be417179f76b40325b500b3c98409757c", size = 411454, upload-time = "2025-10-08T09:14:54.648Z" },
{ url = "https://files.pythonhosted.org/packages/b7/cd/9098fcb6adb32187a70b7ecaabf6339da50553351558f37600e53a4a2a23/msgpack-1.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bafca952dc13907bdfdedfc6a5f579bf4f292bdd506fadb38389afa3ac5b208e", size = 422341, upload-time = "2025-10-08T09:14:56.328Z" },
{ url = "https://files.pythonhosted.org/packages/e6/ae/270cecbcf36c1dc85ec086b33a51a4d7d08fc4f404bdbc15b582255d05ff/msgpack-1.1.2-cp311-cp311-win32.whl", hash = "sha256:602b6740e95ffc55bfb078172d279de3773d7b7db1f703b2f1323566b878b90e", size = 64747, upload-time = "2025-10-08T09:14:57.882Z" },
{ url = "https://files.pythonhosted.org/packages/2a/79/309d0e637f6f37e83c711f547308b91af02b72d2326ddd860b966080ef29/msgpack-1.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:d198d275222dc54244bf3327eb8cbe00307d220241d9cec4d306d49a44e85f68", size = 71633, upload-time = "2025-10-08T09:14:59.177Z" },
{ url = "https://files.pythonhosted.org/packages/73/4d/7c4e2b3d9b1106cd0aa6cb56cc57c6267f59fa8bfab7d91df5adc802c847/msgpack-1.1.2-cp311-cp311-win_arm64.whl", hash = "sha256:86f8136dfa5c116365a8a651a7d7484b65b13339731dd6faebb9a0242151c406", size = 64755, upload-time = "2025-10-08T09:15:00.48Z" },
{ url = "https://files.pythonhosted.org/packages/ad/bd/8b0d01c756203fbab65d265859749860682ccd2a59594609aeec3a144efa/msgpack-1.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:70a0dff9d1f8da25179ffcf880e10cf1aad55fdb63cd59c9a49a1b82290062aa", size = 81939, upload-time = "2025-10-08T09:15:01.472Z" },
{ url = "https://files.pythonhosted.org/packages/34/68/ba4f155f793a74c1483d4bdef136e1023f7bcba557f0db4ef3db3c665cf1/msgpack-1.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:446abdd8b94b55c800ac34b102dffd2f6aa0ce643c55dfc017ad89347db3dbdb", size = 85064, upload-time = "2025-10-08T09:15:03.764Z" },
{ url = "https://files.pythonhosted.org/packages/f2/60/a064b0345fc36c4c3d2c743c82d9100c40388d77f0b48b2f04d6041dbec1/msgpack-1.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c63eea553c69ab05b6747901b97d620bb2a690633c77f23feb0c6a947a8a7b8f", size = 417131, upload-time = "2025-10-08T09:15:05.136Z" },
{ url = "https://files.pythonhosted.org/packages/65/92/a5100f7185a800a5d29f8d14041f61475b9de465ffcc0f3b9fba606e4505/msgpack-1.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:372839311ccf6bdaf39b00b61288e0557916c3729529b301c52c2d88842add42", size = 427556, upload-time = "2025-10-08T09:15:06.837Z" },
{ url = "https://files.pythonhosted.org/packages/f5/87/ffe21d1bf7d9991354ad93949286f643b2bb6ddbeab66373922b44c3b8cc/msgpack-1.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2929af52106ca73fcb28576218476ffbb531a036c2adbcf54a3664de124303e9", size = 404920, upload-time = "2025-10-08T09:15:08.179Z" },
{ url = "https://files.pythonhosted.org/packages/ff/41/8543ed2b8604f7c0d89ce066f42007faac1eaa7d79a81555f206a5cdb889/msgpack-1.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be52a8fc79e45b0364210eef5234a7cf8d330836d0a64dfbb878efa903d84620", size = 415013, upload-time = "2025-10-08T09:15:09.83Z" },
{ url = "https://files.pythonhosted.org/packages/41/0d/2ddfaa8b7e1cee6c490d46cb0a39742b19e2481600a7a0e96537e9c22f43/msgpack-1.1.2-cp312-cp312-win32.whl", hash = "sha256:1fff3d825d7859ac888b0fbda39a42d59193543920eda9d9bea44d958a878029", size = 65096, upload-time = "2025-10-08T09:15:11.11Z" },
{ url = "https://files.pythonhosted.org/packages/8c/ec/d431eb7941fb55a31dd6ca3404d41fbb52d99172df2e7707754488390910/msgpack-1.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:1de460f0403172cff81169a30b9a92b260cb809c4cb7e2fc79ae8d0510c78b6b", size = 72708, upload-time = "2025-10-08T09:15:12.554Z" },
{ url = "https://files.pythonhosted.org/packages/c5/31/5b1a1f70eb0e87d1678e9624908f86317787b536060641d6798e3cf70ace/msgpack-1.1.2-cp312-cp312-win_arm64.whl", hash = "sha256:be5980f3ee0e6bd44f3a9e9dea01054f175b50c3e6cdb692bc9424c0bbb8bf69", size = 64119, upload-time = "2025-10-08T09:15:13.589Z" },
{ url = "https://files.pythonhosted.org/packages/6b/31/b46518ecc604d7edf3a4f94cb3bf021fc62aa301f0cb849936968164ef23/msgpack-1.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4efd7b5979ccb539c221a4c4e16aac1a533efc97f3b759bb5a5ac9f6d10383bf", size = 81212, upload-time = "2025-10-08T09:15:14.552Z" },
{ url = "https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:42eefe2c3e2af97ed470eec850facbe1b5ad1d6eacdbadc42ec98e7dcf68b4b7", size = 84315, upload-time = "2025-10-08T09:15:15.543Z" },
{ url = "https://files.pythonhosted.org/packages/d3/68/93180dce57f684a61a88a45ed13047558ded2be46f03acb8dec6d7c513af/msgpack-1.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1fdf7d83102bf09e7ce3357de96c59b627395352a4024f6e2458501f158bf999", size = 412721, upload-time = "2025-10-08T09:15:16.567Z" },
{ url = "https://files.pythonhosted.org/packages/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fac4be746328f90caa3cd4bc67e6fe36ca2bf61d5c6eb6d895b6527e3f05071e", size = 424657, upload-time = "2025-10-08T09:15:17.825Z" },
{ url = "https://files.pythonhosted.org/packages/38/f8/4398c46863b093252fe67368b44edc6c13b17f4e6b0e4929dbf0bdb13f23/msgpack-1.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fffee09044073e69f2bad787071aeec727183e7580443dfeb8556cbf1978d162", size = 402668, upload-time = "2025-10-08T09:15:19.003Z" },
{ url = "https://files.pythonhosted.org/packages/28/ce/698c1eff75626e4124b4d78e21cca0b4cc90043afb80a507626ea354ab52/msgpack-1.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5928604de9b032bc17f5099496417f113c45bc6bc21b5c6920caf34b3c428794", size = 419040, upload-time = "2025-10-08T09:15:20.183Z" },
{ url = "https://files.pythonhosted.org/packages/67/32/f3cd1667028424fa7001d82e10ee35386eea1408b93d399b09fb0aa7875f/msgpack-1.1.2-cp313-cp313-win32.whl", hash = "sha256:a7787d353595c7c7e145e2331abf8b7ff1e6673a6b974ded96e6d4ec09f00c8c", size = 65037, upload-time = "2025-10-08T09:15:21.416Z" },
{ url = "https://files.pythonhosted.org/packages/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:a465f0dceb8e13a487e54c07d04ae3ba131c7c5b95e2612596eafde1dccf64a9", size = 72631, upload-time = "2025-10-08T09:15:22.431Z" },
{ url = "https://files.pythonhosted.org/packages/e5/db/0314e4e2db56ebcf450f277904ffd84a7988b9e5da8d0d61ab2d057df2b6/msgpack-1.1.2-cp313-cp313-win_arm64.whl", hash = "sha256:e69b39f8c0aa5ec24b57737ebee40be647035158f14ed4b40e6f150077e21a84", size = 64118, upload-time = "2025-10-08T09:15:23.402Z" },
{ url = "https://files.pythonhosted.org/packages/22/71/201105712d0a2ff07b7873ed3c220292fb2ea5120603c00c4b634bcdafb3/msgpack-1.1.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e23ce8d5f7aa6ea6d2a2b326b4ba46c985dbb204523759984430db7114f8aa00", size = 81127, upload-time = "2025-10-08T09:15:24.408Z" },
{ url = "https://files.pythonhosted.org/packages/1b/9f/38ff9e57a2eade7bf9dfee5eae17f39fc0e998658050279cbb14d97d36d9/msgpack-1.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6c15b7d74c939ebe620dd8e559384be806204d73b4f9356320632d783d1f7939", size = 84981, upload-time = "2025-10-08T09:15:25.812Z" },
{ url = "https://files.pythonhosted.org/packages/8e/a9/3536e385167b88c2cc8f4424c49e28d49a6fc35206d4a8060f136e71f94c/msgpack-1.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99e2cb7b9031568a2a5c73aa077180f93dd2e95b4f8d3b8e14a73ae94a9e667e", size = 411885, upload-time = "2025-10-08T09:15:27.22Z" },
{ url = "https://files.pythonhosted.org/packages/2f/40/dc34d1a8d5f1e51fc64640b62b191684da52ca469da9cd74e84936ffa4a6/msgpack-1.1.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:180759d89a057eab503cf62eeec0aa61c4ea1200dee709f3a8e9397dbb3b6931", size = 419658, upload-time = "2025-10-08T09:15:28.4Z" },
{ url = "https://files.pythonhosted.org/packages/3b/ef/2b92e286366500a09a67e03496ee8b8ba00562797a52f3c117aa2b29514b/msgpack-1.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:04fb995247a6e83830b62f0b07bf36540c213f6eac8e851166d8d86d83cbd014", size = 403290, upload-time = "2025-10-08T09:15:29.764Z" },
{ url = "https://files.pythonhosted.org/packages/78/90/e0ea7990abea5764e4655b8177aa7c63cdfa89945b6e7641055800f6c16b/msgpack-1.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8e22ab046fa7ede9e36eeb4cfad44d46450f37bb05d5ec482b02868f451c95e2", size = 415234, upload-time = "2025-10-08T09:15:31.022Z" },
{ url = "https://files.pythonhosted.org/packages/72/4e/9390aed5db983a2310818cd7d3ec0aecad45e1f7007e0cda79c79507bb0d/msgpack-1.1.2-cp314-cp314-win32.whl", hash = "sha256:80a0ff7d4abf5fecb995fcf235d4064b9a9a8a40a3ab80999e6ac1e30b702717", size = 66391, upload-time = "2025-10-08T09:15:32.265Z" },
{ url = "https://files.pythonhosted.org/packages/6e/f1/abd09c2ae91228c5f3998dbd7f41353def9eac64253de3c8105efa2082f7/msgpack-1.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:9ade919fac6a3e7260b7f64cea89df6bec59104987cbea34d34a2fa15d74310b", size = 73787, upload-time = "2025-10-08T09:15:33.219Z" },
{ url = "https://files.pythonhosted.org/packages/6a/b0/9d9f667ab48b16ad4115c1935d94023b82b3198064cb84a123e97f7466c1/msgpack-1.1.2-cp314-cp314-win_arm64.whl", hash = "sha256:59415c6076b1e30e563eb732e23b994a61c159cec44deaf584e5cc1dd662f2af", size = 66453, upload-time = "2025-10-08T09:15:34.225Z" },
{ url = "https://files.pythonhosted.org/packages/16/67/93f80545eb1792b61a217fa7f06d5e5cb9e0055bed867f43e2b8e012e137/msgpack-1.1.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:897c478140877e5307760b0ea66e0932738879e7aa68144d9b78ea4c8302a84a", size = 85264, upload-time = "2025-10-08T09:15:35.61Z" },
{ url = "https://files.pythonhosted.org/packages/87/1c/33c8a24959cf193966ef11a6f6a2995a65eb066bd681fd085afd519a57ce/msgpack-1.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a668204fa43e6d02f89dbe79a30b0d67238d9ec4c5bd8a940fc3a004a47b721b", size = 89076, upload-time = "2025-10-08T09:15:36.619Z" },
{ url = "https://files.pythonhosted.org/packages/fc/6b/62e85ff7193663fbea5c0254ef32f0c77134b4059f8da89b958beb7696f3/msgpack-1.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5559d03930d3aa0f3aacb4c42c776af1a2ace2611871c84a75afe436695e6245", size = 435242, upload-time = "2025-10-08T09:15:37.647Z" },
{ url = "https://files.pythonhosted.org/packages/c1/47/5c74ecb4cc277cf09f64e913947871682ffa82b3b93c8dad68083112f412/msgpack-1.1.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:70c5a7a9fea7f036b716191c29047374c10721c389c21e9ffafad04df8c52c90", size = 432509, upload-time = "2025-10-08T09:15:38.794Z" },
{ url = "https://files.pythonhosted.org/packages/24/a4/e98ccdb56dc4e98c929a3f150de1799831c0a800583cde9fa022fa90602d/msgpack-1.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f2cb069d8b981abc72b41aea1c580ce92d57c673ec61af4c500153a626cb9e20", size = 415957, upload-time = "2025-10-08T09:15:40.238Z" },
{ url = "https://files.pythonhosted.org/packages/da/28/6951f7fb67bc0a4e184a6b38ab71a92d9ba58080b27a77d3e2fb0be5998f/msgpack-1.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d62ce1f483f355f61adb5433ebfd8868c5f078d1a52d042b0a998682b4fa8c27", size = 422910, upload-time = "2025-10-08T09:15:41.505Z" },
{ url = "https://files.pythonhosted.org/packages/f0/03/42106dcded51f0a0b5284d3ce30a671e7bd3f7318d122b2ead66ad289fed/msgpack-1.1.2-cp314-cp314t-win32.whl", hash = "sha256:1d1418482b1ee984625d88aa9585db570180c286d942da463533b238b98b812b", size = 75197, upload-time = "2025-10-08T09:15:42.954Z" },
{ url = "https://files.pythonhosted.org/packages/15/86/d0071e94987f8db59d4eeb386ddc64d0bb9b10820a8d82bcd3e53eeb2da6/msgpack-1.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:5a46bf7e831d09470ad92dff02b8b1ac92175ca36b087f904a0519857c6be3ff", size = 85772, upload-time = "2025-10-08T09:15:43.954Z" },
{ url = "https://files.pythonhosted.org/packages/81/f2/08ace4142eb281c12701fc3b93a10795e4d4dc7f753911d836675050f886/msgpack-1.1.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d99ef64f349d5ec3293688e91486c5fdb925ed03807f64d98d205d2713c60b46", size = 70868, upload-time = "2025-10-08T09:15:44.959Z" },
{ url = "https://files.pythonhosted.org/packages/f9/b3/309de40dc7406b7f3492332c5ee2b492a593c2a9bb97ea48ebf2f5279999/msgpack-1.0.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:576eb384292b139821c41995523654ad82d1916da6a60cff129c715a6223ea84", size = 305096, upload-time = "2023-09-28T13:18:49.678Z" },
{ url = "https://files.pythonhosted.org/packages/15/56/a677cd761a2cefb2e3ffe7e684633294dccb161d78e8ea6da9277e45b4a2/msgpack-1.0.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:730076207cb816138cf1af7f7237b208340a2c5e749707457d70705715c93b93", size = 235210, upload-time = "2023-09-28T13:18:51.039Z" },
{ url = "https://files.pythonhosted.org/packages/f5/4e/1ab4a982cbd90f988e49f849fc1212f2c04a59870c59daabf8950617e2aa/msgpack-1.0.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:85765fdf4b27eb5086f05ac0491090fc76f4f2b28e09d9350c31aac25a5aaff8", size = 231952, upload-time = "2023-09-28T13:18:52.871Z" },
{ url = "https://files.pythonhosted.org/packages/6d/74/bd02044eb628c7361ad2bd8c1a6147af5c6c2bbceb77b3b1da20f4a8a9c5/msgpack-1.0.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3476fae43db72bd11f29a5147ae2f3cb22e2f1a91d575ef130d2bf49afd21c46", size = 549511, upload-time = "2023-09-28T13:18:54.422Z" },
{ url = "https://files.pythonhosted.org/packages/df/09/dee50913ba5cc047f7fd7162f09453a676e7935c84b3bf3a398e12108677/msgpack-1.0.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d4c80667de2e36970ebf74f42d1088cc9ee7ef5f4e8c35eee1b40eafd33ca5b", size = 557980, upload-time = "2023-09-28T13:18:56.058Z" },
{ url = "https://files.pythonhosted.org/packages/26/a5/78a7d87f5f8ffe4c32167afa15d4957db649bab4822f909d8d765339bbab/msgpack-1.0.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b0bf0effb196ed76b7ad883848143427a73c355ae8e569fa538365064188b8e", size = 545547, upload-time = "2023-09-28T13:18:57.396Z" },
{ url = "https://files.pythonhosted.org/packages/d4/53/698c10913947f97f6fe7faad86a34e6aa1b66cea2df6f99105856bd346d9/msgpack-1.0.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f9a7c509542db4eceed3dcf21ee5267ab565a83555c9b88a8109dcecc4709002", size = 554669, upload-time = "2023-09-28T13:18:58.957Z" },
{ url = "https://files.pythonhosted.org/packages/f5/3f/9730c6cb574b15d349b80cd8523a7df4b82058528339f952ea1c32ac8a10/msgpack-1.0.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:84b0daf226913133f899ea9b30618722d45feffa67e4fe867b0b5ae83a34060c", size = 583353, upload-time = "2023-09-28T13:19:01.186Z" },
{ url = "https://files.pythonhosted.org/packages/4c/bc/dc184d943692671149848438fb3bed3a3de288ce7998cb91bc98f40f201b/msgpack-1.0.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ec79ff6159dffcc30853b2ad612ed572af86c92b5168aa3fc01a67b0fa40665e", size = 557455, upload-time = "2023-09-28T13:19:03.201Z" },
{ url = "https://files.pythonhosted.org/packages/cf/7b/1bc69d4a56c8d2f4f2dfbe4722d40344af9a85b6fb3b09cfb350ba6a42f6/msgpack-1.0.7-cp311-cp311-win32.whl", hash = "sha256:3e7bf4442b310ff154b7bb9d81eb2c016b7d597e364f97d72b1acc3817a0fdc1", size = 216367, upload-time = "2023-09-28T13:19:04.554Z" },
{ url = "https://files.pythonhosted.org/packages/b4/3d/c8dd23050eefa3d9b9c5b8329ed3308c2f2f80f65825e9ea4b7fa621cdab/msgpack-1.0.7-cp311-cp311-win_amd64.whl", hash = "sha256:3f0c8c6dfa6605ab8ff0611995ee30d4f9fcff89966cf562733b4008a3d60d82", size = 222860, upload-time = "2023-09-28T13:19:06.397Z" },
{ url = "https://files.pythonhosted.org/packages/d7/47/20dff6b4512cf3575550c8801bc53fe7d540f4efef9c5c37af51760fcdcf/msgpack-1.0.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f0936e08e0003f66bfd97e74ee530427707297b0d0361247e9b4f59ab78ddc8b", size = 305759, upload-time = "2023-09-28T13:19:08.148Z" },
{ url = "https://files.pythonhosted.org/packages/6f/8a/34f1726d2c9feccec3d946776e9bce8f20ae09d8b91899fc20b296c942af/msgpack-1.0.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:98bbd754a422a0b123c66a4c341de0474cad4a5c10c164ceed6ea090f3563db4", size = 235330, upload-time = "2023-09-28T13:19:09.417Z" },
{ url = "https://files.pythonhosted.org/packages/9c/f6/e64c72577d6953789c3cb051b059a4b56317056b3c65013952338ed8a34e/msgpack-1.0.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b291f0ee7961a597cbbcc77709374087fa2a9afe7bdb6a40dbbd9b127e79afee", size = 232537, upload-time = "2023-09-28T13:19:10.898Z" },
{ url = "https://files.pythonhosted.org/packages/89/75/1ed3a96e12941873fd957e016cc40c0c178861a872bd45e75b9a188eb422/msgpack-1.0.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebbbba226f0a108a7366bf4b59bf0f30a12fd5e75100c630267d94d7f0ad20e5", size = 546561, upload-time = "2023-09-28T13:19:12.779Z" },
{ url = "https://files.pythonhosted.org/packages/e5/0a/c6a1390f9c6a31da0fecbbfdb86b1cb39ad302d9e24f9cca3d9e14c364f0/msgpack-1.0.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e2d69948e4132813b8d1131f29f9101bc2c915f26089a6d632001a5c1349672", size = 559009, upload-time = "2023-09-28T13:19:14.373Z" },
{ url = "https://files.pythonhosted.org/packages/a5/74/99f6077754665613ea1f37b3d91c10129f6976b7721ab4d0973023808e5a/msgpack-1.0.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdf38ba2d393c7911ae989c3bbba510ebbcdf4ecbdbfec36272abe350c454075", size = 543882, upload-time = "2023-09-28T13:19:16.277Z" },
{ url = "https://files.pythonhosted.org/packages/9c/7e/dc0dc8de2bf27743b31691149258f9b1bd4bf3c44c105df3df9b97081cd1/msgpack-1.0.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:993584fc821c58d5993521bfdcd31a4adf025c7d745bbd4d12ccfecf695af5ba", size = 546949, upload-time = "2023-09-28T13:19:18.114Z" },
{ url = "https://files.pythonhosted.org/packages/78/61/91bae9474def032f6c333d62889bbeda9e1554c6b123375ceeb1767efd78/msgpack-1.0.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:52700dc63a4676669b341ba33520f4d6e43d3ca58d422e22ba66d1736b0a6e4c", size = 579836, upload-time = "2023-09-28T13:19:19.729Z" },
{ url = "https://files.pythonhosted.org/packages/5d/4d/d98592099d4f18945f89cf3e634dc0cb128bb33b1b93f85a84173d35e181/msgpack-1.0.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e45ae4927759289c30ccba8d9fdce62bb414977ba158286b5ddaf8df2cddb5c5", size = 556587, upload-time = "2023-09-28T13:19:21.666Z" },
{ url = "https://files.pythonhosted.org/packages/5e/44/6556ffe169bf2c0e974e2ea25fb82a7e55ebcf52a81b03a5e01820de5f84/msgpack-1.0.7-cp312-cp312-win32.whl", hash = "sha256:27dcd6f46a21c18fa5e5deed92a43d4554e3df8d8ca5a47bf0615d6a5f39dbc9", size = 216509, upload-time = "2023-09-28T13:19:23.161Z" },
{ url = "https://files.pythonhosted.org/packages/dc/c1/63903f30d51d165e132e5221a2a4a1bbfab7508b68131c871d70bffac78a/msgpack-1.0.7-cp312-cp312-win_amd64.whl", hash = "sha256:7687e22a31e976a0e7fc99c2f4d11ca45eff652a81eb8c8085e9609298916dcf", size = 223287, upload-time = "2023-09-28T13:19:25.097Z" },
]
[[package]]
@@ -1704,29 +1715,12 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/6a/94/a3b20276261f5e64dbd72bda656af988282cff01f18c2685953600e2f810/onnxruntime_gpu-1.24.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2cee7e12b0f4813c62f9a48df83fd01d066cc970400c832252cf3c155a6957", size = 252633096, upload-time = "2026-02-05T17:24:53.248Z" },
]
[[package]]
name = "onnxruntime-migraphx"
version = "1.24.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "flatbuffers" },
{ name = "numpy" },
{ name = "packaging" },
{ name = "protobuf" },
{ name = "sympy" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/dd/da/ca7ebc1a8d1193c97ceb9a05fad50f675eb955dc51beb7eb9ba89c8e7db0/onnxruntime_migraphx-1.24.2-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:a2b434fb8880cac2b268950bdf279f33741d29c1f1c5461d27af835e8e288043", size = 20339710, upload-time = "2026-02-21T07:25:13.17Z" },
{ url = "https://files.pythonhosted.org/packages/fa/2e/8c83ec45a9365b4256495ca55eea30da7f03b02177b6da423c7da1ff5f6a/onnxruntime_migraphx-1.24.2-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:ec814818da952bda3062e26f56c88bb713c00491ef91f86716c8d7346f9bc31b", size = 20341883, upload-time = "2026-02-21T07:25:17.86Z" },
{ url = "https://files.pythonhosted.org/packages/9f/52/4776ac68dbc46ca02c9a14cc9e5c496017f47a18cedf606cc38f4911b96a/onnxruntime_migraphx-1.24.2-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:20e497538362170af639b03a40249d7ed61b873ac354f20d732b90252206e320", size = 20342422, upload-time = "2026-02-21T07:25:22.526Z" },
{ url = "https://files.pythonhosted.org/packages/76/44/db9035204a3363f9c0a4822c68e9a7520c13ef8d261f96b89b1375106dab/onnxruntime_migraphx-1.24.2-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:9d7f1b1a2b9651143a2080b4f42ee99eead02023de1855d1b8a02199a9c179aa", size = 20343783, upload-time = "2026-02-21T07:25:29.155Z" },
]
[[package]]
name = "onnxruntime-openvino"
version = "1.24.1"
version = "1.23.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "coloredlogs" },
{ name = "flatbuffers" },
{ name = "numpy" },
{ name = "packaging" },
@@ -1734,12 +1728,12 @@ dependencies = [
{ name = "sympy" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/99/16/69ca742f0b65c40d4de3ff44bb6abc23c47b23e932bc901116176ae69922/onnxruntime_openvino-1.24.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:3007c803634cc69c6d52af1dea7ce729d9bb62b9a11070fd2f959119199007a8", size = 84430935, upload-time = "2026-02-26T13:44:32.193Z" },
{ url = "https://files.pythonhosted.org/packages/aa/73/619bb416bbfc40aebdd493fd6800d2637359294fe683d8a6bae3ff8d869a/onnxruntime_openvino-1.24.1-cp311-cp311-win_amd64.whl", hash = "sha256:8042698232bf67f1f6b219c2b07728d7ae7ddff17d8524588de3675480609aef", size = 13655357, upload-time = "2026-02-26T13:44:35.555Z" },
{ url = "https://files.pythonhosted.org/packages/50/cf/17ba72de2df0fcba349937d2788f154397bbc2d1a2d67772a97e26f6bc5f/onnxruntime_openvino-1.24.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d617fac2f59a6ab5ea59a788c3e1592240a129642519aaeaa774761dfe35150e", size = 84433207, upload-time = "2026-02-26T13:44:41.395Z" },
{ url = "https://files.pythonhosted.org/packages/59/37/d301f2c68b19a9485ed5db3047e0fb52478f3e73eb08c7d2a7c61be7cc1c/onnxruntime_openvino-1.24.1-cp312-cp312-win_amd64.whl", hash = "sha256:f186335a9c9b255633275290da7521d3d4d14c7773fee3127bfa040234d3fa5a", size = 13658075, upload-time = "2026-02-26T13:44:44.905Z" },
{ url = "https://files.pythonhosted.org/packages/08/07/f225999919f56506b603aaa3ff837ad563ab26f86906ed7fa7e5abcd849e/onnxruntime_openvino-1.24.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:2c3bb73e68ac27f4891af8a595c1faf574ec68b772e6583c90a0b997a1822782", size = 84433183, upload-time = "2026-02-26T13:44:50.254Z" },
{ url = "https://files.pythonhosted.org/packages/3e/92/46ae2cd565961a89189900f385bb2f13a9fa731ea4674001d23720fbb1e0/onnxruntime_openvino-1.24.1-cp313-cp313-win_amd64.whl", hash = "sha256:434bf49aa71393c577a456c9d76c98e6d6958a833fa0876793e3d5437b5a511a", size = 13658485, upload-time = "2026-02-26T13:44:53.889Z" },
{ url = "https://files.pythonhosted.org/packages/5a/10/adcd4ac68ffc8dee003553125ef5c091be822e2d7c1077d0bb85690baa9c/onnxruntime_openvino-1.23.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:91938837e6e92e30c63d12fad68a8a4959c40d2eade2bd60f38bdd5b6392f8d3", size = 70481480, upload-time = "2025-10-14T15:19:45.882Z" },
{ url = "https://files.pythonhosted.org/packages/97/95/25f28d6fecf300aa0af393e96af9e00cc676e5dab650ab84f2122610df50/onnxruntime_openvino-1.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:8f05d2d6a804fb70d3f4329d777ac62439773dcc2df827dd5f42644b10bf1fea", size = 13117353, upload-time = "2025-10-14T15:19:49.014Z" },
{ url = "https://files.pythonhosted.org/packages/42/0c/8d97419dfeedf419c5fe5293f3dbc59284855a63ad22e71f46c0010c9dc4/onnxruntime_openvino-1.23.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b963ea19bf9856f3d6b2f719d451f2eeae482a8f69c729906465aa4f27f4d39c", size = 70483359, upload-time = "2025-10-14T15:19:52.88Z" },
{ url = "https://files.pythonhosted.org/packages/29/30/ff6111b16ffb4187c462824aa4e95acc20fdd90f856d44a339d56c6dacd6/onnxruntime_openvino-1.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:937e52657f94c56990a6e5bd4c3705bd6e970834c7c94e23d300dde6848f2889", size = 13117933, upload-time = "2025-10-14T15:19:58.319Z" },
{ url = "https://files.pythonhosted.org/packages/ce/48/e42f618a8ec5fcf825fed4fdc8125f7105256cc6020b84567ecb88d5e2b7/onnxruntime_openvino-1.23.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:2e93b9a8323e196b7433866054a59260f2206ab6fb0e7223dda91da71f1db8c5", size = 70483088, upload-time = "2025-10-14T15:20:02.425Z" },
{ url = "https://files.pythonhosted.org/packages/4a/f9/a531dc497dc113dc14df9a9de5aacb1676cadebc3ec6cc7cd3ca65cb3db0/onnxruntime_openvino-1.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:0ebbf70929de4ce269371cb255536bbedef588932d744da0b40e66c38a620f35", size = 13118206, upload-time = "2025-10-14T15:20:05.587Z" },
]
[[package]]
@@ -2179,6 +2173,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/39/92/8486ede85fcc088f1b3dba4ce92dd29d126fd96b0008ea213167940a2475/pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb", size = 103139, upload-time = "2023-07-30T15:06:59.829Z" },
]
[[package]]
name = "pyreadline3"
version = "3.4.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d7/86/3d61a61f36a0067874a00cb4dceb9028d34b6060e47828f7fc86fb9f7ee9/pyreadline3-3.4.1.tar.gz", hash = "sha256:6f3d1f7b8a31ba32b73917cefc1f28cc660562f39aea8646d30bd6eff21f7bae", size = 86465, upload-time = "2022-01-24T20:05:11.66Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/56/fc/a3c13ded7b3057680c8ae95a9b6cc83e63657c38e0005c400a5d018a33a7/pyreadline3-3.4.1-py3-none-any.whl", hash = "sha256:b0efb6516fd4fb07b45949053826a62fa4cb353db5be2bbb4a7aa1fdd1e345fb", size = 95203, upload-time = "2022-01-24T20:05:10.442Z" },
]
[[package]]
name = "pytest"
version = "9.0.2"
+4 -4
View File
@@ -15,10 +15,10 @@ config_roots = [
[tools]
node = "24.13.1"
flutter = "3.35.7"
pnpm = "10.30.3"
terragrunt = "0.99.4"
opentofu = "1.11.5"
flutter = "3.41.2"
pnpm = "10.29.3"
terragrunt = "0.98.0"
opentofu = "1.11.4"
java = "21.0.2"
[tools."github:CQLabs/homebrew-dcm"]
@@ -59,7 +59,7 @@ private open class LocalImagesPigeonCodec : StandardMessageCodec() {
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
interface LocalImageApi {
fun requestImage(assetId: String, requestId: Long, width: Long, height: Long, isVideo: Boolean, preferEncoded: Boolean, callback: (Result<Map<String, Long>?>) -> Unit)
fun requestImage(assetId: String, requestId: Long, width: Long, height: Long, isVideo: Boolean, callback: (Result<Map<String, Long>?>) -> Unit)
fun cancelRequest(requestId: Long)
fun getThumbhash(thumbhash: String, callback: (Result<Map<String, Long>>) -> Unit)
@@ -82,8 +82,7 @@ interface LocalImageApi {
val widthArg = args[2] as Long
val heightArg = args[3] as Long
val isVideoArg = args[4] as Boolean
val preferEncodedArg = args[5] as Boolean
api.requestImage(assetIdArg, requestIdArg, widthArg, heightArg, isVideoArg, preferEncodedArg) { result: Result<Map<String, Long>?> ->
api.requestImage(assetIdArg, requestIdArg, widthArg, heightArg, isVideoArg) { result: Result<Map<String, Long>?> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(LocalImagesPigeonUtils.wrapError(error))
@@ -14,7 +14,6 @@ import android.util.Size
import androidx.annotation.RequiresApi
import app.alextran.immich.NativeBuffer
import kotlin.math.*
import java.io.IOException
import java.util.concurrent.Executors
import com.bumptech.glide.Glide
import com.bumptech.glide.Priority
@@ -49,6 +48,7 @@ fun Bitmap.toNativeBuffer(): Map<String, Long> {
try {
val buffer = NativeBuffer.wrap(pointer, size)
copyPixelsToBuffer(buffer)
recycle()
return mapOf(
"pointer" to pointer,
"width" to width.toLong(),
@@ -57,9 +57,8 @@ fun Bitmap.toNativeBuffer(): Map<String, Long> {
)
} catch (e: Exception) {
NativeBuffer.free(pointer)
throw e
} finally {
recycle()
throw e
}
}
@@ -100,17 +99,12 @@ class LocalImagesImpl(context: Context) : LocalImageApi {
width: Long,
height: Long,
isVideo: Boolean,
preferEncoded: Boolean,
callback: (Result<Map<String, Long>?>) -> Unit
) {
val signal = CancellationSignal()
val task = threadPool.submit {
try {
if (preferEncoded) {
getEncodedImageInternal(assetId, callback, signal)
} else {
getThumbnailBufferInternal(assetId, width, height, isVideo, callback, signal)
}
getThumbnailBufferInternal(assetId, width, height, isVideo, callback, signal)
} catch (e: Exception) {
when (e) {
is OperationCanceledException -> callback(CANCELLED)
@@ -139,35 +133,6 @@ class LocalImagesImpl(context: Context) : LocalImageApi {
}
}
private fun getEncodedImageInternal(
assetId: String,
callback: (Result<Map<String, Long>?>) -> Unit,
signal: CancellationSignal
) {
signal.throwIfCanceled()
val id = assetId.toLong()
val uri = ContentUris.withAppendedId(Images.Media.EXTERNAL_CONTENT_URI, id)
signal.throwIfCanceled()
val bytes = resolver.openInputStream(uri)?.use { it.readBytes() }
?: throw IOException("Could not read image data for $assetId")
signal.throwIfCanceled()
val pointer = NativeBuffer.allocate(bytes.size)
try {
val buffer = NativeBuffer.wrap(pointer, bytes.size)
buffer.put(bytes)
signal.throwIfCanceled()
callback(Result.success(mapOf(
"pointer" to pointer,
"length" to bytes.size.toLong()
)))
} catch (e: Exception) {
NativeBuffer.free(pointer)
throw e
}
}
private fun getThumbnailBufferInternal(
assetId: String,
width: Long,
@@ -47,7 +47,7 @@ private open class RemoteImagesPigeonCodec : StandardMessageCodec() {
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
interface RemoteImageApi {
fun requestImage(url: String, headers: Map<String, String>, requestId: Long, preferEncoded: Boolean, callback: (Result<Map<String, Long>?>) -> Unit)
fun requestImage(url: String, headers: Map<String, String>, requestId: Long, callback: (Result<Map<String, Long>?>) -> Unit)
fun cancelRequest(requestId: Long)
fun clearCache(callback: (Result<Long>) -> Unit)
@@ -68,8 +68,7 @@ interface RemoteImageApi {
val urlArg = args[0] as String
val headersArg = args[1] as Map<String, String>
val requestIdArg = args[2] as Long
val preferEncodedArg = args[3] as Boolean
api.requestImage(urlArg, headersArg, requestIdArg, preferEncodedArg) { result: Result<Map<String, Long>?> ->
api.requestImage(urlArg, headersArg, requestIdArg) { result: Result<Map<String, Long>?> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(RemoteImagesPigeonUtils.wrapError(error))
@@ -51,7 +51,6 @@ class RemoteImagesImpl(context: Context) : RemoteImageApi {
url: String,
headers: Map<String, String>,
requestId: Long,
@Suppress("UNUSED_PARAMETER") preferEncoded: Boolean, // always returns encoded; setting has no effect on Android
callback: (Result<Map<String, Long>?>) -> Unit
) {
val signal = CancellationSignal()
@@ -78,21 +78,6 @@ class FlutterError (
val details: Any? = null
) : Throwable()
enum class PlatformAssetPlaybackStyle(val raw: Int) {
UNKNOWN(0),
IMAGE(1),
VIDEO(2),
IMAGE_ANIMATED(3),
LIVE_PHOTO(4),
VIDEO_LOOPING(5);
companion object {
fun ofRaw(raw: Int): PlatformAssetPlaybackStyle? {
return values().firstOrNull { it.raw == raw }
}
}
}
/** Generated class from Pigeon that represents data sent in messages. */
data class PlatformAsset (
val id: String,
@@ -107,8 +92,7 @@ data class PlatformAsset (
val isFavorite: Boolean,
val adjustmentTime: Long? = null,
val latitude: Double? = null,
val longitude: Double? = null,
val playbackStyle: PlatformAssetPlaybackStyle
val longitude: Double? = null
)
{
companion object {
@@ -126,8 +110,7 @@ data class PlatformAsset (
val adjustmentTime = pigeonVar_list[10] as Long?
val latitude = pigeonVar_list[11] as Double?
val longitude = pigeonVar_list[12] as Double?
val playbackStyle = pigeonVar_list[13] as PlatformAssetPlaybackStyle
return PlatformAsset(id, name, type, createdAt, updatedAt, width, height, durationInSeconds, orientation, isFavorite, adjustmentTime, latitude, longitude, playbackStyle)
return PlatformAsset(id, name, type, createdAt, updatedAt, width, height, durationInSeconds, orientation, isFavorite, adjustmentTime, latitude, longitude)
}
}
fun toList(): List<Any?> {
@@ -145,7 +128,6 @@ data class PlatformAsset (
adjustmentTime,
latitude,
longitude,
playbackStyle,
)
}
override fun equals(other: Any?): Boolean {
@@ -308,31 +290,26 @@ private open class MessagesPigeonCodec : StandardMessageCodec() {
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
return when (type) {
129.toByte() -> {
return (readValue(buffer) as Long?)?.let {
PlatformAssetPlaybackStyle.ofRaw(it.toInt())
}
}
130.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
PlatformAsset.fromList(it)
}
}
131.toByte() -> {
130.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
PlatformAlbum.fromList(it)
}
}
132.toByte() -> {
131.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
SyncDelta.fromList(it)
}
}
133.toByte() -> {
132.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
HashResult.fromList(it)
}
}
134.toByte() -> {
133.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
CloudIdResult.fromList(it)
}
@@ -342,28 +319,24 @@ private open class MessagesPigeonCodec : StandardMessageCodec() {
}
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
when (value) {
is PlatformAssetPlaybackStyle -> {
stream.write(129)
writeValue(stream, value.raw)
}
is PlatformAsset -> {
stream.write(130)
stream.write(129)
writeValue(stream, value.toList())
}
is PlatformAlbum -> {
stream.write(131)
stream.write(130)
writeValue(stream, value.toList())
}
is SyncDelta -> {
stream.write(132)
stream.write(131)
writeValue(stream, value.toList())
}
is HashResult -> {
stream.write(133)
stream.write(132)
writeValue(stream, value.toList())
}
is CloudIdResult -> {
stream.write(134)
stream.write(133)
writeValue(stream, value.toList())
}
else -> super.writeValue(stream, value)
@@ -4,18 +4,11 @@ import android.annotation.SuppressLint
import android.content.ContentUris
import android.content.Context
import android.database.Cursor
import androidx.exifinterface.media.ExifInterface
import android.os.Build
import android.os.Bundle
import android.os.ext.SdkExtensions
import android.provider.MediaStore
import android.util.Base64
import android.util.Log
import androidx.core.database.getStringOrNull
import app.alextran.immich.core.ImmichPlugin
import com.bumptech.glide.Glide
import com.bumptech.glide.load.ImageHeaderParser
import com.bumptech.glide.load.ImageHeaderParserUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@@ -35,8 +28,6 @@ sealed class AssetResult {
data class InvalidAsset(val assetId: String) : AssetResult()
}
private const val TAG = "NativeSyncApiImplBase"
@SuppressLint("InlinedApi")
open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() {
private val ctx: Context = context.applicationContext
@@ -48,13 +39,6 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() {
private val hashSemaphore = Semaphore(MAX_CONCURRENT_HASH_OPERATIONS)
private const val HASHING_CANCELLED_CODE = "HASH_CANCELLED"
// MediaStore.Files.FileColumns.SPECIAL_FORMAT — S Extensions 21+
// https://developer.android.com/reference/android/provider/MediaStore.Files.FileColumns#SPECIAL_FORMAT
private const val SPECIAL_FORMAT_COLUMN = "_special_format"
private const val SPECIAL_FORMAT_GIF = 1
private const val SPECIAL_FORMAT_MOTION_PHOTO = 2
private const val SPECIAL_FORMAT_ANIMATED_WEBP = 3
const val MEDIA_SELECTION =
"(${MediaStore.Files.FileColumns.MEDIA_TYPE} = ? OR ${MediaStore.Files.FileColumns.MEDIA_TYPE} = ?)"
val MEDIA_SELECTION_ARGS = arrayOf(
@@ -76,25 +60,12 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() {
add(MediaStore.MediaColumns.DURATION)
add(MediaStore.MediaColumns.ORIENTATION)
// IS_FAVORITE is only available on Android 11 and above
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
add(MediaStore.MediaColumns.IS_FAVORITE)
}
if (hasSpecialFormatColumn()) {
add(SPECIAL_FORMAT_COLUMN)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// Fallback: read XMP from MediaStore to detect Motion Photos
// only needed if SPECIAL_FORMAT column isn't available
add(MediaStore.MediaColumns.XMP)
}
}.toTypedArray()
const val HASH_BUFFER_SIZE = 2 * 1024 * 1024
// _special_format requires S Extensions 21+
// https://developer.android.com/reference/android/provider/MediaStore.Files.FileColumns#SPECIAL_FORMAT
private fun hasSpecialFormatColumn(): Boolean =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
SdkExtensions.getExtensionVersion(Build.VERSION_CODES.S) >= 21
}
protected fun getCursor(
@@ -138,12 +109,9 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() {
val orientationColumn =
c.getColumnIndexOrThrow(MediaStore.MediaColumns.ORIENTATION)
val favoriteColumn = c.getColumnIndex(MediaStore.MediaColumns.IS_FAVORITE)
val specialFormatColumn = c.getColumnIndex(SPECIAL_FORMAT_COLUMN)
val xmpColumn = c.getColumnIndex(MediaStore.MediaColumns.XMP)
while (c.moveToNext()) {
val numericId = c.getLong(idColumn)
val id = numericId.toString()
val id = c.getLong(idColumn).toString()
val name = c.getStringOrNull(nameColumn)
val bucketId = c.getStringOrNull(bucketIdColumn)
val path = c.getStringOrNull(dataColumn)
@@ -157,11 +125,10 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() {
continue
}
val rawMediaType = c.getInt(mediaTypeColumn)
val assetType: Long = when (rawMediaType) {
MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE -> 1L
MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO -> 2L
else -> 0L
val mediaType = when (c.getInt(mediaTypeColumn)) {
MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE -> 1
MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO -> 2
else -> 0
}
// Date taken is milliseconds since epoch, Date added is seconds since epoch
val createdAt = (c.getLong(dateTakenColumn).takeIf { it > 0 }?.div(1000))
@@ -171,19 +138,15 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() {
val width = c.getInt(widthColumn).toLong()
val height = c.getInt(heightColumn).toLong()
// Duration is milliseconds
val duration = if (rawMediaType == MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE) 0L
val duration = if (mediaType == MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE) 0
else c.getLong(durationColumn) / 1000
val orientation = c.getInt(orientationColumn)
val isFavorite = if (favoriteColumn == -1) false else c.getInt(favoriteColumn) != 0
val playbackStyle = detectPlaybackStyle(
numericId, rawMediaType, specialFormatColumn, xmpColumn, c
)
val asset = PlatformAsset(
id,
name,
assetType,
mediaType.toLong(),
createdAt,
modifiedAt,
width,
@@ -191,7 +154,6 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() {
duration,
orientation.toLong(),
isFavorite,
playbackStyle = playbackStyle,
)
yield(AssetResult.ValidAsset(asset, bucketId))
}
@@ -199,81 +161,6 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() {
}
}
/**
* Detects the playback style for an asset using _special_format (API 33+)
* or XMP / MIME / RIFF header fallbacks (pre-33).
*/
@SuppressLint("NewApi")
private fun detectPlaybackStyle(
assetId: Long,
rawMediaType: Int,
specialFormatColumn: Int,
xmpColumn: Int,
cursor: Cursor
): PlatformAssetPlaybackStyle {
// video currently has no special formats, so we can short circuit and avoid unnecessary work
if (rawMediaType == MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO) {
return PlatformAssetPlaybackStyle.VIDEO
}
// API 33+: use _special_format from cursor
if (specialFormatColumn != -1) {
val specialFormat = cursor.getInt(specialFormatColumn)
return when {
specialFormat == SPECIAL_FORMAT_MOTION_PHOTO -> PlatformAssetPlaybackStyle.LIVE_PHOTO
specialFormat == SPECIAL_FORMAT_GIF || specialFormat == SPECIAL_FORMAT_ANIMATED_WEBP -> PlatformAssetPlaybackStyle.IMAGE_ANIMATED
rawMediaType == MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE -> PlatformAssetPlaybackStyle.IMAGE
else -> PlatformAssetPlaybackStyle.UNKNOWN
}
}
if (rawMediaType != MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE) {
return PlatformAssetPlaybackStyle.UNKNOWN
}
// Pre-API 33 fallback
val uri = ContentUris.withAppendedId(
MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL),
assetId
)
// Read XMP from cursor (API 30+) or ExifInterface stream (pre-30)
val xmp: String? = if (xmpColumn != -1) {
cursor.getBlob(xmpColumn)?.toString(Charsets.UTF_8)
} else {
try {
ctx.contentResolver.openInputStream(uri)?.use { stream ->
ExifInterface(stream).getAttribute(ExifInterface.TAG_XMP)
}
} catch (e: Exception) {
Log.w(TAG, "Failed to read XMP for asset $assetId", e)
null
}
}
if (xmp != null && "Camera:MotionPhoto" in xmp) {
return PlatformAssetPlaybackStyle.LIVE_PHOTO
}
try {
ctx.contentResolver.openInputStream(uri)?.use { stream ->
val glide = Glide.get(ctx)
val type = ImageHeaderParserUtils.getType(
glide.registry.imageHeaderParsers,
stream,
glide.arrayPool
)
if (type == ImageHeaderParser.ImageType.GIF || type == ImageHeaderParser.ImageType.ANIMATED_WEBP) {
return PlatformAssetPlaybackStyle.IMAGE_ANIMATED
}
}
} catch (e: Exception) {
Log.w(TAG, "Failed to parse image header for asset $assetId", e)
}
return PlatformAssetPlaybackStyle.IMAGE
}
fun getAlbums(): List<PlatformAlbum> {
val albums = mutableListOf<PlatformAlbum>()
val albumsCount = mutableMapOf<String, Int>()
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+22 -16
View File
@@ -38,10 +38,10 @@ PODS:
- local_auth_darwin (0.0.1):
- Flutter
- FlutterMacOS
- MapLibre (6.14.0)
- maplibre_gl (0.0.1):
- MapLibre (6.23.0)
- maplibre_ios (0.0.1):
- Flutter
- MapLibre (= 6.14.0)
- MapLibre (~> 6.21)
- native_video_player (1.0.0):
- Flutter
- network_info_plus (0.0.1):
@@ -58,6 +58,8 @@ PODS:
- photo_manager (3.7.1):
- Flutter
- FlutterMacOS
- pointer_interceptor_ios (0.0.1):
- Flutter
- SAMKeychain (1.5.3)
- share_handler_ios (0.0.14):
- Flutter
@@ -75,16 +77,16 @@ PODS:
- sqflite_darwin (0.0.4):
- Flutter
- FlutterMacOS
- sqlite3 (3.49.1):
- sqlite3/common (= 3.49.1)
- sqlite3/common (3.49.1)
- sqlite3/dbstatvtab (3.49.1):
- sqlite3 (3.49.2):
- sqlite3/common (= 3.49.2)
- sqlite3/common (3.49.2)
- sqlite3/dbstatvtab (3.49.2):
- sqlite3/common
- sqlite3/fts5 (3.49.1):
- sqlite3/fts5 (3.49.2):
- sqlite3/common
- sqlite3/perf-threadsafe (3.49.1):
- sqlite3/perf-threadsafe (3.49.2):
- sqlite3/common
- sqlite3/rtree (3.49.1):
- sqlite3/rtree (3.49.2):
- sqlite3/common
- sqlite3_flutter_libs (0.0.1):
- Flutter
@@ -118,7 +120,7 @@ DEPENDENCIES:
- integration_test (from `.symlinks/plugins/integration_test/ios`)
- isar_community_flutter_libs (from `.symlinks/plugins/isar_community_flutter_libs/ios`)
- local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`)
- maplibre_gl (from `.symlinks/plugins/maplibre_gl/ios`)
- maplibre_ios (from `.symlinks/plugins/maplibre_ios/ios`)
- native_video_player (from `.symlinks/plugins/native_video_player/ios`)
- network_info_plus (from `.symlinks/plugins/network_info_plus/ios`)
- objective_c (from `.symlinks/plugins/objective_c/ios`)
@@ -126,6 +128,7 @@ DEPENDENCIES:
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- photo_manager (from `.symlinks/plugins/photo_manager/ios`)
- pointer_interceptor_ios (from `.symlinks/plugins/pointer_interceptor_ios/ios`)
- share_handler_ios (from `.symlinks/plugins/share_handler_ios/ios`)
- share_handler_ios_models (from `.symlinks/plugins/share_handler_ios/ios/Models`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
@@ -178,8 +181,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/isar_community_flutter_libs/ios"
local_auth_darwin:
:path: ".symlinks/plugins/local_auth_darwin/darwin"
maplibre_gl:
:path: ".symlinks/plugins/maplibre_gl/ios"
maplibre_ios:
:path: ".symlinks/plugins/maplibre_ios/ios"
native_video_player:
:path: ".symlinks/plugins/native_video_player/ios"
network_info_plus:
@@ -194,6 +197,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/permission_handler_apple/ios"
photo_manager:
:path: ".symlinks/plugins/photo_manager/ios"
pointer_interceptor_ios:
:path: ".symlinks/plugins/pointer_interceptor_ios/ios"
share_handler_ios:
:path: ".symlinks/plugins/share_handler_ios/ios"
share_handler_ios_models:
@@ -230,8 +235,8 @@ SPEC CHECKSUMS:
integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
isar_community_flutter_libs: bede843185a61a05ff364a05c9b23209523f7e0d
local_auth_darwin: 553ce4f9b16d3fdfeafce9cf042e7c9f77c1c391
MapLibre: 69e572367f4ef6287e18246cfafc39c80cdcabcd
maplibre_gl: 3c924e44725147b03dda33430ad216005b40555f
MapLibre: c0fcafabb341f230657d959970c6eb47fb55750e
maplibre_ios: 05031d5f79702672d2c01cc77b6ba3187d4bf896
native_video_player: b65c58951ede2f93d103a25366bdebca95081265
network_info_plus: cf61925ab5205dce05a4f0895989afdb6aade5fc
objective_c: 89e720c30d716b036faf9c9684022048eee1eee2
@@ -239,13 +244,14 @@ SPEC CHECKSUMS:
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
photo_manager: 1d80ae07a89a67dfbcae95953a1e5a24af7c3e62
pointer_interceptor_ios: da06a662d5bfd329602b45b2ab41bc0fb5fdb0f0
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
share_handler_ios: e2244e990f826b2c8eaa291ac3831569438ba0fb
share_handler_ios_models: fc638c9b4330dc7f082586c92aee9dfa0b87b871
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983
sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1
sqlite3_flutter_libs: f8fc13346870e73fe35ebf6dbb997fbcd156b241
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
@@ -446,6 +446,7 @@
packageReferences = (
FEE084F62EC172460045228E /* XCRemoteSwiftPackageReference "sqlite-data" */,
FEE084F92EC1725A0045228E /* XCRemoteSwiftPackageReference "swift-http-structured-headers" */,
A1B2C3D4E5F6A7B8C9D0E1F2 /* XCRemoteSwiftPackageReference "maplibre-gl-native-distribution" */,
);
preferredProjectObjectVersion = 77;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
@@ -1250,6 +1251,14 @@
minimumVersion = 1.5.0;
};
};
A1B2C3D4E5F6A7B8C9D0E1F2 /* XCRemoteSwiftPackageReference "maplibre-gl-native-distribution" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/maplibre/maplibre-gl-native-distribution";
requirement = {
kind = upToNextMinorVersion;
minimumVersion = 6.21.0;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
@@ -10,6 +10,15 @@
"version" : "1.0.3"
}
},
{
"identity" : "maplibre-gl-native-distribution",
"kind" : "remoteSourceControl",
"location" : "https://github.com/maplibre/maplibre-gl-native-distribution",
"state" : {
"revision" : "2aefb4dd47ca6e897c93086f348a457839aac2fe",
"version" : "6.23.0"
}
},
{
"identity" : "grdb.swift",
"kind" : "remoteSourceControl",
+2 -3
View File
@@ -70,7 +70,7 @@ class LocalImagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable {
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
protocol LocalImageApi {
func requestImage(assetId: String, requestId: Int64, width: Int64, height: Int64, isVideo: Bool, preferEncoded: Bool, completion: @escaping (Result<[String: Int64]?, Error>) -> Void)
func requestImage(assetId: String, requestId: Int64, width: Int64, height: Int64, isVideo: Bool, completion: @escaping (Result<[String: Int64]?, Error>) -> Void)
func cancelRequest(requestId: Int64) throws
func getThumbhash(thumbhash: String, completion: @escaping (Result<[String: Int64], Error>) -> Void)
}
@@ -90,8 +90,7 @@ class LocalImageApiSetup {
let widthArg = args[2] as! Int64
let heightArg = args[3] as! Int64
let isVideoArg = args[4] as! Bool
let preferEncodedArg = args[5] as! Bool
api.requestImage(assetId: assetIdArg, requestId: requestIdArg, width: widthArg, height: heightArg, isVideo: isVideoArg, preferEncoded: preferEncodedArg) { result in
api.requestImage(assetId: assetIdArg, requestId: requestIdArg, width: widthArg, height: heightArg, isVideo: isVideoArg) { result in
switch result {
case .success(let res):
reply(wrapResult(res))
+25 -68
View File
@@ -7,7 +7,7 @@ class LocalImageRequest {
weak var workItem: DispatchWorkItem?
var isCancelled = false
let callback: (Result<[String: Int64]?, any Error>) -> Void
init(callback: @escaping (Result<[String: Int64]?, any Error>) -> Void) {
self.callback = callback
}
@@ -30,11 +30,11 @@ class LocalImageApiImpl: LocalImageApi {
requestOptions.version = .current
return requestOptions
}()
private static let assetQueue = DispatchQueue(label: "thumbnail.assets", qos: .userInitiated)
private static let requestQueue = DispatchQueue(label: "thumbnail.requests", qos: .userInitiated)
private static let cancelQueue = DispatchQueue(label: "thumbnail.cancellation", qos: .default)
private static var rgbaFormat = vImage_CGImageFormat(
bitsPerComponent: 8,
bitsPerPixel: 32,
@@ -48,12 +48,12 @@ class LocalImageApiImpl: LocalImageApi {
assetCache.countLimit = 10000
return assetCache
}()
func getThumbhash(thumbhash: String, completion: @escaping (Result<[String : Int64], any Error>) -> Void) {
ImageProcessing.queue.async {
guard let data = Data(base64Encoded: thumbhash)
else { return completion(.failure(PigeonError(code: "", message: "Invalid base64 string: \(thumbhash)", details: nil)))}
let (width, height, pointer) = thumbHashToRGBA(hash: data)
completion(.success([
"pointer": Int64(Int(bitPattern: pointer.baseAddress)),
@@ -63,77 +63,34 @@ class LocalImageApiImpl: LocalImageApi {
]))
}
}
func requestImage(assetId: String, requestId: Int64, width: Int64, height: Int64, isVideo: Bool, preferEncoded: Bool, completion: @escaping (Result<[String: Int64]?, any Error>) -> Void) {
func requestImage(assetId: String, requestId: Int64, width: Int64, height: Int64, isVideo: Bool, completion: @escaping (Result<[String: Int64]?, any Error>) -> Void) {
let request = LocalImageRequest(callback: completion)
let item = DispatchWorkItem {
if request.isCancelled {
return completion(ImageProcessing.cancelledResult)
}
ImageProcessing.semaphore.wait()
defer {
ImageProcessing.semaphore.signal()
}
if request.isCancelled {
return completion(ImageProcessing.cancelledResult)
}
guard let asset = Self.requestAsset(assetId: assetId)
else {
Self.remove(requestId: requestId)
completion(.failure(PigeonError(code: "", message: "Could not get asset data for \(assetId)", details: nil)))
return
}
if request.isCancelled {
return completion(ImageProcessing.cancelledResult)
}
if preferEncoded {
let dataOptions = PHImageRequestOptions()
dataOptions.isNetworkAccessAllowed = true
dataOptions.isSynchronous = true
dataOptions.version = .current
var imageData: Data?
Self.imageManager.requestImageDataAndOrientation(
for: asset,
options: dataOptions,
resultHandler: { (data, _, _, _) in
imageData = data
}
)
if request.isCancelled {
Self.remove(requestId: requestId)
return completion(ImageProcessing.cancelledResult)
}
guard let data = imageData else {
Self.remove(requestId: requestId)
return completion(.failure(PigeonError(code: "", message: "Could not get image data for \(assetId)", details: nil)))
}
let length = data.count
let pointer = malloc(length)!
data.copyBytes(to: pointer.assumingMemoryBound(to: UInt8.self), count: length)
if request.isCancelled {
free(pointer)
Self.remove(requestId: requestId)
return completion(ImageProcessing.cancelledResult)
}
request.callback(.success([
"pointer": Int64(Int(bitPattern: pointer)),
"length": Int64(length),
]))
Self.remove(requestId: requestId)
return
}
var image: UIImage?
Self.imageManager.requestImage(
for: asset,
@@ -144,29 +101,29 @@ class LocalImageApiImpl: LocalImageApi {
image = _image
}
)
if request.isCancelled {
return completion(ImageProcessing.cancelledResult)
}
guard let image = image,
let cgImage = image.cgImage else {
Self.remove(requestId: requestId)
return completion(.failure(PigeonError(code: "", message: "Could not get pixel data for \(assetId)", details: nil)))
}
if request.isCancelled {
return completion(ImageProcessing.cancelledResult)
}
do {
let buffer = try vImage_Buffer(cgImage: cgImage, format: Self.rgbaFormat)
if request.isCancelled {
buffer.free()
return completion(ImageProcessing.cancelledResult)
}
request.callback(.success([
"pointer": Int64(Int(bitPattern: buffer.data)),
"width": Int64(buffer.width),
@@ -179,24 +136,24 @@ class LocalImageApiImpl: LocalImageApi {
return completion(.failure(PigeonError(code: "", message: "Failed to convert image for \(assetId): \(error)", details: nil)))
}
}
request.workItem = item
Self.add(requestId: requestId, request: request)
ImageProcessing.queue.async(execute: item)
}
func cancelRequest(requestId: Int64) {
Self.cancel(requestId: requestId)
}
private static func add(requestId: Int64, request: LocalImageRequest) -> Void {
requestQueue.sync { requests[requestId] = request }
}
private static func remove(requestId: Int64) -> Void {
requestQueue.sync { requests[requestId] = nil }
}
private static func cancel(requestId: Int64) -> Void {
requestQueue.async {
guard let request = requests.removeValue(forKey: requestId) else { return }
@@ -207,12 +164,12 @@ class LocalImageApiImpl: LocalImageApi {
}
}
}
private static func requestAsset(assetId: String) -> PHAsset? {
var asset: PHAsset?
assetQueue.sync { asset = assetCache.object(forKey: assetId as NSString) }
if asset != nil { return asset }
guard let asset = PHAsset.fetchAssets(withLocalIdentifiers: [assetId], options: Self.fetchOptions).firstObject
else { return nil }
assetQueue.async { assetCache.setObject(asset, forKey: assetId as NSString) }
@@ -70,7 +70,7 @@ class RemoteImagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
protocol RemoteImageApi {
func requestImage(url: String, headers: [String: String], requestId: Int64, preferEncoded: Bool, completion: @escaping (Result<[String: Int64]?, Error>) -> Void)
func requestImage(url: String, headers: [String: String], requestId: Int64, completion: @escaping (Result<[String: Int64]?, Error>) -> Void)
func cancelRequest(requestId: Int64) throws
func clearCache(completion: @escaping (Result<Int64, Error>) -> Void)
}
@@ -88,8 +88,7 @@ class RemoteImageApiSetup {
let urlArg = args[0] as! String
let headersArg = args[1] as! [String: String]
let requestIdArg = args[2] as! Int64
let preferEncodedArg = args[3] as! Bool
api.requestImage(url: urlArg, headers: headersArg, requestId: requestIdArg, preferEncoded: preferEncodedArg) { result in
api.requestImage(url: urlArg, headers: headersArg, requestId: requestIdArg) { result in
switch result {
case .success(let res):
reply(wrapResult(res))
+23 -41
View File
@@ -8,7 +8,7 @@ class RemoteImageRequest {
let id: Int64
var isCancelled = false
let completion: (Result<[String: Int64]?, any Error>) -> Void
init(id: Int64, task: URLSessionDataTask, completion: @escaping (Result<[String: Int64]?, any Error>) -> Void) {
self.id = id
self.task = task
@@ -32,93 +32,75 @@ class RemoteImageApiImpl: NSObject, RemoteImageApi {
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceCreateThumbnailFromImageAlways: true
] as CFDictionary
func requestImage(url: String, headers: [String : String], requestId: Int64, preferEncoded: Bool, completion: @escaping (Result<[String : Int64]?, any Error>) -> Void) {
func requestImage(url: String, headers: [String : String], requestId: Int64, completion: @escaping (Result<[String : Int64]?, any Error>) -> Void) {
var urlRequest = URLRequest(url: URL(string: url)!)
urlRequest.cachePolicy = .returnCacheDataElseLoad
for (key, value) in headers {
urlRequest.setValue(value, forHTTPHeaderField: key)
}
let task = URLSessionManager.shared.session.dataTask(with: urlRequest) { data, response, error in
Self.handleCompletion(requestId: requestId, encoded: preferEncoded, data: data, response: response, error: error)
Self.handleCompletion(requestId: requestId, data: data, response: response, error: error)
}
let request = RemoteImageRequest(id: requestId, task: task, completion: completion)
os_unfair_lock_lock(&Self.lock)
Self.requests[requestId] = request
os_unfair_lock_unlock(&Self.lock)
task.resume()
}
private static func handleCompletion(requestId: Int64, encoded: Bool, data: Data?, response: URLResponse?, error: Error?) {
private static func handleCompletion(requestId: Int64, data: Data?, response: URLResponse?, error: Error?) {
os_unfair_lock_lock(&Self.lock)
guard let request = requests[requestId] else {
return os_unfair_lock_unlock(&Self.lock)
}
requests[requestId] = nil
os_unfair_lock_unlock(&Self.lock)
if let error = error {
if request.isCancelled || (error as NSError).code == NSURLErrorCancelled {
return request.completion(ImageProcessing.cancelledResult)
}
return request.completion(.failure(error))
}
if request.isCancelled {
return request.completion(ImageProcessing.cancelledResult)
}
guard let data = data else {
return request.completion(.failure(PigeonError(code: "", message: "No data received", details: nil)))
}
ImageProcessing.queue.async {
ImageProcessing.semaphore.wait()
defer { ImageProcessing.semaphore.signal() }
if request.isCancelled {
return request.completion(ImageProcessing.cancelledResult)
}
// Return raw encoded bytes when requested (for animated images)
if encoded {
let length = data.count
let pointer = malloc(length)!
data.copyBytes(to: pointer.assumingMemoryBound(to: UInt8.self), count: length)
if request.isCancelled {
free(pointer)
return request.completion(ImageProcessing.cancelledResult)
}
return request.completion(
.success([
"pointer": Int64(Int(bitPattern: pointer)),
"length": Int64(length),
]))
}
guard let imageSource = CGImageSourceCreateWithData(data as CFData, nil),
let cgImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, decodeOptions) else {
return request.completion(.failure(PigeonError(code: "", message: "Failed to decode image for request", details: nil)))
}
if request.isCancelled {
return request.completion(ImageProcessing.cancelledResult)
}
do {
let buffer = try vImage_Buffer(cgImage: cgImage, format: rgbaFormat)
if request.isCancelled {
buffer.free()
return request.completion(ImageProcessing.cancelledResult)
}
request.completion(
.success([
"pointer": Int64(Int(bitPattern: buffer.data)),
@@ -131,17 +113,17 @@ class RemoteImageApiImpl: NSObject, RemoteImageApi {
}
}
}
func cancelRequest(requestId: Int64) {
os_unfair_lock_lock(&Self.lock)
let request = Self.requests[requestId]
os_unfair_lock_unlock(&Self.lock)
guard let request = request else { return }
request.isCancelled = true
request.task?.cancel()
}
func clearCache(completion: @escaping (Result<Int64, any Error>) -> Void) {
Task {
let cache = URLSessionManager.shared.session.configuration.urlCache!
+10 -32
View File
@@ -128,15 +128,6 @@ func deepHashMessages(value: Any?, hasher: inout Hasher) {
enum PlatformAssetPlaybackStyle: Int {
case unknown = 0
case image = 1
case video = 2
case imageAnimated = 3
case livePhoto = 4
case videoLooping = 5
}
/// Generated class from Pigeon that represents data sent in messages.
struct PlatformAsset: Hashable {
var id: String
@@ -152,7 +143,6 @@ struct PlatformAsset: Hashable {
var adjustmentTime: Int64? = nil
var latitude: Double? = nil
var longitude: Double? = nil
var playbackStyle: PlatformAssetPlaybackStyle
// swift-format-ignore: AlwaysUseLowerCamelCase
@@ -170,7 +160,6 @@ struct PlatformAsset: Hashable {
let adjustmentTime: Int64? = nilOrValue(pigeonVar_list[10])
let latitude: Double? = nilOrValue(pigeonVar_list[11])
let longitude: Double? = nilOrValue(pigeonVar_list[12])
let playbackStyle = pigeonVar_list[13] as! PlatformAssetPlaybackStyle
return PlatformAsset(
id: id,
@@ -185,8 +174,7 @@ struct PlatformAsset: Hashable {
isFavorite: isFavorite,
adjustmentTime: adjustmentTime,
latitude: latitude,
longitude: longitude,
playbackStyle: playbackStyle
longitude: longitude
)
}
func toList() -> [Any?] {
@@ -204,7 +192,6 @@ struct PlatformAsset: Hashable {
adjustmentTime,
latitude,
longitude,
playbackStyle,
]
}
static func == (lhs: PlatformAsset, rhs: PlatformAsset) -> Bool {
@@ -362,20 +349,14 @@ private class MessagesPigeonCodecReader: FlutterStandardReader {
override func readValue(ofType type: UInt8) -> Any? {
switch type {
case 129:
let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?)
if let enumResultAsInt = enumResultAsInt {
return PlatformAssetPlaybackStyle(rawValue: enumResultAsInt)
}
return nil
case 130:
return PlatformAsset.fromList(self.readValue() as! [Any?])
case 131:
case 130:
return PlatformAlbum.fromList(self.readValue() as! [Any?])
case 132:
case 131:
return SyncDelta.fromList(self.readValue() as! [Any?])
case 133:
case 132:
return HashResult.fromList(self.readValue() as! [Any?])
case 134:
case 133:
return CloudIdResult.fromList(self.readValue() as! [Any?])
default:
return super.readValue(ofType: type)
@@ -385,23 +366,20 @@ private class MessagesPigeonCodecReader: FlutterStandardReader {
private class MessagesPigeonCodecWriter: FlutterStandardWriter {
override func writeValue(_ value: Any) {
if let value = value as? PlatformAssetPlaybackStyle {
if let value = value as? PlatformAsset {
super.writeByte(129)
super.writeValue(value.rawValue)
} else if let value = value as? PlatformAsset {
super.writeByte(130)
super.writeValue(value.toList())
} else if let value = value as? PlatformAlbum {
super.writeByte(131)
super.writeByte(130)
super.writeValue(value.toList())
} else if let value = value as? SyncDelta {
super.writeByte(132)
super.writeByte(131)
super.writeValue(value.toList())
} else if let value = value as? HashResult {
super.writeByte(133)
super.writeByte(132)
super.writeValue(value.toList())
} else if let value = value as? CloudIdResult {
super.writeByte(134)
super.writeByte(133)
super.writeValue(value.toList())
} else {
super.writeValue(value)
+1 -2
View File
@@ -173,8 +173,7 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
type: 0,
durationInSeconds: 0,
orientation: 0,
isFavorite: false,
playbackStyle: .unknown
isFavorite: false
)
if (updatedAssets.contains(AssetWrapper(with: predicate))) {
continue
+2 -14
View File
@@ -1,17 +1,6 @@
import Photos
extension PHAsset {
var platformPlaybackStyle: PlatformAssetPlaybackStyle {
switch playbackStyle {
case .image: return .image
case .imageAnimated: return .imageAnimated
case .livePhoto: return .livePhoto
case .video: return .video
case .videoLooping: return .videoLooping
@unknown default: return .unknown
}
}
func toPlatformAsset() -> PlatformAsset {
return PlatformAsset(
id: localIdentifier,
@@ -26,8 +15,7 @@ extension PHAsset {
isFavorite: isFavorite,
adjustmentTime: adjustmentTimestamp,
latitude: location?.coordinate.latitude,
longitude: location?.coordinate.longitude,
playbackStyle: platformPlaybackStyle
longitude: location?.coordinate.longitude
)
}
@@ -38,7 +26,7 @@ extension PHAsset {
var filename: String? {
return value(forKey: "filename") as? String
}
var adjustmentTimestamp: Int64? {
if let date = value(forKey: "adjustmentTimestamp") as? Date {
return Int64(date.timeIntervalSince1970)
-2
View File
@@ -18,5 +18,3 @@ enum ActionSource { timeline, viewer }
enum CleanupStep { selectDate, scan, delete }
enum AssetKeepType { none, photosOnly, videosOnly }
enum AssetDateAggregation { start, end }
@@ -11,10 +11,6 @@ enum AssetType {
enum AssetState { local, remote, merged }
// do not change!
// keep in sync with PlatformAssetPlaybackStyle
enum AssetPlaybackStyle { unknown, image, video, imageAnimated, livePhoto, videoLooping }
sealed class BaseAsset {
final String name;
final String? checksum;
@@ -47,14 +43,6 @@ sealed class BaseAsset {
bool get isMotionPhoto => livePhotoVideoId != null;
AssetPlaybackStyle get playbackStyle {
if (isVideo) return AssetPlaybackStyle.video;
if (isMotionPhoto) return AssetPlaybackStyle.livePhoto;
if (isImage && durationInSeconds != null && durationInSeconds! > 0) return AssetPlaybackStyle.imageAnimated;
if (isImage) return AssetPlaybackStyle.image;
return AssetPlaybackStyle.unknown;
}
Duration get duration {
final durationInSeconds = this.durationInSeconds;
if (durationInSeconds != null) {
@@ -5,8 +5,6 @@ class LocalAsset extends BaseAsset {
final String? remoteAssetId;
final String? cloudId;
final int orientation;
@override
final AssetPlaybackStyle playbackStyle;
final DateTime? adjustmentTime;
final double? latitude;
@@ -27,7 +25,6 @@ class LocalAsset extends BaseAsset {
super.isFavorite = false,
super.livePhotoVideoId,
this.orientation = 0,
required this.playbackStyle,
this.adjustmentTime,
this.latitude,
this.longitude,
@@ -59,7 +56,6 @@ class LocalAsset extends BaseAsset {
width: ${width ?? "<NA>"},
height: ${height ?? "<NA>"},
durationInSeconds: ${durationInSeconds ?? "<NA>"},
playbackStyle: $playbackStyle,
remoteId: ${remoteId ?? "<NA>"},
cloudId: ${cloudId ?? "<NA>"},
checksum: ${checksum ?? "<NA>"},
@@ -80,7 +76,6 @@ class LocalAsset extends BaseAsset {
id == other.id &&
cloudId == other.cloudId &&
orientation == other.orientation &&
playbackStyle == other.playbackStyle &&
adjustmentTime == other.adjustmentTime &&
latitude == other.latitude &&
longitude == other.longitude;
@@ -92,7 +87,6 @@ class LocalAsset extends BaseAsset {
id.hashCode ^
remoteId.hashCode ^
orientation.hashCode ^
playbackStyle.hashCode ^
adjustmentTime.hashCode ^
latitude.hashCode ^
longitude.hashCode;
@@ -111,7 +105,6 @@ class LocalAsset extends BaseAsset {
int? durationInSeconds,
bool? isFavorite,
int? orientation,
AssetPlaybackStyle? playbackStyle,
DateTime? adjustmentTime,
double? latitude,
double? longitude,
@@ -131,7 +124,6 @@ class LocalAsset extends BaseAsset {
durationInSeconds: durationInSeconds ?? this.durationInSeconds,
isFavorite: isFavorite ?? this.isFavorite,
orientation: orientation ?? this.orientation,
playbackStyle: playbackStyle ?? this.playbackStyle,
adjustmentTime: adjustmentTime ?? this.adjustmentTime,
latitude: latitude ?? this.latitude,
longitude: longitude ?? this.longitude,
+2 -2
View File
@@ -1,7 +1,7 @@
import 'package:maplibre_gl/maplibre_gl.dart';
import 'package:maplibre/maplibre.dart';
class Marker {
final LatLng location;
final Geographic location;
final String assetId;
const Marker({required this.location, required this.assetId});
@@ -3,21 +3,30 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
class SearchResult {
final List<BaseAsset> assets;
final double scrollOffset;
final int? nextPage;
const SearchResult({required this.assets, this.nextPage});
const SearchResult({required this.assets, this.scrollOffset = 0.0, this.nextPage});
SearchResult copyWith({List<BaseAsset>? assets, int? nextPage, double? scrollOffset}) {
return SearchResult(
assets: assets ?? this.assets,
nextPage: nextPage ?? this.nextPage,
scrollOffset: scrollOffset ?? this.scrollOffset,
);
}
@override
String toString() => 'SearchResult(assets: ${assets.length}, nextPage: $nextPage)';
String toString() => 'SearchResult(assets: ${assets.length}, nextPage: $nextPage, scrollOffset: $scrollOffset)';
@override
bool operator ==(covariant SearchResult other) {
if (identical(this, other)) return true;
final listEquals = const DeepCollectionEquality().equals;
return listEquals(other.assets, assets) && other.nextPage == nextPage;
return listEquals(other.assets, assets) && other.nextPage == nextPage && other.scrollOffset == scrollOffset;
}
@override
int get hashCode => assets.hashCode ^ nextPage.hashCode;
int get hashCode => assets.hashCode ^ nextPage.hashCode ^ scrollOffset.hashCode;
}
@@ -73,9 +73,6 @@ enum StoreKey<T> {
autoPlayVideo<bool>._(139),
albumGridView<bool>._(140),
// Image viewer navigation settings
tapToNavigate<bool>._(141),
// Experimental stuff
photoManagerCustomFilter<bool>._(1000),
betaPromptShown<bool>._(1001),
@@ -435,19 +435,9 @@ extension PlatformToLocalAsset on PlatformAsset {
durationInSeconds: durationInSeconds,
isFavorite: isFavorite,
orientation: orientation,
playbackStyle: _toPlaybackStyle(playbackStyle),
adjustmentTime: tryFromSecondsSinceEpoch(adjustmentTime, isUtc: true),
latitude: latitude,
longitude: longitude,
isEdited: false,
);
}
AssetPlaybackStyle _toPlaybackStyle(PlatformAssetPlaybackStyle style) => switch (style) {
PlatformAssetPlaybackStyle.unknown => AssetPlaybackStyle.unknown,
PlatformAssetPlaybackStyle.image => AssetPlaybackStyle.image,
PlatformAssetPlaybackStyle.video => AssetPlaybackStyle.video,
PlatformAssetPlaybackStyle.imageAnimated => AssetPlaybackStyle.imageAnimated,
PlatformAssetPlaybackStyle.livePhoto => AssetPlaybackStyle.livePhoto,
PlatformAssetPlaybackStyle.videoLooping => AssetPlaybackStyle.videoLooping,
};
+3 -3
View File
@@ -1,9 +1,9 @@
import 'package:immich_mobile/domain/models/map.model.dart';
import 'package:immich_mobile/infrastructure/repositories/map.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
import 'package:maplibre/maplibre.dart' hide Marker;
typedef MapMarkerSource = Future<List<Marker>> Function(LatLngBounds? bounds);
typedef MapMarkerSource = Future<List<Marker>> Function(LngLatBounds? bounds);
typedef MapQuery = ({MapMarkerSource markerSource});
@@ -21,5 +21,5 @@ class MapService {
MapService(MapQuery query) : _markerSource = query.markerSource;
Future<List<Marker>> Function(LatLngBounds? bounds) get getMarkers => _markerSource;
Future<List<Marker>> Function(LngLatBounds? bounds) get getMarkers => _markerSource;
}
@@ -43,8 +43,8 @@ class RemoteAlbumService {
AlbumSortMode.title => albums.sortedBy((album) => album.name),
AlbumSortMode.lastModified => albums.sortedBy((album) => album.updatedAt),
AlbumSortMode.assetCount => albums.sortedBy((album) => album.assetCount),
AlbumSortMode.mostRecent => await _sortByAssetDate(albums, aggregation: AssetDateAggregation.end),
AlbumSortMode.mostOldest => await _sortByAssetDate(albums, aggregation: AssetDateAggregation.start),
AlbumSortMode.mostRecent => await _sortByNewestAsset(albums),
AlbumSortMode.mostOldest => await _sortByOldestAsset(albums),
};
final effectiveOrder = isReverse ? sortMode.defaultOrder.reverse() : sortMode.defaultOrder;
@@ -172,25 +172,46 @@ class RemoteAlbumService {
return _repository.getAlbumsContainingAsset(assetId);
}
Future<List<RemoteAlbum>> _sortByAssetDate(
List<RemoteAlbum> albums, {
required AssetDateAggregation aggregation,
}) async {
if (albums.isEmpty) return [];
final albumIds = albums.map((e) => e.id).toList();
final sortedIds = await _repository.getSortedAlbumIds(albumIds, aggregation: aggregation);
final albumMap = Map<String, RemoteAlbum>.fromEntries(albums.map((a) => MapEntry(a.id, a)));
final sortedAlbums = sortedIds.map((id) => albumMap[id]).whereType<RemoteAlbum>().toList();
if (sortedAlbums.length < albums.length) {
final returnedIdSet = sortedIds.toSet();
final emptyAlbums = albums.where((a) => !returnedIdSet.contains(a.id));
sortedAlbums.addAll(emptyAlbums);
Future<List<RemoteAlbum>> _sortByNewestAsset(List<RemoteAlbum> albums) async {
// map album IDs to their newest asset dates
final Map<String, Future<DateTime?>> assetTimestampFutures = {};
for (final album in albums) {
assetTimestampFutures[album.id] = _repository.getNewestAssetTimestamp(album.id);
}
return sortedAlbums;
// await all database queries
final entries = await Future.wait(
assetTimestampFutures.entries.map((entry) async => MapEntry(entry.key, await entry.value)),
);
final assetTimestamps = Map.fromEntries(entries);
final sorted = albums.sorted((a, b) {
final aDate = assetTimestamps[a.id] ?? DateTime.fromMillisecondsSinceEpoch(0);
final bDate = assetTimestamps[b.id] ?? DateTime.fromMillisecondsSinceEpoch(0);
return aDate.compareTo(bDate);
});
return sorted;
}
Future<List<RemoteAlbum>> _sortByOldestAsset(List<RemoteAlbum> albums) async {
// map album IDs to their oldest asset dates
final Map<String, Future<DateTime?>> assetTimestampFutures = {
for (final album in albums) album.id: _repository.getOldestAssetTimestamp(album.id),
};
// await all database queries
final entries = await Future.wait(
assetTimestampFutures.entries.map((entry) async => MapEntry(entry.key, await entry.value)),
);
final assetTimestamps = Map.fromEntries(entries);
final sorted = albums.sorted((a, b) {
final aDate = assetTimestamps[a.id] ?? DateTime.fromMillisecondsSinceEpoch(0);
final bDate = assetTimestamps[b.id] ?? DateTime.fromMillisecondsSinceEpoch(0);
return aDate.compareTo(bDate);
});
return sorted;
}
}
@@ -68,12 +68,12 @@ class SyncStreamService {
return false;
}
final serverSemVer = SemVer(major: serverVersion.major, minor: serverVersion.minor, patch: serverVersion.patch_);
final semVer = SemVer(major: serverVersion.major, minor: serverVersion.minor, patch: serverVersion.patch_);
final value = Store.get(StoreKey.syncMigrationStatus, "[]");
final migrations = (jsonDecode(value) as List).cast<String>();
int previousLength = migrations.length;
await _runPreSyncTasks(migrations, serverSemVer);
await _runPreSyncTasks(migrations, semVer);
if (migrations.length != previousLength) {
_logger.info("Updated pre-sync migration status: $migrations");
@@ -82,14 +82,10 @@ class SyncStreamService {
// Start the sync stream and handle events
bool shouldReset = false;
await _syncApiRepository.streamChanges(
_handleEvents,
serverVersion: serverSemVer,
onReset: () => shouldReset = true,
);
await _syncApiRepository.streamChanges(_handleEvents, onReset: () => shouldReset = true);
if (shouldReset) {
_logger.info("Resetting sync state as requested by server");
await _syncApiRepository.streamChanges(_handleEvents, serverVersion: serverSemVer);
await _syncApiRepository.streamChanges(_handleEvents);
}
previousLength = migrations.length;
@@ -286,8 +282,6 @@ class SyncStreamService {
return _syncStreamRepository.deletePeopleV1(data.cast());
case SyncEntityType.assetFaceV1:
return _syncStreamRepository.updateAssetFacesV1(data.cast());
case SyncEntityType.assetFaceV2:
return _syncStreamRepository.updateAssetFacesV2(data.cast());
case SyncEntityType.assetFaceDeleteV1:
return _syncStreamRepository.deleteAssetFacesV1(data.cast());
default:
@@ -78,9 +78,6 @@ class TimelineFactory {
TimelineService fromAssets(List<BaseAsset> assets, TimelineOrigin type) =>
TimelineService(_timelineRepository.fromAssets(assets, type));
TimelineService fromAssetStream(List<BaseAsset> Function() getAssets, Stream<int> assetCount, TimelineOrigin type) =>
TimelineService(_timelineRepository.fromAssetStream(getAssets, assetCount, type));
TimelineService fromAssetsWithBuckets(List<BaseAsset> assets, TimelineOrigin type) =>
TimelineService(_timelineRepository.fromAssetsWithBuckets(assets, type));
@@ -115,7 +112,7 @@ class TimelineService {
if (totalAssets == 0) {
_bufferOffset = 0;
_buffer = [];
_buffer.clear();
} else {
final int offset;
final int count;
@@ -1,20 +1,23 @@
import 'package:maplibre_gl/maplibre_gl.dart';
import 'package:maplibre/maplibre.dart';
extension WithinBounds on LatLngBounds {
extension WithinBounds on LngLatBounds {
/// Checks whether [point] is inside bounds
bool contains(LatLng point) {
final sw = point;
final ne = point;
return containsBounds(LatLngBounds(southwest: sw, northeast: ne));
bool contains(Geographic point) {
return containsBounds(
LngLatBounds(
longitudeWest: point.lon,
longitudeEast: point.lon,
latitudeSouth: point.lat,
latitudeNorth: point.lat,
),
);
}
/// Checks whether [bounds] is contained inside bounds
bool containsBounds(LatLngBounds bounds) {
final sw = bounds.southwest;
final ne = bounds.northeast;
return (sw.latitude >= southwest.latitude) &&
(ne.latitude <= northeast.latitude) &&
(sw.longitude >= southwest.longitude) &&
(ne.longitude <= northeast.longitude);
bool containsBounds(LngLatBounds bounds) {
return (bounds.latitudeSouth >= latitudeSouth) &&
(bounds.latitudeNorth <= latitudeNorth) &&
(bounds.longitudeWest >= longitudeWest) &&
(bounds.longitudeEast <= longitudeEast);
}
}
@@ -1,19 +1,19 @@
import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'dart:convert';
import 'package:flutter/services.dart';
import 'package:immich_mobile/models/map/map_marker.model.dart';
import 'package:immich_mobile/utils/map_utils.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
import 'package:maplibre/maplibre.dart';
extension MapMarkers on MapLibreMapController {
extension MapMarkers on MapController {
static var _completer = Completer()..complete();
Future<void> addGeoJSONSourceForMarkers(List<MapMarker> markers) async {
return addSource(
MapUtils.defaultSourceId,
GeojsonSourceProperties(data: MapUtils.generateGeoJsonForMarkers(markers.toList())),
return style!.addSource(
GeoJsonSource(
id: MapUtils.defaultSourceId,
data: jsonEncode(MapUtils.generateGeoJsonForMarkers(markers.toList())),
),
);
}
@@ -27,63 +27,28 @@ extension MapMarkers on MapLibreMapController {
// !! Make sure to remove layers before sources else the native
// maplibre library would crash when removing the source saying that
// the source is still in use
final existingLayers = await getLayerIds();
if (existingLayers.contains(MapUtils.defaultHeatMapLayerId)) {
await removeLayer(MapUtils.defaultHeatMapLayerId);
try {
await style!.removeLayer(MapUtils.defaultHeatMapLayerId);
} catch (_) {
// Layer may not exist
}
final existingSources = await getSourceIds();
if (existingSources.contains(MapUtils.defaultSourceId)) {
await removeSource(MapUtils.defaultSourceId);
try {
await style!.removeSource(MapUtils.defaultSourceId);
} catch (_) {
// Source may not exist
}
await addGeoJSONSourceForMarkers(markers);
if (Platform.isAndroid) {
await addCircleLayer(
MapUtils.defaultSourceId,
MapUtils.defaultHeatMapLayerId,
const CircleLayerProperties(
circleRadius: 10,
circleColor: "rgba(150,86,34,0.7)",
circleBlur: 1.0,
circleOpacity: 0.7,
circleStrokeWidth: 0.1,
circleStrokeColor: "rgba(203,46,19,0.5)",
circleStrokeOpacity: 0.7,
),
);
}
if (Platform.isIOS) {
await addHeatmapLayer(
MapUtils.defaultSourceId,
MapUtils.defaultHeatMapLayerId,
MapUtils.defaultHeatMapLayerProperties,
);
}
await style!.addLayer(
const HeatmapStyleLayer(
id: MapUtils.defaultHeatMapLayerId,
sourceId: MapUtils.defaultSourceId,
paint: MapUtils.defaultHeatMapLayerPaint,
),
);
_completer.complete();
}
Future<Symbol?> addMarkerAtLatLng(LatLng centre) async {
// no marker is displayed if asset-path is incorrect
try {
final ByteData bytes = await rootBundle.load("assets/location-pin.png");
await addImage("mapMarker", bytes.buffer.asUint8List());
return addSymbol(SymbolOptions(geometry: centre, iconImage: "mapMarker", iconSize: 0.15, iconAnchor: "bottom"));
} finally {
// no-op
}
}
Future<LatLngBounds> getBoundsFromPoint(Point<double> point, double distance) async {
final southWestPx = Point(point.x - distance, point.y + distance);
final northEastPx = Point(point.x + distance, point.y - distance);
final southWest = await toLatLng(southWestPx);
final northEast = await toLatLng(northEastPx);
return LatLngBounds(southwest: southWest, northeast: northEast);
}
}
+82 -27
View File
@@ -33,27 +33,12 @@ class FastClampingScrollPhysics extends ClampingScrollPhysics {
);
}
class SnapScrollController extends ScrollController {
SnapScrollPosition get snapPosition => position as SnapScrollPosition;
@override
ScrollPosition createScrollPosition(ScrollPhysics physics, ScrollContext context, ScrollPosition? oldPosition) =>
SnapScrollPosition(physics: physics, context: context, oldPosition: oldPosition);
}
class SnapScrollPosition extends ScrollPositionWithSingleContext {
double snapOffset;
SnapScrollPosition({required super.physics, required super.context, super.oldPosition, this.snapOffset = 0.0});
@override
bool get shouldIgnorePointer => false;
}
class SnapScrollPhysics extends ScrollPhysics {
static const _minFlingVelocity = 700.0;
static const minSnapDistance = 30.0;
static final _spring = SpringDescription.withDampingRatio(mass: .5, stiffness: 300);
const SnapScrollPhysics({super.parent});
@override
@@ -81,21 +66,91 @@ class SnapScrollPhysics extends ScrollPhysics {
}
}
return ScrollSpringSimulation(spring, position.pixels, target(position, velocity, snapOffset), velocity);
return ScrollSpringSimulation(
_spring,
position.pixels,
target(position, velocity, snapOffset),
velocity,
tolerance: toleranceFor(position),
);
}
@override
SpringDescription get spring => SpringDescription.withDampingRatio(mass: .5, stiffness: 300);
@override
bool get allowImplicitScrolling => false;
@override
bool get allowUserScrolling => false;
static double target(ScrollMetrics position, double velocity, double snapOffset) {
if (velocity > _minFlingVelocity) return snapOffset;
if (velocity < -_minFlingVelocity) return position.pixels < snapOffset ? 0.0 : snapOffset;
return position.pixels < minSnapDistance ? 0.0 : snapOffset;
}
}
class SnapScrollPosition extends ScrollPositionWithSingleContext {
double snapOffset;
SnapScrollPosition({this.snapOffset = 0.0, required super.physics, required super.context, super.oldPosition});
}
class ProxyScrollController extends ScrollController {
final ScrollController scrollController;
ProxyScrollController({required this.scrollController});
SnapScrollPosition get snapPosition => position as SnapScrollPosition;
@override
ScrollPosition createScrollPosition(ScrollPhysics physics, ScrollContext context, ScrollPosition? oldPosition) {
return ProxyScrollPosition(
scrollController: scrollController,
physics: physics,
context: context,
oldPosition: oldPosition,
);
}
@override
void dispose() {
scrollController.dispose();
super.dispose();
}
}
class ProxyScrollPosition extends SnapScrollPosition {
final ScrollController scrollController;
ProxyScrollPosition({
required this.scrollController,
required super.physics,
required super.context,
super.oldPosition,
});
@override
double setPixels(double newPixels) {
final overscroll = super.setPixels(newPixels);
if (scrollController.hasClients && scrollController.position.pixels != pixels) {
scrollController.position.forcePixels(pixels);
}
return overscroll;
}
@override
void forcePixels(double value) {
super.forcePixels(value);
if (scrollController.hasClients && scrollController.position.pixels != pixels) {
scrollController.position.forcePixels(pixels);
}
}
@override
double get maxScrollExtent => scrollController.hasClients && scrollController.position.hasContentDimensions
? scrollController.position.maxScrollExtent
: super.maxScrollExtent;
@override
double get minScrollExtent => scrollController.hasClients && scrollController.position.hasContentDimensions
? scrollController.position.minScrollExtent
: super.minScrollExtent;
@override
double get viewportDimension => scrollController.hasClients && scrollController.position.hasViewportDimension
? scrollController.position.viewportDimension
: super.viewportDimension;
}
@@ -28,10 +28,6 @@ class AssetFaceEntity extends Table with DriftDefaultsMixin {
TextColumn get sourceType => text()();
BoolColumn get isVisible => boolean().withDefault(const Constant(true))();
DateTimeColumn get deletedAt => dateTime().nullable()();
@override
Set<Column> get primaryKey => {id};
}
+68 -202
View File
@@ -5,12 +5,11 @@ import 'package:immich_mobile/infrastructure/entities/asset_face.entity.drift.da
as i1;
import 'package:immich_mobile/infrastructure/entities/asset_face.entity.dart'
as i2;
import 'package:drift/src/runtime/query_builder/query_builder.dart' as i3;
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart'
as i4;
import 'package:drift/internal/modular.dart' as i5;
as i3;
import 'package:drift/internal/modular.dart' as i4;
import 'package:immich_mobile/infrastructure/entities/person.entity.drift.dart'
as i6;
as i5;
typedef $$AssetFaceEntityTableCreateCompanionBuilder =
i1.AssetFaceEntityCompanion Function({
@@ -24,8 +23,6 @@ typedef $$AssetFaceEntityTableCreateCompanionBuilder =
required int boundingBoxX2,
required int boundingBoxY2,
required String sourceType,
i0.Value<bool> isVisible,
i0.Value<DateTime?> deletedAt,
});
typedef $$AssetFaceEntityTableUpdateCompanionBuilder =
i1.AssetFaceEntityCompanion Function({
@@ -39,8 +36,6 @@ typedef $$AssetFaceEntityTableUpdateCompanionBuilder =
i0.Value<int> boundingBoxX2,
i0.Value<int> boundingBoxY2,
i0.Value<String> sourceType,
i0.Value<bool> isVisible,
i0.Value<DateTime?> deletedAt,
});
final class $$AssetFaceEntityTableReferences
@@ -56,29 +51,29 @@ final class $$AssetFaceEntityTableReferences
super.$_typedResult,
);
static i4.$RemoteAssetEntityTable _assetIdTable(i0.GeneratedDatabase db) =>
i5.ReadDatabaseContainer(db)
.resultSet<i4.$RemoteAssetEntityTable>('remote_asset_entity')
static i3.$RemoteAssetEntityTable _assetIdTable(i0.GeneratedDatabase db) =>
i4.ReadDatabaseContainer(db)
.resultSet<i3.$RemoteAssetEntityTable>('remote_asset_entity')
.createAlias(
i0.$_aliasNameGenerator(
i5.ReadDatabaseContainer(db)
i4.ReadDatabaseContainer(db)
.resultSet<i1.$AssetFaceEntityTable>('asset_face_entity')
.assetId,
i5.ReadDatabaseContainer(
i4.ReadDatabaseContainer(
db,
).resultSet<i4.$RemoteAssetEntityTable>('remote_asset_entity').id,
).resultSet<i3.$RemoteAssetEntityTable>('remote_asset_entity').id,
),
);
i4.$$RemoteAssetEntityTableProcessedTableManager get assetId {
i3.$$RemoteAssetEntityTableProcessedTableManager get assetId {
final $_column = $_itemColumn<String>('asset_id')!;
final manager = i4
final manager = i3
.$$RemoteAssetEntityTableTableManager(
$_db,
i5.ReadDatabaseContainer(
i4.ReadDatabaseContainer(
$_db,
).resultSet<i4.$RemoteAssetEntityTable>('remote_asset_entity'),
).resultSet<i3.$RemoteAssetEntityTable>('remote_asset_entity'),
)
.filter((f) => f.id.sqlEquals($_column));
final item = $_typedResult.readTableOrNull(_assetIdTable($_db));
@@ -88,29 +83,29 @@ final class $$AssetFaceEntityTableReferences
);
}
static i6.$PersonEntityTable _personIdTable(i0.GeneratedDatabase db) =>
i5.ReadDatabaseContainer(db)
.resultSet<i6.$PersonEntityTable>('person_entity')
static i5.$PersonEntityTable _personIdTable(i0.GeneratedDatabase db) =>
i4.ReadDatabaseContainer(db)
.resultSet<i5.$PersonEntityTable>('person_entity')
.createAlias(
i0.$_aliasNameGenerator(
i5.ReadDatabaseContainer(db)
i4.ReadDatabaseContainer(db)
.resultSet<i1.$AssetFaceEntityTable>('asset_face_entity')
.personId,
i5.ReadDatabaseContainer(
i4.ReadDatabaseContainer(
db,
).resultSet<i6.$PersonEntityTable>('person_entity').id,
).resultSet<i5.$PersonEntityTable>('person_entity').id,
),
);
i6.$$PersonEntityTableProcessedTableManager? get personId {
i5.$$PersonEntityTableProcessedTableManager? get personId {
final $_column = $_itemColumn<String>('person_id');
if ($_column == null) return null;
final manager = i6
final manager = i5
.$$PersonEntityTableTableManager(
$_db,
i5.ReadDatabaseContainer(
i4.ReadDatabaseContainer(
$_db,
).resultSet<i6.$PersonEntityTable>('person_entity'),
).resultSet<i5.$PersonEntityTable>('person_entity'),
)
.filter((f) => f.id.sqlEquals($_column));
final item = $_typedResult.readTableOrNull(_personIdTable($_db));
@@ -170,34 +165,24 @@ class $$AssetFaceEntityTableFilterComposer
builder: (column) => i0.ColumnFilters(column),
);
i0.ColumnFilters<bool> get isVisible => $composableBuilder(
column: $table.isVisible,
builder: (column) => i0.ColumnFilters(column),
);
i0.ColumnFilters<DateTime> get deletedAt => $composableBuilder(
column: $table.deletedAt,
builder: (column) => i0.ColumnFilters(column),
);
i4.$$RemoteAssetEntityTableFilterComposer get assetId {
final i4.$$RemoteAssetEntityTableFilterComposer composer = $composerBuilder(
i3.$$RemoteAssetEntityTableFilterComposer get assetId {
final i3.$$RemoteAssetEntityTableFilterComposer composer = $composerBuilder(
composer: this,
getCurrentColumn: (t) => t.assetId,
referencedTable: i5.ReadDatabaseContainer(
referencedTable: i4.ReadDatabaseContainer(
$db,
).resultSet<i4.$RemoteAssetEntityTable>('remote_asset_entity'),
).resultSet<i3.$RemoteAssetEntityTable>('remote_asset_entity'),
getReferencedColumn: (t) => t.id,
builder:
(
joinBuilder, {
$addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer,
}) => i4.$$RemoteAssetEntityTableFilterComposer(
}) => i3.$$RemoteAssetEntityTableFilterComposer(
$db: $db,
$table: i5.ReadDatabaseContainer(
$table: i4.ReadDatabaseContainer(
$db,
).resultSet<i4.$RemoteAssetEntityTable>('remote_asset_entity'),
).resultSet<i3.$RemoteAssetEntityTable>('remote_asset_entity'),
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer:
@@ -207,24 +192,24 @@ class $$AssetFaceEntityTableFilterComposer
return composer;
}
i6.$$PersonEntityTableFilterComposer get personId {
final i6.$$PersonEntityTableFilterComposer composer = $composerBuilder(
i5.$$PersonEntityTableFilterComposer get personId {
final i5.$$PersonEntityTableFilterComposer composer = $composerBuilder(
composer: this,
getCurrentColumn: (t) => t.personId,
referencedTable: i5.ReadDatabaseContainer(
referencedTable: i4.ReadDatabaseContainer(
$db,
).resultSet<i6.$PersonEntityTable>('person_entity'),
).resultSet<i5.$PersonEntityTable>('person_entity'),
getReferencedColumn: (t) => t.id,
builder:
(
joinBuilder, {
$addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer,
}) => i6.$$PersonEntityTableFilterComposer(
}) => i5.$$PersonEntityTableFilterComposer(
$db: $db,
$table: i5.ReadDatabaseContainer(
$table: i4.ReadDatabaseContainer(
$db,
).resultSet<i6.$PersonEntityTable>('person_entity'),
).resultSet<i5.$PersonEntityTable>('person_entity'),
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer:
@@ -284,35 +269,25 @@ class $$AssetFaceEntityTableOrderingComposer
builder: (column) => i0.ColumnOrderings(column),
);
i0.ColumnOrderings<bool> get isVisible => $composableBuilder(
column: $table.isVisible,
builder: (column) => i0.ColumnOrderings(column),
);
i0.ColumnOrderings<DateTime> get deletedAt => $composableBuilder(
column: $table.deletedAt,
builder: (column) => i0.ColumnOrderings(column),
);
i4.$$RemoteAssetEntityTableOrderingComposer get assetId {
final i4.$$RemoteAssetEntityTableOrderingComposer composer =
i3.$$RemoteAssetEntityTableOrderingComposer get assetId {
final i3.$$RemoteAssetEntityTableOrderingComposer composer =
$composerBuilder(
composer: this,
getCurrentColumn: (t) => t.assetId,
referencedTable: i5.ReadDatabaseContainer(
referencedTable: i4.ReadDatabaseContainer(
$db,
).resultSet<i4.$RemoteAssetEntityTable>('remote_asset_entity'),
).resultSet<i3.$RemoteAssetEntityTable>('remote_asset_entity'),
getReferencedColumn: (t) => t.id,
builder:
(
joinBuilder, {
$addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer,
}) => i4.$$RemoteAssetEntityTableOrderingComposer(
}) => i3.$$RemoteAssetEntityTableOrderingComposer(
$db: $db,
$table: i5.ReadDatabaseContainer(
$table: i4.ReadDatabaseContainer(
$db,
).resultSet<i4.$RemoteAssetEntityTable>('remote_asset_entity'),
).resultSet<i3.$RemoteAssetEntityTable>('remote_asset_entity'),
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer:
@@ -322,24 +297,24 @@ class $$AssetFaceEntityTableOrderingComposer
return composer;
}
i6.$$PersonEntityTableOrderingComposer get personId {
final i6.$$PersonEntityTableOrderingComposer composer = $composerBuilder(
i5.$$PersonEntityTableOrderingComposer get personId {
final i5.$$PersonEntityTableOrderingComposer composer = $composerBuilder(
composer: this,
getCurrentColumn: (t) => t.personId,
referencedTable: i5.ReadDatabaseContainer(
referencedTable: i4.ReadDatabaseContainer(
$db,
).resultSet<i6.$PersonEntityTable>('person_entity'),
).resultSet<i5.$PersonEntityTable>('person_entity'),
getReferencedColumn: (t) => t.id,
builder:
(
joinBuilder, {
$addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer,
}) => i6.$$PersonEntityTableOrderingComposer(
}) => i5.$$PersonEntityTableOrderingComposer(
$db: $db,
$table: i5.ReadDatabaseContainer(
$table: i4.ReadDatabaseContainer(
$db,
).resultSet<i6.$PersonEntityTable>('person_entity'),
).resultSet<i5.$PersonEntityTable>('person_entity'),
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer:
@@ -397,31 +372,25 @@ class $$AssetFaceEntityTableAnnotationComposer
builder: (column) => column,
);
i0.GeneratedColumn<bool> get isVisible =>
$composableBuilder(column: $table.isVisible, builder: (column) => column);
i0.GeneratedColumn<DateTime> get deletedAt =>
$composableBuilder(column: $table.deletedAt, builder: (column) => column);
i4.$$RemoteAssetEntityTableAnnotationComposer get assetId {
final i4.$$RemoteAssetEntityTableAnnotationComposer composer =
i3.$$RemoteAssetEntityTableAnnotationComposer get assetId {
final i3.$$RemoteAssetEntityTableAnnotationComposer composer =
$composerBuilder(
composer: this,
getCurrentColumn: (t) => t.assetId,
referencedTable: i5.ReadDatabaseContainer(
referencedTable: i4.ReadDatabaseContainer(
$db,
).resultSet<i4.$RemoteAssetEntityTable>('remote_asset_entity'),
).resultSet<i3.$RemoteAssetEntityTable>('remote_asset_entity'),
getReferencedColumn: (t) => t.id,
builder:
(
joinBuilder, {
$addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer,
}) => i4.$$RemoteAssetEntityTableAnnotationComposer(
}) => i3.$$RemoteAssetEntityTableAnnotationComposer(
$db: $db,
$table: i5.ReadDatabaseContainer(
$table: i4.ReadDatabaseContainer(
$db,
).resultSet<i4.$RemoteAssetEntityTable>('remote_asset_entity'),
).resultSet<i3.$RemoteAssetEntityTable>('remote_asset_entity'),
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer:
@@ -431,24 +400,24 @@ class $$AssetFaceEntityTableAnnotationComposer
return composer;
}
i6.$$PersonEntityTableAnnotationComposer get personId {
final i6.$$PersonEntityTableAnnotationComposer composer = $composerBuilder(
i5.$$PersonEntityTableAnnotationComposer get personId {
final i5.$$PersonEntityTableAnnotationComposer composer = $composerBuilder(
composer: this,
getCurrentColumn: (t) => t.personId,
referencedTable: i5.ReadDatabaseContainer(
referencedTable: i4.ReadDatabaseContainer(
$db,
).resultSet<i6.$PersonEntityTable>('person_entity'),
).resultSet<i5.$PersonEntityTable>('person_entity'),
getReferencedColumn: (t) => t.id,
builder:
(
joinBuilder, {
$addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer,
}) => i6.$$PersonEntityTableAnnotationComposer(
}) => i5.$$PersonEntityTableAnnotationComposer(
$db: $db,
$table: i5.ReadDatabaseContainer(
$table: i4.ReadDatabaseContainer(
$db,
).resultSet<i6.$PersonEntityTable>('person_entity'),
).resultSet<i5.$PersonEntityTable>('person_entity'),
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer:
@@ -499,8 +468,6 @@ class $$AssetFaceEntityTableTableManager
i0.Value<int> boundingBoxX2 = const i0.Value.absent(),
i0.Value<int> boundingBoxY2 = const i0.Value.absent(),
i0.Value<String> sourceType = const i0.Value.absent(),
i0.Value<bool> isVisible = const i0.Value.absent(),
i0.Value<DateTime?> deletedAt = const i0.Value.absent(),
}) => i1.AssetFaceEntityCompanion(
id: id,
assetId: assetId,
@@ -512,8 +479,6 @@ class $$AssetFaceEntityTableTableManager
boundingBoxX2: boundingBoxX2,
boundingBoxY2: boundingBoxY2,
sourceType: sourceType,
isVisible: isVisible,
deletedAt: deletedAt,
),
createCompanionCallback:
({
@@ -527,8 +492,6 @@ class $$AssetFaceEntityTableTableManager
required int boundingBoxX2,
required int boundingBoxY2,
required String sourceType,
i0.Value<bool> isVisible = const i0.Value.absent(),
i0.Value<DateTime?> deletedAt = const i0.Value.absent(),
}) => i1.AssetFaceEntityCompanion.insert(
id: id,
assetId: assetId,
@@ -540,8 +503,6 @@ class $$AssetFaceEntityTableTableManager
boundingBoxX2: boundingBoxX2,
boundingBoxY2: boundingBoxY2,
sourceType: sourceType,
isVisible: isVisible,
deletedAt: deletedAt,
),
withReferenceMapper: (p0) => p0
.map(
@@ -748,33 +709,6 @@ class $AssetFaceEntityTable extends i2.AssetFaceEntity
type: i0.DriftSqlType.string,
requiredDuringInsert: true,
);
static const i0.VerificationMeta _isVisibleMeta = const i0.VerificationMeta(
'isVisible',
);
@override
late final i0.GeneratedColumn<bool> isVisible = i0.GeneratedColumn<bool>(
'is_visible',
aliasedName,
false,
type: i0.DriftSqlType.bool,
requiredDuringInsert: false,
defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
'CHECK ("is_visible" IN (0, 1))',
),
defaultValue: const i3.Constant(true),
);
static const i0.VerificationMeta _deletedAtMeta = const i0.VerificationMeta(
'deletedAt',
);
@override
late final i0.GeneratedColumn<DateTime> deletedAt =
i0.GeneratedColumn<DateTime>(
'deleted_at',
aliasedName,
true,
type: i0.DriftSqlType.dateTime,
requiredDuringInsert: false,
);
@override
List<i0.GeneratedColumn> get $columns => [
id,
@@ -787,8 +721,6 @@ class $AssetFaceEntityTable extends i2.AssetFaceEntity
boundingBoxX2,
boundingBoxY2,
sourceType,
isVisible,
deletedAt,
];
@override
String get aliasedName => _alias ?? actualTableName;
@@ -892,18 +824,6 @@ class $AssetFaceEntityTable extends i2.AssetFaceEntity
} else if (isInserting) {
context.missing(_sourceTypeMeta);
}
if (data.containsKey('is_visible')) {
context.handle(
_isVisibleMeta,
isVisible.isAcceptableOrUnknown(data['is_visible']!, _isVisibleMeta),
);
}
if (data.containsKey('deleted_at')) {
context.handle(
_deletedAtMeta,
deletedAt.isAcceptableOrUnknown(data['deleted_at']!, _deletedAtMeta),
);
}
return context;
}
@@ -953,14 +873,6 @@ class $AssetFaceEntityTable extends i2.AssetFaceEntity
i0.DriftSqlType.string,
data['${effectivePrefix}source_type'],
)!,
isVisible: attachedDatabase.typeMapping.read(
i0.DriftSqlType.bool,
data['${effectivePrefix}is_visible'],
)!,
deletedAt: attachedDatabase.typeMapping.read(
i0.DriftSqlType.dateTime,
data['${effectivePrefix}deleted_at'],
),
);
}
@@ -987,8 +899,6 @@ class AssetFaceEntityData extends i0.DataClass
final int boundingBoxX2;
final int boundingBoxY2;
final String sourceType;
final bool isVisible;
final DateTime? deletedAt;
const AssetFaceEntityData({
required this.id,
required this.assetId,
@@ -1000,8 +910,6 @@ class AssetFaceEntityData extends i0.DataClass
required this.boundingBoxX2,
required this.boundingBoxY2,
required this.sourceType,
required this.isVisible,
this.deletedAt,
});
@override
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
@@ -1018,10 +926,6 @@ class AssetFaceEntityData extends i0.DataClass
map['bounding_box_x2'] = i0.Variable<int>(boundingBoxX2);
map['bounding_box_y2'] = i0.Variable<int>(boundingBoxY2);
map['source_type'] = i0.Variable<String>(sourceType);
map['is_visible'] = i0.Variable<bool>(isVisible);
if (!nullToAbsent || deletedAt != null) {
map['deleted_at'] = i0.Variable<DateTime>(deletedAt);
}
return map;
}
@@ -1041,8 +945,6 @@ class AssetFaceEntityData extends i0.DataClass
boundingBoxX2: serializer.fromJson<int>(json['boundingBoxX2']),
boundingBoxY2: serializer.fromJson<int>(json['boundingBoxY2']),
sourceType: serializer.fromJson<String>(json['sourceType']),
isVisible: serializer.fromJson<bool>(json['isVisible']),
deletedAt: serializer.fromJson<DateTime?>(json['deletedAt']),
);
}
@override
@@ -1059,8 +961,6 @@ class AssetFaceEntityData extends i0.DataClass
'boundingBoxX2': serializer.toJson<int>(boundingBoxX2),
'boundingBoxY2': serializer.toJson<int>(boundingBoxY2),
'sourceType': serializer.toJson<String>(sourceType),
'isVisible': serializer.toJson<bool>(isVisible),
'deletedAt': serializer.toJson<DateTime?>(deletedAt),
};
}
@@ -1075,8 +975,6 @@ class AssetFaceEntityData extends i0.DataClass
int? boundingBoxX2,
int? boundingBoxY2,
String? sourceType,
bool? isVisible,
i0.Value<DateTime?> deletedAt = const i0.Value.absent(),
}) => i1.AssetFaceEntityData(
id: id ?? this.id,
assetId: assetId ?? this.assetId,
@@ -1088,8 +986,6 @@ class AssetFaceEntityData extends i0.DataClass
boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2,
boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2,
sourceType: sourceType ?? this.sourceType,
isVisible: isVisible ?? this.isVisible,
deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt,
);
AssetFaceEntityData copyWithCompanion(i1.AssetFaceEntityCompanion data) {
return AssetFaceEntityData(
@@ -1117,8 +1013,6 @@ class AssetFaceEntityData extends i0.DataClass
sourceType: data.sourceType.present
? data.sourceType.value
: this.sourceType,
isVisible: data.isVisible.present ? data.isVisible.value : this.isVisible,
deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt,
);
}
@@ -1134,9 +1028,7 @@ class AssetFaceEntityData extends i0.DataClass
..write('boundingBoxY1: $boundingBoxY1, ')
..write('boundingBoxX2: $boundingBoxX2, ')
..write('boundingBoxY2: $boundingBoxY2, ')
..write('sourceType: $sourceType, ')
..write('isVisible: $isVisible, ')
..write('deletedAt: $deletedAt')
..write('sourceType: $sourceType')
..write(')'))
.toString();
}
@@ -1153,8 +1045,6 @@ class AssetFaceEntityData extends i0.DataClass
boundingBoxX2,
boundingBoxY2,
sourceType,
isVisible,
deletedAt,
);
@override
bool operator ==(Object other) =>
@@ -1169,9 +1059,7 @@ class AssetFaceEntityData extends i0.DataClass
other.boundingBoxY1 == this.boundingBoxY1 &&
other.boundingBoxX2 == this.boundingBoxX2 &&
other.boundingBoxY2 == this.boundingBoxY2 &&
other.sourceType == this.sourceType &&
other.isVisible == this.isVisible &&
other.deletedAt == this.deletedAt);
other.sourceType == this.sourceType);
}
class AssetFaceEntityCompanion
@@ -1186,8 +1074,6 @@ class AssetFaceEntityCompanion
final i0.Value<int> boundingBoxX2;
final i0.Value<int> boundingBoxY2;
final i0.Value<String> sourceType;
final i0.Value<bool> isVisible;
final i0.Value<DateTime?> deletedAt;
const AssetFaceEntityCompanion({
this.id = const i0.Value.absent(),
this.assetId = const i0.Value.absent(),
@@ -1199,8 +1085,6 @@ class AssetFaceEntityCompanion
this.boundingBoxX2 = const i0.Value.absent(),
this.boundingBoxY2 = const i0.Value.absent(),
this.sourceType = const i0.Value.absent(),
this.isVisible = const i0.Value.absent(),
this.deletedAt = const i0.Value.absent(),
});
AssetFaceEntityCompanion.insert({
required String id,
@@ -1213,8 +1097,6 @@ class AssetFaceEntityCompanion
required int boundingBoxX2,
required int boundingBoxY2,
required String sourceType,
this.isVisible = const i0.Value.absent(),
this.deletedAt = const i0.Value.absent(),
}) : id = i0.Value(id),
assetId = i0.Value(assetId),
imageWidth = i0.Value(imageWidth),
@@ -1235,8 +1117,6 @@ class AssetFaceEntityCompanion
i0.Expression<int>? boundingBoxX2,
i0.Expression<int>? boundingBoxY2,
i0.Expression<String>? sourceType,
i0.Expression<bool>? isVisible,
i0.Expression<DateTime>? deletedAt,
}) {
return i0.RawValuesInsertable({
if (id != null) 'id': id,
@@ -1249,8 +1129,6 @@ class AssetFaceEntityCompanion
if (boundingBoxX2 != null) 'bounding_box_x2': boundingBoxX2,
if (boundingBoxY2 != null) 'bounding_box_y2': boundingBoxY2,
if (sourceType != null) 'source_type': sourceType,
if (isVisible != null) 'is_visible': isVisible,
if (deletedAt != null) 'deleted_at': deletedAt,
});
}
@@ -1265,8 +1143,6 @@ class AssetFaceEntityCompanion
i0.Value<int>? boundingBoxX2,
i0.Value<int>? boundingBoxY2,
i0.Value<String>? sourceType,
i0.Value<bool>? isVisible,
i0.Value<DateTime?>? deletedAt,
}) {
return i1.AssetFaceEntityCompanion(
id: id ?? this.id,
@@ -1279,8 +1155,6 @@ class AssetFaceEntityCompanion
boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2,
boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2,
sourceType: sourceType ?? this.sourceType,
isVisible: isVisible ?? this.isVisible,
deletedAt: deletedAt ?? this.deletedAt,
);
}
@@ -1317,12 +1191,6 @@ class AssetFaceEntityCompanion
if (sourceType.present) {
map['source_type'] = i0.Variable<String>(sourceType.value);
}
if (isVisible.present) {
map['is_visible'] = i0.Variable<bool>(isVisible.value);
}
if (deletedAt.present) {
map['deleted_at'] = i0.Variable<DateTime>(deletedAt.value);
}
return map;
}
@@ -1338,9 +1206,7 @@ class AssetFaceEntityCompanion
..write('boundingBoxY1: $boundingBoxY1, ')
..write('boundingBoxX2: $boundingBoxX2, ')
..write('boundingBoxY2: $boundingBoxY2, ')
..write('sourceType: $sourceType, ')
..write('isVisible: $isVisible, ')
..write('deletedAt: $deletedAt')
..write('sourceType: $sourceType')
..write(')'))
.toString();
}
@@ -25,8 +25,6 @@ class LocalAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin {
RealColumn get longitude => real().nullable()();
IntColumn get playbackStyle => intEnum<AssetPlaybackStyle>().withDefault(const Constant(0))();
@override
Set<Column> get primaryKey => {id};
}
@@ -45,7 +43,6 @@ extension LocalAssetEntityDataDomainExtension on LocalAssetEntityData {
width: width,
remoteId: remoteId,
orientation: orientation,
playbackStyle: playbackStyle,
adjustmentTime: adjustmentTime,
latitude: latitude,
longitude: longitude,
@@ -25,7 +25,6 @@ typedef $$LocalAssetEntityTableCreateCompanionBuilder =
i0.Value<DateTime?> adjustmentTime,
i0.Value<double?> latitude,
i0.Value<double?> longitude,
i0.Value<i2.AssetPlaybackStyle> playbackStyle,
});
typedef $$LocalAssetEntityTableUpdateCompanionBuilder =
i1.LocalAssetEntityCompanion Function({
@@ -44,7 +43,6 @@ typedef $$LocalAssetEntityTableUpdateCompanionBuilder =
i0.Value<DateTime?> adjustmentTime,
i0.Value<double?> latitude,
i0.Value<double?> longitude,
i0.Value<i2.AssetPlaybackStyle> playbackStyle,
});
class $$LocalAssetEntityTableFilterComposer
@@ -131,16 +129,6 @@ class $$LocalAssetEntityTableFilterComposer
column: $table.longitude,
builder: (column) => i0.ColumnFilters(column),
);
i0.ColumnWithTypeConverterFilters<
i2.AssetPlaybackStyle,
i2.AssetPlaybackStyle,
int
>
get playbackStyle => $composableBuilder(
column: $table.playbackStyle,
builder: (column) => i0.ColumnWithTypeConverterFilters(column),
);
}
class $$LocalAssetEntityTableOrderingComposer
@@ -226,11 +214,6 @@ class $$LocalAssetEntityTableOrderingComposer
column: $table.longitude,
builder: (column) => i0.ColumnOrderings(column),
);
i0.ColumnOrderings<int> get playbackStyle => $composableBuilder(
column: $table.playbackStyle,
builder: (column) => i0.ColumnOrderings(column),
);
}
class $$LocalAssetEntityTableAnnotationComposer
@@ -294,12 +277,6 @@ class $$LocalAssetEntityTableAnnotationComposer
i0.GeneratedColumn<double> get longitude =>
$composableBuilder(column: $table.longitude, builder: (column) => column);
i0.GeneratedColumnWithTypeConverter<i2.AssetPlaybackStyle, int>
get playbackStyle => $composableBuilder(
column: $table.playbackStyle,
builder: (column) => column,
);
}
class $$LocalAssetEntityTableTableManager
@@ -357,8 +334,6 @@ class $$LocalAssetEntityTableTableManager
i0.Value<DateTime?> adjustmentTime = const i0.Value.absent(),
i0.Value<double?> latitude = const i0.Value.absent(),
i0.Value<double?> longitude = const i0.Value.absent(),
i0.Value<i2.AssetPlaybackStyle> playbackStyle =
const i0.Value.absent(),
}) => i1.LocalAssetEntityCompanion(
name: name,
type: type,
@@ -375,7 +350,6 @@ class $$LocalAssetEntityTableTableManager
adjustmentTime: adjustmentTime,
latitude: latitude,
longitude: longitude,
playbackStyle: playbackStyle,
),
createCompanionCallback:
({
@@ -394,8 +368,6 @@ class $$LocalAssetEntityTableTableManager
i0.Value<DateTime?> adjustmentTime = const i0.Value.absent(),
i0.Value<double?> latitude = const i0.Value.absent(),
i0.Value<double?> longitude = const i0.Value.absent(),
i0.Value<i2.AssetPlaybackStyle> playbackStyle =
const i0.Value.absent(),
}) => i1.LocalAssetEntityCompanion.insert(
name: name,
type: type,
@@ -412,7 +384,6 @@ class $$LocalAssetEntityTableTableManager
adjustmentTime: adjustmentTime,
latitude: latitude,
longitude: longitude,
playbackStyle: playbackStyle,
),
withReferenceMapper: (p0) => p0
.map((e) => (e.readTable(table), i0.BaseReferences(db, table, e)))
@@ -625,19 +596,6 @@ class $LocalAssetEntityTable extends i3.LocalAssetEntity
requiredDuringInsert: false,
);
@override
late final i0.GeneratedColumnWithTypeConverter<i2.AssetPlaybackStyle, int>
playbackStyle =
i0.GeneratedColumn<int>(
'playback_style',
aliasedName,
false,
type: i0.DriftSqlType.int,
requiredDuringInsert: false,
defaultValue: const i4.Constant(0),
).withConverter<i2.AssetPlaybackStyle>(
i1.$LocalAssetEntityTable.$converterplaybackStyle,
);
@override
List<i0.GeneratedColumn> get $columns => [
name,
type,
@@ -654,7 +612,6 @@ class $LocalAssetEntityTable extends i3.LocalAssetEntity
adjustmentTime,
latitude,
longitude,
playbackStyle,
];
@override
String get aliasedName => _alias ?? actualTableName;
@@ -836,12 +793,6 @@ class $LocalAssetEntityTable extends i3.LocalAssetEntity
i0.DriftSqlType.double,
data['${effectivePrefix}longitude'],
),
playbackStyle: i1.$LocalAssetEntityTable.$converterplaybackStyle.fromSql(
attachedDatabase.typeMapping.read(
i0.DriftSqlType.int,
data['${effectivePrefix}playback_style'],
)!,
),
);
}
@@ -852,10 +803,6 @@ class $LocalAssetEntityTable extends i3.LocalAssetEntity
static i0.JsonTypeConverter2<i2.AssetType, int, int> $convertertype =
const i0.EnumIndexConverter<i2.AssetType>(i2.AssetType.values);
static i0.JsonTypeConverter2<i2.AssetPlaybackStyle, int, int>
$converterplaybackStyle = const i0.EnumIndexConverter<i2.AssetPlaybackStyle>(
i2.AssetPlaybackStyle.values,
);
@override
bool get withoutRowId => true;
@override
@@ -879,7 +826,6 @@ class LocalAssetEntityData extends i0.DataClass
final DateTime? adjustmentTime;
final double? latitude;
final double? longitude;
final i2.AssetPlaybackStyle playbackStyle;
const LocalAssetEntityData({
required this.name,
required this.type,
@@ -896,7 +842,6 @@ class LocalAssetEntityData extends i0.DataClass
this.adjustmentTime,
this.latitude,
this.longitude,
required this.playbackStyle,
});
@override
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
@@ -936,11 +881,6 @@ class LocalAssetEntityData extends i0.DataClass
if (!nullToAbsent || longitude != null) {
map['longitude'] = i0.Variable<double>(longitude);
}
{
map['playback_style'] = i0.Variable<int>(
i1.$LocalAssetEntityTable.$converterplaybackStyle.toSql(playbackStyle),
);
}
return map;
}
@@ -967,9 +907,6 @@ class LocalAssetEntityData extends i0.DataClass
adjustmentTime: serializer.fromJson<DateTime?>(json['adjustmentTime']),
latitude: serializer.fromJson<double?>(json['latitude']),
longitude: serializer.fromJson<double?>(json['longitude']),
playbackStyle: i1.$LocalAssetEntityTable.$converterplaybackStyle.fromJson(
serializer.fromJson<int>(json['playbackStyle']),
),
);
}
@override
@@ -993,9 +930,6 @@ class LocalAssetEntityData extends i0.DataClass
'adjustmentTime': serializer.toJson<DateTime?>(adjustmentTime),
'latitude': serializer.toJson<double?>(latitude),
'longitude': serializer.toJson<double?>(longitude),
'playbackStyle': serializer.toJson<int>(
i1.$LocalAssetEntityTable.$converterplaybackStyle.toJson(playbackStyle),
),
};
}
@@ -1015,7 +949,6 @@ class LocalAssetEntityData extends i0.DataClass
i0.Value<DateTime?> adjustmentTime = const i0.Value.absent(),
i0.Value<double?> latitude = const i0.Value.absent(),
i0.Value<double?> longitude = const i0.Value.absent(),
i2.AssetPlaybackStyle? playbackStyle,
}) => i1.LocalAssetEntityData(
name: name ?? this.name,
type: type ?? this.type,
@@ -1036,7 +969,6 @@ class LocalAssetEntityData extends i0.DataClass
: this.adjustmentTime,
latitude: latitude.present ? latitude.value : this.latitude,
longitude: longitude.present ? longitude.value : this.longitude,
playbackStyle: playbackStyle ?? this.playbackStyle,
);
LocalAssetEntityData copyWithCompanion(i1.LocalAssetEntityCompanion data) {
return LocalAssetEntityData(
@@ -1063,9 +995,6 @@ class LocalAssetEntityData extends i0.DataClass
: this.adjustmentTime,
latitude: data.latitude.present ? data.latitude.value : this.latitude,
longitude: data.longitude.present ? data.longitude.value : this.longitude,
playbackStyle: data.playbackStyle.present
? data.playbackStyle.value
: this.playbackStyle,
);
}
@@ -1086,8 +1015,7 @@ class LocalAssetEntityData extends i0.DataClass
..write('iCloudId: $iCloudId, ')
..write('adjustmentTime: $adjustmentTime, ')
..write('latitude: $latitude, ')
..write('longitude: $longitude, ')
..write('playbackStyle: $playbackStyle')
..write('longitude: $longitude')
..write(')'))
.toString();
}
@@ -1109,7 +1037,6 @@ class LocalAssetEntityData extends i0.DataClass
adjustmentTime,
latitude,
longitude,
playbackStyle,
);
@override
bool operator ==(Object other) =>
@@ -1129,8 +1056,7 @@ class LocalAssetEntityData extends i0.DataClass
other.iCloudId == this.iCloudId &&
other.adjustmentTime == this.adjustmentTime &&
other.latitude == this.latitude &&
other.longitude == this.longitude &&
other.playbackStyle == this.playbackStyle);
other.longitude == this.longitude);
}
class LocalAssetEntityCompanion
@@ -1150,7 +1076,6 @@ class LocalAssetEntityCompanion
final i0.Value<DateTime?> adjustmentTime;
final i0.Value<double?> latitude;
final i0.Value<double?> longitude;
final i0.Value<i2.AssetPlaybackStyle> playbackStyle;
const LocalAssetEntityCompanion({
this.name = const i0.Value.absent(),
this.type = const i0.Value.absent(),
@@ -1167,7 +1092,6 @@ class LocalAssetEntityCompanion
this.adjustmentTime = const i0.Value.absent(),
this.latitude = const i0.Value.absent(),
this.longitude = const i0.Value.absent(),
this.playbackStyle = const i0.Value.absent(),
});
LocalAssetEntityCompanion.insert({
required String name,
@@ -1185,7 +1109,6 @@ class LocalAssetEntityCompanion
this.adjustmentTime = const i0.Value.absent(),
this.latitude = const i0.Value.absent(),
this.longitude = const i0.Value.absent(),
this.playbackStyle = const i0.Value.absent(),
}) : name = i0.Value(name),
type = i0.Value(type),
id = i0.Value(id);
@@ -1205,7 +1128,6 @@ class LocalAssetEntityCompanion
i0.Expression<DateTime>? adjustmentTime,
i0.Expression<double>? latitude,
i0.Expression<double>? longitude,
i0.Expression<int>? playbackStyle,
}) {
return i0.RawValuesInsertable({
if (name != null) 'name': name,
@@ -1223,7 +1145,6 @@ class LocalAssetEntityCompanion
if (adjustmentTime != null) 'adjustment_time': adjustmentTime,
if (latitude != null) 'latitude': latitude,
if (longitude != null) 'longitude': longitude,
if (playbackStyle != null) 'playback_style': playbackStyle,
});
}
@@ -1243,7 +1164,6 @@ class LocalAssetEntityCompanion
i0.Value<DateTime?>? adjustmentTime,
i0.Value<double?>? latitude,
i0.Value<double?>? longitude,
i0.Value<i2.AssetPlaybackStyle>? playbackStyle,
}) {
return i1.LocalAssetEntityCompanion(
name: name ?? this.name,
@@ -1261,7 +1181,6 @@ class LocalAssetEntityCompanion
adjustmentTime: adjustmentTime ?? this.adjustmentTime,
latitude: latitude ?? this.latitude,
longitude: longitude ?? this.longitude,
playbackStyle: playbackStyle ?? this.playbackStyle,
);
}
@@ -1315,13 +1234,6 @@ class LocalAssetEntityCompanion
if (longitude.present) {
map['longitude'] = i0.Variable<double>(longitude.value);
}
if (playbackStyle.present) {
map['playback_style'] = i0.Variable<int>(
i1.$LocalAssetEntityTable.$converterplaybackStyle.toSql(
playbackStyle.value,
),
);
}
return map;
}
@@ -1342,8 +1254,7 @@ class LocalAssetEntityCompanion
..write('iCloudId: $iCloudId, ')
..write('adjustmentTime: $adjustmentTime, ')
..write('latitude: $latitude, ')
..write('longitude: $longitude, ')
..write('playbackStyle: $playbackStyle')
..write('longitude: $longitude')
..write(')'))
.toString();
}
@@ -26,8 +26,7 @@ SELECT
NULL as latitude,
NULL as longitude,
NULL as adjustmentTime,
rae.is_edited,
0 as playback_style
rae.is_edited
FROM
remote_asset_entity rae
LEFT JOIN
@@ -64,8 +63,7 @@ SELECT
lae.latitude,
lae.longitude,
lae.adjustment_time,
0 as is_edited,
lae.playback_style
0 as is_edited
FROM
local_asset_entity lae
WHERE NOT EXISTS (
+1 -4
View File
@@ -29,7 +29,7 @@ class MergedAssetDrift extends i1.ModularAccessor {
);
$arrayStartIndex += generatedlimit.amountOfVariables;
return customSelect(
'SELECT rae.id AS remote_id, (SELECT lae.id FROM local_asset_entity AS lae WHERE lae.checksum = rae.checksum LIMIT 1) AS local_id, rae.name, rae.type, rae.created_at AS created_at, rae.updated_at, rae.width, rae.height, rae.duration_in_seconds, rae.is_favorite, rae.thumb_hash, rae.checksum, rae.owner_id, rae.live_photo_video_id, 0 AS orientation, rae.stack_id, NULL AS i_cloud_id, NULL AS latitude, NULL AS longitude, NULL AS adjustmentTime, rae.is_edited, 0 AS playback_style FROM remote_asset_entity AS rae LEFT JOIN stack_entity AS se ON rae.stack_id = se.id WHERE rae.deleted_at IS NULL AND rae.visibility = 0 AND rae.owner_id IN ($expandeduserIds) AND(rae.stack_id IS NULL OR rae.id = se.primary_asset_id)UNION ALL SELECT NULL AS remote_id, lae.id AS local_id, lae.name, lae.type, lae.created_at AS created_at, lae.updated_at, lae.width, lae.height, lae.duration_in_seconds, lae.is_favorite, NULL AS thumb_hash, lae.checksum, NULL AS owner_id, NULL AS live_photo_video_id, lae.orientation, NULL AS stack_id, lae.i_cloud_id, lae.latitude, lae.longitude, lae.adjustment_time, 0 AS is_edited, lae.playback_style FROM local_asset_entity AS lae WHERE NOT EXISTS (SELECT 1 FROM remote_asset_entity AS rae WHERE rae.checksum = lae.checksum AND rae.owner_id IN ($expandeduserIds)) AND EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 0) AND NOT EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 2) ORDER BY created_at DESC ${generatedlimit.sql}',
'SELECT rae.id AS remote_id, (SELECT lae.id FROM local_asset_entity AS lae WHERE lae.checksum = rae.checksum LIMIT 1) AS local_id, rae.name, rae.type, rae.created_at AS created_at, rae.updated_at, rae.width, rae.height, rae.duration_in_seconds, rae.is_favorite, rae.thumb_hash, rae.checksum, rae.owner_id, rae.live_photo_video_id, 0 AS orientation, rae.stack_id, NULL AS i_cloud_id, NULL AS latitude, NULL AS longitude, NULL AS adjustmentTime, rae.is_edited FROM remote_asset_entity AS rae LEFT JOIN stack_entity AS se ON rae.stack_id = se.id WHERE rae.deleted_at IS NULL AND rae.visibility = 0 AND rae.owner_id IN ($expandeduserIds) AND(rae.stack_id IS NULL OR rae.id = se.primary_asset_id)UNION ALL SELECT NULL AS remote_id, lae.id AS local_id, lae.name, lae.type, lae.created_at AS created_at, lae.updated_at, lae.width, lae.height, lae.duration_in_seconds, lae.is_favorite, NULL AS thumb_hash, lae.checksum, NULL AS owner_id, NULL AS live_photo_video_id, lae.orientation, NULL AS stack_id, lae.i_cloud_id, lae.latitude, lae.longitude, lae.adjustment_time, 0 AS is_edited FROM local_asset_entity AS lae WHERE NOT EXISTS (SELECT 1 FROM remote_asset_entity AS rae WHERE rae.checksum = lae.checksum AND rae.owner_id IN ($expandeduserIds)) AND EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 0) AND NOT EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 2) ORDER BY created_at DESC ${generatedlimit.sql}',
variables: [
for (var $ in userIds) i0.Variable<String>($),
...generatedlimit.introducedVariables,
@@ -67,7 +67,6 @@ class MergedAssetDrift extends i1.ModularAccessor {
longitude: row.readNullable<double>('longitude'),
adjustmentTime: row.readNullable<DateTime>('adjustmentTime'),
isEdited: row.read<bool>('is_edited'),
playbackStyle: row.read<int>('playback_style'),
),
);
}
@@ -140,7 +139,6 @@ class MergedAssetResult {
final double? longitude;
final DateTime? adjustmentTime;
final bool isEdited;
final int playbackStyle;
MergedAssetResult({
this.remoteId,
this.localId,
@@ -163,7 +161,6 @@ class MergedAssetResult {
this.longitude,
this.adjustmentTime,
required this.isEdited,
required this.playbackStyle,
});
}
@@ -28,8 +28,6 @@ class TrashedLocalAssetEntity extends Table with DriftDefaultsMixin, AssetEntity
IntColumn get source => intEnum<TrashOrigin>()();
IntColumn get playbackStyle => intEnum<AssetPlaybackStyle>().withDefault(const Constant(0))();
@override
Set<Column> get primaryKey => {id, albumId};
}
@@ -47,7 +45,6 @@ extension TrashedLocalAssetEntityDataDomainExtension on TrashedLocalAssetEntityD
height: height,
width: width,
orientation: orientation,
playbackStyle: playbackStyle,
isEdited: false,
);
}
@@ -23,7 +23,6 @@ typedef $$TrashedLocalAssetEntityTableCreateCompanionBuilder =
i0.Value<bool> isFavorite,
i0.Value<int> orientation,
required i3.TrashOrigin source,
i0.Value<i2.AssetPlaybackStyle> playbackStyle,
});
typedef $$TrashedLocalAssetEntityTableUpdateCompanionBuilder =
i1.TrashedLocalAssetEntityCompanion Function({
@@ -40,7 +39,6 @@ typedef $$TrashedLocalAssetEntityTableUpdateCompanionBuilder =
i0.Value<bool> isFavorite,
i0.Value<int> orientation,
i0.Value<i3.TrashOrigin> source,
i0.Value<i2.AssetPlaybackStyle> playbackStyle,
});
class $$TrashedLocalAssetEntityTableFilterComposer
@@ -119,16 +117,6 @@ class $$TrashedLocalAssetEntityTableFilterComposer
column: $table.source,
builder: (column) => i0.ColumnWithTypeConverterFilters(column),
);
i0.ColumnWithTypeConverterFilters<
i2.AssetPlaybackStyle,
i2.AssetPlaybackStyle,
int
>
get playbackStyle => $composableBuilder(
column: $table.playbackStyle,
builder: (column) => i0.ColumnWithTypeConverterFilters(column),
);
}
class $$TrashedLocalAssetEntityTableOrderingComposer
@@ -205,11 +193,6 @@ class $$TrashedLocalAssetEntityTableOrderingComposer
column: $table.source,
builder: (column) => i0.ColumnOrderings(column),
);
i0.ColumnOrderings<int> get playbackStyle => $composableBuilder(
column: $table.playbackStyle,
builder: (column) => i0.ColumnOrderings(column),
);
}
class $$TrashedLocalAssetEntityTableAnnotationComposer
@@ -266,12 +249,6 @@ class $$TrashedLocalAssetEntityTableAnnotationComposer
i0.GeneratedColumnWithTypeConverter<i3.TrashOrigin, int> get source =>
$composableBuilder(column: $table.source, builder: (column) => column);
i0.GeneratedColumnWithTypeConverter<i2.AssetPlaybackStyle, int>
get playbackStyle => $composableBuilder(
column: $table.playbackStyle,
builder: (column) => column,
);
}
class $$TrashedLocalAssetEntityTableTableManager
@@ -333,8 +310,6 @@ class $$TrashedLocalAssetEntityTableTableManager
i0.Value<bool> isFavorite = const i0.Value.absent(),
i0.Value<int> orientation = const i0.Value.absent(),
i0.Value<i3.TrashOrigin> source = const i0.Value.absent(),
i0.Value<i2.AssetPlaybackStyle> playbackStyle =
const i0.Value.absent(),
}) => i1.TrashedLocalAssetEntityCompanion(
name: name,
type: type,
@@ -349,7 +324,6 @@ class $$TrashedLocalAssetEntityTableTableManager
isFavorite: isFavorite,
orientation: orientation,
source: source,
playbackStyle: playbackStyle,
),
createCompanionCallback:
({
@@ -366,8 +340,6 @@ class $$TrashedLocalAssetEntityTableTableManager
i0.Value<bool> isFavorite = const i0.Value.absent(),
i0.Value<int> orientation = const i0.Value.absent(),
required i3.TrashOrigin source,
i0.Value<i2.AssetPlaybackStyle> playbackStyle =
const i0.Value.absent(),
}) => i1.TrashedLocalAssetEntityCompanion.insert(
name: name,
type: type,
@@ -382,7 +354,6 @@ class $$TrashedLocalAssetEntityTableTableManager
isFavorite: isFavorite,
orientation: orientation,
source: source,
playbackStyle: playbackStyle,
),
withReferenceMapper: (p0) => p0
.map((e) => (e.readTable(table), i0.BaseReferences(db, table, e)))
@@ -579,19 +550,6 @@ class $TrashedLocalAssetEntityTable extends i3.TrashedLocalAssetEntity
i1.$TrashedLocalAssetEntityTable.$convertersource,
);
@override
late final i0.GeneratedColumnWithTypeConverter<i2.AssetPlaybackStyle, int>
playbackStyle =
i0.GeneratedColumn<int>(
'playback_style',
aliasedName,
false,
type: i0.DriftSqlType.int,
requiredDuringInsert: false,
defaultValue: const i4.Constant(0),
).withConverter<i2.AssetPlaybackStyle>(
i1.$TrashedLocalAssetEntityTable.$converterplaybackStyle,
);
@override
List<i0.GeneratedColumn> get $columns => [
name,
type,
@@ -606,7 +564,6 @@ class $TrashedLocalAssetEntityTable extends i3.TrashedLocalAssetEntity
isFavorite,
orientation,
source,
playbackStyle,
];
@override
String get aliasedName => _alias ?? actualTableName;
@@ -763,13 +720,6 @@ class $TrashedLocalAssetEntityTable extends i3.TrashedLocalAssetEntity
data['${effectivePrefix}source'],
)!,
),
playbackStyle: i1.$TrashedLocalAssetEntityTable.$converterplaybackStyle
.fromSql(
attachedDatabase.typeMapping.read(
i0.DriftSqlType.int,
data['${effectivePrefix}playback_style'],
)!,
),
);
}
@@ -782,10 +732,6 @@ class $TrashedLocalAssetEntityTable extends i3.TrashedLocalAssetEntity
const i0.EnumIndexConverter<i2.AssetType>(i2.AssetType.values);
static i0.JsonTypeConverter2<i3.TrashOrigin, int, int> $convertersource =
const i0.EnumIndexConverter<i3.TrashOrigin>(i3.TrashOrigin.values);
static i0.JsonTypeConverter2<i2.AssetPlaybackStyle, int, int>
$converterplaybackStyle = const i0.EnumIndexConverter<i2.AssetPlaybackStyle>(
i2.AssetPlaybackStyle.values,
);
@override
bool get withoutRowId => true;
@override
@@ -807,7 +753,6 @@ class TrashedLocalAssetEntityData extends i0.DataClass
final bool isFavorite;
final int orientation;
final i3.TrashOrigin source;
final i2.AssetPlaybackStyle playbackStyle;
const TrashedLocalAssetEntityData({
required this.name,
required this.type,
@@ -822,7 +767,6 @@ class TrashedLocalAssetEntityData extends i0.DataClass
required this.isFavorite,
required this.orientation,
required this.source,
required this.playbackStyle,
});
@override
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
@@ -856,13 +800,6 @@ class TrashedLocalAssetEntityData extends i0.DataClass
i1.$TrashedLocalAssetEntityTable.$convertersource.toSql(source),
);
}
{
map['playback_style'] = i0.Variable<int>(
i1.$TrashedLocalAssetEntityTable.$converterplaybackStyle.toSql(
playbackStyle,
),
);
}
return map;
}
@@ -889,8 +826,6 @@ class TrashedLocalAssetEntityData extends i0.DataClass
source: i1.$TrashedLocalAssetEntityTable.$convertersource.fromJson(
serializer.fromJson<int>(json['source']),
),
playbackStyle: i1.$TrashedLocalAssetEntityTable.$converterplaybackStyle
.fromJson(serializer.fromJson<int>(json['playbackStyle'])),
);
}
@override
@@ -914,11 +849,6 @@ class TrashedLocalAssetEntityData extends i0.DataClass
'source': serializer.toJson<int>(
i1.$TrashedLocalAssetEntityTable.$convertersource.toJson(source),
),
'playbackStyle': serializer.toJson<int>(
i1.$TrashedLocalAssetEntityTable.$converterplaybackStyle.toJson(
playbackStyle,
),
),
};
}
@@ -936,7 +866,6 @@ class TrashedLocalAssetEntityData extends i0.DataClass
bool? isFavorite,
int? orientation,
i3.TrashOrigin? source,
i2.AssetPlaybackStyle? playbackStyle,
}) => i1.TrashedLocalAssetEntityData(
name: name ?? this.name,
type: type ?? this.type,
@@ -953,7 +882,6 @@ class TrashedLocalAssetEntityData extends i0.DataClass
isFavorite: isFavorite ?? this.isFavorite,
orientation: orientation ?? this.orientation,
source: source ?? this.source,
playbackStyle: playbackStyle ?? this.playbackStyle,
);
TrashedLocalAssetEntityData copyWithCompanion(
i1.TrashedLocalAssetEntityCompanion data,
@@ -978,9 +906,6 @@ class TrashedLocalAssetEntityData extends i0.DataClass
? data.orientation.value
: this.orientation,
source: data.source.present ? data.source.value : this.source,
playbackStyle: data.playbackStyle.present
? data.playbackStyle.value
: this.playbackStyle,
);
}
@@ -999,8 +924,7 @@ class TrashedLocalAssetEntityData extends i0.DataClass
..write('checksum: $checksum, ')
..write('isFavorite: $isFavorite, ')
..write('orientation: $orientation, ')
..write('source: $source, ')
..write('playbackStyle: $playbackStyle')
..write('source: $source')
..write(')'))
.toString();
}
@@ -1020,7 +944,6 @@ class TrashedLocalAssetEntityData extends i0.DataClass
isFavorite,
orientation,
source,
playbackStyle,
);
@override
bool operator ==(Object other) =>
@@ -1038,8 +961,7 @@ class TrashedLocalAssetEntityData extends i0.DataClass
other.checksum == this.checksum &&
other.isFavorite == this.isFavorite &&
other.orientation == this.orientation &&
other.source == this.source &&
other.playbackStyle == this.playbackStyle);
other.source == this.source);
}
class TrashedLocalAssetEntityCompanion
@@ -1057,7 +979,6 @@ class TrashedLocalAssetEntityCompanion
final i0.Value<bool> isFavorite;
final i0.Value<int> orientation;
final i0.Value<i3.TrashOrigin> source;
final i0.Value<i2.AssetPlaybackStyle> playbackStyle;
const TrashedLocalAssetEntityCompanion({
this.name = const i0.Value.absent(),
this.type = const i0.Value.absent(),
@@ -1072,7 +993,6 @@ class TrashedLocalAssetEntityCompanion
this.isFavorite = const i0.Value.absent(),
this.orientation = const i0.Value.absent(),
this.source = const i0.Value.absent(),
this.playbackStyle = const i0.Value.absent(),
});
TrashedLocalAssetEntityCompanion.insert({
required String name,
@@ -1088,7 +1008,6 @@ class TrashedLocalAssetEntityCompanion
this.isFavorite = const i0.Value.absent(),
this.orientation = const i0.Value.absent(),
required i3.TrashOrigin source,
this.playbackStyle = const i0.Value.absent(),
}) : name = i0.Value(name),
type = i0.Value(type),
id = i0.Value(id),
@@ -1108,7 +1027,6 @@ class TrashedLocalAssetEntityCompanion
i0.Expression<bool>? isFavorite,
i0.Expression<int>? orientation,
i0.Expression<int>? source,
i0.Expression<int>? playbackStyle,
}) {
return i0.RawValuesInsertable({
if (name != null) 'name': name,
@@ -1124,7 +1042,6 @@ class TrashedLocalAssetEntityCompanion
if (isFavorite != null) 'is_favorite': isFavorite,
if (orientation != null) 'orientation': orientation,
if (source != null) 'source': source,
if (playbackStyle != null) 'playback_style': playbackStyle,
});
}
@@ -1142,7 +1059,6 @@ class TrashedLocalAssetEntityCompanion
i0.Value<bool>? isFavorite,
i0.Value<int>? orientation,
i0.Value<i3.TrashOrigin>? source,
i0.Value<i2.AssetPlaybackStyle>? playbackStyle,
}) {
return i1.TrashedLocalAssetEntityCompanion(
name: name ?? this.name,
@@ -1158,7 +1074,6 @@ class TrashedLocalAssetEntityCompanion
isFavorite: isFavorite ?? this.isFavorite,
orientation: orientation ?? this.orientation,
source: source ?? this.source,
playbackStyle: playbackStyle ?? this.playbackStyle,
);
}
@@ -1208,13 +1123,6 @@ class TrashedLocalAssetEntityCompanion
i1.$TrashedLocalAssetEntityTable.$convertersource.toSql(source.value),
);
}
if (playbackStyle.present) {
map['playback_style'] = i0.Variable<int>(
i1.$TrashedLocalAssetEntityTable.$converterplaybackStyle.toSql(
playbackStyle.value,
),
);
}
return map;
}
@@ -1233,8 +1141,7 @@ class TrashedLocalAssetEntityCompanion
..write('checksum: $checksum, ')
..write('isFavorite: $isFavorite, ')
..write('orientation: $orientation, ')
..write('source: $source, ')
..write('playbackStyle: $playbackStyle')
..write('source: $source')
..write(')'))
.toString();
}
@@ -24,8 +24,6 @@ abstract class ImageRequest {
Future<ImageInfo?> load(ImageDecoderCallback decode, {double scale = 1.0});
Future<ui.Codec?> loadCodec();
void cancel() {
if (_isCancelled) {
return;
@@ -36,7 +34,7 @@ abstract class ImageRequest {
void _onCancelled();
Future<(ui.Codec, ui.ImageDescriptor)?> _codecFromEncodedPlatformImage(int address, int length) async {
Future<ui.FrameInfo?> _fromEncodedPlatformImage(int address, int length) async {
final pointer = Pointer<Uint8>.fromAddress(address);
if (_isCancelled) {
malloc.free(pointer);
@@ -69,20 +67,6 @@ abstract class ImageRequest {
return null;
}
return (codec, descriptor);
}
Future<ui.FrameInfo?> _fromEncodedPlatformImage(int address, int length) async {
final result = await _codecFromEncodedPlatformImage(address, length);
if (result == null) return null;
final (codec, descriptor) = result;
if (_isCancelled) {
descriptor.dispose();
codec.dispose();
return null;
}
final frame = await codec.getNextFrame();
descriptor.dispose();
codec.dispose();
@@ -22,7 +22,6 @@ class LocalImageRequest extends ImageRequest {
width: width,
height: height,
isVideo: assetType == AssetType.video,
preferEncoded: false,
);
if (info == null) {
return null;
@@ -32,26 +31,6 @@ class LocalImageRequest extends ImageRequest {
return frame == null ? null : ImageInfo(image: frame.image, scale: scale);
}
@override
Future<ui.Codec?> loadCodec() async {
if (_isCancelled) {
return null;
}
final info = await localImageApi.requestImage(
localId,
requestId: requestId,
width: width,
height: height,
isVideo: assetType == AssetType.video,
preferEncoded: true,
);
if (info == null) return null;
final (codec, _) = await _codecFromEncodedPlatformImage(info['pointer']!, info['length']!) ?? (null, null);
return codec;
}
@override
Future<void> _onCancelled() {
return localImageApi.cancelRequest(requestId);
@@ -12,8 +12,7 @@ class RemoteImageRequest extends ImageRequest {
return null;
}
final info = await remoteImageApi.requestImage(uri, headers: headers, requestId: requestId, preferEncoded: false);
// Android always returns encoded data, so we need to check for both shapes of the response.
final info = await remoteImageApi.requestImage(uri, headers: headers, requestId: requestId);
final frame = switch (info) {
{'pointer': int pointer, 'length': int length} => await _fromEncodedPlatformImage(pointer, length),
{'pointer': int pointer, 'width': int width, 'height': int height, 'rowBytes': int rowBytes} =>
@@ -23,19 +22,6 @@ class RemoteImageRequest extends ImageRequest {
return frame == null ? null : ImageInfo(image: frame.image, scale: scale);
}
@override
Future<ui.Codec?> loadCodec() async {
if (_isCancelled) {
return null;
}
final info = await remoteImageApi.requestImage(uri, headers: headers, requestId: requestId, preferEncoded: true);
if (info == null) return null;
final (codec, _) = await _codecFromEncodedPlatformImage(info['pointer']!, info['length']!) ?? (null, null);
return codec;
}
@override
Future<void> _onCancelled() {
return remoteImageApi.cancelRequest(requestId);

Some files were not shown because too many files have changed in this diff Show More