Compare commits

...

103 Commits

Author SHA1 Message Date
Alex The Bot
9c0f444e4d Version v1.62.0 2023-06-19 15:43:49 +00:00
Jason Rasmussen
6b0f91cafd fix(server): only show assets 'on-this-day' with thumbnails (#2851) 2023-06-19 09:12:18 -05:00
Dan Cowell
3f71d2d33d chore(deps): change compose service dependencies to use alpine variants (#2825)
* chore(deps): change compose service dependencies to use alpine variants

* chore(deps): pin manifest hashes for dependency containers
2023-06-18 20:51:46 -05:00
Sergey Kondrikov
f2942588f2 chore(mobile): Add debug build type suffix to the applicationId and version (#2826) 2023-06-17 23:10:57 -05:00
Alex
b47027efc2 fix(mobile): Sort newest first for asset selection in album (#2833) 2023-06-17 23:09:55 -05:00
Zeeshan Khan
34201be74c feat(ml) backend takes image over HTTP (#2783)
* using pydantic BaseSetting

* ML API takes image file as input

* keeping image in memory

* reducing duplicate code

* using bytes instead of UploadFile & other small code improvements

* removed form-multipart, using HTTP body

* format code

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-06-17 22:49:19 -05:00
Covalent
3e804f16df feat(web,server): add thumbhash support (#2649)
* add thumbhash: server generation and web impl

* move logic to infra & use byta in db

* remove unnecesary logs

* update generated API and simplify thumbhash gen

* fix check errors

* removed unnecessary library and css tag

* style edits

* syntax mistake

* update server test, change thumbhash job name

* fix tests

* Update server/src/domain/asset/response-dto/asset-response.dto.ts

Co-authored-by: Thomas <9749173+uhthomas@users.noreply.github.com>

* add unit test, change migration date

* change to official thumbhash impl

* update call method to not use eval

* "generate missing" looks for thumbhash

* improve queue & improve syntax

* update syntax again

* update tests

* fix thumbhash generation

* consolidate queueing to avoid duplication

* cover all types of incorrect thumbnail cases

* split out jest tasks

* put back thumbnail duration loading for images without thumbhash

* Remove stray package.json

---------

Co-authored-by: Luke McCarthy <mail@lukehmcc.com>
Co-authored-by: Thomas <9749173+uhthomas@users.noreply.github.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-06-17 22:22:31 -05:00
Thomas
3512140148 feat(web): add padding to memory asset navigation (#2822)
The bars are 2 pixels tall, which can be tricky to click. Additional padding
increases the height to 16 pixels, without changing how it looks, and makes for
much easier clicking.

In addition, remove the onDestroy lifecycle for the tween as it's not
necessary. It was a relic from using animation frames.
2023-06-16 23:37:11 +01:00
Jason Rasmussen
bff6914a73 chore(server): organize imports (#2779)
* feat: lint rule for organize imports

* chore: organize imports
2023-06-16 19:54:17 +00:00
Jason Rasmussen
652add635f refactor: rename get auth user decorator (#2778)
Co-authored-by: Alex <alex.tran1502@gmail.com>
2023-06-16 19:39:53 +00:00
Jason Rasmussen
fde410e2ac refactor(server): send job command (#2777)
* refactor: send job command

* chore: open api
2023-06-16 14:36:07 -05:00
Jason Rasmussen
f04e47803c refactor(server): access checks (#2776)
* refactor(server): access checks

* chore: simply asset module
2023-06-16 14:01:34 -05:00
Thomas
61d74263d9 fix(web): hide memory lane navigation properly on scaled resolutions (#2819)
Fixes: #2817
2023-06-16 13:55:11 -05:00
Alex Tran
66ee065c0c pause renovate 2023-06-16 13:52:29 -05:00
Thomas
09bcf6974e feat(web): show number of assets in memory progress bar (#2813)
Fixes: #2810
2023-06-16 13:17:39 -05:00
renovate[bot]
5d7d615433 chore(deps): update web (#2806)
* chore(deps): update web

* fixed svelte-check being a nuisance

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-06-16 12:45:05 -05:00
renovate[bot]
5387048dc3 fix(deps): update dependency tailwindcss to v3.3.2 (#2808)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-16 17:14:29 +00:00
renovate[bot]
6930df71cf fix(deps): update dependency docusaurus-preset-openapi to v0.6.4 (#2800)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-16 11:40:04 -05:00
renovate[bot]
52bbf6da5d fix(deps): update dependency url to v0.11.1 (#2802)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-16 11:39:54 -05:00
Alex
1cd5df7558 fix(web): not displaying assets in album after adding shared user (#2804) 2023-06-16 11:39:40 -05:00
renovate[bot]
74429798e2 fix(deps): update dependency autoprefixer to v10.4.14 (#2799)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-16 11:39:25 -05:00
renovate[bot]
651f3ea5eb chore(deps): update typesense/typesense docker tag to v0.24.1 (#2798)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-16 11:39:11 -05:00
renovate[bot]
0909335d02 chore(deps): update python:3.11.4-slim-bullseye docker digest to 91d194f (#2797)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-16 11:38:59 -05:00
renovate[bot]
827e4b5f75 chore(deps): update python:3.11.4-bullseye docker digest to 5b40167 (#2796)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-16 11:17:23 -05:00
renovate[bot]
c8ff07fff0 fix(deps): update dependency postcss to v8.4.24 (#2801)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-16 11:16:44 -05:00
Thomas
4a21cb2d00 chore(web): hide memory lane navigation when it's no longer possible to scroll (#2791)
Fixes: #2790
2023-06-16 11:06:38 -05:00
Jason Rasmussen
07f7fffae7 refactor(server): album count (#2746)
* refactor(server): album count

* chore: open api
2023-06-16 10:48:48 -05:00
renovate[bot]
441ee2ef90 chore(deps): update dependency typescript to v5 (#2795)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-16 10:43:40 -05:00
renovate[bot]
acad133e3a chore(deps): update dependency @tsconfig/docusaurus to v1.0.7 (#2793)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-16 15:30:14 +00:00
renovate[bot]
ef8714fda9 chore(deps): update dependency vite to v4.1.5 [security] (#2792)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-16 15:28:49 +00:00
Thomas
16171eee8d pin image digests (#2754)
Manifest list digests can be found with:

```sh
docker buildx imagetools inspect python:3.11.4-bullseye
docker buildx imagetools inspect python:3.11.4-slim-bullseye
docker buildx imagetools inspect ghcr.io/nginxinc/nginx-unprivileged:1.25.0-alpine3.17
```

The node images are pinned in #2736

Fixes #2751
Partially fixes #2752
2023-06-16 10:28:41 -05:00
renovate[bot]
d3c1781478 Configure Renovate (#2739)
* Add renovate.json

* Update renovate.json

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Alex <alex.tran1502@gmail.com>
2023-06-16 10:22:52 -05:00
Thomas
329b52e670 use svelte motion tweening for animation (#2788)
It look like Svelte has a concept of 'tweening' for writing animations, which should reduce the complexity of the animation code.

Thanks to @probablykasper for finding this.

A lot of the logic has been rewritten for reactivity, which further reduces
complexity.
2023-06-16 10:09:28 -05:00
Alex
a1b9a1d244 fix(web): error when refreshing asset view in memory page (#2789) 2023-06-16 10:09:16 -05:00
phillibl
377cec9fb1 Update xmp-sidecars.md (#2785)
Fixed a spelling mistake
2023-06-16 09:42:49 -05:00
phillibl
48b9c63268 Update README.md (#2787)
Changed Partner Sharing to Yes for mobile
2023-06-16 09:39:06 -05:00
Alex The Bot
caccb1094d Version v1.61.0 2023-06-16 02:29:11 +00:00
Thomas
43ffcf7e8f use animation frames for memory autoplay (#2771)
The current implementation mixes intervals and animation frames, which is a
little convoluted. The use of intervals means that the animation is not going
to be smooth and may have strange behaviour when the window is moved to the
background. It's possible that the current animation frames could pile up and
run all at once which would be undesirable.

Moving everything into animation frames means the code is simpler and easier to
reason about. It should also be more performant and less buggy.

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-06-15 21:27:32 -05:00
Alex
77fe2e55be fix(web): show memory in main timeline (#2775) 2023-06-15 14:11:14 -05:00
Alex Tran
a59e9e1d9e fix(web): center name 2023-06-15 14:10:21 -05:00
Jason Rasmussen
896645130b fix(server): memory lane title (#2772)
* fix(server): memory lane title

* feat: parallel requests

* pr feedback

* fix test

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-06-15 13:05:30 -05:00
Zack Pollard
045bb855d2 fix: increase request timeout from 5 minutes to 30 minutes (#2769)
since node 18 the default request timeout was changed from unlimited to 5 minutes
2023-06-15 08:21:31 -05:00
Thomas
3b4f6edbdb don't fallback to exiftool for embedded image previews (#2747)
Given #2668 introduced support for imagemagick and libraw, this should no
longer be necessary which allow for reduced code footprint and complexity.

Fixes: #2744
2023-06-14 22:42:35 -05:00
Zack Pollard
1cbf9ff621 fix: increase request timeout from 5 minutes to 30 minutes (#2766)
since node 18 the default request timeout was changed from unlimited to 5 minutes
2023-06-14 21:35:54 -05:00
Thomas
41c2c8b82d use imagemagick and libraw for raw image support (#2668)
* use imagemagick and libraw for raw image support

imagemagick and libraw have generally good support for raw images, including
Sony's ARW format. These tools should also allow Immich to support many more
image formats in future without any major code changes.

https://www.libraw.org/supported-cameras

I've tested and verified this change with .ARW files and other standard formats.

Fixes: #2156

* Add additional type for awr

* pr feedback

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-06-14 21:34:03 -05:00
Alex
43ec0b77a0 feat(web): Memory (#2759)
* Add on this day

* add query for x year

* dev: add query

* dev: front end

* dev: styling

* styling

* more styling

* add new page

* navigating

* navigate back and forth

* styling

* show gallery

* fix test

* fix test

* show previous and next title

* fix test

* show up down scrolling button

* more styling

* styling

* fix app bar

* fix height of next/previous

* autoplay

* auto play

* refactor

* refactor

* refactor

* show date

* Navigate

* finish

* pr feedback
2023-06-14 20:47:18 -05:00
Thomas
408fa45c51 allow emails without a tld (#2762)
It's perfectly valid to have an email address without a TLD, for instance:

- test@localhost
- test@svc-in-same-k8s-namespace
- test@internal-corp

Fixes #2667
2023-06-14 16:26:17 -05:00
dependabot[bot]
eed1243263 chore(deps): bump docker/build-push-action from 4.1.0 to 4.1.1 (#2761)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v4.1.0...v4.1.1)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-14 16:26:05 -05:00
Fynn Petersen-Frey
8f5214724c feat(mobile): sync remote assets without thumbs (#2705) 2023-06-14 16:35:32 +02:00
Thomas
55b6b28afb update node images (#2736)
This is required to support raw images as Alpine 3.18 included fixes to imagemagick.

Related: #2156

In addtion, the images have stricter tags and are pinned with a digest. The
manifest list digest can be found using:

```sh
❯ docker buildx imagetools inspect node:18.16.0-alpine3.18
```
2023-06-13 07:17:07 -05:00
dependabot[bot]
5a48034e33 chore(deps): bump docker/setup-buildx-action from 2.6.0 to 2.7.0 (#2755) 2023-06-13 06:36:51 -05:00
Jason Rasmussen
756f4e5986 fix(web): empty user initials (#2737) 2023-06-12 09:11:28 -05:00
TruongSinh Tran-Nguyen
48492b9f4e feat(web): support uploading Insta360 file format (#2725)
Insta360 "raw" formats `insv` and `insp` are actually
mp4 (video) and jpeg (picture) respectively.
However, we don't want user to rename the original files,
because they follow Insta360 convention, which is required
by Insta360 Studio.
2023-06-12 08:29:03 -05:00
Sergey Kondrikov
e101e40c47 fix(mobile): Disable hit testing for transparent bars (#2727) 2023-06-11 13:10:17 -05:00
Yonggan
9a80a2151c feat(web): Add select all button to all views (#2714)
* Add select all to photos

* Add selection of favorites

* Add select all button to albums

* Add select all to archive

* Add select all to search

* try to fix identation

* Revert "try to fix identation"

This reverts commit 40c727b74a.

* try to fix identation

* try to fix identation

* try to fix identation

* try to fix identation

* fix bucketposition

* Run prettier

---------

Co-authored-by: Yonggan <yonggan@obco.pro>
2023-06-10 14:06:13 -05:00
Fynn Petersen-Frey
73075c64d1 feature(mobile): hash assets & sync via checksum (#2592)
* compare different sha1 implementations

* remove openssl sha1

* sync via checksum

* hash assets in batches

* hash in background, show spinner in tab

* undo tmp changes

* migrate by clearing assets

* ignore duplicate assets

* error handling

* trigger sync/merge after download and update view

* review feedback improvements

* hash in background isolate on iOS

* rework linking assets with existing from DB

* fine-grained errors on unique index violation

* hash lenth validation

* revert compute in background on iOS

* ignore duplicate assets on device

* fix bug with batching based on accumulated size

---------

Co-authored-by: Fynn Petersen-Frey <zoodyy@users.noreply.github.com>
2023-06-10 13:13:59 -05:00
Sergey Kondrikov
053a0482b4 fix(web): Timeline narrow date groups style (#2713)
* Truncate date group title

* Precalculate justified layout width

* Add title to date group title to show when truncated

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-06-10 13:13:06 -05:00
Mert
9cdec62918 feat(server): option to transcode to original resolution (#2709)
* option to transcode to original resolution

* changed value for target res setting

* updated test, clarified scaling condition
2023-06-09 23:15:12 -05:00
Jason Rasmussen
e3694695ae chore: sort open api spec keys (#2710) 2023-06-09 23:14:18 -05:00
Jason Rasmussen
9a3a01ca78 chore: remove unused code (#2700) 2023-06-09 15:21:00 -05:00
Jason Rasmussen
f0bc318712 chore: fix test coverage (#2699) 2023-06-09 15:20:15 -05:00
dependabot[bot]
53adb0c515 chore(deps): bump docker/build-push-action from 4.0.0 to 4.1.0 (#2702)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 4.0.0 to 4.1.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v4.0.0...v4.1.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-09 10:07:44 -05:00
Evan
747afa0cee Fix trailing slash in server address while using docker (#2704) 2023-06-09 09:24:13 -05:00
Sergey Kondrikov
104e489000 fix(server): Filter out deleted partners (#2697)
* Filter out deleted partners

* Add separate filter clause for soft deleted users

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-06-08 10:45:06 -05:00
Sergey Kondrikov
5764bf16f3 feat(web, server): Implement justified layout for AssetGrid (#2666)
* Implement justified layout for timeline

* Add withoutThumbs field to GetTimelineLayotDto

* Back to rough estimation of initial buckets height

* Remove getTimelineLayout endpoint

* Estimate rough viewport height better

* Fix shift/jump issues while scrolling up

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-06-08 10:22:45 -05:00
Jason Rasmussen
8ebac41318 refactor(server)*: tsconfigs (#2689)
* refactor(server): tsconfigs

* chore: dummy commit

* fix: start.sh

* chore: restore original entry scripts
2023-06-08 10:01:07 -05:00
dependabot[bot]
a2130aa6c5 chore(deps): bump docker/setup-buildx-action from 2.5.0 to 2.6.0 (#2696)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2.5.0 to 2.6.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2.5.0...v2.6.0)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-08 08:41:09 -05:00
dependabot[bot]
5dbf46ac3c chore(deps): bump docker/setup-qemu-action from 2.1.0 to 2.2.0 (#2695)
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 2.1.0 to 2.2.0.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v2.1.0...v2.2.0)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-08 08:40:27 -05:00
Jason Rasmussen
b7d42e7e8e feat(web): mobile job cards (#2688) 2023-06-07 11:10:31 -05:00
Jason Rasmussen
d08535e7f6 refactor(server): bootstrap code (#2682)
* refactor(server): bootstrap code

* Add service name

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-06-07 09:56:08 -05:00
Jason Rasmussen
eb1225a0a5 refactor(server,web): add/remove album users (#2681)
* refactor(server,web): add/remove album users

* fix(web): bug fixes for multiple users

* fix: linting
2023-06-07 09:37:25 -05:00
Jason Rasmussen
284edd97d6 refactor(server): shared link asset access check (#2680) 2023-06-06 23:34:42 -05:00
Jason Rasmussen
d1b0b64d59 refactor(server): album download check (#2679) 2023-06-06 23:27:28 -05:00
Mert
d0cc231782 feat(ml): model unloading (#2661)
* model cache

* fixed revalidation when using cache namespace

* fixed ttl not being set, added lock
2023-06-06 20:48:51 -05:00
Jason Rasmussen
6ce35d47f5 refactor(server): partner core (#2678)
* refactor(server): partner core

* refactor(server): partner access check
2023-06-06 15:18:38 -05:00
Jason Rasmussen
d1db479727 refactor(server): move asset checks to service (#2640) 2023-06-06 14:17:15 -05:00
Mert
1e748864c5 chore(ml): updated dockerfile, added typing, packaging (#2642)
* updated dockerfile, added typing, packaging

apply env change

* added arm64 support

* added ml version pump, second try for arm64

* added linting config to pyproject.toml

* renamed ml input field

* fixed linter config

* fixed dev docker compose
2023-06-05 09:40:48 -05:00
wittymap
c92c442356 Update restore example to address SQL errors on database import. (#2663) 2023-06-05 08:56:39 -05:00
Alex The Bot
1f4993350a Version v1.60.0 2023-06-04 15:45:06 +00:00
Michel Heusschen
f9b1d1edaf fix(server): better metadata extraction for images (#2653) 2023-06-03 21:55:30 -05:00
Michel Heusschen
cab5477656 fix(web+server): showing assets without thumbnail (#2652)
* fix(web+server): showing assets without thumbnail

* missed change
2023-06-03 21:41:27 -05:00
Mert
b8de668f5f feat(ml): env variables for tags, faces and eager startup (#2626)
* env variables for tags, faces and eager startup

* chore(server,ml): remove object detection job and endpoint (#2627)

* removed object detection job

* removed object detection endpoint

* env variables for tags, faces and eager startup

* download without caching models if not eager

* simplified `get_cached_model`

* re-added env for clip text model
2023-06-02 21:42:47 -05:00
Jason Rasmussen
c5234731d6 fix(server): add executable permission to start scripts (#2650) 2023-06-02 14:04:26 -05:00
Michel Heusschen
ef86a77946 refactor(server): remove invalid exif coordinates (#2651) 2023-06-02 14:04:07 -05:00
Michel Heusschen
1b301984dd fix(server): handle invalid coordinates (#2648) 2023-06-02 11:29:12 -05:00
Michel Heusschen
9807f76aff chore(web): improve type checking (#2644)
* fix(web): use id instead of assetId

* chore(web): improve type checking

* fix test jobs

* improve type checking and resolve errors
2023-06-02 08:55:08 -05:00
Michel Heusschen
47673dd773 fix(web): use id instead of assetId (#2643) 2023-06-02 08:50:35 -05:00
Jason Rasmussen
a9fb1d435a refactor(server): use UUID dto in asset controller (#2641)
* refactor: assetId => id

* chore: open api

* chore: remove unused dto

* fix(web): assetId => id

* fix: web test
2023-06-01 21:19:25 -05:00
Jason Rasmussen
422ad20641 refactor(server): use swagger (#2639) 2023-06-02 02:12:22 +00:00
Jason Rasmussen
3ea2fe1c48 refactor(server): shared links (#2632)
* refactor: rename share => shared-link

* refactor: shared link crud methods

* chore: open api
2023-06-01 21:09:57 -05:00
Jason Rasmussen
038e064e60 refactor(server): handle download (#2637) 2023-06-01 21:03:15 -05:00
Jason Rasmussen
800f010383 refactor(server): app init (#2638) 2023-06-01 20:54:16 -05:00
Jason Rasmussen
4350f9363d feat(server): use base64 shared links (#2633)
* feat(server): use base64 shared links

* fix: handle array values
2023-06-01 15:56:37 -05:00
Jason Rasmussen
76a1629e75 test(server): job service (#2634) 2023-06-01 16:07:45 -04:00
Jason Rasmussen
2493dfaba3 feat(server): dynamic job concurrency (#2622)
* feat(server): dynamic job concurrency

* styling and add setting info to top of the job list

* regenerate api

* remove DETECT_OBJECT job

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-06-01 05:32:51 -05:00
Jason Rasmussen
656dc08406 refactor(server): tags (#2589)
* refactor: tags

* chore: open api

* chore: unused import

* feat: add/remove/get tag assets

* chore: open api

* chore: finish tag tests for add/remove assets
2023-05-31 20:51:28 -05:00
Mert
631f13cf2f chore(server,ml): remove object detection job and endpoint (#2627)
* removed object detection job

* removed object detection endpoint
2023-05-31 20:49:51 -05:00
Jason Rasmussen
9730bf0acc fix(server): without queries (#2621) 2023-05-31 10:00:37 -05:00
Stormrover
9f2b5ea86e Allow docker image version pinning (#2617)
* Allow docker image version pinning

* Updating docker-compose documentation.

* Fixing formatting

* Added Optional to documentation.
2023-05-30 20:53:24 -05:00
Sergey Kondrikov
5702442783 fix: remove mbtree files (#2620) 2023-05-30 20:52:57 -05:00
Jason Rasmussen
74c2f446e9 fix: missing faces job (#2618) 2023-05-30 13:51:53 -05:00
Alex The Bot
da1710bcd2 Version v1.59.1 2023-05-30 17:56:47 +00:00
Jason Rasmussen
2dfd56b49b fix: reload assets from typesense results (#2615)
* fix: reload assets from typesense results

* chore: coverage
2023-05-30 12:55:06 -05:00
690 changed files with 21135 additions and 11336 deletions

View File

@@ -42,10 +42,10 @@ jobs:
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2.1.0
uses: docker/setup-qemu-action@v2.2.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.5.0
uses: docker/setup-buildx-action@v2.7.0
# Workaround to fix error:
# failed to push: failed to copy: io: read/write on closed pipe
# See https://github.com/docker/build-push-action/issues/761
@@ -100,7 +100,7 @@ jobs:
fi
- name: Build and push image
uses: docker/build-push-action@v4.0.0
uses: docker/build-push-action@v4.1.1
with:
context: ${{ matrix.context }}
platforms: ${{ matrix.platforms }}

View File

@@ -96,7 +96,11 @@ jobs:
if: ${{ !cancelled() }}
- name: Run svelte checks
run: npm run check
run: npm run check:svelte
if: ${{ !cancelled() }}
- name: Run tsc
run: npm run check:typescript
if: ${{ !cancelled() }}
- name: Run unit tests & coverage
@@ -163,13 +167,13 @@ jobs:
run: npm --prefix server run typeorm:migrations:run
- name: Generate new migrations
continue-on-error: true
run: npm --prefix server run typeorm:migrations:generate ./libs/infra/src/migrations/TestMigration
run: npm --prefix server run typeorm:migrations:generate ./src/infra/migrations/TestMigration
- name: Find file changes
uses: tj-actions/verify-changed-files@v13.1
id: verify-changed-files
with:
files: |
server/libs/infra/src/migrations/
server/src/infra/migrations/
- name: Verify files have not changed
if: steps.verify-changed-files.outputs.files_changed == 'true'
run: |

View File

@@ -82,7 +82,7 @@ Spec: Free-tier Oracle VM - Amsterdam - 2.4Ghz quad-core ARM64 CPU, 24GB RAM
| Public Sharing | No | Yes |
| Archive and Favorites | Yes | Yes |
| Global Map | No | Yes |
| Partner Sharing | No | Yes |
| Partner Sharing | Yes | Yes |
| Facial recognition and clustering | No | Yes |
# Support the project

View File

@@ -35,8 +35,7 @@ services:
ports:
- 3003:3003
volumes:
- ../machine-learning/src:/usr/src/app
- ${UPLOAD_LOCATION}:/usr/src/app/upload
- ../machine-learning/app:/usr/src/app
- model-cache:/cache
env_file:
- .env
@@ -95,7 +94,7 @@ services:
typesense:
container_name: immich_typesense
image: typesense/typesense:0.24.0
image: typesense/typesense:0.24.1@sha256:9bcff2b829f12074426ca044b56160ca9d777a0c488303469143dd9f8259d4dd
environment:
- TYPESENSE_API_KEY=${TYPESENSE_API_KEY}
- TYPESENSE_DATA_DIR=/data
@@ -106,11 +105,11 @@ services:
redis:
container_name: immich_redis
image: redis:6.2
image: redis:6.2-alpine@sha256:70a7a5b641117670beae0d80658430853896b5ef269ccf00d1827427e3263fa3
database:
container_name: immich_postgres
image: postgres:14
image: postgres:14-alpine@sha256:28407a9961e76f2d285dc6991e8e48893503cc3836a4755bbc2d40bcc272a441
env_file:
- .env
environment:

View File

@@ -25,12 +25,12 @@ services:
- immich-test-network
immich-redis-test:
container_name: immich-redis-test
image: redis:6.2
image: redis:6.2-alpine@sha256:70a7a5b641117670beae0d80658430853896b5ef269ccf00d1827427e3263fa3
networks:
- immich-test-network
immich-database-test:
container_name: immich-database-test
image: postgres:14
image: postgres:14-alpine@sha256:28407a9961e76f2d285dc6991e8e48893503cc3836a4755bbc2d40bcc272a441
env_file:
- .env.test
environment:

View File

@@ -3,8 +3,8 @@ version: "3.8"
services:
immich-server:
container_name: immich_server
image: ghcr.io/immich-app/immich-server:release
command: ["start-server.sh"]
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
command: [ "start.sh", "immich" ]
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
env_file:
@@ -17,8 +17,8 @@ services:
immich-microservices:
container_name: immich_microservices
image: ghcr.io/immich-app/immich-server:release
command: ["start-microservices.sh"]
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
command: [ "start.sh", "microservices" ]
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
env_file:
@@ -31,9 +31,8 @@ services:
immich-machine-learning:
container_name: immich_machine_learning
image: ghcr.io/immich-app/immich-machine-learning:release
image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
- model-cache:/cache
env_file:
- .env
@@ -41,14 +40,14 @@ services:
immich-web:
container_name: immich_web
image: ghcr.io/immich-app/immich-web:release
image: ghcr.io/immich-app/immich-web:${IMMICH_VERSION:-release}
env_file:
- .env
restart: always
typesense:
container_name: immich_typesense
image: typesense/typesense:0.24.0
image: typesense/typesense:0.24.1@sha256:9bcff2b829f12074426ca044b56160ca9d777a0c488303469143dd9f8259d4dd
environment:
- TYPESENSE_API_KEY=${TYPESENSE_API_KEY}
- TYPESENSE_DATA_DIR=/data
@@ -60,12 +59,12 @@ services:
redis:
container_name: immich_redis
image: redis:6.2
image: redis:6.2-alpine@sha256:70a7a5b641117670beae0d80658430853896b5ef269ccf00d1827427e3263fa3
restart: always
database:
container_name: immich_postgres
image: postgres:14
image: postgres:14-alpine@sha256:28407a9961e76f2d285dc6991e8e48893503cc3836a4755bbc2d40bcc272a441
env_file:
- .env
environment:
@@ -79,7 +78,7 @@ services:
immich-proxy:
container_name: immich_proxy
image: ghcr.io/immich-app/immich-proxy:release
image: ghcr.io/immich-app/immich-proxy:${IMMICH_VERSION:-release}
environment:
# Make sure these values get passed through from the env file
- IMMICH_SERVER_URL

View File

@@ -105,3 +105,12 @@ IMMICH_MACHINE_LEARNING_URL=http://immich-machine-learning:3003
####################################################################################
#IMMICH_API_URL_EXTERNAL=http://localhost:3001
###################################################################################
# Immich Version - Optional
#
# This allows all immich docker images to be pinned to a specific version. By default,
# the version is "release" but could be a specific version, like "v1.59.0".
###################################################################################
#IMMICH_VERSION=

View File

@@ -13,9 +13,17 @@ docker exec -t immich_postgres pg_dumpall -c -U postgres | gzip > "/path/to/back
```
```bash title='Restore'
gunzip < /path/to/backup/dump.sql.gz | docker exec -i immich_postgres psql -U postgres -d immich
docker-compose down -v # CAUTION! Deletes all Immich data to start from scratch.
docker-compose pull # Update to latest version of Immich (if desired)
docker-compose create # Create Docker containers for Immich apps without running them.
docker start immich_postgres # Start Postgres server
sleep 10 # Wait for Postgres server to start up
gunzip < "/path/to/backup/dump.sql.gz" | docker exec -i immich_postgres psql -U postgres -d immich # Restore Backup
docker-compose up -d # Start remainder of Immich apps
```
Note that for the database restore to proceed properly, it requires a completely fresh install (i.e. the Immich server has never run since creating the Docker containers). If the Immich app has run, Postgres conflicts may be encountered upon database restoration (relation already exists, violated foreign key constraints, multiple primary keys, etc.).
The database dumps can also be automated (using [this image](https://github.com/prodrigestivill/docker-postgres-backup-local)) by editing the docker compose file to match the following:
```yaml

View File

@@ -1,14 +1,14 @@
# Database Migrations
After making any changes in the `server/libs/database/src/entities`, a database migration need to run in order to register the changes in the database. Follow the steps below to create a new migration.
After making any changes in the `server/src/infra/database/entities`, a database migration need to run in order to register the changes in the database. Follow the steps below to create a new migration.
1. Run the command
```bash
npm run typeorm:migrations:generate ./libs/infra/src/<migration-name>
npm run typeorm:migrations:generate ./src/infra/<migration-name>
```
2. Check if the migration file makes sense.
3. Move the migration file to folder `server/libs/database/src/migrations` in your code editor.
3. Move the migration file to folder `./src/infra/database/migrations` in your code editor.
The server will automatically detect `*.ts` file changes and restart. Part of the server start-up process includes running any new migrations, so it will be applied immediately.

View File

@@ -30,4 +30,4 @@ The Open API client libraries need to be regenerated whenever there are changes
## Database Migrations
A database migration needs to be generated whenever there are changes to `server/libs/infra/src/entities`. See [Database Migration](/docs/developer/database-migrations.md) for more details.
A database migration needs to be generated whenever there are changes to `server/src/infra/src/entities`. See [Database Migration](/docs/developer/database-migrations.md) for more details.

View File

@@ -10,9 +10,9 @@ sidebar_position: 2
This environment includes the following services:
- Core server - `/server/apps/immich`
- Core server - `/server/src/immich`
- Machine learning - `/machine-learning`
- Microservices - `/server/apps/microservicess`
- Microservices - `/server/src/microservicess`
- Web app - `/web`
- Redis
- PostgreSQL development database with exposed port `5432` so you can use any database client to acess it

View File

@@ -76,10 +76,10 @@ If you are running the CLI container on the same machine as your Immich server,
1. Find the internal Docker network used by Immich via `docker network ls`.
2. Adapt the above command to pass the `--network <immich_network>` argument to `docker run`, substituting `<immich_network>` with the result from step 1.
3. Use `--server http://immich-server:3001/` for the upload command instead of the external address.
3. Use `--server http://immich-server:3001` for the upload command instead of the external address.
```bash title="Upload to internal address"
docker run --network immich_default -it --rm -v "$(pwd):/import" ghcr.io/immich-app/immich-cli:latest upload --key HFEJ38DNSDUEG --server http://immich-server:3001/
docker run --network immich_default -it --rm -v "$(pwd):/import" ghcr.io/immich-app/immich-cli:latest upload --key HFEJ38DNSDUEG --server http://immich-server:3001
```
:::

View File

@@ -4,7 +4,7 @@ Immich can ingest XMP sidecars on file upload (via the CLI) as well as detect ne
<img src={require('./img/xmp-sidecars.png').default} title='XMP sidecars' />
XMP sidecars are external XML files that contain metadata related to media files. Many applications read and write these files either exclusively or in addition to the metadata written to image files. They can be a powerful tool for editing and storing metadata of a media file without modifying the mdia file itself. When Immich receives or detects an XMP sidecar for a media file, it will attempt to extract the metadata from both the sidecar as well as the media file. It will prioritize the metadata for fields in the sidecar but will fall back and use the metadata in the media file if necessary.
XMP sidecars are external XML files that contain metadata related to media files. Many applications read and write these files either exclusively or in addition to the metadata written to image files. They can be a powerful tool for editing and storing metadata of a media file without modifying the media file itself. When Immich receives or detects an XMP sidecar for a media file, it will attempt to extract the metadata from both the sidecar as well as the media file. It will prioritize the metadata for fields in the sidecar but will fall back and use the metadata in the media file if necessary.
When importing files via the CLI bulk uploader, Immich will automatically detect XMP sidecar files as files that exist next to the original media file and have the exact same name with an additional `.xmp` file extension (i.e., `PXL_20230401_203352928.MP.jpg` and `PXL_20230401_203352928.MP.jpg.xmp`).

View File

@@ -136,6 +136,15 @@ IMMICH_MACHINE_LEARNING_URL=http://immich-machine-learning:3003
####################################################################################
#IMMICH_API_URL_EXTERNAL=http://localhost:3001
###################################################################################
# Immich Version - Optional
#
# This allows all immich docker images to be pinned to a specific version. By default,
# the version is "release" but could be a specific version, like "v1.59.0".
###################################################################################
#IMMICH_VERSION=
```
</details>
@@ -159,6 +168,8 @@ For more information on how to use the application, please refer to the [Post In
### Step 4 - Upgrading
If `IMMICH_VERSION` is set, it will need to be updated to the latest or desired version.
When a new version of Immich is [released](https://github.com/immich-app/immich/releases), the application can be upgraded with the following commands, run in the directory with the `docker-compose.yml` file:
```bash title="Upgrade Immich"

776
docs/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -35,7 +35,7 @@
"@docusaurus/module-type-aliases": "2.1.0",
"@tsconfig/docusaurus": "^1.0.5",
"prettier": "^2.8.8",
"typescript": "^4.7.4"
"typescript": "^5.0.0"
},
"browserslist": {
"production": [

View File

@@ -1,29 +1,28 @@
FROM python:3.10 as builder
FROM python:3.11.4-bullseye@sha256:5b401676aff858495a5c9c726c60b8b73fe52833e9e16eccdb59e93d52741727 as builder
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=true
RUN pip install --upgrade pip && pip install poetry
RUN poetry config installer.max-workers 10 && \
poetry config virtualenvs.create false
RUN python -m venv /opt/venv
RUN /opt/venv/bin/pip install torch --index-url https://download.pytorch.org/whl/cpu
RUN /opt/venv/bin/pip install transformers tqdm numpy scikit-learn scipy nltk sentencepiece fastapi Pillow uvicorn[standard]
RUN /opt/venv/bin/pip install --no-deps sentence-transformers
# Facial Recognition Stuff
RUN /opt/venv/bin/pip install insightface onnxruntime
ENV VIRTUAL_ENV="/opt/venv" PATH="/opt/venv/bin:${PATH}"
FROM python:3.10-slim
COPY poetry.lock pyproject.toml ./
RUN poetry install --sync --no-interaction --no-ansi --no-root --only main
ENV NODE_ENV=production
COPY --from=builder /opt/venv /opt/venv
ENV TRANSFORMERS_CACHE=/cache \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PATH="/opt/venv/bin:$PATH"
FROM python:3.11.4-slim-bullseye@sha256:91d194f58f50594cda71dcd2e8fdefd90e7ecc57d07823813b67c8521e565dcd
WORKDIR /usr/src/app
ENV NODE_ENV=production \
TRANSFORMERS_CACHE=/cache \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PATH="/opt/venv/bin:$PATH" \
PYTHONPATH=`pwd`
COPY . .
ENV PYTHONPATH=`pwd`
CMD ["python", "src/main.py"]
COPY --from=builder /opt/venv /opt/venv
COPY app .
ENTRYPOINT ["python", "main.py"]

View File

@@ -1,5 +1,13 @@
# Immich Machine Learning
- Object Detection
- Image Classification
- Image classification
- CLIP embeddings
- Facial recognition
# Setup
This project uses [Poetry](https://python-poetry.org/docs/#installation), so be sure to install it first.
Running `poetry install --no-root --with dev` will install everything you need in an isolated virtual environment.
To add or remove dependencies, you can use the commands `poetry add $PACKAGE_NAME` and `poetry remove $PACKAGE_NAME`, respectively.
Be sure to commit the `poetry.lock` and `pyproject.toml` files to reflect any changes in dependencies.

View File

@@ -0,0 +1,84 @@
from aiocache.plugins import TimingPlugin, BasePlugin
from aiocache.backends.memory import SimpleMemoryCache
from aiocache.lock import OptimisticLock
from typing import Any
from models import get_model
class ModelCache:
"""Fetches a model from an in-memory cache, instantiating it if it's missing."""
def __init__(
self,
ttl: int | None = None,
revalidate: bool = False,
timeout: int | None = None,
profiling: bool = False,
):
"""
Args:
ttl: Unloads model after this duration. Disabled if None. Defaults to None.
revalidate: Resets TTL on cache hit. Useful to keep models in memory while active. Defaults to False.
timeout: Maximum allowed time for model to load. Disabled if None. Defaults to None.
profiling: Collects metrics for cache operations, adding slight overhead. Defaults to False.
"""
self.ttl = ttl
plugins = []
if revalidate:
plugins.append(RevalidationPlugin())
if profiling:
plugins.append(TimingPlugin())
self.cache = SimpleMemoryCache(
ttl=ttl, timeout=timeout, plugins=plugins, namespace=None
)
async def get_cached_model(
self, model_name: str, model_type: str, **model_kwargs
) -> Any:
"""
Args:
model_name: Name of model in the model hub used for the task.
model_type: Model type or task, which determines which model zoo is used.
Returns:
model: The requested model.
"""
key = self.cache.build_key(model_name, model_type)
model = await self.cache.get(key)
if model is None:
async with OptimisticLock(self.cache, key) as lock:
model = get_model(model_name, model_type, **model_kwargs)
await lock.cas(model, ttl=self.ttl)
return model
async def get_profiling(self) -> dict[str, float] | None:
if not hasattr(self.cache, "profiling"):
return None
return self.cache.profiling # type: ignore
class RevalidationPlugin(BasePlugin):
"""Revalidates cache item's TTL after cache hit."""
async def post_get(self, client, key, ret=None, namespace=None, **kwargs):
if ret is None:
return
if namespace is not None:
key = client.build_key(key, namespace)
if key in client._handlers:
await client.expire(key, client.ttl)
async def post_multi_get(self, client, keys, ret=None, namespace=None, **kwargs):
if ret is None:
return
for key, val in zip(keys, ret):
if namespace is not None:
key = client.build_key(key, namespace)
if val is not None and key in client._handlers:
await client.expire(key, client.ttl)

View File

@@ -0,0 +1,22 @@
from pydantic import BaseSettings
class Settings(BaseSettings):
cache_folder: str = "/cache"
classification_model: str = "microsoft/resnet-50"
clip_image_model: str = "clip-ViT-B-32"
clip_text_model: str = "clip-ViT-B-32"
facial_recognition_model: str = "buffalo_l"
min_tag_score: float = 0.9
eager_startup: bool = True
model_ttl: int = 300
host: str = "0.0.0.0"
port: int = 3003
workers: int = 1
min_face_score: float = 0.7
class Config(BaseSettings.Config):
env_prefix = 'MACHINE_LEARNING_'
case_sensitive = False
settings = Settings()

View File

@@ -0,0 +1,133 @@
import os
import io
from typing import Any
from cache import ModelCache
from schemas import (
EmbeddingResponse,
FaceResponse,
TagResponse,
MessageResponse,
TextModelRequest,
TextResponse,
)
import uvicorn
from PIL import Image
from fastapi import FastAPI, HTTPException, Depends, Body
from models import get_model, run_classification, run_facial_recognition
from config import settings
_model_cache = None
app = FastAPI()
@app.on_event("startup")
async def startup_event() -> None:
global _model_cache
_model_cache = ModelCache(ttl=settings.model_ttl, revalidate=True)
models = [
(settings.classification_model, "image-classification"),
(settings.clip_image_model, "clip"),
(settings.clip_text_model, "clip"),
(settings.facial_recognition_model, "facial-recognition"),
]
# Get all models
for model_name, model_type in models:
if settings.eager_startup:
await _model_cache.get_cached_model(model_name, model_type)
else:
get_model(model_name, model_type)
def dep_model_cache():
if _model_cache is None:
raise HTTPException(status_code=500, detail="Unable to load model.")
def dep_input_image(image: bytes = Body(...)) -> Image:
return Image.open(io.BytesIO(image))
@app.get("/", response_model=MessageResponse)
async def root() -> dict[str, str]:
return {"message": "Immich ML"}
@app.get("/ping", response_model=TextResponse)
def ping() -> str:
return "pong"
@app.post(
"/image-classifier/tag-image",
response_model=TagResponse,
status_code=200,
dependencies=[Depends(dep_model_cache)],
)
async def image_classification(
image: Image = Depends(dep_input_image)
) -> list[str]:
try:
model = await _model_cache.get_cached_model(
settings.classification_model, "image-classification"
)
labels = run_classification(model, image, settings.min_tag_score)
except Exception as ex:
raise HTTPException(status_code=500, detail=str(ex))
else:
return labels
@app.post(
"/sentence-transformer/encode-image",
response_model=EmbeddingResponse,
status_code=200,
dependencies=[Depends(dep_model_cache)],
)
async def clip_encode_image(
image: Image = Depends(dep_input_image)
) -> list[float]:
model = await _model_cache.get_cached_model(settings.clip_image_model, "clip")
embedding = model.encode(image).tolist()
return embedding
@app.post(
"/sentence-transformer/encode-text",
response_model=EmbeddingResponse,
status_code=200,
dependencies=[Depends(dep_model_cache)],
)
async def clip_encode_text(
payload: TextModelRequest
) -> list[float]:
model = await _model_cache.get_cached_model(settings.clip_text_model, "clip")
embedding = model.encode(payload.text).tolist()
return embedding
@app.post(
"/facial-recognition/detect-faces",
response_model=FaceResponse,
status_code=200,
dependencies=[Depends(dep_model_cache)],
)
async def facial_recognition(
image: bytes = Body(...),
) -> list[dict[str, Any]]:
model = await _model_cache.get_cached_model(
settings.facial_recognition_model, "facial-recognition"
)
faces = run_facial_recognition(model, image)
return faces
if __name__ == "__main__":
is_dev = os.getenv("NODE_ENV") == "development"
uvicorn.run(
"main:app",
host=settings.host,
port=settings.port,
reload=is_dev,
workers=settings.workers,
)

View File

@@ -0,0 +1,119 @@
import torch
from insightface.app import FaceAnalysis
from pathlib import Path
from transformers import pipeline, Pipeline
from sentence_transformers import SentenceTransformer
from typing import Any, BinaryIO
import cv2 as cv
import numpy as np
from PIL import Image
from config import settings
device = "cuda" if torch.cuda.is_available() else "cpu"
def get_model(model_name: str, model_type: str, **model_kwargs):
"""
Instantiates the specified model.
Args:
model_name: Name of model in the model hub used for the task.
model_type: Model type or task, which determines which model zoo is used.
`facial-recognition` uses Insightface, while all other models use the HF Model Hub.
Options:
`image-classification`, `clip`,`facial-recognition`, `tokenizer`, `processor`
Returns:
model: The requested model.
"""
cache_dir = _get_cache_dir(model_name, model_type)
match model_type:
case "facial-recognition":
model = _load_facial_recognition(
model_name, cache_dir=cache_dir, **model_kwargs
)
case "clip":
model = SentenceTransformer(
model_name, cache_folder=cache_dir, **model_kwargs
)
case _:
model = pipeline(
model_type,
model_name,
model_kwargs={"cache_dir": cache_dir, **model_kwargs},
)
return model
def run_classification(
model: Pipeline, image: Image, min_score: float | None = None
):
predictions: list[dict[str, Any]] = model(image) # type: ignore
result = {
tag
for pred in predictions
for tag in pred["label"].split(", ")
if min_score is None or pred["score"] >= min_score
}
return list(result)
def run_facial_recognition(
model: FaceAnalysis, image: bytes
) -> list[dict[str, Any]]:
file_bytes = np.frombuffer(image, dtype=np.uint8)
img = cv.imdecode(file_bytes, cv.IMREAD_COLOR)
height, width, _ = img.shape
results = []
faces = model.get(img)
for face in faces:
x1, y1, x2, y2 = face.bbox
results.append(
{
"imageWidth": width,
"imageHeight": height,
"boundingBox": {
"x1": round(x1),
"y1": round(y1),
"x2": round(x2),
"y2": round(y2),
},
"score": face.det_score.item(),
"embedding": face.normed_embedding.tolist(),
}
)
return results
def _load_facial_recognition(
model_name: str,
min_face_score: float | None = None,
cache_dir: Path | str | None = None,
**model_kwargs,
):
if cache_dir is None:
cache_dir = _get_cache_dir(model_name, "facial-recognition")
if isinstance(cache_dir, Path):
cache_dir = cache_dir.as_posix()
if min_face_score is None:
min_face_score = settings.min_face_score
model = FaceAnalysis(
name=model_name,
root=cache_dir,
allowed_modules=["detection", "recognition"],
**model_kwargs,
)
model.prepare(ctx_id=0, det_thresh=min_face_score, det_size=(640, 640))
return model
def _get_cache_dir(model_name: str, model_type: str) -> Path:
return Path(settings.cache_folder, device, model_type, model_name)

View File

@@ -0,0 +1,56 @@
from pydantic import BaseModel
def to_lower_camel(string: str) -> str:
tokens = [
token.capitalize() if i > 0 else token
for i, token in enumerate(string.split("_"))
]
return "".join(tokens)
class TextModelRequest(BaseModel):
text: str
class TextResponse(BaseModel):
__root__: str
class MessageResponse(BaseModel):
message: str
class TagResponse(BaseModel):
__root__: list[str]
class Embedding(BaseModel):
__root__: list[float]
class EmbeddingResponse(BaseModel):
__root__: Embedding
class BoundingBox(BaseModel):
x1: int
y1: int
x2: int
y2: int
class Face(BaseModel):
image_width: int
image_height: int
bounding_box: BoundingBox
score: float
embedding: Embedding
class Config:
alias_generator = to_lower_camel
allow_population_by_field_name = True
class FaceResponse(BaseModel):
__root__: list[Face]

2472
machine-learning/poetry.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,57 @@
[tool.poetry]
name = "machine-learning"
version = "1.59.1"
description = ""
authors = ["Hau Tran <alex.tran1502@gmail.com>"]
readme = "README.md"
packages = [{include = "app"}]
[tool.poetry.dependencies]
python = "^3.11"
torch = [
{markers = "platform_machine == 'arm64' or platform_machine == 'aarch64'", version = "=2.0.1", source = "pypi"},
{markers = "platform_machine == 'amd64' or platform_machine == 'x86_64'", version = "=2.0.1", source = "pytorch-cpu"}
]
transformers = "^4.29.2"
sentence-transformers = "^2.2.2"
onnxruntime = "^1.15.0"
insightface = "^0.7.3"
opencv-python-headless = "^4.7.0.72"
pillow = "^9.5.0"
fastapi = "^0.95.2"
uvicorn = {extras = ["standard"], version = "^0.22.0"}
pydantic = "^1.10.8"
aiocache = "^0.12.1"
[tool.poetry.group.dev.dependencies]
mypy = "^1.3.0"
black = "^23.3.0"
pytest = "^7.3.1"
[[tool.poetry.source]]
name = "pytorch-cpu"
url = "https://download.pytorch.org/whl/cpu"
priority = "explicit"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.flake8]
max-line-length = 120
[tool.mypy]
python_version = "3.11"
plugins = "pydantic.mypy"
follow_imports = "silent"
warn_redundant_casts = true
disallow_any_generics = true
check_untyped_defs = true
no_implicit_reexport = true
disallow_untyped_defs = true
[tool.pydantic-mypy]
init_forbid_extra = true
init_typed = true
warn_required_dynamic_aliases = true
warn_untyped_fields = true

View File

@@ -1,158 +0,0 @@
import os
import numpy as np
import cv2 as cv
import uvicorn
from insightface.app import FaceAnalysis
from transformers import pipeline
from sentence_transformers import SentenceTransformer
from PIL import Image
from fastapi import FastAPI
from pydantic import BaseModel
class MlRequestBody(BaseModel):
thumbnailPath: str
class ClipRequestBody(BaseModel):
text: str
classification_model = os.getenv(
"MACHINE_LEARNING_CLASSIFICATION_MODEL", "microsoft/resnet-50"
)
object_model = os.getenv("MACHINE_LEARNING_OBJECT_MODEL", "hustvl/yolos-tiny")
clip_image_model = os.getenv("MACHINE_LEARNING_CLIP_IMAGE_MODEL", "clip-ViT-B-32")
clip_text_model = os.getenv("MACHINE_LEARNING_CLIP_TEXT_MODEL", "clip-ViT-B-32")
facial_recognition_model = os.getenv(
"MACHINE_LEARNING_FACIAL_RECOGNITION_MODEL", "buffalo_l"
)
cache_folder = os.getenv("MACHINE_LEARNING_CACHE_FOLDER", "/cache")
_model_cache = {}
app = FastAPI()
@app.on_event("startup")
async def startup_event():
# Get all models
_get_model(object_model, "object-detection")
_get_model(classification_model, "image-classification")
_get_model(clip_image_model)
_get_model(clip_text_model)
_get_model(facial_recognition_model, "facial-recognition")
@app.get("/")
async def root():
return {"message": "Immich ML"}
@app.get("/ping")
def ping():
return "pong"
@app.post("/object-detection/detect-object", status_code=200)
def object_detection(payload: MlRequestBody):
model = _get_model(object_model, "object-detection")
assetPath = payload.thumbnailPath
return run_engine(model, assetPath)
@app.post("/image-classifier/tag-image", status_code=200)
def image_classification(payload: MlRequestBody):
model = _get_model(classification_model, "image-classification")
assetPath = payload.thumbnailPath
return run_engine(model, assetPath)
@app.post("/sentence-transformer/encode-image", status_code=200)
def clip_encode_image(payload: MlRequestBody):
model = _get_model(clip_image_model)
assetPath = payload.thumbnailPath
return model.encode(Image.open(assetPath)).tolist()
@app.post("/sentence-transformer/encode-text", status_code=200)
def clip_encode_text(payload: ClipRequestBody):
model = _get_model(clip_text_model)
text = payload.text
return model.encode(text).tolist()
@app.post("/facial-recognition/detect-faces", status_code=200)
def facial_recognition(payload: MlRequestBody):
model = _get_model(facial_recognition_model, "facial-recognition")
assetPath = payload.thumbnailPath
img = cv.imread(assetPath)
height, width, _ = img.shape
results = []
faces = model.get(img)
for face in faces:
if face.det_score < 0.7:
continue
x1, y1, x2, y2 = face.bbox
results.append(
{
"imageWidth": width,
"imageHeight": height,
"boundingBox": {
"x1": round(x1),
"y1": round(y1),
"x2": round(x2),
"y2": round(y2),
},
"score": face.det_score.item(),
"embedding": face.normed_embedding.tolist(),
}
)
return results
def run_engine(engine, path):
result = []
predictions = engine(path)
for index, pred in enumerate(predictions):
tags = pred["label"].split(", ")
if pred["score"] > 0.9:
result = [*result, *tags]
if len(result) > 1:
result = list(set(result))
return result
def _get_model(model, task=None):
global _model_cache
key = "|".join([model, str(task)])
if key not in _model_cache:
if task:
if task == "facial-recognition":
face_model = FaceAnalysis(
name=model,
root=cache_folder,
allowed_modules=["detection", "recognition"],
)
face_model.prepare(ctx_id=0, det_size=(640, 640))
_model_cache[key] = face_model
else:
_model_cache[key] = pipeline(model=model, task=task)
else:
_model_cache[key] = SentenceTransformer(model, cache_folder=cache_folder)
return _model_cache[key]
if __name__ == "__main__":
host = os.getenv("MACHINE_LEARNING_HOST", "0.0.0.0")
port = int(os.getenv("MACHINE_LEARNING_PORT", 3003))
is_dev = os.getenv("NODE_ENV") == "development"
uvicorn.run("main:app", host=host, port=port, reload=is_dev, workers=1)

View File

@@ -63,6 +63,7 @@ if [ "$CURRENT_SERVER" != "$NEXT_SERVER" ]; then
echo "Pumping Server: $CURRENT_SERVER => $NEXT_SERVER"
npm --prefix server version $SERVER_PUMP
npm --prefix server run api:generate
poetry --directory machine-learning version $SERVER_PUMP
fi
if [ "$CURRENT_MOBILE" != "$NEXT_MOBILE" ]; then

View File

@@ -72,6 +72,11 @@ android {
}
buildTypes {
debug {
applicationIdSuffix '.debug'
versionNameSuffix '-DEBUG'
}
release {
signingConfig signingConfigs.release
}
@@ -84,6 +89,7 @@ flutter {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
implementation "androidx.work:work-runtime-ktx:$work_version"
implementation "androidx.concurrent:concurrent-futures:$concurrent_version"
implementation "com.google.guava:guava:$guava_version"

View File

@@ -1,10 +1,15 @@
package app.alextran.immich
import android.content.Context
import android.util.Log
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import java.security.MessageDigest
import java.io.File
import java.io.FileInputStream
import kotlinx.coroutines.*
/**
* Android plugin for Dart `BackgroundService`
@@ -16,6 +21,7 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
private var methodChannel: MethodChannel? = null
private var context: Context? = null
private val sha1: MessageDigest = MessageDigest.getInstance("SHA-1")
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
onAttachedToEngine(binding.applicationContext, binding.binaryMessenger)
@@ -70,9 +76,40 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
"isIgnoringBatteryOptimizations" -> {
result.success(BackupWorker.isIgnoringBatteryOptimizations(ctx))
}
"digestFiles" -> {
val args = call.arguments<ArrayList<String>>()!!
GlobalScope.launch(Dispatchers.IO) {
val buf = ByteArray(BUFSIZE)
val digest: MessageDigest = MessageDigest.getInstance("SHA-1")
val hashes = arrayOfNulls<ByteArray>(args.size)
for (i in args.indices) {
val path = args[i]
var len = 0
try {
val file = FileInputStream(path)
try {
while (true) {
len = file.read(buf)
if (len != BUFSIZE) break
digest.update(buf)
}
} finally {
file.close()
}
digest.update(buf, 0, len)
hashes[i] = digest.digest()
} catch (e: Exception) {
// skip this file
Log.w(TAG, "Failed to hash file ${args[i]}: $e")
}
}
result.success(hashes.asList())
}
}
else -> result.notImplemented()
}
}
}
private const val TAG = "BackgroundServicePlugin"
private const val TAG = "BackgroundServicePlugin"
private const val BUFSIZE = 2*1024*1024;

View File

@@ -1,5 +1,6 @@
buildscript {
ext.kotlin_version = '1.8.20'
ext.kotlin_coroutines_version = '1.7.1'
ext.work_version = '2.7.1'
ext.concurrent_version = '1.1.0'
ext.guava_version = '31.0.1-android'

View File

@@ -35,8 +35,8 @@ platform :android do
task: 'bundle',
build_type: 'Release',
properties: {
"android.injected.version.code" => 82,
"android.injected.version.name" => "1.59.0",
"android.injected.version.code" => 85,
"android.injected.version.name" => "1.62.0",
}
)
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')

View File

@@ -19,7 +19,7 @@ platform :ios do
desc "iOS Beta"
lane :beta do
increment_version_number(
version_number: "1.59.0"
version_number: "1.62.0"
)
increment_build_number(
build_number: latest_testflight_build_number + 1,

View File

@@ -19,9 +19,11 @@ import 'package:immich_mobile/modules/settings/providers/notification_permission
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/routing/tab_navigation_observer.dart';
import 'package:immich_mobile/shared/models/album.dart';
import 'package:immich_mobile/shared/models/android_device_asset.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/models/etag.dart';
import 'package:immich_mobile/shared/models/exif_info.dart';
import 'package:immich_mobile/shared/models/ios_device_asset.dart';
import 'package:immich_mobile/shared/models/logger_message.model.dart';
import 'package:immich_mobile/shared/models/store.dart';
import 'package:immich_mobile/shared/models/user.dart';
@@ -91,6 +93,7 @@ Future<Isar> loadDb() async {
DuplicatedAssetSchema,
LoggerMessageSchema,
ETagSchema,
Platform.isAndroid ? AndroidDeviceAssetSchema : IOSDeviceAssetSchema,
],
directory: dir.path,
maxSizeMiB: 256,

View File

@@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/album/services/album.service.dart';
import 'package:immich_mobile/modules/asset_viewer/models/image_viewer_page_state.model.dart';
import 'package:immich_mobile/modules/asset_viewer/services/image_viewer.service.dart';
import 'package:immich_mobile/shared/models/asset.dart';
@@ -12,9 +13,13 @@ import 'package:immich_mobile/shared/ui/share_dialog.dart';
class ImageViewerStateNotifier extends StateNotifier<ImageViewerPageState> {
final ImageViewerService _imageViewerService;
final ShareService _shareService;
final AlbumService _albumService;
ImageViewerStateNotifier(this._imageViewerService, this._shareService)
: super(
ImageViewerStateNotifier(
this._imageViewerService,
this._shareService,
this._albumService,
) : super(
ImageViewerPageState(
downloadAssetStatus: DownloadAssetStatus.idle,
),
@@ -34,6 +39,7 @@ class ImageViewerStateNotifier extends StateNotifier<ImageViewerPageState> {
toastType: ToastType.success,
gravity: ToastGravity.BOTTOM,
);
_albumService.refreshDeviceAlbums();
} else {
state = state.copyWith(downloadAssetStatus: DownloadAssetStatus.error);
ImmichToast.show(
@@ -66,5 +72,6 @@ final imageViewerStateProvider =
((ref) => ImageViewerStateNotifier(
ref.watch(imageViewerServiceProvider),
ref.watch(shareServiceProvider),
ref.watch(albumServiceProvider),
)),
);

View File

@@ -72,15 +72,7 @@ class TopControlAppBar extends HookConsumerWidget {
color: Colors.grey[200],
),
),
if (!asset.isLocal)
IconButton(
onPressed: onDownloadPressed,
icon: Icon(
Icons.cloud_download_outlined,
color: Colors.grey[200],
),
),
if (asset.storage == AssetState.merged)
if (asset.storage == AssetState.remote)
IconButton(
onPressed: onDownloadPressed,
icon: Icon(

View File

@@ -276,28 +276,33 @@ class GalleryViewerPage extends HookConsumerWidget {
(showAppBar.value && !isZoomed.value)) &&
!isPlayingVideo.value;
return AnimatedOpacity(
duration: const Duration(milliseconds: 100),
opacity: show ? 1.0 : 0.0,
child: Container(
color: Colors.black.withOpacity(0.4),
child: TopControlAppBar(
isPlayingMotionVideo: isPlayingMotionVideo.value,
asset: asset(),
isFavorite: asset().isFavorite,
onMoreInfoPressed: showInfo,
onFavorite: asset().isRemote ? () => toggleFavorite(asset()) : null,
onDownloadPressed: asset().storage == AssetState.local
? null
: () =>
ref.watch(imageViewerStateProvider.notifier).downloadAsset(
asset(),
context,
),
onToggleMotionVideo: (() {
isPlayingMotionVideo.value = !isPlayingMotionVideo.value;
}),
onAddToAlbumPressed: () => addToAlbum(asset()),
return IgnorePointer(
ignoring: !show,
child: AnimatedOpacity(
duration: const Duration(milliseconds: 100),
opacity: show ? 1.0 : 0.0,
child: Container(
color: Colors.black.withOpacity(0.4),
child: TopControlAppBar(
isPlayingMotionVideo: isPlayingMotionVideo.value,
asset: asset(),
isFavorite: asset().isFavorite,
onMoreInfoPressed: showInfo,
onFavorite:
asset().isRemote ? () => toggleFavorite(asset()) : null,
onDownloadPressed: asset().isLocal
? null
: () => ref
.watch(imageViewerStateProvider.notifier)
.downloadAsset(
asset(),
context,
),
onToggleMotionVideo: (() {
isPlayingMotionVideo.value = !isPlayingMotionVideo.value;
}),
onAddToAlbumPressed: () => addToAlbum(asset()),
),
),
),
);
@@ -307,53 +312,57 @@ class GalleryViewerPage extends HookConsumerWidget {
final show = (showAppBar.value || // onTap has the final say
(showAppBar.value && !isZoomed.value)) &&
!isPlayingVideo.value;
return AnimatedOpacity(
duration: const Duration(milliseconds: 100),
opacity: show ? 1.0 : 0.0,
child: BottomNavigationBar(
backgroundColor: Colors.black.withOpacity(0.4),
unselectedIconTheme: const IconThemeData(color: Colors.white),
selectedIconTheme: const IconThemeData(color: Colors.white),
unselectedLabelStyle: const TextStyle(color: Colors.black),
selectedLabelStyle: const TextStyle(color: Colors.black),
showSelectedLabels: false,
showUnselectedLabels: false,
items: [
BottomNavigationBarItem(
icon: const Icon(Icons.ios_share_rounded),
label: 'control_bottom_app_bar_share'.tr(),
tooltip: 'control_bottom_app_bar_share'.tr(),
),
asset().isArchived
? BottomNavigationBarItem(
icon: const Icon(Icons.unarchive_rounded),
label: 'control_bottom_app_bar_unarchive'.tr(),
tooltip: 'control_bottom_app_bar_unarchive'.tr(),
)
: BottomNavigationBarItem(
icon: const Icon(Icons.archive_outlined),
label: 'control_bottom_app_bar_archive'.tr(),
tooltip: 'control_bottom_app_bar_archive'.tr(),
),
BottomNavigationBarItem(
icon: const Icon(Icons.delete_outline),
label: 'control_bottom_app_bar_delete'.tr(),
tooltip: 'control_bottom_app_bar_delete'.tr(),
),
],
onTap: (index) {
switch (index) {
case 0:
shareAsset();
break;
case 1:
handleArchive(asset());
break;
case 2:
handleDelete(asset());
break;
}
},
return IgnorePointer(
ignoring: !show,
child: AnimatedOpacity(
duration: const Duration(milliseconds: 100),
opacity: show ? 1.0 : 0.0,
child: BottomNavigationBar(
backgroundColor: Colors.black.withOpacity(0.4),
unselectedIconTheme: const IconThemeData(color: Colors.white),
selectedIconTheme: const IconThemeData(color: Colors.white),
unselectedLabelStyle: const TextStyle(color: Colors.black),
selectedLabelStyle: const TextStyle(color: Colors.black),
showSelectedLabels: false,
showUnselectedLabels: false,
items: [
BottomNavigationBarItem(
icon: const Icon(Icons.ios_share_rounded),
label: 'control_bottom_app_bar_share'.tr(),
tooltip: 'control_bottom_app_bar_share'.tr(),
),
asset().isArchived
? BottomNavigationBarItem(
icon: const Icon(Icons.unarchive_rounded),
label: 'control_bottom_app_bar_unarchive'.tr(),
tooltip: 'control_bottom_app_bar_unarchive'.tr(),
)
: BottomNavigationBarItem(
icon: const Icon(Icons.archive_outlined),
label: 'control_bottom_app_bar_archive'.tr(),
tooltip: 'control_bottom_app_bar_archive'.tr(),
),
BottomNavigationBarItem(
icon: const Icon(Icons.delete_outline),
label: 'control_bottom_app_bar_delete'.tr(),
tooltip: 'control_bottom_app_bar_delete'.tr(),
),
],
onTap: (index) {
switch (index) {
case 0:
shareAsset();
break;
case 1:
handleArchive(asset());
break;
case 2:
handleDelete(asset());
break;
}
},
),
),
);
}

View File

@@ -132,6 +132,17 @@ class BackgroundService {
}
}
Future<Uint8List?> digestFile(String path) {
return _foregroundChannel.invokeMethod<Uint8List>("digestFile", [path]);
}
Future<List<Uint8List?>?> digestFiles(List<String> paths) {
return _foregroundChannel.invokeListMethod<Uint8List?>(
"digestFiles",
paths,
);
}
/// Updates the notification shown by the background service
Future<bool?> _updateNotification({
String? title,

View File

@@ -47,11 +47,11 @@ class HomePage extends HookConsumerWidget {
useEffect(
() {
ref.watch(websocketProvider.notifier).connect();
ref.watch(assetProvider.notifier).getAllAsset();
ref.watch(albumProvider.notifier).getAllAlbums();
ref.watch(sharedAlbumProvider.notifier).getAllSharedAlbums();
ref.watch(serverInfoProvider.notifier).getServerVersion();
ref.read(websocketProvider.notifier).connect();
Future(() => ref.read(assetProvider.notifier).getAllAsset());
ref.read(albumProvider.notifier).getAllAlbums();
ref.read(sharedAlbumProvider.notifier).getAllSharedAlbums();
ref.read(serverInfoProvider.notifier).getServerVersion();
selectionEnabledHook.addListener(() {
multiselectEnabled.state = selectionEnabledHook.value;
@@ -144,7 +144,7 @@ class HomePage extends HookConsumerWidget {
);
if (remoteAssets.isNotEmpty) {
await ref
.watch(assetProvider.notifier)
.read(assetProvider.notifier)
.toggleArchive(remoteAssets, true);
final assetOrAssets = remoteAssets.length > 1 ? 'assets' : 'asset';
@@ -163,7 +163,7 @@ class HomePage extends HookConsumerWidget {
void onDelete() async {
processing.value = true;
try {
await ref.watch(assetProvider.notifier).deleteAssets(selection.value);
await ref.read(assetProvider.notifier).deleteAssets(selection.value);
selectionEnabledHook.value = false;
} finally {
processing.value = false;

View File

@@ -166,23 +166,10 @@ extension AssetsHelper on IsarCollection<Album> {
}
}
extension AssetPathEntityHelper on AssetPathEntity {
Future<List<Asset>> getAssets({
int start = 0,
int end = 0x7fffffffffffffff,
Set<String>? excludedAssets,
}) async {
final assetEntities = await getAssetListRange(start: start, end: end);
if (excludedAssets != null) {
return assetEntities
.where((e) => !excludedAssets.contains(e.id))
.map(Asset.local)
.toList();
}
return assetEntities.map(Asset.local).toList();
}
}
extension AlbumResponseDtoHelper on AlbumResponseDto {
List<Asset> getAssets() => assets.map(Asset.remote).toList();
}
extension AssetPathEntityHelper on AssetPathEntity {
String get eTagKeyAssetCount => "device-album-$id-asset-count";
}

View File

@@ -0,0 +1,10 @@
import 'package:immich_mobile/shared/models/device_asset.dart';
import 'package:isar/isar.dart';
part 'android_device_asset.g.dart';
@Collection()
class AndroidDeviceAsset extends DeviceAsset {
AndroidDeviceAsset({required this.id, required super.hash});
Id id;
}

View File

@@ -0,0 +1,493 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'android_device_asset.dart';
// **************************************************************************
// IsarCollectionGenerator
// **************************************************************************
// coverage:ignore-file
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
extension GetAndroidDeviceAssetCollection on Isar {
IsarCollection<AndroidDeviceAsset> get androidDeviceAssets =>
this.collection();
}
const AndroidDeviceAssetSchema = CollectionSchema(
name: r'AndroidDeviceAsset',
id: -6758387181232899335,
properties: {
r'hash': PropertySchema(
id: 0,
name: r'hash',
type: IsarType.byteList,
)
},
estimateSize: _androidDeviceAssetEstimateSize,
serialize: _androidDeviceAssetSerialize,
deserialize: _androidDeviceAssetDeserialize,
deserializeProp: _androidDeviceAssetDeserializeProp,
idName: r'id',
indexes: {
r'hash': IndexSchema(
id: -7973251393006690288,
name: r'hash',
unique: false,
replace: false,
properties: [
IndexPropertySchema(
name: r'hash',
type: IndexType.hash,
caseSensitive: false,
)
],
)
},
links: {},
embeddedSchemas: {},
getId: _androidDeviceAssetGetId,
getLinks: _androidDeviceAssetGetLinks,
attach: _androidDeviceAssetAttach,
version: '3.1.0+1',
);
int _androidDeviceAssetEstimateSize(
AndroidDeviceAsset object,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
var bytesCount = offsets.last;
bytesCount += 3 + object.hash.length;
return bytesCount;
}
void _androidDeviceAssetSerialize(
AndroidDeviceAsset object,
IsarWriter writer,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
writer.writeByteList(offsets[0], object.hash);
}
AndroidDeviceAsset _androidDeviceAssetDeserialize(
Id id,
IsarReader reader,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
final object = AndroidDeviceAsset(
hash: reader.readByteList(offsets[0]) ?? [],
id: id,
);
return object;
}
P _androidDeviceAssetDeserializeProp<P>(
IsarReader reader,
int propertyId,
int offset,
Map<Type, List<int>> allOffsets,
) {
switch (propertyId) {
case 0:
return (reader.readByteList(offset) ?? []) as P;
default:
throw IsarError('Unknown property with id $propertyId');
}
}
Id _androidDeviceAssetGetId(AndroidDeviceAsset object) {
return object.id;
}
List<IsarLinkBase<dynamic>> _androidDeviceAssetGetLinks(
AndroidDeviceAsset object) {
return [];
}
void _androidDeviceAssetAttach(
IsarCollection<dynamic> col, Id id, AndroidDeviceAsset object) {
object.id = id;
}
extension AndroidDeviceAssetQueryWhereSort
on QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QWhere> {
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterWhere> anyId() {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(const IdWhereClause.any());
});
}
}
extension AndroidDeviceAssetQueryWhere
on QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QWhereClause> {
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterWhereClause>
idEqualTo(Id id) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IdWhereClause.between(
lower: id,
upper: id,
));
});
}
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterWhereClause>
idNotEqualTo(Id id) {
return QueryBuilder.apply(this, (query) {
if (query.whereSort == Sort.asc) {
return query
.addWhereClause(
IdWhereClause.lessThan(upper: id, includeUpper: false),
)
.addWhereClause(
IdWhereClause.greaterThan(lower: id, includeLower: false),
);
} else {
return query
.addWhereClause(
IdWhereClause.greaterThan(lower: id, includeLower: false),
)
.addWhereClause(
IdWhereClause.lessThan(upper: id, includeUpper: false),
);
}
});
}
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterWhereClause>
idGreaterThan(Id id, {bool include = false}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(
IdWhereClause.greaterThan(lower: id, includeLower: include),
);
});
}
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterWhereClause>
idLessThan(Id id, {bool include = false}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(
IdWhereClause.lessThan(upper: id, includeUpper: include),
);
});
}
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterWhereClause>
idBetween(
Id lowerId,
Id upperId, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IdWhereClause.between(
lower: lowerId,
includeLower: includeLower,
upper: upperId,
includeUpper: includeUpper,
));
});
}
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterWhereClause>
hashEqualTo(List<int> hash) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IndexWhereClause.equalTo(
indexName: r'hash',
value: [hash],
));
});
}
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterWhereClause>
hashNotEqualTo(List<int> hash) {
return QueryBuilder.apply(this, (query) {
if (query.whereSort == Sort.asc) {
return query
.addWhereClause(IndexWhereClause.between(
indexName: r'hash',
lower: [],
upper: [hash],
includeUpper: false,
))
.addWhereClause(IndexWhereClause.between(
indexName: r'hash',
lower: [hash],
includeLower: false,
upper: [],
));
} else {
return query
.addWhereClause(IndexWhereClause.between(
indexName: r'hash',
lower: [hash],
includeLower: false,
upper: [],
))
.addWhereClause(IndexWhereClause.between(
indexName: r'hash',
lower: [],
upper: [hash],
includeUpper: false,
));
}
});
}
}
extension AndroidDeviceAssetQueryFilter
on QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QFilterCondition> {
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterFilterCondition>
hashElementEqualTo(int value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'hash',
value: value,
));
});
}
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterFilterCondition>
hashElementGreaterThan(
int value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
include: include,
property: r'hash',
value: value,
));
});
}
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterFilterCondition>
hashElementLessThan(
int value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.lessThan(
include: include,
property: r'hash',
value: value,
));
});
}
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterFilterCondition>
hashElementBetween(
int lower,
int upper, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.between(
property: r'hash',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
));
});
}
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterFilterCondition>
hashLengthEqualTo(int length) {
return QueryBuilder.apply(this, (query) {
return query.listLength(
r'hash',
length,
true,
length,
true,
);
});
}
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterFilterCondition>
hashIsEmpty() {
return QueryBuilder.apply(this, (query) {
return query.listLength(
r'hash',
0,
true,
0,
true,
);
});
}
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterFilterCondition>
hashIsNotEmpty() {
return QueryBuilder.apply(this, (query) {
return query.listLength(
r'hash',
0,
false,
999999,
true,
);
});
}
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterFilterCondition>
hashLengthLessThan(
int length, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.listLength(
r'hash',
0,
true,
length,
include,
);
});
}
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterFilterCondition>
hashLengthGreaterThan(
int length, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.listLength(
r'hash',
length,
include,
999999,
true,
);
});
}
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterFilterCondition>
hashLengthBetween(
int lower,
int upper, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.listLength(
r'hash',
lower,
includeLower,
upper,
includeUpper,
);
});
}
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterFilterCondition>
idEqualTo(Id value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'id',
value: value,
));
});
}
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterFilterCondition>
idGreaterThan(
Id value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
include: include,
property: r'id',
value: value,
));
});
}
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterFilterCondition>
idLessThan(
Id value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.lessThan(
include: include,
property: r'id',
value: value,
));
});
}
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterFilterCondition>
idBetween(
Id lower,
Id upper, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.between(
property: r'id',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
));
});
}
}
extension AndroidDeviceAssetQueryObject
on QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QFilterCondition> {}
extension AndroidDeviceAssetQueryLinks
on QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QFilterCondition> {}
extension AndroidDeviceAssetQuerySortBy
on QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QSortBy> {}
extension AndroidDeviceAssetQuerySortThenBy
on QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QSortThenBy> {
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterSortBy>
thenById() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'id', Sort.asc);
});
}
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterSortBy>
thenByIdDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'id', Sort.desc);
});
}
}
extension AndroidDeviceAssetQueryWhereDistinct
on QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QDistinct> {
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QDistinct>
distinctByHash() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'hash');
});
}
}
extension AndroidDeviceAssetQueryProperty
on QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QQueryProperty> {
QueryBuilder<AndroidDeviceAsset, int, QQueryOperations> idProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'id');
});
}
QueryBuilder<AndroidDeviceAsset, List<int>, QQueryOperations> hashProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'hash');
});
}
}

View File

@@ -1,3 +1,5 @@
import 'dart:convert';
import 'package:immich_mobile/shared/models/exif_info.dart';
import 'package:immich_mobile/shared/models/store.dart';
import 'package:immich_mobile/utils/hash.dart';
@@ -14,7 +16,7 @@ part 'asset.g.dart';
class Asset {
Asset.remote(AssetResponseDto remote)
: remoteId = remote.id,
isLocal = false,
checksum = remote.checksum,
fileCreatedAt = remote.fileCreatedAt,
fileModifiedAt = remote.fileModifiedAt,
updatedAt = remote.updatedAt,
@@ -24,23 +26,20 @@ class Asset {
height = remote.exifInfo?.exifImageHeight?.toInt(),
width = remote.exifInfo?.exifImageWidth?.toInt(),
livePhotoVideoId = remote.livePhotoVideoId,
localId = remote.deviceAssetId,
deviceId = fastHash(remote.deviceId),
ownerId = fastHash(remote.ownerId),
exifInfo =
remote.exifInfo != null ? ExifInfo.fromDto(remote.exifInfo!) : null,
isFavorite = remote.isFavorite,
isArchived = remote.isArchived;
Asset.local(AssetEntity local)
Asset.local(AssetEntity local, List<int> hash)
: localId = local.id,
isLocal = true,
checksum = base64.encode(hash),
durationInSeconds = local.duration,
type = AssetType.values[local.typeInt],
height = local.height,
width = local.width,
fileName = local.title!,
deviceId = Store.get(StoreKey.deviceIdHash),
ownerId = Store.get(StoreKey.currentUser).isarId,
fileModifiedAt = local.modifiedDateTime,
updatedAt = local.modifiedDateTime,
@@ -53,13 +52,15 @@ class Asset {
if (local.latitude != null) {
exifInfo = ExifInfo(lat: local.latitude, long: local.longitude);
}
_local = local;
assert(hash.length == 20, "invalid SHA1 hash");
}
Asset({
this.id = Isar.autoIncrement,
required this.checksum,
this.remoteId,
required this.localId,
required this.deviceId,
required this.ownerId,
required this.fileCreatedAt,
required this.fileModifiedAt,
@@ -72,7 +73,6 @@ class Asset {
this.livePhotoVideoId,
this.exifInfo,
required this.isFavorite,
required this.isLocal,
required this.isArchived,
});
@@ -83,7 +83,7 @@ class Asset {
AssetEntity? get local {
if (isLocal && _local == null) {
_local = AssetEntity(
id: localId,
id: localId!,
typeInt: isImage ? 1 : 2,
width: width ?? 0,
height: height ?? 0,
@@ -98,18 +98,21 @@ class Asset {
Id id = Isar.autoIncrement;
/// stores the raw SHA1 bytes as a base64 String
/// because Isar cannot sort lists of byte arrays
@Index(
unique: true,
replace: false,
type: IndexType.hash,
composite: [CompositeIndex("ownerId")],
)
String checksum;
@Index(unique: false, replace: false, type: IndexType.hash)
String? remoteId;
@Index(
unique: false,
replace: false,
type: IndexType.hash,
composite: [CompositeIndex('deviceId')],
)
String localId;
int deviceId;
@Index(unique: false, replace: false, type: IndexType.hash)
String? localId;
int ownerId;
@@ -134,14 +137,15 @@ class Asset {
bool isFavorite;
/// `true` if this [Asset] is present on the device
bool isLocal;
bool isArchived;
@ignore
ExifInfo? exifInfo;
/// `true` if this [Asset] is present on the device
@ignore
bool get isLocal => localId != null;
@ignore
bool get isInDb => id != Isar.autoIncrement;
@@ -175,9 +179,9 @@ class Asset {
bool operator ==(other) {
if (other is! Asset) return false;
return id == other.id &&
checksum == other.checksum &&
remoteId == other.remoteId &&
localId == other.localId &&
deviceId == other.deviceId &&
ownerId == other.ownerId &&
fileCreatedAt.isAtSameMomentAs(other.fileCreatedAt) &&
fileModifiedAt.isAtSameMomentAs(other.fileModifiedAt) &&
@@ -197,9 +201,9 @@ class Asset {
@ignore
int get hashCode =>
id.hashCode ^
checksum.hashCode ^
remoteId.hashCode ^
localId.hashCode ^
deviceId.hashCode ^
ownerId.hashCode ^
fileCreatedAt.hashCode ^
fileModifiedAt.hashCode ^
@@ -217,8 +221,7 @@ class Asset {
/// Returns `true` if this [Asset] can updated with values from parameter [a]
bool canUpdate(Asset a) {
assert(isInDb);
assert(localId == a.localId);
assert(deviceId == a.deviceId);
assert(checksum == a.checksum);
assert(a.storage != AssetState.merged);
return a.updatedAt.isAfter(updatedAt) ||
a.isRemote && !isRemote ||
@@ -239,11 +242,18 @@ class Asset {
if (a.isRemote) {
return a._copyWith(
id: id,
isLocal: isLocal,
localId: localId,
width: a.width ?? width,
height: a.height ?? height,
exifInfo: a.exifInfo?.copyWith(id: id) ?? exifInfo,
);
} else if (isRemote) {
return _copyWith(
localId: localId ?? a.localId,
width: width ?? a.width,
height: height ?? a.height,
exifInfo: exifInfo ?? a.exifInfo?.copyWith(id: id),
);
} else {
return a._copyWith(
id: id,
@@ -270,7 +280,7 @@ class Asset {
} else {
// add only missing values (and set isLocal to true)
return _copyWith(
isLocal: true,
localId: localId ?? a.localId,
width: width ?? a.width,
height: height ?? a.height,
exifInfo: exifInfo ?? a.exifInfo?.copyWith(id: id),
@@ -281,9 +291,9 @@ class Asset {
Asset _copyWith({
Id? id,
String? checksum,
String? remoteId,
String? localId,
int? deviceId,
int? ownerId,
DateTime? fileCreatedAt,
DateTime? fileModifiedAt,
@@ -295,15 +305,14 @@ class Asset {
String? fileName,
String? livePhotoVideoId,
bool? isFavorite,
bool? isLocal,
bool? isArchived,
ExifInfo? exifInfo,
}) =>
Asset(
id: id ?? this.id,
checksum: checksum ?? this.checksum,
remoteId: remoteId ?? this.remoteId,
localId: localId ?? this.localId,
deviceId: deviceId ?? this.deviceId,
ownerId: ownerId ?? this.ownerId,
fileCreatedAt: fileCreatedAt ?? this.fileCreatedAt,
fileModifiedAt: fileModifiedAt ?? this.fileModifiedAt,
@@ -315,7 +324,6 @@ class Asset {
fileName: fileName ?? this.fileName,
livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId,
isFavorite: isFavorite ?? this.isFavorite,
isLocal: isLocal ?? this.isLocal,
isArchived: isArchived ?? this.isArchived,
exifInfo: exifInfo ?? this.exifInfo,
);
@@ -328,39 +336,36 @@ class Asset {
}
}
/// compares assets by [ownerId], [deviceId], [localId]
static int compareByOwnerDeviceLocalId(Asset a, Asset b) {
final int ownerIdOrder = a.ownerId.compareTo(b.ownerId);
if (ownerIdOrder != 0) {
return ownerIdOrder;
}
final int deviceIdOrder = a.deviceId.compareTo(b.deviceId);
if (deviceIdOrder != 0) {
return deviceIdOrder;
}
final int localIdOrder = a.localId.compareTo(b.localId);
return localIdOrder;
}
/// compares assets by [ownerId], [deviceId], [localId], [fileModifiedAt]
static int compareByOwnerDeviceLocalIdModified(Asset a, Asset b) {
final int order = compareByOwnerDeviceLocalId(a, b);
return order != 0 ? order : a.fileModifiedAt.compareTo(b.fileModifiedAt);
}
static int compareById(Asset a, Asset b) => a.id.compareTo(b.id);
static int compareByLocalId(Asset a, Asset b) =>
a.localId.compareTo(b.localId);
static int compareByChecksum(Asset a, Asset b) =>
a.checksum.compareTo(b.checksum);
static int compareByOwnerChecksum(Asset a, Asset b) {
final int ownerIdOrder = a.ownerId.compareTo(b.ownerId);
if (ownerIdOrder != 0) return ownerIdOrder;
return compareByChecksum(a, b);
}
static int compareByOwnerChecksumCreatedModified(Asset a, Asset b) {
final int ownerIdOrder = a.ownerId.compareTo(b.ownerId);
if (ownerIdOrder != 0) return ownerIdOrder;
final int checksumOrder = compareByChecksum(a, b);
if (checksumOrder != 0) return checksumOrder;
final int createdOrder = a.fileCreatedAt.compareTo(b.fileCreatedAt);
if (createdOrder != 0) return createdOrder;
return a.fileModifiedAt.compareTo(b.fileModifiedAt);
}
@override
String toString() {
return """
{
"id": ${id == Isar.autoIncrement ? '"N/A"' : id},
"remoteId": "${remoteId ?? "N/A"}",
"localId": "$localId",
"deviceId": "$deviceId",
"ownerId": "$ownerId",
"localId": "${localId ?? "N/A"}",
"checksum": "$checksum",
"ownerId": $ownerId,
"livePhotoVideoId": "${livePhotoVideoId ?? "N/A"}",
"fileCreatedAt": "$fileCreatedAt",
"fileModifiedAt": "$fileModifiedAt",
@@ -369,9 +374,8 @@ class Asset {
"type": "$type",
"fileName": "$fileName",
"isFavorite": $isFavorite,
"isLocal": $isLocal,
"isRemote: $isRemote,
"storage": $storage,
"storage": "$storage",
"width": ${width ?? "N/A"},
"height": ${height ?? "N/A"},
"isArchived": $isArchived
@@ -424,10 +428,6 @@ extension AssetsHelper on IsarCollection<Asset> {
QueryBuilder<Asset, Asset, QAfterWhereClause> _remote(Iterable<String> ids) =>
where().anyOf(ids, (q, String e) => q.remoteIdEqualTo(e));
QueryBuilder<Asset, Asset, QAfterWhereClause> _local(Iterable<String> ids) {
return where().anyOf(
ids,
(q, String e) =>
q.localIdDeviceIdEqualTo(e, Store.get(StoreKey.deviceIdHash)),
);
return where().anyOf(ids, (q, String e) => q.localIdEqualTo(e));
}
}

View File

@@ -17,10 +17,10 @@ const AssetSchema = CollectionSchema(
name: r'Asset',
id: -2933289051367723566,
properties: {
r'deviceId': PropertySchema(
r'checksum': PropertySchema(
id: 0,
name: r'deviceId',
type: IsarType.long,
name: r'checksum',
type: IsarType.string,
),
r'durationInSeconds': PropertySchema(
id: 1,
@@ -57,44 +57,39 @@ const AssetSchema = CollectionSchema(
name: r'isFavorite',
type: IsarType.bool,
),
r'isLocal': PropertySchema(
id: 8,
name: r'isLocal',
type: IsarType.bool,
),
r'livePhotoVideoId': PropertySchema(
id: 9,
id: 8,
name: r'livePhotoVideoId',
type: IsarType.string,
),
r'localId': PropertySchema(
id: 10,
id: 9,
name: r'localId',
type: IsarType.string,
),
r'ownerId': PropertySchema(
id: 11,
id: 10,
name: r'ownerId',
type: IsarType.long,
),
r'remoteId': PropertySchema(
id: 12,
id: 11,
name: r'remoteId',
type: IsarType.string,
),
r'type': PropertySchema(
id: 13,
id: 12,
name: r'type',
type: IsarType.byte,
enumMap: _AssettypeEnumValueMap,
),
r'updatedAt': PropertySchema(
id: 14,
id: 13,
name: r'updatedAt',
type: IsarType.dateTime,
),
r'width': PropertySchema(
id: 15,
id: 14,
name: r'width',
type: IsarType.int,
)
@@ -105,6 +100,24 @@ const AssetSchema = CollectionSchema(
deserializeProp: _assetDeserializeProp,
idName: r'id',
indexes: {
r'checksum_ownerId': IndexSchema(
id: 5611361749756160119,
name: r'checksum_ownerId',
unique: true,
replace: false,
properties: [
IndexPropertySchema(
name: r'checksum',
type: IndexType.hash,
caseSensitive: true,
),
IndexPropertySchema(
name: r'ownerId',
type: IndexType.value,
caseSensitive: false,
)
],
),
r'remoteId': IndexSchema(
id: 6301175856541681032,
name: r'remoteId',
@@ -118,9 +131,9 @@ const AssetSchema = CollectionSchema(
)
],
),
r'localId_deviceId': IndexSchema(
id: 7649417350086526165,
name: r'localId_deviceId',
r'localId': IndexSchema(
id: 1199848425898359622,
name: r'localId',
unique: false,
replace: false,
properties: [
@@ -128,11 +141,6 @@ const AssetSchema = CollectionSchema(
name: r'localId',
type: IndexType.hash,
caseSensitive: true,
),
IndexPropertySchema(
name: r'deviceId',
type: IndexType.value,
caseSensitive: false,
)
],
)
@@ -151,6 +159,7 @@ int _assetEstimateSize(
Map<Type, List<int>> allOffsets,
) {
var bytesCount = offsets.last;
bytesCount += 3 + object.checksum.length * 3;
bytesCount += 3 + object.fileName.length * 3;
{
final value = object.livePhotoVideoId;
@@ -158,7 +167,12 @@ int _assetEstimateSize(
bytesCount += 3 + value.length * 3;
}
}
bytesCount += 3 + object.localId.length * 3;
{
final value = object.localId;
if (value != null) {
bytesCount += 3 + value.length * 3;
}
}
{
final value = object.remoteId;
if (value != null) {
@@ -174,7 +188,7 @@ void _assetSerialize(
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
writer.writeLong(offsets[0], object.deviceId);
writer.writeString(offsets[0], object.checksum);
writer.writeLong(offsets[1], object.durationInSeconds);
writer.writeDateTime(offsets[2], object.fileCreatedAt);
writer.writeDateTime(offsets[3], object.fileModifiedAt);
@@ -182,14 +196,13 @@ void _assetSerialize(
writer.writeInt(offsets[5], object.height);
writer.writeBool(offsets[6], object.isArchived);
writer.writeBool(offsets[7], object.isFavorite);
writer.writeBool(offsets[8], object.isLocal);
writer.writeString(offsets[9], object.livePhotoVideoId);
writer.writeString(offsets[10], object.localId);
writer.writeLong(offsets[11], object.ownerId);
writer.writeString(offsets[12], object.remoteId);
writer.writeByte(offsets[13], object.type.index);
writer.writeDateTime(offsets[14], object.updatedAt);
writer.writeInt(offsets[15], object.width);
writer.writeString(offsets[8], object.livePhotoVideoId);
writer.writeString(offsets[9], object.localId);
writer.writeLong(offsets[10], object.ownerId);
writer.writeString(offsets[11], object.remoteId);
writer.writeByte(offsets[12], object.type.index);
writer.writeDateTime(offsets[13], object.updatedAt);
writer.writeInt(offsets[14], object.width);
}
Asset _assetDeserialize(
@@ -199,7 +212,7 @@ Asset _assetDeserialize(
Map<Type, List<int>> allOffsets,
) {
final object = Asset(
deviceId: reader.readLong(offsets[0]),
checksum: reader.readString(offsets[0]),
durationInSeconds: reader.readLong(offsets[1]),
fileCreatedAt: reader.readDateTime(offsets[2]),
fileModifiedAt: reader.readDateTime(offsets[3]),
@@ -208,15 +221,14 @@ Asset _assetDeserialize(
id: id,
isArchived: reader.readBool(offsets[6]),
isFavorite: reader.readBool(offsets[7]),
isLocal: reader.readBool(offsets[8]),
livePhotoVideoId: reader.readStringOrNull(offsets[9]),
localId: reader.readString(offsets[10]),
ownerId: reader.readLong(offsets[11]),
remoteId: reader.readStringOrNull(offsets[12]),
type: _AssettypeValueEnumMap[reader.readByteOrNull(offsets[13])] ??
livePhotoVideoId: reader.readStringOrNull(offsets[8]),
localId: reader.readStringOrNull(offsets[9]),
ownerId: reader.readLong(offsets[10]),
remoteId: reader.readStringOrNull(offsets[11]),
type: _AssettypeValueEnumMap[reader.readByteOrNull(offsets[12])] ??
AssetType.other,
updatedAt: reader.readDateTime(offsets[14]),
width: reader.readIntOrNull(offsets[15]),
updatedAt: reader.readDateTime(offsets[13]),
width: reader.readIntOrNull(offsets[14]),
);
return object;
}
@@ -229,7 +241,7 @@ P _assetDeserializeProp<P>(
) {
switch (propertyId) {
case 0:
return (reader.readLong(offset)) as P;
return (reader.readString(offset)) as P;
case 1:
return (reader.readLong(offset)) as P;
case 2:
@@ -245,21 +257,19 @@ P _assetDeserializeProp<P>(
case 7:
return (reader.readBool(offset)) as P;
case 8:
return (reader.readBool(offset)) as P;
return (reader.readStringOrNull(offset)) as P;
case 9:
return (reader.readStringOrNull(offset)) as P;
case 10:
return (reader.readString(offset)) as P;
case 11:
return (reader.readLong(offset)) as P;
case 12:
case 11:
return (reader.readStringOrNull(offset)) as P;
case 13:
case 12:
return (_AssettypeValueEnumMap[reader.readByteOrNull(offset)] ??
AssetType.other) as P;
case 14:
case 13:
return (reader.readDateTime(offset)) as P;
case 15:
case 14:
return (reader.readIntOrNull(offset)) as P;
default:
throw IsarError('Unknown property with id $propertyId');
@@ -291,6 +301,94 @@ void _assetAttach(IsarCollection<dynamic> col, Id id, Asset object) {
object.id = id;
}
extension AssetByIndex on IsarCollection<Asset> {
Future<Asset?> getByChecksumOwnerId(String checksum, int ownerId) {
return getByIndex(r'checksum_ownerId', [checksum, ownerId]);
}
Asset? getByChecksumOwnerIdSync(String checksum, int ownerId) {
return getByIndexSync(r'checksum_ownerId', [checksum, ownerId]);
}
Future<bool> deleteByChecksumOwnerId(String checksum, int ownerId) {
return deleteByIndex(r'checksum_ownerId', [checksum, ownerId]);
}
bool deleteByChecksumOwnerIdSync(String checksum, int ownerId) {
return deleteByIndexSync(r'checksum_ownerId', [checksum, ownerId]);
}
Future<List<Asset?>> getAllByChecksumOwnerId(
List<String> checksumValues, List<int> ownerIdValues) {
final len = checksumValues.length;
assert(ownerIdValues.length == len,
'All index values must have the same length');
final values = <List<dynamic>>[];
for (var i = 0; i < len; i++) {
values.add([checksumValues[i], ownerIdValues[i]]);
}
return getAllByIndex(r'checksum_ownerId', values);
}
List<Asset?> getAllByChecksumOwnerIdSync(
List<String> checksumValues, List<int> ownerIdValues) {
final len = checksumValues.length;
assert(ownerIdValues.length == len,
'All index values must have the same length');
final values = <List<dynamic>>[];
for (var i = 0; i < len; i++) {
values.add([checksumValues[i], ownerIdValues[i]]);
}
return getAllByIndexSync(r'checksum_ownerId', values);
}
Future<int> deleteAllByChecksumOwnerId(
List<String> checksumValues, List<int> ownerIdValues) {
final len = checksumValues.length;
assert(ownerIdValues.length == len,
'All index values must have the same length');
final values = <List<dynamic>>[];
for (var i = 0; i < len; i++) {
values.add([checksumValues[i], ownerIdValues[i]]);
}
return deleteAllByIndex(r'checksum_ownerId', values);
}
int deleteAllByChecksumOwnerIdSync(
List<String> checksumValues, List<int> ownerIdValues) {
final len = checksumValues.length;
assert(ownerIdValues.length == len,
'All index values must have the same length');
final values = <List<dynamic>>[];
for (var i = 0; i < len; i++) {
values.add([checksumValues[i], ownerIdValues[i]]);
}
return deleteAllByIndexSync(r'checksum_ownerId', values);
}
Future<Id> putByChecksumOwnerId(Asset object) {
return putByIndex(r'checksum_ownerId', object);
}
Id putByChecksumOwnerIdSync(Asset object, {bool saveLinks = true}) {
return putByIndexSync(r'checksum_ownerId', object, saveLinks: saveLinks);
}
Future<List<Id>> putAllByChecksumOwnerId(List<Asset> objects) {
return putAllByIndex(r'checksum_ownerId', objects);
}
List<Id> putAllByChecksumOwnerIdSync(List<Asset> objects,
{bool saveLinks = true}) {
return putAllByIndexSync(r'checksum_ownerId', objects,
saveLinks: saveLinks);
}
}
extension AssetQueryWhereSort on QueryBuilder<Asset, Asset, QWhere> {
QueryBuilder<Asset, Asset, QAfterWhere> anyId() {
return QueryBuilder.apply(this, (query) {
@@ -365,6 +463,145 @@ extension AssetQueryWhere on QueryBuilder<Asset, Asset, QWhereClause> {
});
}
QueryBuilder<Asset, Asset, QAfterWhereClause> checksumEqualToAnyOwnerId(
String checksum) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IndexWhereClause.equalTo(
indexName: r'checksum_ownerId',
value: [checksum],
));
});
}
QueryBuilder<Asset, Asset, QAfterWhereClause> checksumNotEqualToAnyOwnerId(
String checksum) {
return QueryBuilder.apply(this, (query) {
if (query.whereSort == Sort.asc) {
return query
.addWhereClause(IndexWhereClause.between(
indexName: r'checksum_ownerId',
lower: [],
upper: [checksum],
includeUpper: false,
))
.addWhereClause(IndexWhereClause.between(
indexName: r'checksum_ownerId',
lower: [checksum],
includeLower: false,
upper: [],
));
} else {
return query
.addWhereClause(IndexWhereClause.between(
indexName: r'checksum_ownerId',
lower: [checksum],
includeLower: false,
upper: [],
))
.addWhereClause(IndexWhereClause.between(
indexName: r'checksum_ownerId',
lower: [],
upper: [checksum],
includeUpper: false,
));
}
});
}
QueryBuilder<Asset, Asset, QAfterWhereClause> checksumOwnerIdEqualTo(
String checksum, int ownerId) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IndexWhereClause.equalTo(
indexName: r'checksum_ownerId',
value: [checksum, ownerId],
));
});
}
QueryBuilder<Asset, Asset, QAfterWhereClause>
checksumEqualToOwnerIdNotEqualTo(String checksum, int ownerId) {
return QueryBuilder.apply(this, (query) {
if (query.whereSort == Sort.asc) {
return query
.addWhereClause(IndexWhereClause.between(
indexName: r'checksum_ownerId',
lower: [checksum],
upper: [checksum, ownerId],
includeUpper: false,
))
.addWhereClause(IndexWhereClause.between(
indexName: r'checksum_ownerId',
lower: [checksum, ownerId],
includeLower: false,
upper: [checksum],
));
} else {
return query
.addWhereClause(IndexWhereClause.between(
indexName: r'checksum_ownerId',
lower: [checksum, ownerId],
includeLower: false,
upper: [checksum],
))
.addWhereClause(IndexWhereClause.between(
indexName: r'checksum_ownerId',
lower: [checksum],
upper: [checksum, ownerId],
includeUpper: false,
));
}
});
}
QueryBuilder<Asset, Asset, QAfterWhereClause>
checksumEqualToOwnerIdGreaterThan(
String checksum,
int ownerId, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IndexWhereClause.between(
indexName: r'checksum_ownerId',
lower: [checksum, ownerId],
includeLower: include,
upper: [checksum],
));
});
}
QueryBuilder<Asset, Asset, QAfterWhereClause> checksumEqualToOwnerIdLessThan(
String checksum,
int ownerId, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IndexWhereClause.between(
indexName: r'checksum_ownerId',
lower: [checksum],
upper: [checksum, ownerId],
includeUpper: include,
));
});
}
QueryBuilder<Asset, Asset, QAfterWhereClause> checksumEqualToOwnerIdBetween(
String checksum,
int lowerOwnerId,
int upperOwnerId, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IndexWhereClause.between(
indexName: r'checksum_ownerId',
lower: [checksum, lowerOwnerId],
includeLower: includeLower,
upper: [checksum, upperOwnerId],
includeUpper: includeUpper,
));
});
}
QueryBuilder<Asset, Asset, QAfterWhereClause> remoteIdIsNull() {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IndexWhereClause.equalTo(
@@ -430,29 +667,49 @@ extension AssetQueryWhere on QueryBuilder<Asset, Asset, QWhereClause> {
});
}
QueryBuilder<Asset, Asset, QAfterWhereClause> localIdEqualToAnyDeviceId(
String localId) {
QueryBuilder<Asset, Asset, QAfterWhereClause> localIdIsNull() {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IndexWhereClause.equalTo(
indexName: r'localId_deviceId',
indexName: r'localId',
value: [null],
));
});
}
QueryBuilder<Asset, Asset, QAfterWhereClause> localIdIsNotNull() {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IndexWhereClause.between(
indexName: r'localId',
lower: [null],
includeLower: false,
upper: [],
));
});
}
QueryBuilder<Asset, Asset, QAfterWhereClause> localIdEqualTo(
String? localId) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IndexWhereClause.equalTo(
indexName: r'localId',
value: [localId],
));
});
}
QueryBuilder<Asset, Asset, QAfterWhereClause> localIdNotEqualToAnyDeviceId(
String localId) {
QueryBuilder<Asset, Asset, QAfterWhereClause> localIdNotEqualTo(
String? localId) {
return QueryBuilder.apply(this, (query) {
if (query.whereSort == Sort.asc) {
return query
.addWhereClause(IndexWhereClause.between(
indexName: r'localId_deviceId',
indexName: r'localId',
lower: [],
upper: [localId],
includeUpper: false,
))
.addWhereClause(IndexWhereClause.between(
indexName: r'localId_deviceId',
indexName: r'localId',
lower: [localId],
includeLower: false,
upper: [],
@@ -460,13 +717,13 @@ extension AssetQueryWhere on QueryBuilder<Asset, Asset, QWhereClause> {
} else {
return query
.addWhereClause(IndexWhereClause.between(
indexName: r'localId_deviceId',
indexName: r'localId',
lower: [localId],
includeLower: false,
upper: [],
))
.addWhereClause(IndexWhereClause.between(
indexName: r'localId_deviceId',
indexName: r'localId',
lower: [],
upper: [localId],
includeUpper: false,
@@ -474,151 +731,135 @@ extension AssetQueryWhere on QueryBuilder<Asset, Asset, QWhereClause> {
}
});
}
QueryBuilder<Asset, Asset, QAfterWhereClause> localIdDeviceIdEqualTo(
String localId, int deviceId) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IndexWhereClause.equalTo(
indexName: r'localId_deviceId',
value: [localId, deviceId],
));
});
}
QueryBuilder<Asset, Asset, QAfterWhereClause>
localIdEqualToDeviceIdNotEqualTo(String localId, int deviceId) {
return QueryBuilder.apply(this, (query) {
if (query.whereSort == Sort.asc) {
return query
.addWhereClause(IndexWhereClause.between(
indexName: r'localId_deviceId',
lower: [localId],
upper: [localId, deviceId],
includeUpper: false,
))
.addWhereClause(IndexWhereClause.between(
indexName: r'localId_deviceId',
lower: [localId, deviceId],
includeLower: false,
upper: [localId],
));
} else {
return query
.addWhereClause(IndexWhereClause.between(
indexName: r'localId_deviceId',
lower: [localId, deviceId],
includeLower: false,
upper: [localId],
))
.addWhereClause(IndexWhereClause.between(
indexName: r'localId_deviceId',
lower: [localId],
upper: [localId, deviceId],
includeUpper: false,
));
}
});
}
QueryBuilder<Asset, Asset, QAfterWhereClause>
localIdEqualToDeviceIdGreaterThan(
String localId,
int deviceId, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IndexWhereClause.between(
indexName: r'localId_deviceId',
lower: [localId, deviceId],
includeLower: include,
upper: [localId],
));
});
}
QueryBuilder<Asset, Asset, QAfterWhereClause> localIdEqualToDeviceIdLessThan(
String localId,
int deviceId, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IndexWhereClause.between(
indexName: r'localId_deviceId',
lower: [localId],
upper: [localId, deviceId],
includeUpper: include,
));
});
}
QueryBuilder<Asset, Asset, QAfterWhereClause> localIdEqualToDeviceIdBetween(
String localId,
int lowerDeviceId,
int upperDeviceId, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IndexWhereClause.between(
indexName: r'localId_deviceId',
lower: [localId, lowerDeviceId],
includeLower: includeLower,
upper: [localId, upperDeviceId],
includeUpper: includeUpper,
));
});
}
}
extension AssetQueryFilter on QueryBuilder<Asset, Asset, QFilterCondition> {
QueryBuilder<Asset, Asset, QAfterFilterCondition> deviceIdEqualTo(int value) {
QueryBuilder<Asset, Asset, QAfterFilterCondition> checksumEqualTo(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'deviceId',
property: r'checksum',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<Asset, Asset, QAfterFilterCondition> deviceIdGreaterThan(
int value, {
QueryBuilder<Asset, Asset, QAfterFilterCondition> checksumGreaterThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
include: include,
property: r'deviceId',
property: r'checksum',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<Asset, Asset, QAfterFilterCondition> deviceIdLessThan(
int value, {
QueryBuilder<Asset, Asset, QAfterFilterCondition> checksumLessThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.lessThan(
include: include,
property: r'deviceId',
property: r'checksum',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<Asset, Asset, QAfterFilterCondition> deviceIdBetween(
int lower,
int upper, {
QueryBuilder<Asset, Asset, QAfterFilterCondition> checksumBetween(
String lower,
String upper, {
bool includeLower = true,
bool includeUpper = true,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.between(
property: r'deviceId',
property: r'checksum',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<Asset, Asset, QAfterFilterCondition> checksumStartsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.startsWith(
property: r'checksum',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<Asset, Asset, QAfterFilterCondition> checksumEndsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.endsWith(
property: r'checksum',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<Asset, Asset, QAfterFilterCondition> checksumContains(
String value,
{bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.contains(
property: r'checksum',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<Asset, Asset, QAfterFilterCondition> checksumMatches(
String pattern,
{bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.matches(
property: r'checksum',
wildcard: pattern,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<Asset, Asset, QAfterFilterCondition> checksumIsEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'checksum',
value: '',
));
});
}
QueryBuilder<Asset, Asset, QAfterFilterCondition> checksumIsNotEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
property: r'checksum',
value: '',
));
});
}
@@ -1053,15 +1294,6 @@ extension AssetQueryFilter on QueryBuilder<Asset, Asset, QFilterCondition> {
});
}
QueryBuilder<Asset, Asset, QAfterFilterCondition> isLocalEqualTo(bool value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'isLocal',
value: value,
));
});
}
QueryBuilder<Asset, Asset, QAfterFilterCondition> livePhotoVideoIdIsNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNull(
@@ -1210,8 +1442,24 @@ extension AssetQueryFilter on QueryBuilder<Asset, Asset, QFilterCondition> {
});
}
QueryBuilder<Asset, Asset, QAfterFilterCondition> localIdIsNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNull(
property: r'localId',
));
});
}
QueryBuilder<Asset, Asset, QAfterFilterCondition> localIdIsNotNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNotNull(
property: r'localId',
));
});
}
QueryBuilder<Asset, Asset, QAfterFilterCondition> localIdEqualTo(
String value, {
String? value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
@@ -1224,7 +1472,7 @@ extension AssetQueryFilter on QueryBuilder<Asset, Asset, QFilterCondition> {
}
QueryBuilder<Asset, Asset, QAfterFilterCondition> localIdGreaterThan(
String value, {
String? value, {
bool include = false,
bool caseSensitive = true,
}) {
@@ -1239,7 +1487,7 @@ extension AssetQueryFilter on QueryBuilder<Asset, Asset, QFilterCondition> {
}
QueryBuilder<Asset, Asset, QAfterFilterCondition> localIdLessThan(
String value, {
String? value, {
bool include = false,
bool caseSensitive = true,
}) {
@@ -1254,8 +1502,8 @@ extension AssetQueryFilter on QueryBuilder<Asset, Asset, QFilterCondition> {
}
QueryBuilder<Asset, Asset, QAfterFilterCondition> localIdBetween(
String lower,
String upper, {
String? lower,
String? upper, {
bool includeLower = true,
bool includeUpper = true,
bool caseSensitive = true,
@@ -1718,15 +1966,15 @@ extension AssetQueryObject on QueryBuilder<Asset, Asset, QFilterCondition> {}
extension AssetQueryLinks on QueryBuilder<Asset, Asset, QFilterCondition> {}
extension AssetQuerySortBy on QueryBuilder<Asset, Asset, QSortBy> {
QueryBuilder<Asset, Asset, QAfterSortBy> sortByDeviceId() {
QueryBuilder<Asset, Asset, QAfterSortBy> sortByChecksum() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'deviceId', Sort.asc);
return query.addSortBy(r'checksum', Sort.asc);
});
}
QueryBuilder<Asset, Asset, QAfterSortBy> sortByDeviceIdDesc() {
QueryBuilder<Asset, Asset, QAfterSortBy> sortByChecksumDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'deviceId', Sort.desc);
return query.addSortBy(r'checksum', Sort.desc);
});
}
@@ -1814,18 +2062,6 @@ extension AssetQuerySortBy on QueryBuilder<Asset, Asset, QSortBy> {
});
}
QueryBuilder<Asset, Asset, QAfterSortBy> sortByIsLocal() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'isLocal', Sort.asc);
});
}
QueryBuilder<Asset, Asset, QAfterSortBy> sortByIsLocalDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'isLocal', Sort.desc);
});
}
QueryBuilder<Asset, Asset, QAfterSortBy> sortByLivePhotoVideoId() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'livePhotoVideoId', Sort.asc);
@@ -1912,15 +2148,15 @@ extension AssetQuerySortBy on QueryBuilder<Asset, Asset, QSortBy> {
}
extension AssetQuerySortThenBy on QueryBuilder<Asset, Asset, QSortThenBy> {
QueryBuilder<Asset, Asset, QAfterSortBy> thenByDeviceId() {
QueryBuilder<Asset, Asset, QAfterSortBy> thenByChecksum() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'deviceId', Sort.asc);
return query.addSortBy(r'checksum', Sort.asc);
});
}
QueryBuilder<Asset, Asset, QAfterSortBy> thenByDeviceIdDesc() {
QueryBuilder<Asset, Asset, QAfterSortBy> thenByChecksumDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'deviceId', Sort.desc);
return query.addSortBy(r'checksum', Sort.desc);
});
}
@@ -2020,18 +2256,6 @@ extension AssetQuerySortThenBy on QueryBuilder<Asset, Asset, QSortThenBy> {
});
}
QueryBuilder<Asset, Asset, QAfterSortBy> thenByIsLocal() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'isLocal', Sort.asc);
});
}
QueryBuilder<Asset, Asset, QAfterSortBy> thenByIsLocalDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'isLocal', Sort.desc);
});
}
QueryBuilder<Asset, Asset, QAfterSortBy> thenByLivePhotoVideoId() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'livePhotoVideoId', Sort.asc);
@@ -2118,9 +2342,10 @@ extension AssetQuerySortThenBy on QueryBuilder<Asset, Asset, QSortThenBy> {
}
extension AssetQueryWhereDistinct on QueryBuilder<Asset, Asset, QDistinct> {
QueryBuilder<Asset, Asset, QDistinct> distinctByDeviceId() {
QueryBuilder<Asset, Asset, QDistinct> distinctByChecksum(
{bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'deviceId');
return query.addDistinctBy(r'checksum', caseSensitive: caseSensitive);
});
}
@@ -2167,12 +2392,6 @@ extension AssetQueryWhereDistinct on QueryBuilder<Asset, Asset, QDistinct> {
});
}
QueryBuilder<Asset, Asset, QDistinct> distinctByIsLocal() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'isLocal');
});
}
QueryBuilder<Asset, Asset, QDistinct> distinctByLivePhotoVideoId(
{bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
@@ -2227,9 +2446,9 @@ extension AssetQueryProperty on QueryBuilder<Asset, Asset, QQueryProperty> {
});
}
QueryBuilder<Asset, int, QQueryOperations> deviceIdProperty() {
QueryBuilder<Asset, String, QQueryOperations> checksumProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'deviceId');
return query.addPropertyName(r'checksum');
});
}
@@ -2275,19 +2494,13 @@ extension AssetQueryProperty on QueryBuilder<Asset, Asset, QQueryProperty> {
});
}
QueryBuilder<Asset, bool, QQueryOperations> isLocalProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'isLocal');
});
}
QueryBuilder<Asset, String?, QQueryOperations> livePhotoVideoIdProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'livePhotoVideoId');
});
}
QueryBuilder<Asset, String, QQueryOperations> localIdProperty() {
QueryBuilder<Asset, String?, QQueryOperations> localIdProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'localId');
});

View File

@@ -0,0 +1,8 @@
import 'package:isar/isar.dart';
class DeviceAsset {
DeviceAsset({required this.hash});
@Index(unique: false, type: IndexType.hash)
List<byte> hash;
}

View File

@@ -0,0 +1,14 @@
import 'package:immich_mobile/shared/models/device_asset.dart';
import 'package:immich_mobile/utils/hash.dart';
import 'package:isar/isar.dart';
part 'ios_device_asset.g.dart';
@Collection()
class IOSDeviceAsset extends DeviceAsset {
IOSDeviceAsset({required this.id, required super.hash});
@Index(replace: true, unique: true, type: IndexType.hash)
String id;
Id get isarId => fastHash(id);
}

View File

@@ -0,0 +1,780 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'ios_device_asset.dart';
// **************************************************************************
// IsarCollectionGenerator
// **************************************************************************
// coverage:ignore-file
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
extension GetIOSDeviceAssetCollection on Isar {
IsarCollection<IOSDeviceAsset> get iOSDeviceAssets => this.collection();
}
const IOSDeviceAssetSchema = CollectionSchema(
name: r'IOSDeviceAsset',
id: -1671546753821948030,
properties: {
r'hash': PropertySchema(
id: 0,
name: r'hash',
type: IsarType.byteList,
),
r'id': PropertySchema(
id: 1,
name: r'id',
type: IsarType.string,
)
},
estimateSize: _iOSDeviceAssetEstimateSize,
serialize: _iOSDeviceAssetSerialize,
deserialize: _iOSDeviceAssetDeserialize,
deserializeProp: _iOSDeviceAssetDeserializeProp,
idName: r'isarId',
indexes: {
r'id': IndexSchema(
id: -3268401673993471357,
name: r'id',
unique: true,
replace: true,
properties: [
IndexPropertySchema(
name: r'id',
type: IndexType.hash,
caseSensitive: true,
)
],
),
r'hash': IndexSchema(
id: -7973251393006690288,
name: r'hash',
unique: false,
replace: false,
properties: [
IndexPropertySchema(
name: r'hash',
type: IndexType.hash,
caseSensitive: false,
)
],
)
},
links: {},
embeddedSchemas: {},
getId: _iOSDeviceAssetGetId,
getLinks: _iOSDeviceAssetGetLinks,
attach: _iOSDeviceAssetAttach,
version: '3.1.0+1',
);
int _iOSDeviceAssetEstimateSize(
IOSDeviceAsset object,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
var bytesCount = offsets.last;
bytesCount += 3 + object.hash.length;
bytesCount += 3 + object.id.length * 3;
return bytesCount;
}
void _iOSDeviceAssetSerialize(
IOSDeviceAsset object,
IsarWriter writer,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
writer.writeByteList(offsets[0], object.hash);
writer.writeString(offsets[1], object.id);
}
IOSDeviceAsset _iOSDeviceAssetDeserialize(
Id id,
IsarReader reader,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
final object = IOSDeviceAsset(
hash: reader.readByteList(offsets[0]) ?? [],
id: reader.readString(offsets[1]),
);
return object;
}
P _iOSDeviceAssetDeserializeProp<P>(
IsarReader reader,
int propertyId,
int offset,
Map<Type, List<int>> allOffsets,
) {
switch (propertyId) {
case 0:
return (reader.readByteList(offset) ?? []) as P;
case 1:
return (reader.readString(offset)) as P;
default:
throw IsarError('Unknown property with id $propertyId');
}
}
Id _iOSDeviceAssetGetId(IOSDeviceAsset object) {
return object.isarId;
}
List<IsarLinkBase<dynamic>> _iOSDeviceAssetGetLinks(IOSDeviceAsset object) {
return [];
}
void _iOSDeviceAssetAttach(
IsarCollection<dynamic> col, Id id, IOSDeviceAsset object) {}
extension IOSDeviceAssetByIndex on IsarCollection<IOSDeviceAsset> {
Future<IOSDeviceAsset?> getById(String id) {
return getByIndex(r'id', [id]);
}
IOSDeviceAsset? getByIdSync(String id) {
return getByIndexSync(r'id', [id]);
}
Future<bool> deleteById(String id) {
return deleteByIndex(r'id', [id]);
}
bool deleteByIdSync(String id) {
return deleteByIndexSync(r'id', [id]);
}
Future<List<IOSDeviceAsset?>> getAllById(List<String> idValues) {
final values = idValues.map((e) => [e]).toList();
return getAllByIndex(r'id', values);
}
List<IOSDeviceAsset?> getAllByIdSync(List<String> idValues) {
final values = idValues.map((e) => [e]).toList();
return getAllByIndexSync(r'id', values);
}
Future<int> deleteAllById(List<String> idValues) {
final values = idValues.map((e) => [e]).toList();
return deleteAllByIndex(r'id', values);
}
int deleteAllByIdSync(List<String> idValues) {
final values = idValues.map((e) => [e]).toList();
return deleteAllByIndexSync(r'id', values);
}
Future<Id> putById(IOSDeviceAsset object) {
return putByIndex(r'id', object);
}
Id putByIdSync(IOSDeviceAsset object, {bool saveLinks = true}) {
return putByIndexSync(r'id', object, saveLinks: saveLinks);
}
Future<List<Id>> putAllById(List<IOSDeviceAsset> objects) {
return putAllByIndex(r'id', objects);
}
List<Id> putAllByIdSync(List<IOSDeviceAsset> objects,
{bool saveLinks = true}) {
return putAllByIndexSync(r'id', objects, saveLinks: saveLinks);
}
}
extension IOSDeviceAssetQueryWhereSort
on QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QWhere> {
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterWhere> anyIsarId() {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(const IdWhereClause.any());
});
}
}
extension IOSDeviceAssetQueryWhere
on QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QWhereClause> {
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterWhereClause> isarIdEqualTo(
Id isarId) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IdWhereClause.between(
lower: isarId,
upper: isarId,
));
});
}
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterWhereClause>
isarIdNotEqualTo(Id isarId) {
return QueryBuilder.apply(this, (query) {
if (query.whereSort == Sort.asc) {
return query
.addWhereClause(
IdWhereClause.lessThan(upper: isarId, includeUpper: false),
)
.addWhereClause(
IdWhereClause.greaterThan(lower: isarId, includeLower: false),
);
} else {
return query
.addWhereClause(
IdWhereClause.greaterThan(lower: isarId, includeLower: false),
)
.addWhereClause(
IdWhereClause.lessThan(upper: isarId, includeUpper: false),
);
}
});
}
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterWhereClause>
isarIdGreaterThan(Id isarId, {bool include = false}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(
IdWhereClause.greaterThan(lower: isarId, includeLower: include),
);
});
}
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterWhereClause>
isarIdLessThan(Id isarId, {bool include = false}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(
IdWhereClause.lessThan(upper: isarId, includeUpper: include),
);
});
}
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterWhereClause> isarIdBetween(
Id lowerIsarId,
Id upperIsarId, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IdWhereClause.between(
lower: lowerIsarId,
includeLower: includeLower,
upper: upperIsarId,
includeUpper: includeUpper,
));
});
}
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterWhereClause> idEqualTo(
String id) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IndexWhereClause.equalTo(
indexName: r'id',
value: [id],
));
});
}
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterWhereClause> idNotEqualTo(
String id) {
return QueryBuilder.apply(this, (query) {
if (query.whereSort == Sort.asc) {
return query
.addWhereClause(IndexWhereClause.between(
indexName: r'id',
lower: [],
upper: [id],
includeUpper: false,
))
.addWhereClause(IndexWhereClause.between(
indexName: r'id',
lower: [id],
includeLower: false,
upper: [],
));
} else {
return query
.addWhereClause(IndexWhereClause.between(
indexName: r'id',
lower: [id],
includeLower: false,
upper: [],
))
.addWhereClause(IndexWhereClause.between(
indexName: r'id',
lower: [],
upper: [id],
includeUpper: false,
));
}
});
}
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterWhereClause> hashEqualTo(
List<int> hash) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IndexWhereClause.equalTo(
indexName: r'hash',
value: [hash],
));
});
}
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterWhereClause>
hashNotEqualTo(List<int> hash) {
return QueryBuilder.apply(this, (query) {
if (query.whereSort == Sort.asc) {
return query
.addWhereClause(IndexWhereClause.between(
indexName: r'hash',
lower: [],
upper: [hash],
includeUpper: false,
))
.addWhereClause(IndexWhereClause.between(
indexName: r'hash',
lower: [hash],
includeLower: false,
upper: [],
));
} else {
return query
.addWhereClause(IndexWhereClause.between(
indexName: r'hash',
lower: [hash],
includeLower: false,
upper: [],
))
.addWhereClause(IndexWhereClause.between(
indexName: r'hash',
lower: [],
upper: [hash],
includeUpper: false,
));
}
});
}
}
extension IOSDeviceAssetQueryFilter
on QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QFilterCondition> {
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
hashElementEqualTo(int value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'hash',
value: value,
));
});
}
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
hashElementGreaterThan(
int value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
include: include,
property: r'hash',
value: value,
));
});
}
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
hashElementLessThan(
int value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.lessThan(
include: include,
property: r'hash',
value: value,
));
});
}
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
hashElementBetween(
int lower,
int upper, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.between(
property: r'hash',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
));
});
}
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
hashLengthEqualTo(int length) {
return QueryBuilder.apply(this, (query) {
return query.listLength(
r'hash',
length,
true,
length,
true,
);
});
}
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
hashIsEmpty() {
return QueryBuilder.apply(this, (query) {
return query.listLength(
r'hash',
0,
true,
0,
true,
);
});
}
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
hashIsNotEmpty() {
return QueryBuilder.apply(this, (query) {
return query.listLength(
r'hash',
0,
false,
999999,
true,
);
});
}
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
hashLengthLessThan(
int length, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.listLength(
r'hash',
0,
true,
length,
include,
);
});
}
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
hashLengthGreaterThan(
int length, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.listLength(
r'hash',
length,
include,
999999,
true,
);
});
}
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
hashLengthBetween(
int lower,
int upper, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.listLength(
r'hash',
lower,
includeLower,
upper,
includeUpper,
);
});
}
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition> idEqualTo(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'id',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
idGreaterThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
include: include,
property: r'id',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
idLessThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.lessThan(
include: include,
property: r'id',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition> idBetween(
String lower,
String upper, {
bool includeLower = true,
bool includeUpper = true,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.between(
property: r'id',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
idStartsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.startsWith(
property: r'id',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
idEndsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.endsWith(
property: r'id',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
idContains(String value, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.contains(
property: r'id',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition> idMatches(
String pattern,
{bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.matches(
property: r'id',
wildcard: pattern,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
idIsEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'id',
value: '',
));
});
}
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
idIsNotEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
property: r'id',
value: '',
));
});
}
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
isarIdEqualTo(Id value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'isarId',
value: value,
));
});
}
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
isarIdGreaterThan(
Id value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
include: include,
property: r'isarId',
value: value,
));
});
}
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
isarIdLessThan(
Id value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.lessThan(
include: include,
property: r'isarId',
value: value,
));
});
}
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
isarIdBetween(
Id lower,
Id upper, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.between(
property: r'isarId',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
));
});
}
}
extension IOSDeviceAssetQueryObject
on QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QFilterCondition> {}
extension IOSDeviceAssetQueryLinks
on QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QFilterCondition> {}
extension IOSDeviceAssetQuerySortBy
on QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QSortBy> {
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterSortBy> sortById() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'id', Sort.asc);
});
}
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterSortBy> sortByIdDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'id', Sort.desc);
});
}
}
extension IOSDeviceAssetQuerySortThenBy
on QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QSortThenBy> {
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterSortBy> thenById() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'id', Sort.asc);
});
}
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterSortBy> thenByIdDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'id', Sort.desc);
});
}
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterSortBy> thenByIsarId() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'isarId', Sort.asc);
});
}
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterSortBy>
thenByIsarIdDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'isarId', Sort.desc);
});
}
}
extension IOSDeviceAssetQueryWhereDistinct
on QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QDistinct> {
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QDistinct> distinctByHash() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'hash');
});
}
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QDistinct> distinctById(
{bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'id', caseSensitive: caseSensitive);
});
}
}
extension IOSDeviceAssetQueryProperty
on QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QQueryProperty> {
QueryBuilder<IOSDeviceAsset, int, QQueryOperations> isarIdProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'isarId');
});
}
QueryBuilder<IOSDeviceAsset, List<int>, QQueryOperations> hashProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'hash');
});
}
QueryBuilder<IOSDeviceAsset, String, QQueryOperations> idProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'id');
});
}
}

View File

@@ -18,11 +18,7 @@ import 'package:logging/logging.dart';
import 'package:openapi/api.dart';
import 'package:photo_manager/photo_manager.dart';
/// State does not contain archived assets.
/// Use database provider if you want to access the isArchived assets
class AssetsState {}
class AssetNotifier extends StateNotifier<AssetsState> {
class AssetNotifier extends StateNotifier<bool> {
final AssetService _assetService;
final AlbumService _albumService;
final UserService _userService;
@@ -38,7 +34,7 @@ class AssetNotifier extends StateNotifier<AssetsState> {
this._userService,
this._syncService,
this._db,
) : super(AssetsState());
) : super(false);
Future<void> getAllAsset({bool clear = false}) async {
if (_getAllAssetInProgress || _deleteInProgress) {
@@ -48,14 +44,15 @@ class AssetNotifier extends StateNotifier<AssetsState> {
final stopwatch = Stopwatch()..start();
try {
_getAllAssetInProgress = true;
state = true;
if (clear) {
await clearAssetsAndAlbums(_db);
log.info("Manual refresh requested, cleared assets and albums from db");
}
await _userService.refreshUsers();
final bool newRemote = await _assetService.refreshRemoteAssets();
final bool newLocal = await _albumService.refreshDeviceAlbums();
debugPrint("newRemote: $newRemote, newLocal: $newLocal");
await _userService.refreshUsers();
final List<User> partners =
await _db.users.filter().isPartnerSharedWithEqualTo(true).findAll();
for (User u in partners) {
@@ -64,6 +61,7 @@ class AssetNotifier extends StateNotifier<AssetsState> {
log.info("Load assets: ${stopwatch.elapsedMilliseconds}ms");
} finally {
_getAllAssetInProgress = false;
state = false;
}
}
@@ -79,6 +77,7 @@ class AssetNotifier extends StateNotifier<AssetsState> {
Future<void> deleteAssets(Set<Asset> deleteAssets) async {
_deleteInProgress = true;
state = true;
try {
final localDeleted = await _deleteLocalAssets(deleteAssets);
final remoteDeleted = await _deleteRemoteAssets(deleteAssets);
@@ -91,24 +90,14 @@ class AssetNotifier extends StateNotifier<AssetsState> {
}
} finally {
_deleteInProgress = false;
state = false;
}
}
Future<List<String>> _deleteLocalAssets(Set<Asset> assetsToDelete) async {
final int deviceId = Store.get(StoreKey.deviceIdHash);
final List<String> local = [];
final List<String> local =
assetsToDelete.where((a) => a.isLocal).map((a) => a.localId!).toList();
// Delete asset from device
for (final Asset asset in assetsToDelete) {
if (asset.isLocal) {
local.add(asset.localId);
} else if (asset.deviceId == deviceId) {
// Delete asset on device if it is still present
var localAsset = await AssetEntity.fromId(asset.localId);
if (localAsset != null) {
local.add(localAsset.id);
}
}
}
if (local.isNotEmpty) {
try {
return await PhotoManager.editor.deleteWithIds(local);
@@ -153,7 +142,7 @@ class AssetNotifier extends StateNotifier<AssetsState> {
}
}
final assetProvider = StateNotifierProvider<AssetNotifier, AssetsState>((ref) {
final assetProvider = StateNotifierProvider<AssetNotifier, bool>((ref) {
return AssetNotifier(
ref.watch(assetServiceProvider),
ref.watch(albumServiceProvider),
@@ -201,7 +190,7 @@ final remoteAssetsProvider =
.remoteIdIsNotNull()
.filter()
.ownerIdEqualTo(userId)
.sortByFileCreatedAt();
.sortByFileCreatedAtDesc();
final settings = ref.watch(appSettingsServiceProvider);
final groupBy =
GroupAssetsBy.values[settings.getSetting(AppSettingsEnum.groupAssetsBy)];

View File

@@ -76,7 +76,7 @@ class ServerInfoNotifier extends StateNotifier<ServerInfoState> {
return {
"major": int.parse(major),
"minor": int.parse(minor),
"patch": int.parse(patch),
"patch": int.parse(patch.replaceAll("-DEBUG", "")),
};
}
}

View File

@@ -66,8 +66,11 @@ class AssetService {
try {
final etag = hasCache ? _db.eTags.getByIdSync(user.id)?.value : null;
final (List<AssetResponseDto>? assets, String? newETag) =
await _apiService.assetApi
.getAllAssetsWithETag(eTag: etag, userId: user.id);
await _apiService.assetApi.getAllAssetsWithETag(
eTag: etag,
userId: user.id,
withoutThumbs: true,
);
if (assets == null) {
return null;
} else if (assets.isNotEmpty && assets.first.ownerId != user.id) {

View File

@@ -0,0 +1,175 @@
import 'dart:convert';
import 'dart:io';
import 'package:crypto/crypto.dart';
import 'package:flutter/foundation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/backup/background_service/background.service.dart';
import 'package:immich_mobile/shared/models/android_device_asset.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/models/device_asset.dart';
import 'package:immich_mobile/shared/models/ios_device_asset.dart';
import 'package:immich_mobile/shared/providers/db.provider.dart';
import 'package:immich_mobile/utils/builtin_extensions.dart';
import 'package:isar/isar.dart';
import 'package:logging/logging.dart';
import 'package:photo_manager/photo_manager.dart';
class HashService {
HashService(this._db, this._backgroundService);
final Isar _db;
final BackgroundService _backgroundService;
final _log = Logger('HashService');
/// Returns all assets that were successfully hashed
Future<List<Asset>> getHashedAssets(
AssetPathEntity album, {
int start = 0,
int end = 0x7fffffffffffffff,
Set<String>? excludedAssets,
}) async {
final entities = await album.getAssetListRange(start: start, end: end);
final filtered = excludedAssets == null
? entities
: entities.where((e) => !excludedAssets.contains(e.id)).toList();
return _hashAssets(filtered);
}
/// Converts a list of [AssetEntity]s to [Asset]s including only those
/// that were successfully hashed. Hashes are looked up in a DB table
/// [AndroidDeviceAsset] / [IOSDeviceAsset] by local id. Only missing
/// entries are newly hashed and added to the DB table.
Future<List<Asset>> _hashAssets(List<AssetEntity> assetEntities) async {
const int batchFileCount = 128;
const int batchDataSize = 1024 * 1024 * 1024; // 1GB
final ids = assetEntities
.map(Platform.isAndroid ? (a) => a.id.toInt() : (a) => a.id)
.toList();
final List<DeviceAsset?> hashes = await _lookupHashes(ids);
final List<DeviceAsset> toAdd = [];
final List<String> toHash = [];
int bytes = 0;
for (int i = 0; i < assetEntities.length; i++) {
if (hashes[i] != null) {
continue;
}
final file = await assetEntities[i].originFile;
if (file == null) {
_log.warning(
"Failed to get file for asset ${assetEntities[i].id}, skipping",
);
continue;
}
bytes += await file.length();
toHash.add(file.path);
final deviceAsset = Platform.isAndroid
? AndroidDeviceAsset(id: ids[i] as int, hash: const [])
: IOSDeviceAsset(id: ids[i] as String, hash: const []);
toAdd.add(deviceAsset);
hashes[i] = deviceAsset;
if (toHash.length == batchFileCount || bytes >= batchDataSize) {
await _processBatch(toHash, toAdd);
toAdd.clear();
toHash.clear();
bytes = 0;
}
}
if (toHash.isNotEmpty) {
await _processBatch(toHash, toAdd);
}
return _mapAllHashedAssets(assetEntities, hashes);
}
/// Lookup hashes of assets by their local ID
Future<List<DeviceAsset?>> _lookupHashes(List<Object> ids) =>
Platform.isAndroid
? _db.androidDeviceAssets.getAll(ids.cast())
: _db.iOSDeviceAssets.getAllById(ids.cast());
/// Processes a batch of files and saves any successfully hashed
/// values to the DB table.
Future<void> _processBatch(
final List<String> toHash,
final List<DeviceAsset> toAdd,
) async {
final hashes = await _hashFiles(toHash);
bool anyNull = false;
for (int j = 0; j < hashes.length; j++) {
if (hashes[j]?.length == 20) {
toAdd[j].hash = hashes[j]!;
} else {
_log.warning("Failed to hash file ${toHash[j]}, skipping");
anyNull = true;
}
}
final validHashes = anyNull
? toAdd.where((e) => e.hash.length == 20).toList(growable: false)
: toAdd;
await _db.writeTxn(
() => Platform.isAndroid
? _db.androidDeviceAssets.putAll(validHashes.cast())
: _db.iOSDeviceAssets.putAll(validHashes.cast()),
);
_log.fine("Hashed ${validHashes.length}/${toHash.length} assets");
}
/// Hashes the given files and returns a list of the same length
/// files that could not be hashed have a `null` value
Future<List<Uint8List?>> _hashFiles(List<String> paths) async {
if (Platform.isAndroid) {
final List<Uint8List?>? hashes =
await _backgroundService.digestFiles(paths);
if (hashes == null) {
throw Exception("Hashing ${paths.length} files failed");
}
return hashes;
} else if (Platform.isIOS) {
final List<Uint8List?> result = List.filled(paths.length, null);
for (int i = 0; i < paths.length; i++) {
result[i] = await _hashAssetDart(File(paths[i]));
}
return result;
} else {
throw Exception("_hashFiles implementation missing");
}
}
/// Hashes a single file using Dart's crypto package
Future<Uint8List?> _hashAssetDart(File f) async {
late Digest output;
final sink = sha1.startChunkedConversion(
ChunkedConversionSink<Digest>.withCallback((accumulated) {
output = accumulated.first;
}),
);
await for (final chunk in f.openRead()) {
sink.add(chunk);
}
sink.close();
return Uint8List.fromList(output.bytes);
}
/// Converts [AssetEntity]s that were successfully hashed to [Asset]s
List<Asset> _mapAllHashedAssets(
List<AssetEntity> assets,
List<DeviceAsset?> hashes,
) {
final List<Asset> result = [];
for (int i = 0; i < assets.length; i++) {
if (hashes[i] != null && hashes[i]!.hash.isNotEmpty) {
result.add(Asset.local(assets[i], hashes[i]!.hash));
}
}
return result;
}
}
final hashServiceProvider = Provider(
(ref) => HashService(
ref.watch(dbProvider),
ref.watch(backgroundServiceProvider),
),
);

View File

@@ -4,10 +4,12 @@ import 'package:collection/collection.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/shared/models/album.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/models/etag.dart';
import 'package:immich_mobile/shared/models/exif_info.dart';
import 'package:immich_mobile/shared/models/store.dart';
import 'package:immich_mobile/shared/models/user.dart';
import 'package:immich_mobile/shared/providers/db.provider.dart';
import 'package:immich_mobile/shared/services/hash.service.dart';
import 'package:immich_mobile/utils/async_mutex.dart';
import 'package:immich_mobile/utils/builtin_extensions.dart';
import 'package:immich_mobile/utils/diff.dart';
@@ -16,15 +18,17 @@ import 'package:logging/logging.dart';
import 'package:openapi/api.dart';
import 'package:photo_manager/photo_manager.dart';
final syncServiceProvider =
Provider((ref) => SyncService(ref.watch(dbProvider)));
final syncServiceProvider = Provider(
(ref) => SyncService(ref.watch(dbProvider), ref.watch(hashServiceProvider)),
);
class SyncService {
final Isar _db;
final HashService _hashService;
final AsyncMutex _lock = AsyncMutex();
final Logger _log = Logger('SyncService');
SyncService(this._db);
SyncService(this._db, this._hashService);
// public methods:
@@ -33,6 +37,7 @@ class SyncService {
Future<bool> syncUsersFromServer(List<User> users) async {
users.sortBy((u) => u.id);
final dbUsers = await _db.users.where().sortById().findAll();
assert(dbUsers.isSortedBy((u) => u.id), "dbUsers not sorted!");
final List<int> toDelete = [];
final List<User> toUpsert = [];
final changes = diffSortedListsSync(
@@ -108,40 +113,16 @@ class SyncService {
// private methods:
/// Syncs a new asset to the db. Returns `true` if successful
Future<bool> _syncNewAssetToDb(Asset newAsset) async {
final List<Asset> inDb = await _db.assets
.where()
.localIdDeviceIdEqualTo(newAsset.localId, newAsset.deviceId)
.findAll();
Asset? match;
if (inDb.length == 1) {
// exactly one match: trivial case
match = inDb.first;
} else if (inDb.length > 1) {
// TODO instead of this heuristics: match by checksum once available
for (Asset a in inDb) {
if (a.ownerId == newAsset.ownerId &&
a.fileModifiedAt.isAtSameMomentAs(newAsset.fileModifiedAt)) {
assert(match == null);
match = a;
}
}
if (match == null) {
for (Asset a in inDb) {
if (a.ownerId == newAsset.ownerId) {
assert(match == null);
match = a;
}
}
}
}
if (match != null) {
Future<bool> _syncNewAssetToDb(Asset a) async {
final Asset? inDb =
await _db.assets.getByChecksumOwnerId(a.checksum, a.ownerId);
if (inDb != null) {
// unify local/remote assets by replacing the
// local-only asset in the DB with a local&remote asset
newAsset = match.updatedCopy(newAsset);
a = inDb.updatedCopy(a);
}
try {
await _db.writeTxn(() => newAsset.put(_db));
await _db.writeTxn(() => a.put(_db));
} on IsarError catch (e) {
_log.severe("Failed to put new asset into db: $e");
return false;
@@ -162,11 +143,11 @@ class SyncService {
final List<Asset> inDb = await _db.assets
.filter()
.ownerIdEqualTo(user.isarId)
.sortByDeviceId()
.thenByLocalId()
.thenByFileModifiedAt()
.sortByChecksum()
.findAll();
remote.sort(Asset.compareByOwnerDeviceLocalIdModified);
assert(inDb.isSorted(Asset.compareByChecksum), "inDb not sorted!");
remote.sort(Asset.compareByChecksum);
final (toAdd, toUpdate, toRemove) = _diffAssets(remote, inDb, remote: true);
if (toAdd.isEmpty && toUpdate.isEmpty && toRemove.isEmpty) {
return false;
@@ -199,6 +180,7 @@ class SyncService {
query = baseQuery.owner((q) => q.isarIdEqualTo(me.isarId));
}
final List<Album> dbAlbums = await query.sortByRemoteId().findAll();
assert(dbAlbums.isSortedBy((e) => e.remoteId!), "dbAlbums not sorted!");
final List<Asset> toDelete = [];
final List<Asset> existing = [];
@@ -245,16 +227,16 @@ class SyncService {
if (dto.assetCount != dto.assets.length) {
return false;
}
final assetsInDb = await album.assets
.filter()
.sortByOwnerId()
.thenByDeviceId()
.thenByLocalId()
.thenByFileModifiedAt()
.findAll();
final assetsInDb =
await album.assets.filter().sortByOwnerId().thenByChecksum().findAll();
assert(assetsInDb.isSorted(Asset.compareByOwnerChecksum), "inDb unsorted!");
final List<Asset> assetsOnRemote = dto.getAssets();
assetsOnRemote.sort(Asset.compareByOwnerDeviceLocalIdModified);
final (toAdd, toUpdate, toUnlink) = _diffAssets(assetsOnRemote, assetsInDb);
assetsOnRemote.sort(Asset.compareByOwnerChecksum);
final (toAdd, toUpdate, toUnlink) = _diffAssets(
assetsOnRemote,
assetsInDb,
compare: Asset.compareByOwnerChecksum,
);
// update shared users
final List<User> sharedUsers = album.sharedUsers.toList(growable: false);
@@ -297,6 +279,7 @@ class SyncService {
await album.assets.update(link: assetsToLink, unlink: toUnlink.cast());
await _db.albums.put(album);
});
_log.info("Synced changes of remote album ${album.name} to DB");
} on IsarError catch (e) {
_log.severe("Failed to sync remote album to database $e");
}
@@ -382,10 +365,11 @@ class SyncService {
Set<String>? excludedAssets,
]) async {
onDevice.sort((a, b) => a.id.compareTo(b.id));
final List<Album> inDb =
final inDb =
await _db.albums.where().localIdIsNotNull().sortByLocalId().findAll();
final List<Asset> deleteCandidates = [];
final List<Asset> existing = [];
assert(inDb.isSorted((a, b) => a.localId!.compareTo(b.localId!)), "sort!");
final bool anyChanges = await diffSortedLists(
onDevice,
inDb,
@@ -447,14 +431,15 @@ class SyncService {
final inDb = await album.assets
.filter()
.ownerIdEqualTo(Store.get(StoreKey.currentUser).isarId)
.deviceIdEqualTo(Store.get(StoreKey.deviceIdHash))
.sortByLocalId()
.sortByChecksum()
.findAll();
assert(inDb.isSorted(Asset.compareByChecksum), "inDb not sorted!");
final int assetCountOnDevice = await ape.assetCountAsync;
final List<Asset> onDevice =
await ape.getAssets(excludedAssets: excludedAssets);
onDevice.sort(Asset.compareByLocalId);
final (toAdd, toUpdate, toDelete) =
_diffAssets(onDevice, inDb, compare: Asset.compareByLocalId);
await _hashService.getHashedAssets(ape, excludedAssets: excludedAssets);
_removeDuplicates(onDevice);
// _removeDuplicates sorts `onDevice` by checksum
final (toAdd, toUpdate, toDelete) = _diffAssets(onDevice, inDb);
if (toAdd.isEmpty &&
toUpdate.isEmpty &&
toDelete.isEmpty &&
@@ -491,6 +476,9 @@ class SyncService {
await _db.albums.put(album);
album.thumbnail.value ??= await album.assets.filter().findFirst();
await album.thumbnail.save();
await _db.eTags.put(
ETag(id: ape.eTagKeyAssetCount, value: assetCountOnDevice.toString()),
);
});
_log.info("Synced changes of local album ${ape.name} to DB");
} on IsarError catch (e) {
@@ -503,8 +491,13 @@ class SyncService {
/// fast path for common case: only new assets were added to device album
/// returns `true` if successfull, else `false`
Future<bool> _syncDeviceAlbumFast(AssetPathEntity ape, Album album) async {
if (!(ape.lastModified ?? DateTime.now()).isAfter(album.modifiedAt)) {
return false;
}
final int totalOnDevice = await ape.assetCountAsync;
final AssetPathEntity? modified = totalOnDevice > album.assetCount
final int lastKnownTotal =
(await _db.eTags.getById(ape.eTagKeyAssetCount))?.value?.toInt() ?? 0;
final AssetPathEntity? modified = totalOnDevice > lastKnownTotal
? await ape.fetchPathProperties(
filterOptionGroup: FilterOptionGroup(
updateTimeCond: DateTimeCond(
@@ -517,17 +510,22 @@ class SyncService {
if (modified == null) {
return false;
}
final List<Asset> newAssets = await modified.getAssets();
if (totalOnDevice != album.assets.length + newAssets.length) {
final List<Asset> newAssets = await _hashService.getHashedAssets(modified);
if (totalOnDevice != lastKnownTotal + newAssets.length) {
return false;
}
album.modifiedAt = ape.lastModified ?? DateTime.now();
_removeDuplicates(newAssets);
final (existingInDb, updated) = await _linkWithExistingFromDb(newAssets);
try {
await _db.writeTxn(() async {
await _db.assets.putAll(updated);
await album.assets.update(link: existingInDb + updated);
await _db.albums.put(album);
await _db.eTags.put(
ETag(id: ape.eTagKeyAssetCount, value: totalOnDevice.toString()),
);
});
_log.info("Fast synced local album ${ape.name} to DB");
} on IsarError catch (e) {
@@ -547,7 +545,9 @@ class SyncService {
]) async {
_log.info("Syncing a new local album to DB: ${ape.name}");
final Album a = Album.local(ape);
final assets = await ape.getAssets(excludedAssets: excludedAssets);
final assets =
await _hashService.getHashedAssets(ape, excludedAssets: excludedAssets);
_removeDuplicates(assets);
final (existingInDb, updated) = await _linkWithExistingFromDb(assets);
_log.info(
"${existingInDb.length} assets already existed in DB, to upsert ${updated.length}",
@@ -570,44 +570,29 @@ class SyncService {
Future<(List<Asset> existing, List<Asset> updated)> _linkWithExistingFromDb(
List<Asset> assets,
) async {
if (assets.isEmpty) {
return ([].cast<Asset>(), [].cast<Asset>());
}
final List<Asset> inDb = await _db.assets
.where()
.anyOf(
assets,
(q, Asset e) => q.localIdDeviceIdEqualTo(e.localId, e.deviceId),
)
.sortByOwnerId()
.thenByDeviceId()
.thenByLocalId()
.thenByFileModifiedAt()
.findAll();
assets.sort(Asset.compareByOwnerDeviceLocalIdModified);
final List<Asset> existing = [], toUpsert = [];
diffSortedListsSync(
inDb,
assets,
// do not compare by modified date because for some assets dates differ on
// client and server, thus never reaching "both" case below
compare: Asset.compareByOwnerDeviceLocalId,
both: (Asset a, Asset b) {
if (a.canUpdate(b)) {
toUpsert.add(a.updatedCopy(b));
return true;
} else {
existing.add(a);
return false;
}
},
onlyFirst: (Asset a) => _log.finer(
"_linkWithExistingFromDb encountered asset only in DB: $a",
null,
StackTrace.current,
),
onlySecond: (Asset b) => toUpsert.add(b),
if (assets.isEmpty) return ([].cast<Asset>(), [].cast<Asset>());
final List<Asset?> inDb = await _db.assets.getAllByChecksumOwnerId(
assets.map((a) => a.checksum).toList(growable: false),
assets.map((a) => a.ownerId).toInt64List(),
);
assert(inDb.length == assets.length);
final List<Asset> existing = [], toUpsert = [];
for (int i = 0; i < assets.length; i++) {
final Asset? b = inDb[i];
if (b == null) {
toUpsert.add(assets[i]);
continue;
}
if (b.canUpdate(assets[i])) {
final updated = b.updatedCopy(assets[i]);
assert(updated.id != Isar.autoIncrement);
toUpsert.add(updated);
} else {
existing.add(b);
}
}
assert(existing.length + toUpsert.length == assets.length);
return (existing, toUpsert);
}
@@ -627,11 +612,63 @@ class SyncService {
});
_log.info("Upserted ${assets.length} assets into the DB");
} on IsarError catch (e) {
_log.warning(
_log.severe(
"Failed to upsert ${assets.length} assets into the DB: ${e.toString()}",
);
// give details on the errors
assets.sort(Asset.compareByOwnerChecksum);
final inDb = await _db.assets.getAllByChecksumOwnerId(
assets.map((e) => e.checksum).toList(growable: false),
assets.map((e) => e.ownerId).toInt64List(),
);
for (int i = 0; i < assets.length; i++) {
final Asset a = assets[i];
final Asset? b = inDb[i];
if (b == null) {
if (a.id != Isar.autoIncrement) {
_log.warning(
"Trying to update an asset that does not exist in DB:\n$a",
);
}
} else if (a.id != b.id) {
_log.warning(
"Trying to insert another asset with the same checksum+owner. In DB:\n$b\nTo insert:\n$a",
);
}
}
for (int i = 1; i < assets.length; i++) {
if (Asset.compareByOwnerChecksum(assets[i - 1], assets[i]) == 0) {
_log.warning(
"Trying to insert duplicate assets:\n${assets[i - 1]}\n${assets[i]}",
);
}
}
}
}
List<Asset> _removeDuplicates(List<Asset> assets) {
final int before = assets.length;
assets.sort(Asset.compareByOwnerChecksumCreatedModified);
assets.uniqueConsecutive(
compare: Asset.compareByOwnerChecksum,
onDuplicate: (a, b) =>
_log.info("Ignoring duplicate assets on device:\n$a\n$b"),
);
final int duplicates = before - assets.length;
if (duplicates > 0) {
_log.warning("Ignored $duplicates duplicate assets on device");
}
return assets;
}
/// returns `true` if the albums differ on the surface
Future<bool> _hasAssetPathEntityChanged(AssetPathEntity a, Album b) async {
return a.name != b.name ||
a.lastModified == null ||
!a.lastModified!.isAtSameMomentAs(b.modifiedAt) ||
await a.assetCountAsync !=
(await _db.eTags.getById(a.eTagKeyAssetCount))?.value?.toInt();
}
}
/// Returns a triple(toAdd, toUpdate, toRemove)
@@ -639,7 +676,7 @@ class SyncService {
List<Asset> assets,
List<Asset> inDb, {
bool? remote,
int Function(Asset, Asset) compare = Asset.compareByOwnerDeviceLocalId,
int Function(Asset, Asset) compare = Asset.compareByChecksum,
}) {
final List<Asset> toAdd = [];
final List<Asset> toUpdate = [];
@@ -663,7 +700,7 @@ class SyncService {
}
} else if (remote == false && a.isRemote) {
if (a.isLocal) {
a.isLocal = false;
a.localId = null;
toUpdate.add(a);
}
} else {
@@ -685,9 +722,9 @@ class SyncService {
return const ([], []);
}
deleteCandidates.sort(Asset.compareById);
deleteCandidates.uniqueConsecutive((a) => a.id);
deleteCandidates.uniqueConsecutive(compare: Asset.compareById);
existing.sort(Asset.compareById);
existing.uniqueConsecutive((a) => a.id);
existing.uniqueConsecutive(compare: Asset.compareById);
final (tooAdd, toUpdate, toRemove) = _diffAssets(
existing,
deleteCandidates,
@@ -698,14 +735,6 @@ class SyncService {
return (toRemove.map((e) => e.id).toList(), toUpdate);
}
/// returns `true` if the albums differ on the surface
Future<bool> _hasAssetPathEntityChanged(AssetPathEntity a, Album b) async {
return a.name != b.name ||
a.lastModified == null ||
!a.lastModified!.isAtSameMomentAs(b.modifiedAt) ||
await a.assetCountAsync != b.assetCount;
}
/// returns `true` if the albums differ on the surface
bool _hasAlbumResponseDtoChanged(AlbumResponseDto dto, Album a) {
return dto.assetCount != a.assetCount ||

View File

@@ -1,6 +1,7 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/models/store.dart';
import 'package:immich_mobile/utils/image_url_builder.dart';
@@ -110,8 +111,12 @@ class ImmichImage extends StatelessWidget {
);
},
errorWidget: (context, url, error) {
debugPrint("Error getting thumbnail $url = $error");
CachedNetworkImage.evictFromCache(thumbnailRequestUrl);
if (error is HttpExceptionWithStatus &&
error.statusCode >= 400 &&
error.statusCode < 500) {
debugPrint("Evicting thumbnail '$url' from cache: $error");
CachedNetworkImage.evictFromCache(url);
}
return Icon(
Icons.image_not_supported_outlined,
color: Theme.of(context).primaryColor,

View File

@@ -6,12 +6,39 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/asset_viewer/providers/scroll_notifier.provider.dart';
import 'package:immich_mobile/modules/home/providers/multiselect.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/shared/providers/asset.provider.dart';
class TabControllerPage extends ConsumerWidget {
class TabControllerPage extends HookConsumerWidget {
const TabControllerPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final refreshing = ref.watch(assetProvider);
Widget buildIcon(Widget icon) {
if (!refreshing) return icon;
return Stack(
alignment: Alignment.center,
clipBehavior: Clip.none,
children: [
icon,
Positioned(
right: -14,
child: SizedBox(
height: 12,
width: 12,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
Theme.of(context).primaryColor,
),
),
),
),
],
);
}
navigationRail(TabsRouter tabsRouter) {
return NavigationRail(
labelType: NavigationRailLabelType.all,
@@ -83,9 +110,12 @@ class TabControllerPage extends ConsumerWidget {
icon: const Icon(
Icons.photo_library_outlined,
),
selectedIcon: Icon(
Icons.photo_library,
color: Theme.of(context).primaryColor,
selectedIcon: buildIcon(
Icon(
size: 24,
Icons.photo_library,
color: Theme.of(context).primaryColor,
),
),
),
NavigationDestination(
@@ -113,9 +143,11 @@ class TabControllerPage extends ConsumerWidget {
icon: const Icon(
Icons.photo_album_outlined,
),
selectedIcon: Icon(
Icons.photo_album_rounded,
color: Theme.of(context).primaryColor,
selectedIcon: buildIcon(
Icon(
Icons.photo_album_rounded,
color: Theme.of(context).primaryColor,
),
),
)
],

View File

@@ -1,3 +1,5 @@
import 'dart:typed_data';
import 'package:collection/collection.dart';
extension DurationExtension on String {
@@ -22,15 +24,20 @@ extension DurationExtension on String {
}
extension ListExtension<E> on List<E> {
List<E> uniqueConsecutive<T>([T Function(E element)? key]) {
key ??= (E e) => e as T;
List<E> uniqueConsecutive({
int Function(E a, E b)? compare,
void Function(E a, E b)? onDuplicate,
}) {
compare ??= (E a, E b) => a == b ? 0 : 1;
int i = 1, j = 1;
for (; i < length; i++) {
if (key(this[i]) != key(this[i - 1])) {
if (compare(this[i - 1], this[i]) != 0) {
if (i != j) {
this[j] = this[i];
}
j++;
} else if (onDuplicate != null) {
onDuplicate(this[i - 1], this[i]);
}
}
length = length == 0 ? 0 : j;
@@ -45,3 +52,11 @@ extension ListExtension<E> on List<E> {
return ListSlice<E>(this, start, end);
}
}
extension IntListExtension on Iterable<int> {
Int64List toInt64List() {
final list = Int64List(length);
list.setAll(0, this);
return list;
}
}

View File

@@ -47,6 +47,15 @@ class FileHelper {
case 'webm':
return {"type": "video", "subType": "webm"};
case 'insp':
return {"type": "image", "subType": "jpeg"};
case 'insv':
return {"type": "video", "subType": "mp4"};
case 'arw':
return {"type": "image", "subType": "x-sony-arw"};
default:
return {"type": "unsupport", "subType": "unsupport"};
}

View File

@@ -8,11 +8,13 @@ Future<void> migrateDatabaseIfNeeded(Isar db) async {
final int version = Store.get(StoreKey.version, 1);
switch (version) {
case 1:
await _migrateV1ToV2(db);
await _migrateTo(db, 2);
case 2:
await _migrateTo(db, 3);
}
}
Future<void> _migrateV1ToV2(Isar db) async {
Future<void> _migrateTo(Isar db, int version) async {
await clearAssetsAndAlbums(db);
await Store.put(StoreKey.version, 2);
await Store.put(StoreKey.version, version);
}

View File

@@ -15,10 +15,16 @@ extension WithETag on AssetApi {
Future<(List<AssetResponseDto>? assets, String? eTag)> getAllAssetsWithETag({
String? eTag,
String? userId,
bool? isFavorite,
bool? isArchived,
bool? withoutThumbs,
}) async {
final response = await getAllAssetsWithHttpInfo(
ifNoneMatch: eTag,
userId: userId,
isFavorite: isFavorite,
isArchived: isArchived,
withoutThumbs: withoutThumbs,
);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));

View File

@@ -25,6 +25,8 @@ doc/AssetCountByTimeBucket.md
doc/AssetCountByTimeBucketResponseDto.md
doc/AssetCountByUserIdResponseDto.md
doc/AssetFileUploadResponseDto.md
doc/AssetIdsDto.md
doc/AssetIdsResponseDto.md
doc/AssetResponseDto.md
doc/AssetTypeEnum.md
doc/AuthDeviceResponseDto.md
@@ -55,11 +57,13 @@ doc/JobCommand.md
doc/JobCommandDto.md
doc/JobCountsDto.md
doc/JobName.md
doc/JobSettingsDto.md
doc/JobStatusDto.md
doc/LoginCredentialDto.md
doc/LoginResponseDto.md
doc/LogoutResponseDto.md
doc/MapMarkerResponseDto.md
doc/MemoryLaneResponseDto.md
doc/OAuthApi.md
doc/OAuthCallbackDto.md
doc/OAuthConfigDto.md
@@ -93,6 +97,7 @@ doc/SmartInfoResponseDto.md
doc/SystemConfigApi.md
doc/SystemConfigDto.md
doc/SystemConfigFFmpegDto.md
doc/SystemConfigJobDto.md
doc/SystemConfigOAuthDto.md
doc/SystemConfigPasswordLoginDto.md
doc/SystemConfigStorageTemplateDto.md
@@ -154,6 +159,8 @@ lib/model/asset_count_by_time_bucket.dart
lib/model/asset_count_by_time_bucket_response_dto.dart
lib/model/asset_count_by_user_id_response_dto.dart
lib/model/asset_file_upload_response_dto.dart
lib/model/asset_ids_dto.dart
lib/model/asset_ids_response_dto.dart
lib/model/asset_response_dto.dart
lib/model/asset_type_enum.dart
lib/model/auth_device_response_dto.dart
@@ -182,11 +189,13 @@ lib/model/job_command.dart
lib/model/job_command_dto.dart
lib/model/job_counts_dto.dart
lib/model/job_name.dart
lib/model/job_settings_dto.dart
lib/model/job_status_dto.dart
lib/model/login_credential_dto.dart
lib/model/login_response_dto.dart
lib/model/logout_response_dto.dart
lib/model/map_marker_response_dto.dart
lib/model/memory_lane_response_dto.dart
lib/model/o_auth_callback_dto.dart
lib/model/o_auth_config_dto.dart
lib/model/o_auth_config_response_dto.dart
@@ -213,6 +222,7 @@ lib/model/sign_up_dto.dart
lib/model/smart_info_response_dto.dart
lib/model/system_config_dto.dart
lib/model/system_config_f_fmpeg_dto.dart
lib/model/system_config_job_dto.dart
lib/model/system_config_o_auth_dto.dart
lib/model/system_config_password_login_dto.dart
lib/model/system_config_storage_template_dto.dart
@@ -252,6 +262,8 @@ test/asset_count_by_time_bucket_response_dto_test.dart
test/asset_count_by_time_bucket_test.dart
test/asset_count_by_user_id_response_dto_test.dart
test/asset_file_upload_response_dto_test.dart
test/asset_ids_dto_test.dart
test/asset_ids_response_dto_test.dart
test/asset_response_dto_test.dart
test/asset_type_enum_test.dart
test/auth_device_response_dto_test.dart
@@ -282,11 +294,13 @@ test/job_command_dto_test.dart
test/job_command_test.dart
test/job_counts_dto_test.dart
test/job_name_test.dart
test/job_settings_dto_test.dart
test/job_status_dto_test.dart
test/login_credential_dto_test.dart
test/login_response_dto_test.dart
test/logout_response_dto_test.dart
test/map_marker_response_dto_test.dart
test/memory_lane_response_dto_test.dart
test/o_auth_api_test.dart
test/o_auth_callback_dto_test.dart
test/o_auth_config_dto_test.dart
@@ -320,6 +334,7 @@ test/smart_info_response_dto_test.dart
test/system_config_api_test.dart
test/system_config_dto_test.dart
test/system_config_f_fmpeg_dto_test.dart
test/system_config_job_dto_test.dart
test/system_config_o_auth_dto_test.dart
test/system_config_password_login_dto_test.dart
test/system_config_storage_template_dto_test.dart

View File

@@ -3,7 +3,7 @@ Immich API
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
- API version: 1.59.0
- API version: 1.62.0
- Build package: org.openapitools.codegen.languages.DartClientCodegen
## Requirements
@@ -83,7 +83,7 @@ Class | Method | HTTP request | Description
*AlbumApi* | [**createAlbumSharedLink**](doc//AlbumApi.md#createalbumsharedlink) | **POST** /album/create-shared-link |
*AlbumApi* | [**deleteAlbum**](doc//AlbumApi.md#deletealbum) | **DELETE** /album/{id} |
*AlbumApi* | [**downloadArchive**](doc//AlbumApi.md#downloadarchive) | **GET** /album/{id}/download |
*AlbumApi* | [**getAlbumCountByUserId**](doc//AlbumApi.md#getalbumcountbyuserid) | **GET** /album/count-by-user-id |
*AlbumApi* | [**getAlbumCount**](doc//AlbumApi.md#getalbumcount) | **GET** /album/count |
*AlbumApi* | [**getAlbumInfo**](doc//AlbumApi.md#getalbuminfo) | **GET** /album/{id} |
*AlbumApi* | [**getAllAlbums**](doc//AlbumApi.md#getallalbums) | **GET** /album |
*AlbumApi* | [**removeAssetFromAlbum**](doc//AlbumApi.md#removeassetfromalbum) | **DELETE** /album/{id}/assets |
@@ -95,25 +95,26 @@ Class | Method | HTTP request | Description
*AssetApi* | [**checkExistingAssets**](doc//AssetApi.md#checkexistingassets) | **POST** /asset/exist |
*AssetApi* | [**createAssetsSharedLink**](doc//AssetApi.md#createassetssharedlink) | **POST** /asset/shared-link |
*AssetApi* | [**deleteAsset**](doc//AssetApi.md#deleteasset) | **DELETE** /asset |
*AssetApi* | [**downloadFile**](doc//AssetApi.md#downloadfile) | **GET** /asset/download/{assetId} |
*AssetApi* | [**downloadFile**](doc//AssetApi.md#downloadfile) | **GET** /asset/download/{id} |
*AssetApi* | [**downloadFiles**](doc//AssetApi.md#downloadfiles) | **POST** /asset/download-files |
*AssetApi* | [**downloadLibrary**](doc//AssetApi.md#downloadlibrary) | **GET** /asset/download-library |
*AssetApi* | [**getAllAssets**](doc//AssetApi.md#getallassets) | **GET** /asset |
*AssetApi* | [**getArchivedAssetCountByUserId**](doc//AssetApi.md#getarchivedassetcountbyuserid) | **GET** /asset/stat/archive |
*AssetApi* | [**getAssetById**](doc//AssetApi.md#getassetbyid) | **GET** /asset/assetById/{assetId} |
*AssetApi* | [**getAssetById**](doc//AssetApi.md#getassetbyid) | **GET** /asset/assetById/{id} |
*AssetApi* | [**getAssetByTimeBucket**](doc//AssetApi.md#getassetbytimebucket) | **POST** /asset/time-bucket |
*AssetApi* | [**getAssetCountByTimeBucket**](doc//AssetApi.md#getassetcountbytimebucket) | **POST** /asset/count-by-time-bucket |
*AssetApi* | [**getAssetCountByUserId**](doc//AssetApi.md#getassetcountbyuserid) | **GET** /asset/count-by-user-id |
*AssetApi* | [**getAssetSearchTerms**](doc//AssetApi.md#getassetsearchterms) | **GET** /asset/search-terms |
*AssetApi* | [**getAssetThumbnail**](doc//AssetApi.md#getassetthumbnail) | **GET** /asset/thumbnail/{assetId} |
*AssetApi* | [**getAssetThumbnail**](doc//AssetApi.md#getassetthumbnail) | **GET** /asset/thumbnail/{id} |
*AssetApi* | [**getCuratedLocations**](doc//AssetApi.md#getcuratedlocations) | **GET** /asset/curated-locations |
*AssetApi* | [**getCuratedObjects**](doc//AssetApi.md#getcuratedobjects) | **GET** /asset/curated-objects |
*AssetApi* | [**getMapMarkers**](doc//AssetApi.md#getmapmarkers) | **GET** /asset/map-marker |
*AssetApi* | [**getMemoryLane**](doc//AssetApi.md#getmemorylane) | **GET** /asset/memory-lane |
*AssetApi* | [**getUserAssetsByDeviceId**](doc//AssetApi.md#getuserassetsbydeviceid) | **GET** /asset/{deviceId} |
*AssetApi* | [**removeAssetsFromSharedLink**](doc//AssetApi.md#removeassetsfromsharedlink) | **PATCH** /asset/shared-link/remove |
*AssetApi* | [**searchAsset**](doc//AssetApi.md#searchasset) | **POST** /asset/search |
*AssetApi* | [**serveFile**](doc//AssetApi.md#servefile) | **GET** /asset/file/{assetId} |
*AssetApi* | [**updateAsset**](doc//AssetApi.md#updateasset) | **PUT** /asset/{assetId} |
*AssetApi* | [**serveFile**](doc//AssetApi.md#servefile) | **GET** /asset/file/{id} |
*AssetApi* | [**updateAsset**](doc//AssetApi.md#updateasset) | **PUT** /asset/{id} |
*AssetApi* | [**uploadFile**](doc//AssetApi.md#uploadfile) | **POST** /asset/upload |
*AuthenticationApi* | [**adminSignUp**](doc//AuthenticationApi.md#adminsignup) | **POST** /auth/admin-sign-up |
*AuthenticationApi* | [**changePassword**](doc//AuthenticationApi.md#changepassword) | **POST** /auth/change-password |
@@ -124,7 +125,7 @@ Class | Method | HTTP request | Description
*AuthenticationApi* | [**logoutAuthDevices**](doc//AuthenticationApi.md#logoutauthdevices) | **DELETE** /auth/devices |
*AuthenticationApi* | [**validateAccessToken**](doc//AuthenticationApi.md#validateaccesstoken) | **POST** /auth/validateToken |
*JobApi* | [**getAllJobsStatus**](doc//JobApi.md#getalljobsstatus) | **GET** /jobs |
*JobApi* | [**sendJobCommand**](doc//JobApi.md#sendjobcommand) | **PUT** /jobs/{jobId} |
*JobApi* | [**sendJobCommand**](doc//JobApi.md#sendjobcommand) | **PUT** /jobs/{id} |
*OAuthApi* | [**callback**](doc//OAuthApi.md#callback) | **POST** /oauth/callback |
*OAuthApi* | [**generateConfig**](doc//OAuthApi.md#generateconfig) | **POST** /oauth/config |
*OAuthApi* | [**link**](doc//OAuthApi.md#link) | **POST** /oauth/link |
@@ -145,20 +146,23 @@ Class | Method | HTTP request | Description
*ServerInfoApi* | [**getServerVersion**](doc//ServerInfoApi.md#getserverversion) | **GET** /server-info/version |
*ServerInfoApi* | [**getStats**](doc//ServerInfoApi.md#getstats) | **GET** /server-info/stats |
*ServerInfoApi* | [**pingServer**](doc//ServerInfoApi.md#pingserver) | **GET** /server-info/ping |
*ShareApi* | [**editSharedLink**](doc//ShareApi.md#editsharedlink) | **PATCH** /share/{id} |
*ShareApi* | [**getAllSharedLinks**](doc//ShareApi.md#getallsharedlinks) | **GET** /share |
*ShareApi* | [**getMySharedLink**](doc//ShareApi.md#getmysharedlink) | **GET** /share/me |
*ShareApi* | [**getSharedLinkById**](doc//ShareApi.md#getsharedlinkbyid) | **GET** /share/{id} |
*ShareApi* | [**removeSharedLink**](doc//ShareApi.md#removesharedlink) | **DELETE** /share/{id} |
*ShareApi* | [**updateSharedLink**](doc//ShareApi.md#updatesharedlink) | **PATCH** /share/{id} |
*SystemConfigApi* | [**getConfig**](doc//SystemConfigApi.md#getconfig) | **GET** /system-config |
*SystemConfigApi* | [**getDefaults**](doc//SystemConfigApi.md#getdefaults) | **GET** /system-config/defaults |
*SystemConfigApi* | [**getStorageTemplateOptions**](doc//SystemConfigApi.md#getstoragetemplateoptions) | **GET** /system-config/storage-template-options |
*SystemConfigApi* | [**updateConfig**](doc//SystemConfigApi.md#updateconfig) | **PUT** /system-config |
*TagApi* | [**create**](doc//TagApi.md#create) | **POST** /tag |
*TagApi* | [**delete**](doc//TagApi.md#delete) | **DELETE** /tag/{id} |
*TagApi* | [**findAll**](doc//TagApi.md#findall) | **GET** /tag |
*TagApi* | [**findOne**](doc//TagApi.md#findone) | **GET** /tag/{id} |
*TagApi* | [**update**](doc//TagApi.md#update) | **PATCH** /tag/{id} |
*TagApi* | [**createTag**](doc//TagApi.md#createtag) | **POST** /tag |
*TagApi* | [**deleteTag**](doc//TagApi.md#deletetag) | **DELETE** /tag/{id} |
*TagApi* | [**getAllTags**](doc//TagApi.md#getalltags) | **GET** /tag |
*TagApi* | [**getTagAssets**](doc//TagApi.md#gettagassets) | **GET** /tag/{id}/assets |
*TagApi* | [**getTagById**](doc//TagApi.md#gettagbyid) | **GET** /tag/{id} |
*TagApi* | [**tagAssets**](doc//TagApi.md#tagassets) | **PUT** /tag/{id}/assets |
*TagApi* | [**untagAssets**](doc//TagApi.md#untagassets) | **DELETE** /tag/{id}/assets |
*TagApi* | [**updateTag**](doc//TagApi.md#updatetag) | **PATCH** /tag/{id} |
*UserApi* | [**createProfileImage**](doc//UserApi.md#createprofileimage) | **POST** /user/profile-image |
*UserApi* | [**createUser**](doc//UserApi.md#createuser) | **POST** /user |
*UserApi* | [**deleteUser**](doc//UserApi.md#deleteuser) | **DELETE** /user/{userId} |
@@ -192,6 +196,8 @@ Class | Method | HTTP request | Description
- [AssetCountByTimeBucketResponseDto](doc//AssetCountByTimeBucketResponseDto.md)
- [AssetCountByUserIdResponseDto](doc//AssetCountByUserIdResponseDto.md)
- [AssetFileUploadResponseDto](doc//AssetFileUploadResponseDto.md)
- [AssetIdsDto](doc//AssetIdsDto.md)
- [AssetIdsResponseDto](doc//AssetIdsResponseDto.md)
- [AssetResponseDto](doc//AssetResponseDto.md)
- [AssetTypeEnum](doc//AssetTypeEnum.md)
- [AuthDeviceResponseDto](doc//AuthDeviceResponseDto.md)
@@ -220,11 +226,13 @@ Class | Method | HTTP request | Description
- [JobCommandDto](doc//JobCommandDto.md)
- [JobCountsDto](doc//JobCountsDto.md)
- [JobName](doc//JobName.md)
- [JobSettingsDto](doc//JobSettingsDto.md)
- [JobStatusDto](doc//JobStatusDto.md)
- [LoginCredentialDto](doc//LoginCredentialDto.md)
- [LoginResponseDto](doc//LoginResponseDto.md)
- [LogoutResponseDto](doc//LogoutResponseDto.md)
- [MapMarkerResponseDto](doc//MapMarkerResponseDto.md)
- [MemoryLaneResponseDto](doc//MemoryLaneResponseDto.md)
- [OAuthCallbackDto](doc//OAuthCallbackDto.md)
- [OAuthConfigDto](doc//OAuthConfigDto.md)
- [OAuthConfigResponseDto](doc//OAuthConfigResponseDto.md)
@@ -251,6 +259,7 @@ Class | Method | HTTP request | Description
- [SmartInfoResponseDto](doc//SmartInfoResponseDto.md)
- [SystemConfigDto](doc//SystemConfigDto.md)
- [SystemConfigFFmpegDto](doc//SystemConfigFFmpegDto.md)
- [SystemConfigJobDto](doc//SystemConfigJobDto.md)
- [SystemConfigOAuthDto](doc//SystemConfigOAuthDto.md)
- [SystemConfigPasswordLoginDto](doc//SystemConfigPasswordLoginDto.md)
- [SystemConfigStorageTemplateDto](doc//SystemConfigStorageTemplateDto.md)

View File

@@ -15,7 +15,7 @@ Method | HTTP request | Description
[**createAlbumSharedLink**](AlbumApi.md#createalbumsharedlink) | **POST** /album/create-shared-link |
[**deleteAlbum**](AlbumApi.md#deletealbum) | **DELETE** /album/{id} |
[**downloadArchive**](AlbumApi.md#downloadarchive) | **GET** /album/{id}/download |
[**getAlbumCountByUserId**](AlbumApi.md#getalbumcountbyuserid) | **GET** /album/count-by-user-id |
[**getAlbumCount**](AlbumApi.md#getalbumcount) | **GET** /album/count |
[**getAlbumInfo**](AlbumApi.md#getalbuminfo) | **GET** /album/{id} |
[**getAllAlbums**](AlbumApi.md#getallalbums) | **GET** /album |
[**removeAssetFromAlbum**](AlbumApi.md#removeassetfromalbum) | **DELETE** /album/{id}/assets |
@@ -364,8 +364,8 @@ Name | Type | Description | Notes
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **getAlbumCountByUserId**
> AlbumCountResponseDto getAlbumCountByUserId()
# **getAlbumCount**
> AlbumCountResponseDto getAlbumCount()
@@ -390,10 +390,10 @@ import 'package:openapi/api.dart';
final api_instance = AlbumApi();
try {
final result = api_instance.getAlbumCountByUserId();
final result = api_instance.getAlbumCount();
print(result);
} catch (e) {
print('Exception when calling AlbumApi->getAlbumCountByUserId: $e\n');
print('Exception when calling AlbumApi->getAlbumCount: $e\n');
}
```

View File

@@ -10,7 +10,7 @@ Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**owned** | **int** | |
**shared** | **int** | |
**sharing** | **int** | |
**notShared** | **int** | |
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -8,16 +8,16 @@ import 'package:openapi/api.dart';
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**thumbnailGenerationQueue** | [**JobStatusDto**](JobStatusDto.md) | |
**metadataExtractionQueue** | [**JobStatusDto**](JobStatusDto.md) | |
**videoConversionQueue** | [**JobStatusDto**](JobStatusDto.md) | |
**objectTaggingQueue** | [**JobStatusDto**](JobStatusDto.md) | |
**clipEncodingQueue** | [**JobStatusDto**](JobStatusDto.md) | |
**storageTemplateMigrationQueue** | [**JobStatusDto**](JobStatusDto.md) | |
**backgroundTaskQueue** | [**JobStatusDto**](JobStatusDto.md) | |
**searchQueue** | [**JobStatusDto**](JobStatusDto.md) | |
**recognizeFacesQueue** | [**JobStatusDto**](JobStatusDto.md) | |
**sidecarQueue** | [**JobStatusDto**](JobStatusDto.md) | |
**thumbnailGeneration** | [**JobStatusDto**](JobStatusDto.md) | |
**metadataExtraction** | [**JobStatusDto**](JobStatusDto.md) | |
**videoConversion** | [**JobStatusDto**](JobStatusDto.md) | |
**objectTagging** | [**JobStatusDto**](JobStatusDto.md) | |
**clipEncoding** | [**JobStatusDto**](JobStatusDto.md) | |
**storageTemplateMigration** | [**JobStatusDto**](JobStatusDto.md) | |
**backgroundTask** | [**JobStatusDto**](JobStatusDto.md) | |
**search** | [**JobStatusDto**](JobStatusDto.md) | |
**recognizeFaces** | [**JobStatusDto**](JobStatusDto.md) | |
**sidecar** | [**JobStatusDto**](JobStatusDto.md) | |
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -15,25 +15,26 @@ Method | HTTP request | Description
[**checkExistingAssets**](AssetApi.md#checkexistingassets) | **POST** /asset/exist |
[**createAssetsSharedLink**](AssetApi.md#createassetssharedlink) | **POST** /asset/shared-link |
[**deleteAsset**](AssetApi.md#deleteasset) | **DELETE** /asset |
[**downloadFile**](AssetApi.md#downloadfile) | **GET** /asset/download/{assetId} |
[**downloadFile**](AssetApi.md#downloadfile) | **GET** /asset/download/{id} |
[**downloadFiles**](AssetApi.md#downloadfiles) | **POST** /asset/download-files |
[**downloadLibrary**](AssetApi.md#downloadlibrary) | **GET** /asset/download-library |
[**getAllAssets**](AssetApi.md#getallassets) | **GET** /asset |
[**getArchivedAssetCountByUserId**](AssetApi.md#getarchivedassetcountbyuserid) | **GET** /asset/stat/archive |
[**getAssetById**](AssetApi.md#getassetbyid) | **GET** /asset/assetById/{assetId} |
[**getAssetById**](AssetApi.md#getassetbyid) | **GET** /asset/assetById/{id} |
[**getAssetByTimeBucket**](AssetApi.md#getassetbytimebucket) | **POST** /asset/time-bucket |
[**getAssetCountByTimeBucket**](AssetApi.md#getassetcountbytimebucket) | **POST** /asset/count-by-time-bucket |
[**getAssetCountByUserId**](AssetApi.md#getassetcountbyuserid) | **GET** /asset/count-by-user-id |
[**getAssetSearchTerms**](AssetApi.md#getassetsearchterms) | **GET** /asset/search-terms |
[**getAssetThumbnail**](AssetApi.md#getassetthumbnail) | **GET** /asset/thumbnail/{assetId} |
[**getAssetThumbnail**](AssetApi.md#getassetthumbnail) | **GET** /asset/thumbnail/{id} |
[**getCuratedLocations**](AssetApi.md#getcuratedlocations) | **GET** /asset/curated-locations |
[**getCuratedObjects**](AssetApi.md#getcuratedobjects) | **GET** /asset/curated-objects |
[**getMapMarkers**](AssetApi.md#getmapmarkers) | **GET** /asset/map-marker |
[**getMemoryLane**](AssetApi.md#getmemorylane) | **GET** /asset/memory-lane |
[**getUserAssetsByDeviceId**](AssetApi.md#getuserassetsbydeviceid) | **GET** /asset/{deviceId} |
[**removeAssetsFromSharedLink**](AssetApi.md#removeassetsfromsharedlink) | **PATCH** /asset/shared-link/remove |
[**searchAsset**](AssetApi.md#searchasset) | **POST** /asset/search |
[**serveFile**](AssetApi.md#servefile) | **GET** /asset/file/{assetId} |
[**updateAsset**](AssetApi.md#updateasset) | **PUT** /asset/{assetId} |
[**serveFile**](AssetApi.md#servefile) | **GET** /asset/file/{id} |
[**updateAsset**](AssetApi.md#updateasset) | **PUT** /asset/{id} |
[**uploadFile**](AssetApi.md#uploadfile) | **POST** /asset/upload |
@@ -378,7 +379,7 @@ Name | Type | Description | Notes
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **downloadFile**
> MultipartFile downloadFile(assetId, key)
> MultipartFile downloadFile(id, key)
@@ -401,11 +402,11 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
final api_instance = AssetApi();
final assetId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final key = key_example; // String |
try {
final result = api_instance.downloadFile(assetId, key);
final result = api_instance.downloadFile(id, key);
print(result);
} catch (e) {
print('Exception when calling AssetApi->downloadFile: $e\n');
@@ -416,7 +417,7 @@ try {
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**assetId** | **String**| |
**id** | **String**| |
**key** | **String**| | [optional]
### Return type
@@ -553,7 +554,7 @@ Name | Type | Description | Notes
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **getAllAssets**
> List<AssetResponseDto> getAllAssets(userId, isFavorite, isArchived, skip, ifNoneMatch)
> List<AssetResponseDto> getAllAssets(userId, isFavorite, isArchived, withoutThumbs, skip, ifNoneMatch)
@@ -581,11 +582,12 @@ final api_instance = AssetApi();
final userId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final isFavorite = true; // bool |
final isArchived = true; // bool |
final withoutThumbs = true; // bool | Include assets without thumbnails
final skip = 8.14; // num |
final ifNoneMatch = ifNoneMatch_example; // String | ETag of data already cached on the client
try {
final result = api_instance.getAllAssets(userId, isFavorite, isArchived, skip, ifNoneMatch);
final result = api_instance.getAllAssets(userId, isFavorite, isArchived, withoutThumbs, skip, ifNoneMatch);
print(result);
} catch (e) {
print('Exception when calling AssetApi->getAllAssets: $e\n');
@@ -599,6 +601,7 @@ Name | Type | Description | Notes
**userId** | **String**| | [optional]
**isFavorite** | **bool**| | [optional]
**isArchived** | **bool**| | [optional]
**withoutThumbs** | **bool**| Include assets without thumbnails | [optional]
**skip** | **num**| | [optional]
**ifNoneMatch** | **String**| ETag of data already cached on the client | [optional]
@@ -669,7 +672,7 @@ This endpoint does not need any parameter.
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **getAssetById**
> AssetResponseDto getAssetById(assetId, key)
> AssetResponseDto getAssetById(id, key)
@@ -694,11 +697,11 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
final api_instance = AssetApi();
final assetId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final key = key_example; // String |
try {
final result = api_instance.getAssetById(assetId, key);
final result = api_instance.getAssetById(id, key);
print(result);
} catch (e) {
print('Exception when calling AssetApi->getAssetById: $e\n');
@@ -709,7 +712,7 @@ try {
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**assetId** | **String**| |
**id** | **String**| |
**key** | **String**| | [optional]
### Return type
@@ -940,7 +943,7 @@ This endpoint does not need any parameter.
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **getAssetThumbnail**
> MultipartFile getAssetThumbnail(assetId, format, key)
> MultipartFile getAssetThumbnail(id, format, key)
@@ -963,12 +966,12 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
final api_instance = AssetApi();
final assetId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final format = ; // ThumbnailFormat |
final key = key_example; // String |
try {
final result = api_instance.getAssetThumbnail(assetId, format, key);
final result = api_instance.getAssetThumbnail(id, format, key);
print(result);
} catch (e) {
print('Exception when calling AssetApi->getAssetThumbnail: $e\n');
@@ -979,7 +982,7 @@ try {
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**assetId** | **String**| |
**id** | **String**| |
**format** | [**ThumbnailFormat**](.md)| | [optional]
**key** | **String**| | [optional]
@@ -1159,6 +1162,61 @@ Name | Type | Description | Notes
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **getMemoryLane**
> List<MemoryLaneResponseDto> getMemoryLane(timestamp)
### Example
```dart
import 'package:openapi/api.dart';
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
// TODO Configure API key authorization: api_key
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKeyPrefix = 'Bearer';
// TODO Configure HTTP Bearer authorization: bearer
// Case 1. Use String Token
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
final api_instance = AssetApi();
final timestamp = 2013-10-20T19:20:30+01:00; // DateTime | Get pictures for +24 hours from this time going back x years
try {
final result = api_instance.getMemoryLane(timestamp);
print(result);
} catch (e) {
print('Exception when calling AssetApi->getMemoryLane: $e\n');
}
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**timestamp** | **DateTime**| Get pictures for +24 hours from this time going back x years |
### Return type
[**List<MemoryLaneResponseDto>**](MemoryLaneResponseDto.md)
### Authorization
[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer)
### HTTP request headers
- **Content-Type**: Not defined
- **Accept**: application/json
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **getUserAssetsByDeviceId**
> List<String> getUserAssetsByDeviceId(deviceId)
@@ -1329,7 +1387,7 @@ Name | Type | Description | Notes
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **serveFile**
> MultipartFile serveFile(assetId, isThumb, isWeb, key)
> MultipartFile serveFile(id, isThumb, isWeb, key)
@@ -1352,13 +1410,13 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
final api_instance = AssetApi();
final assetId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final isThumb = true; // bool |
final isWeb = true; // bool |
final key = key_example; // String |
try {
final result = api_instance.serveFile(assetId, isThumb, isWeb, key);
final result = api_instance.serveFile(id, isThumb, isWeb, key);
print(result);
} catch (e) {
print('Exception when calling AssetApi->serveFile: $e\n');
@@ -1369,7 +1427,7 @@ try {
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**assetId** | **String**| |
**id** | **String**| |
**isThumb** | **bool**| | [optional]
**isWeb** | **bool**| | [optional]
**key** | **String**| | [optional]
@@ -1390,7 +1448,7 @@ Name | Type | Description | Notes
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **updateAsset**
> AssetResponseDto updateAsset(assetId, updateAssetDto)
> AssetResponseDto updateAsset(id, updateAssetDto)
@@ -1415,11 +1473,11 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
final api_instance = AssetApi();
final assetId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final updateAssetDto = UpdateAssetDto(); // UpdateAssetDto |
try {
final result = api_instance.updateAsset(assetId, updateAssetDto);
final result = api_instance.updateAsset(id, updateAssetDto);
print(result);
} catch (e) {
print('Exception when calling AssetApi->updateAsset: $e\n');
@@ -1430,7 +1488,7 @@ try {
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**assetId** | **String**| |
**id** | **String**| |
**updateAssetDto** | [**UpdateAssetDto**](UpdateAssetDto.md)| |
### Return type

15
mobile/openapi/doc/AssetIdsDto.md generated Normal file
View File

@@ -0,0 +1,15 @@
# openapi.model.AssetIdsDto
## Load the model package
```dart
import 'package:openapi/api.dart';
```
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**assetIds** | **List<String>** | | [default to const []]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -0,0 +1,17 @@
# openapi.model.AssetIdsResponseDto
## Load the model package
```dart
import 'package:openapi/api.dart';
```
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**assetId** | **String** | |
**success** | **bool** | |
**error** | **String** | | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -16,6 +16,7 @@ Name | Type | Description | Notes
**originalPath** | **String** | |
**originalFileName** | **String** | |
**resized** | **bool** | |
**thumbhash** | **String** | base64 encoded thumbhash |
**fileCreatedAt** | [**DateTime**](DateTime.md) | |
**fileModifiedAt** | [**DateTime**](DateTime.md) | |
**updatedAt** | [**DateTime**](DateTime.md) | |

View File

@@ -10,6 +10,7 @@ Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**timeGroup** | [**TimeGroupEnum**](TimeGroupEnum.md) | |
**userId** | **String** | | [optional]
**withoutThumbs** | **bool** | Include assets without thumbnails | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -10,7 +10,7 @@ All URIs are relative to */api*
Method | HTTP request | Description
------------- | ------------- | -------------
[**getAllJobsStatus**](JobApi.md#getalljobsstatus) | **GET** /jobs |
[**sendJobCommand**](JobApi.md#sendjobcommand) | **PUT** /jobs/{jobId} |
[**sendJobCommand**](JobApi.md#sendjobcommand) | **PUT** /jobs/{id} |
# **getAllJobsStatus**
@@ -65,7 +65,7 @@ This endpoint does not need any parameter.
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **sendJobCommand**
> JobStatusDto sendJobCommand(jobId, jobCommandDto)
> JobStatusDto sendJobCommand(id, jobCommandDto)
@@ -88,11 +88,11 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
final api_instance = JobApi();
final jobId = ; // JobName |
final id = ; // JobName |
final jobCommandDto = JobCommandDto(); // JobCommandDto |
try {
final result = api_instance.sendJobCommand(jobId, jobCommandDto);
final result = api_instance.sendJobCommand(id, jobCommandDto);
print(result);
} catch (e) {
print('Exception when calling JobApi->sendJobCommand: $e\n');
@@ -103,7 +103,7 @@ try {
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**jobId** | [**JobName**](.md)| |
**id** | [**JobName**](.md)| |
**jobCommandDto** | [**JobCommandDto**](JobCommandDto.md)| |
### Return type

15
mobile/openapi/doc/JobSettingsDto.md generated Normal file
View File

@@ -0,0 +1,15 @@
# openapi.model.JobSettingsDto
## Load the model package
```dart
import 'package:openapi/api.dart';
```
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**concurrency** | **int** | |
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -0,0 +1,16 @@
# openapi.model.MemoryLaneResponseDto
## Load the model package
```dart
import 'package:openapi/api.dart';
```
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**title** | **String** | |
**assets** | [**List<AssetResponseDto>**](AssetResponseDto.md) | | [default to const []]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -9,70 +9,13 @@ All URIs are relative to */api*
Method | HTTP request | Description
------------- | ------------- | -------------
[**editSharedLink**](ShareApi.md#editsharedlink) | **PATCH** /share/{id} |
[**getAllSharedLinks**](ShareApi.md#getallsharedlinks) | **GET** /share |
[**getMySharedLink**](ShareApi.md#getmysharedlink) | **GET** /share/me |
[**getSharedLinkById**](ShareApi.md#getsharedlinkbyid) | **GET** /share/{id} |
[**removeSharedLink**](ShareApi.md#removesharedlink) | **DELETE** /share/{id} |
[**updateSharedLink**](ShareApi.md#updatesharedlink) | **PATCH** /share/{id} |
# **editSharedLink**
> SharedLinkResponseDto editSharedLink(id, editSharedLinkDto)
### Example
```dart
import 'package:openapi/api.dart';
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
// TODO Configure API key authorization: api_key
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKeyPrefix = 'Bearer';
// TODO Configure HTTP Bearer authorization: bearer
// Case 1. Use String Token
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
final api_instance = ShareApi();
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final editSharedLinkDto = EditSharedLinkDto(); // EditSharedLinkDto |
try {
final result = api_instance.editSharedLink(id, editSharedLinkDto);
print(result);
} catch (e) {
print('Exception when calling ShareApi->editSharedLink: $e\n');
}
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**id** | **String**| |
**editSharedLinkDto** | [**EditSharedLinkDto**](EditSharedLinkDto.md)| |
### Return type
[**SharedLinkResponseDto**](SharedLinkResponseDto.md)
### Authorization
[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer)
### HTTP request headers
- **Content-Type**: application/json
- **Accept**: application/json
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **getAllSharedLinks**
> List<SharedLinkResponseDto> getAllSharedLinks()
@@ -288,3 +231,60 @@ void (empty response body)
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **updateSharedLink**
> SharedLinkResponseDto updateSharedLink(id, editSharedLinkDto)
### Example
```dart
import 'package:openapi/api.dart';
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
// TODO Configure API key authorization: api_key
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKeyPrefix = 'Bearer';
// TODO Configure HTTP Bearer authorization: bearer
// Case 1. Use String Token
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
final api_instance = ShareApi();
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final editSharedLinkDto = EditSharedLinkDto(); // EditSharedLinkDto |
try {
final result = api_instance.updateSharedLink(id, editSharedLinkDto);
print(result);
} catch (e) {
print('Exception when calling ShareApi->updateSharedLink: $e\n');
}
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**id** | **String**| |
**editSharedLinkDto** | [**EditSharedLinkDto**](EditSharedLinkDto.md)| |
### Return type
[**SharedLinkResponseDto**](SharedLinkResponseDto.md)
### Authorization
[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer)
### HTTP request headers
- **Content-Type**: application/json
- **Accept**: application/json
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)

View File

@@ -12,6 +12,7 @@ Name | Type | Description | Notes
**oauth** | [**SystemConfigOAuthDto**](SystemConfigOAuthDto.md) | |
**passwordLogin** | [**SystemConfigPasswordLoginDto**](SystemConfigPasswordLoginDto.md) | |
**storageTemplate** | [**SystemConfigStorageTemplateDto**](SystemConfigStorageTemplateDto.md) | |
**job** | [**SystemConfigJobDto**](SystemConfigJobDto.md) | |
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

24
mobile/openapi/doc/SystemConfigJobDto.md generated Normal file
View File

@@ -0,0 +1,24 @@
# openapi.model.SystemConfigJobDto
## Load the model package
```dart
import 'package:openapi/api.dart';
```
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**thumbnailGeneration** | [**JobSettingsDto**](JobSettingsDto.md) | |
**metadataExtraction** | [**JobSettingsDto**](JobSettingsDto.md) | |
**videoConversion** | [**JobSettingsDto**](JobSettingsDto.md) | |
**objectTagging** | [**JobSettingsDto**](JobSettingsDto.md) | |
**clipEncoding** | [**JobSettingsDto**](JobSettingsDto.md) | |
**storageTemplateMigration** | [**JobSettingsDto**](JobSettingsDto.md) | |
**backgroundTask** | [**JobSettingsDto**](JobSettingsDto.md) | |
**search** | [**JobSettingsDto**](JobSettingsDto.md) | |
**recognizeFaces** | [**JobSettingsDto**](JobSettingsDto.md) | |
**sidecar** | [**JobSettingsDto**](JobSettingsDto.md) | |
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -9,15 +9,18 @@ All URIs are relative to */api*
Method | HTTP request | Description
------------- | ------------- | -------------
[**create**](TagApi.md#create) | **POST** /tag |
[**delete**](TagApi.md#delete) | **DELETE** /tag/{id} |
[**findAll**](TagApi.md#findall) | **GET** /tag |
[**findOne**](TagApi.md#findone) | **GET** /tag/{id} |
[**update**](TagApi.md#update) | **PATCH** /tag/{id} |
[**createTag**](TagApi.md#createtag) | **POST** /tag |
[**deleteTag**](TagApi.md#deletetag) | **DELETE** /tag/{id} |
[**getAllTags**](TagApi.md#getalltags) | **GET** /tag |
[**getTagAssets**](TagApi.md#gettagassets) | **GET** /tag/{id}/assets |
[**getTagById**](TagApi.md#gettagbyid) | **GET** /tag/{id} |
[**tagAssets**](TagApi.md#tagassets) | **PUT** /tag/{id}/assets |
[**untagAssets**](TagApi.md#untagassets) | **DELETE** /tag/{id}/assets |
[**updateTag**](TagApi.md#updatetag) | **PATCH** /tag/{id} |
# **create**
> TagResponseDto create(createTagDto)
# **createTag**
> TagResponseDto createTag(createTagDto)
@@ -43,10 +46,10 @@ final api_instance = TagApi();
final createTagDto = CreateTagDto(); // CreateTagDto |
try {
final result = api_instance.create(createTagDto);
final result = api_instance.createTag(createTagDto);
print(result);
} catch (e) {
print('Exception when calling TagApi->create: $e\n');
print('Exception when calling TagApi->createTag: $e\n');
}
```
@@ -71,8 +74,8 @@ Name | Type | Description | Notes
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **delete**
> delete(id)
# **deleteTag**
> deleteTag(id)
@@ -98,9 +101,9 @@ final api_instance = TagApi();
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
try {
api_instance.delete(id);
api_instance.deleteTag(id);
} catch (e) {
print('Exception when calling TagApi->delete: $e\n');
print('Exception when calling TagApi->deleteTag: $e\n');
}
```
@@ -125,8 +128,8 @@ void (empty response body)
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **findAll**
> List<TagResponseDto> findAll()
# **getAllTags**
> List<TagResponseDto> getAllTags()
@@ -151,10 +154,10 @@ import 'package:openapi/api.dart';
final api_instance = TagApi();
try {
final result = api_instance.findAll();
final result = api_instance.getAllTags();
print(result);
} catch (e) {
print('Exception when calling TagApi->findAll: $e\n');
print('Exception when calling TagApi->getAllTags: $e\n');
}
```
@@ -176,8 +179,8 @@ This endpoint does not need any parameter.
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **findOne**
> TagResponseDto findOne(id)
# **getTagAssets**
> List<AssetResponseDto> getTagAssets(id)
@@ -203,10 +206,65 @@ final api_instance = TagApi();
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
try {
final result = api_instance.findOne(id);
final result = api_instance.getTagAssets(id);
print(result);
} catch (e) {
print('Exception when calling TagApi->findOne: $e\n');
print('Exception when calling TagApi->getTagAssets: $e\n');
}
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**id** | **String**| |
### Return type
[**List<AssetResponseDto>**](AssetResponseDto.md)
### Authorization
[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer)
### HTTP request headers
- **Content-Type**: Not defined
- **Accept**: application/json
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **getTagById**
> TagResponseDto getTagById(id)
### Example
```dart
import 'package:openapi/api.dart';
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
// TODO Configure API key authorization: api_key
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKeyPrefix = 'Bearer';
// TODO Configure HTTP Bearer authorization: bearer
// Case 1. Use String Token
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
final api_instance = TagApi();
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
try {
final result = api_instance.getTagById(id);
print(result);
} catch (e) {
print('Exception when calling TagApi->getTagById: $e\n');
}
```
@@ -231,8 +289,122 @@ Name | Type | Description | Notes
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **update**
> TagResponseDto update(id, updateTagDto)
# **tagAssets**
> List<AssetIdsResponseDto> tagAssets(id, assetIdsDto)
### Example
```dart
import 'package:openapi/api.dart';
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
// TODO Configure API key authorization: api_key
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKeyPrefix = 'Bearer';
// TODO Configure HTTP Bearer authorization: bearer
// Case 1. Use String Token
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
final api_instance = TagApi();
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final assetIdsDto = AssetIdsDto(); // AssetIdsDto |
try {
final result = api_instance.tagAssets(id, assetIdsDto);
print(result);
} catch (e) {
print('Exception when calling TagApi->tagAssets: $e\n');
}
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**id** | **String**| |
**assetIdsDto** | [**AssetIdsDto**](AssetIdsDto.md)| |
### Return type
[**List<AssetIdsResponseDto>**](AssetIdsResponseDto.md)
### Authorization
[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer)
### HTTP request headers
- **Content-Type**: application/json
- **Accept**: application/json
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **untagAssets**
> List<AssetIdsResponseDto> untagAssets(id, assetIdsDto)
### Example
```dart
import 'package:openapi/api.dart';
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
// TODO Configure API key authorization: api_key
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKeyPrefix = 'Bearer';
// TODO Configure HTTP Bearer authorization: bearer
// Case 1. Use String Token
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
final api_instance = TagApi();
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final assetIdsDto = AssetIdsDto(); // AssetIdsDto |
try {
final result = api_instance.untagAssets(id, assetIdsDto);
print(result);
} catch (e) {
print('Exception when calling TagApi->untagAssets: $e\n');
}
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**id** | **String**| |
**assetIdsDto** | [**AssetIdsDto**](AssetIdsDto.md)| |
### Return type
[**List<AssetIdsResponseDto>**](AssetIdsResponseDto.md)
### Authorization
[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer)
### HTTP request headers
- **Content-Type**: application/json
- **Accept**: application/json
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **updateTag**
> TagResponseDto updateTag(id, updateTagDto)
@@ -259,10 +431,10 @@ final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final updateTagDto = UpdateTagDto(); // UpdateTagDto |
try {
final result = api_instance.update(id, updateTagDto);
final result = api_instance.updateTag(id, updateTagDto);
print(result);
} catch (e) {
print('Exception when calling TagApi->update: $e\n');
print('Exception when calling TagApi->updateTag: $e\n');
}
```

View File

@@ -8,11 +8,10 @@ import 'package:openapi/api.dart';
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**id** | **String** | |
**type** | [**TagTypeEnum**](TagTypeEnum.md) | |
**id** | **String** | |
**name** | **String** | |
**userId** | **String** | |
**renameTagId** | **String** | | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -9,7 +9,6 @@ import 'package:openapi/api.dart';
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**name** | **String** | | [optional]
**renameTagId** | **String** | | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -62,6 +62,8 @@ part 'model/asset_count_by_time_bucket.dart';
part 'model/asset_count_by_time_bucket_response_dto.dart';
part 'model/asset_count_by_user_id_response_dto.dart';
part 'model/asset_file_upload_response_dto.dart';
part 'model/asset_ids_dto.dart';
part 'model/asset_ids_response_dto.dart';
part 'model/asset_response_dto.dart';
part 'model/asset_type_enum.dart';
part 'model/auth_device_response_dto.dart';
@@ -90,11 +92,13 @@ part 'model/job_command.dart';
part 'model/job_command_dto.dart';
part 'model/job_counts_dto.dart';
part 'model/job_name.dart';
part 'model/job_settings_dto.dart';
part 'model/job_status_dto.dart';
part 'model/login_credential_dto.dart';
part 'model/login_response_dto.dart';
part 'model/logout_response_dto.dart';
part 'model/map_marker_response_dto.dart';
part 'model/memory_lane_response_dto.dart';
part 'model/o_auth_callback_dto.dart';
part 'model/o_auth_config_dto.dart';
part 'model/o_auth_config_response_dto.dart';
@@ -121,6 +125,7 @@ part 'model/sign_up_dto.dart';
part 'model/smart_info_response_dto.dart';
part 'model/system_config_dto.dart';
part 'model/system_config_f_fmpeg_dto.dart';
part 'model/system_config_job_dto.dart';
part 'model/system_config_o_auth_dto.dart';
part 'model/system_config_password_login_dto.dart';
part 'model/system_config_storage_template_dto.dart';

View File

@@ -332,10 +332,10 @@ class AlbumApi {
return null;
}
/// Performs an HTTP 'GET /album/count-by-user-id' operation and returns the [Response].
Future<Response> getAlbumCountByUserIdWithHttpInfo() async {
/// Performs an HTTP 'GET /album/count' operation and returns the [Response].
Future<Response> getAlbumCountWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/album/count-by-user-id';
final path = r'/album/count';
// ignore: prefer_final_locals
Object? postBody;
@@ -358,8 +358,8 @@ class AlbumApi {
);
}
Future<AlbumCountResponseDto?> getAlbumCountByUserId() async {
final response = await getAlbumCountByUserIdWithHttpInfo();
Future<AlbumCountResponseDto?> getAlbumCount() async {
final response = await getAlbumCountWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}

View File

@@ -332,16 +332,16 @@ class AssetApi {
return null;
}
/// Performs an HTTP 'GET /asset/download/{assetId}' operation and returns the [Response].
/// Performs an HTTP 'GET /asset/download/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [String] assetId (required):
/// * [String] id (required):
///
/// * [String] key:
Future<Response> downloadFileWithHttpInfo(String assetId, { String? key, }) async {
Future<Response> downloadFileWithHttpInfo(String id, { String? key, }) async {
// ignore: prefer_const_declarations
final path = r'/asset/download/{assetId}'
.replaceAll('{assetId}', assetId);
final path = r'/asset/download/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
Object? postBody;
@@ -370,11 +370,11 @@ class AssetApi {
/// Parameters:
///
/// * [String] assetId (required):
/// * [String] id (required):
///
/// * [String] key:
Future<MultipartFile?> downloadFile(String assetId, { String? key, }) async {
final response = await downloadFileWithHttpInfo(assetId, key: key, );
Future<MultipartFile?> downloadFile(String id, { String? key, }) async {
final response = await downloadFileWithHttpInfo(id, key: key, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
@@ -525,11 +525,14 @@ class AssetApi {
///
/// * [bool] isArchived:
///
/// * [bool] withoutThumbs:
/// Include assets without thumbnails
///
/// * [num] skip:
///
/// * [String] ifNoneMatch:
/// ETag of data already cached on the client
Future<Response> getAllAssetsWithHttpInfo({ String? userId, bool? isFavorite, bool? isArchived, num? skip, String? ifNoneMatch, }) async {
Future<Response> getAllAssetsWithHttpInfo({ String? userId, bool? isFavorite, bool? isArchived, bool? withoutThumbs, num? skip, String? ifNoneMatch, }) async {
// ignore: prefer_const_declarations
final path = r'/asset';
@@ -549,6 +552,9 @@ class AssetApi {
if (isArchived != null) {
queryParams.addAll(_queryParams('', 'isArchived', isArchived));
}
if (withoutThumbs != null) {
queryParams.addAll(_queryParams('', 'withoutThumbs', withoutThumbs));
}
if (skip != null) {
queryParams.addAll(_queryParams('', 'skip', skip));
}
@@ -581,12 +587,15 @@ class AssetApi {
///
/// * [bool] isArchived:
///
/// * [bool] withoutThumbs:
/// Include assets without thumbnails
///
/// * [num] skip:
///
/// * [String] ifNoneMatch:
/// ETag of data already cached on the client
Future<List<AssetResponseDto>?> getAllAssets({ String? userId, bool? isFavorite, bool? isArchived, num? skip, String? ifNoneMatch, }) async {
final response = await getAllAssetsWithHttpInfo( userId: userId, isFavorite: isFavorite, isArchived: isArchived, skip: skip, ifNoneMatch: ifNoneMatch, );
Future<List<AssetResponseDto>?> getAllAssets({ String? userId, bool? isFavorite, bool? isArchived, bool? withoutThumbs, num? skip, String? ifNoneMatch, }) async {
final response = await getAllAssetsWithHttpInfo( userId: userId, isFavorite: isFavorite, isArchived: isArchived, withoutThumbs: withoutThumbs, skip: skip, ifNoneMatch: ifNoneMatch, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
@@ -650,13 +659,13 @@ class AssetApi {
///
/// Parameters:
///
/// * [String] assetId (required):
/// * [String] id (required):
///
/// * [String] key:
Future<Response> getAssetByIdWithHttpInfo(String assetId, { String? key, }) async {
Future<Response> getAssetByIdWithHttpInfo(String id, { String? key, }) async {
// ignore: prefer_const_declarations
final path = r'/asset/assetById/{assetId}'
.replaceAll('{assetId}', assetId);
final path = r'/asset/assetById/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
Object? postBody;
@@ -687,11 +696,11 @@ class AssetApi {
///
/// Parameters:
///
/// * [String] assetId (required):
/// * [String] id (required):
///
/// * [String] key:
Future<AssetResponseDto?> getAssetById(String assetId, { String? key, }) async {
final response = await getAssetByIdWithHttpInfo(assetId, key: key, );
Future<AssetResponseDto?> getAssetById(String id, { String? key, }) async {
final response = await getAssetByIdWithHttpInfo(id, key: key, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
@@ -887,18 +896,18 @@ class AssetApi {
return null;
}
/// Performs an HTTP 'GET /asset/thumbnail/{assetId}' operation and returns the [Response].
/// Performs an HTTP 'GET /asset/thumbnail/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [String] assetId (required):
/// * [String] id (required):
///
/// * [ThumbnailFormat] format:
///
/// * [String] key:
Future<Response> getAssetThumbnailWithHttpInfo(String assetId, { ThumbnailFormat? format, String? key, }) async {
Future<Response> getAssetThumbnailWithHttpInfo(String id, { ThumbnailFormat? format, String? key, }) async {
// ignore: prefer_const_declarations
final path = r'/asset/thumbnail/{assetId}'
.replaceAll('{assetId}', assetId);
final path = r'/asset/thumbnail/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
Object? postBody;
@@ -930,13 +939,13 @@ class AssetApi {
/// Parameters:
///
/// * [String] assetId (required):
/// * [String] id (required):
///
/// * [ThumbnailFormat] format:
///
/// * [String] key:
Future<MultipartFile?> getAssetThumbnail(String assetId, { ThumbnailFormat? format, String? key, }) async {
final response = await getAssetThumbnailWithHttpInfo(assetId, format: format, key: key, );
Future<MultipartFile?> getAssetThumbnail(String id, { ThumbnailFormat? format, String? key, }) async {
final response = await getAssetThumbnailWithHttpInfo(id, format: format, key: key, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
@@ -1106,6 +1115,60 @@ class AssetApi {
return null;
}
/// Performs an HTTP 'GET /asset/memory-lane' operation and returns the [Response].
/// Parameters:
///
/// * [DateTime] timestamp (required):
/// Get pictures for +24 hours from this time going back x years
Future<Response> getMemoryLaneWithHttpInfo(DateTime timestamp,) async {
// ignore: prefer_const_declarations
final path = r'/asset/memory-lane';
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
queryParams.addAll(_queryParams('', 'timestamp', timestamp));
const contentTypes = <String>[];
return apiClient.invokeAPI(
path,
'GET',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Parameters:
///
/// * [DateTime] timestamp (required):
/// Get pictures for +24 hours from this time going back x years
Future<List<MemoryLaneResponseDto>?> getMemoryLane(DateTime timestamp,) async {
final response = await getMemoryLaneWithHttpInfo(timestamp,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
final responseBody = await _decodeBodyBytes(response);
return (await apiClient.deserializeAsync(responseBody, 'List<MemoryLaneResponseDto>') as List)
.cast<MemoryLaneResponseDto>()
.toList();
}
return null;
}
/// Get all asset of a device that are in the database, ID only.
///
/// Note: This method returns the HTTP [Response].
@@ -1267,20 +1330,20 @@ class AssetApi {
return null;
}
/// Performs an HTTP 'GET /asset/file/{assetId}' operation and returns the [Response].
/// Performs an HTTP 'GET /asset/file/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [String] assetId (required):
/// * [String] id (required):
///
/// * [bool] isThumb:
///
/// * [bool] isWeb:
///
/// * [String] key:
Future<Response> serveFileWithHttpInfo(String assetId, { bool? isThumb, bool? isWeb, String? key, }) async {
Future<Response> serveFileWithHttpInfo(String id, { bool? isThumb, bool? isWeb, String? key, }) async {
// ignore: prefer_const_declarations
final path = r'/asset/file/{assetId}'
.replaceAll('{assetId}', assetId);
final path = r'/asset/file/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
Object? postBody;
@@ -1315,15 +1378,15 @@ class AssetApi {
/// Parameters:
///
/// * [String] assetId (required):
/// * [String] id (required):
///
/// * [bool] isThumb:
///
/// * [bool] isWeb:
///
/// * [String] key:
Future<MultipartFile?> serveFile(String assetId, { bool? isThumb, bool? isWeb, String? key, }) async {
final response = await serveFileWithHttpInfo(assetId, isThumb: isThumb, isWeb: isWeb, key: key, );
Future<MultipartFile?> serveFile(String id, { bool? isThumb, bool? isWeb, String? key, }) async {
final response = await serveFileWithHttpInfo(id, isThumb: isThumb, isWeb: isWeb, key: key, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
@@ -1343,13 +1406,13 @@ class AssetApi {
///
/// Parameters:
///
/// * [String] assetId (required):
/// * [String] id (required):
///
/// * [UpdateAssetDto] updateAssetDto (required):
Future<Response> updateAssetWithHttpInfo(String assetId, UpdateAssetDto updateAssetDto,) async {
Future<Response> updateAssetWithHttpInfo(String id, UpdateAssetDto updateAssetDto,) async {
// ignore: prefer_const_declarations
final path = r'/asset/{assetId}'
.replaceAll('{assetId}', assetId);
final path = r'/asset/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
Object? postBody = updateAssetDto;
@@ -1376,11 +1439,11 @@ class AssetApi {
///
/// Parameters:
///
/// * [String] assetId (required):
/// * [String] id (required):
///
/// * [UpdateAssetDto] updateAssetDto (required):
Future<AssetResponseDto?> updateAsset(String assetId, UpdateAssetDto updateAssetDto,) async {
final response = await updateAssetWithHttpInfo(assetId, updateAssetDto,);
Future<AssetResponseDto?> updateAsset(String id, UpdateAssetDto updateAssetDto,) async {
final response = await updateAssetWithHttpInfo(id, updateAssetDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}

View File

@@ -57,16 +57,16 @@ class JobApi {
return null;
}
/// Performs an HTTP 'PUT /jobs/{jobId}' operation and returns the [Response].
/// Performs an HTTP 'PUT /jobs/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [JobName] jobId (required):
/// * [JobName] id (required):
///
/// * [JobCommandDto] jobCommandDto (required):
Future<Response> sendJobCommandWithHttpInfo(JobName jobId, JobCommandDto jobCommandDto,) async {
Future<Response> sendJobCommandWithHttpInfo(JobName id, JobCommandDto jobCommandDto,) async {
// ignore: prefer_const_declarations
final path = r'/jobs/{jobId}'
.replaceAll('{jobId}', jobId.toString());
final path = r'/jobs/{id}'
.replaceAll('{id}', id.toString());
// ignore: prefer_final_locals
Object? postBody = jobCommandDto;
@@ -91,11 +91,11 @@ class JobApi {
/// Parameters:
///
/// * [JobName] jobId (required):
/// * [JobName] id (required):
///
/// * [JobCommandDto] jobCommandDto (required):
Future<JobStatusDto?> sendJobCommand(JobName jobId, JobCommandDto jobCommandDto,) async {
final response = await sendJobCommandWithHttpInfo(jobId, jobCommandDto,);
Future<JobStatusDto?> sendJobCommand(JobName id, JobCommandDto jobCommandDto,) async {
final response = await sendJobCommandWithHttpInfo(id, jobCommandDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}

View File

@@ -16,58 +16,6 @@ class ShareApi {
final ApiClient apiClient;
/// Performs an HTTP 'PATCH /share/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
///
/// * [EditSharedLinkDto] editSharedLinkDto (required):
Future<Response> editSharedLinkWithHttpInfo(String id, EditSharedLinkDto editSharedLinkDto,) async {
// ignore: prefer_const_declarations
final path = r'/share/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
Object? postBody = editSharedLinkDto;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>['application/json'];
return apiClient.invokeAPI(
path,
'PATCH',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Parameters:
///
/// * [String] id (required):
///
/// * [EditSharedLinkDto] editSharedLinkDto (required):
Future<SharedLinkResponseDto?> editSharedLink(String id, EditSharedLinkDto editSharedLinkDto,) async {
final response = await editSharedLinkWithHttpInfo(id, editSharedLinkDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'SharedLinkResponseDto',) as SharedLinkResponseDto;
}
return null;
}
/// Performs an HTTP 'GET /share' operation and returns the [Response].
Future<Response> getAllSharedLinksWithHttpInfo() async {
// ignore: prefer_const_declarations
@@ -250,4 +198,56 @@ class ShareApi {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
}
/// Performs an HTTP 'PATCH /share/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
///
/// * [EditSharedLinkDto] editSharedLinkDto (required):
Future<Response> updateSharedLinkWithHttpInfo(String id, EditSharedLinkDto editSharedLinkDto,) async {
// ignore: prefer_const_declarations
final path = r'/share/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
Object? postBody = editSharedLinkDto;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>['application/json'];
return apiClient.invokeAPI(
path,
'PATCH',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Parameters:
///
/// * [String] id (required):
///
/// * [EditSharedLinkDto] editSharedLinkDto (required):
Future<SharedLinkResponseDto?> updateSharedLink(String id, EditSharedLinkDto editSharedLinkDto,) async {
final response = await updateSharedLinkWithHttpInfo(id, editSharedLinkDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'SharedLinkResponseDto',) as SharedLinkResponseDto;
}
return null;
}
}

View File

@@ -20,7 +20,7 @@ class TagApi {
/// Parameters:
///
/// * [CreateTagDto] createTagDto (required):
Future<Response> createWithHttpInfo(CreateTagDto createTagDto,) async {
Future<Response> createTagWithHttpInfo(CreateTagDto createTagDto,) async {
// ignore: prefer_const_declarations
final path = r'/tag';
@@ -48,8 +48,8 @@ class TagApi {
/// Parameters:
///
/// * [CreateTagDto] createTagDto (required):
Future<TagResponseDto?> create(CreateTagDto createTagDto,) async {
final response = await createWithHttpInfo(createTagDto,);
Future<TagResponseDto?> createTag(CreateTagDto createTagDto,) async {
final response = await createTagWithHttpInfo(createTagDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
@@ -67,7 +67,7 @@ class TagApi {
/// Parameters:
///
/// * [String] id (required):
Future<Response> deleteWithHttpInfo(String id,) async {
Future<Response> deleteTagWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final path = r'/tag/{id}'
.replaceAll('{id}', id);
@@ -96,15 +96,15 @@ class TagApi {
/// Parameters:
///
/// * [String] id (required):
Future<void> delete(String id,) async {
final response = await deleteWithHttpInfo(id,);
Future<void> deleteTag(String id,) async {
final response = await deleteTagWithHttpInfo(id,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
}
/// Performs an HTTP 'GET /tag' operation and returns the [Response].
Future<Response> findAllWithHttpInfo() async {
Future<Response> getAllTagsWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/tag';
@@ -129,8 +129,8 @@ class TagApi {
);
}
Future<List<TagResponseDto>?> findAll() async {
final response = await findAllWithHttpInfo();
Future<List<TagResponseDto>?> getAllTags() async {
final response = await getAllTagsWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
@@ -147,11 +147,62 @@ class TagApi {
return null;
}
/// Performs an HTTP 'GET /tag/{id}/assets' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
Future<Response> getTagAssetsWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final path = r'/tag/{id}/assets'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>[];
return apiClient.invokeAPI(
path,
'GET',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Parameters:
///
/// * [String] id (required):
Future<List<AssetResponseDto>?> getTagAssets(String id,) async {
final response = await getTagAssetsWithHttpInfo(id,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
final responseBody = await _decodeBodyBytes(response);
return (await apiClient.deserializeAsync(responseBody, 'List<AssetResponseDto>') as List)
.cast<AssetResponseDto>()
.toList();
}
return null;
}
/// Performs an HTTP 'GET /tag/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
Future<Response> findOneWithHttpInfo(String id,) async {
Future<Response> getTagByIdWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final path = r'/tag/{id}'
.replaceAll('{id}', id);
@@ -180,8 +231,8 @@ class TagApi {
/// Parameters:
///
/// * [String] id (required):
Future<TagResponseDto?> findOne(String id,) async {
final response = await findOneWithHttpInfo(id,);
Future<TagResponseDto?> getTagById(String id,) async {
final response = await getTagByIdWithHttpInfo(id,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
@@ -195,13 +246,123 @@ class TagApi {
return null;
}
/// Performs an HTTP 'PUT /tag/{id}/assets' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
///
/// * [AssetIdsDto] assetIdsDto (required):
Future<Response> tagAssetsWithHttpInfo(String id, AssetIdsDto assetIdsDto,) async {
// ignore: prefer_const_declarations
final path = r'/tag/{id}/assets'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
Object? postBody = assetIdsDto;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>['application/json'];
return apiClient.invokeAPI(
path,
'PUT',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Parameters:
///
/// * [String] id (required):
///
/// * [AssetIdsDto] assetIdsDto (required):
Future<List<AssetIdsResponseDto>?> tagAssets(String id, AssetIdsDto assetIdsDto,) async {
final response = await tagAssetsWithHttpInfo(id, assetIdsDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
final responseBody = await _decodeBodyBytes(response);
return (await apiClient.deserializeAsync(responseBody, 'List<AssetIdsResponseDto>') as List)
.cast<AssetIdsResponseDto>()
.toList();
}
return null;
}
/// Performs an HTTP 'DELETE /tag/{id}/assets' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
///
/// * [AssetIdsDto] assetIdsDto (required):
Future<Response> untagAssetsWithHttpInfo(String id, AssetIdsDto assetIdsDto,) async {
// ignore: prefer_const_declarations
final path = r'/tag/{id}/assets'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
Object? postBody = assetIdsDto;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>['application/json'];
return apiClient.invokeAPI(
path,
'DELETE',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Parameters:
///
/// * [String] id (required):
///
/// * [AssetIdsDto] assetIdsDto (required):
Future<List<AssetIdsResponseDto>?> untagAssets(String id, AssetIdsDto assetIdsDto,) async {
final response = await untagAssetsWithHttpInfo(id, assetIdsDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
final responseBody = await _decodeBodyBytes(response);
return (await apiClient.deserializeAsync(responseBody, 'List<AssetIdsResponseDto>') as List)
.cast<AssetIdsResponseDto>()
.toList();
}
return null;
}
/// Performs an HTTP 'PATCH /tag/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
///
/// * [UpdateTagDto] updateTagDto (required):
Future<Response> updateWithHttpInfo(String id, UpdateTagDto updateTagDto,) async {
Future<Response> updateTagWithHttpInfo(String id, UpdateTagDto updateTagDto,) async {
// ignore: prefer_const_declarations
final path = r'/tag/{id}'
.replaceAll('{id}', id);
@@ -232,8 +393,8 @@ class TagApi {
/// * [String] id (required):
///
/// * [UpdateTagDto] updateTagDto (required):
Future<TagResponseDto?> update(String id, UpdateTagDto updateTagDto,) async {
final response = await updateWithHttpInfo(id, updateTagDto,);
Future<TagResponseDto?> updateTag(String id, UpdateTagDto updateTagDto,) async {
final response = await updateTagWithHttpInfo(id, updateTagDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}

View File

@@ -219,6 +219,10 @@ class ApiClient {
return AssetCountByUserIdResponseDto.fromJson(value);
case 'AssetFileUploadResponseDto':
return AssetFileUploadResponseDto.fromJson(value);
case 'AssetIdsDto':
return AssetIdsDto.fromJson(value);
case 'AssetIdsResponseDto':
return AssetIdsResponseDto.fromJson(value);
case 'AssetResponseDto':
return AssetResponseDto.fromJson(value);
case 'AssetTypeEnum':
@@ -275,6 +279,8 @@ class ApiClient {
return JobCountsDto.fromJson(value);
case 'JobName':
return JobNameTypeTransformer().decode(value);
case 'JobSettingsDto':
return JobSettingsDto.fromJson(value);
case 'JobStatusDto':
return JobStatusDto.fromJson(value);
case 'LoginCredentialDto':
@@ -285,6 +291,8 @@ class ApiClient {
return LogoutResponseDto.fromJson(value);
case 'MapMarkerResponseDto':
return MapMarkerResponseDto.fromJson(value);
case 'MemoryLaneResponseDto':
return MemoryLaneResponseDto.fromJson(value);
case 'OAuthCallbackDto':
return OAuthCallbackDto.fromJson(value);
case 'OAuthConfigDto':
@@ -337,6 +345,8 @@ class ApiClient {
return SystemConfigDto.fromJson(value);
case 'SystemConfigFFmpegDto':
return SystemConfigFFmpegDto.fromJson(value);
case 'SystemConfigJobDto':
return SystemConfigJobDto.fromJson(value);
case 'SystemConfigOAuthDto':
return SystemConfigOAuthDto.fromJson(value);
case 'SystemConfigPasswordLoginDto':

View File

@@ -15,36 +15,36 @@ class AlbumCountResponseDto {
AlbumCountResponseDto({
required this.owned,
required this.shared,
required this.sharing,
required this.notShared,
});
int owned;
int shared;
int sharing;
int notShared;
@override
bool operator ==(Object other) => identical(this, other) || other is AlbumCountResponseDto &&
other.owned == owned &&
other.shared == shared &&
other.sharing == sharing;
other.notShared == notShared;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(owned.hashCode) +
(shared.hashCode) +
(sharing.hashCode);
(notShared.hashCode);
@override
String toString() => 'AlbumCountResponseDto[owned=$owned, shared=$shared, sharing=$sharing]';
String toString() => 'AlbumCountResponseDto[owned=$owned, shared=$shared, notShared=$notShared]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'owned'] = this.owned;
json[r'shared'] = this.shared;
json[r'sharing'] = this.sharing;
json[r'notShared'] = this.notShared;
return json;
}
@@ -69,7 +69,7 @@ class AlbumCountResponseDto {
return AlbumCountResponseDto(
owned: mapValueOfType<int>(json, r'owned')!,
shared: mapValueOfType<int>(json, r'shared')!,
sharing: mapValueOfType<int>(json, r'sharing')!,
notShared: mapValueOfType<int>(json, r'notShared')!,
);
}
return null;
@@ -119,7 +119,7 @@ class AlbumCountResponseDto {
static const requiredKeys = <String>{
'owned',
'shared',
'sharing',
'notShared',
};
}

View File

@@ -13,80 +13,80 @@ part of openapi.api;
class AllJobStatusResponseDto {
/// Returns a new [AllJobStatusResponseDto] instance.
AllJobStatusResponseDto({
required this.thumbnailGenerationQueue,
required this.metadataExtractionQueue,
required this.videoConversionQueue,
required this.objectTaggingQueue,
required this.clipEncodingQueue,
required this.storageTemplateMigrationQueue,
required this.backgroundTaskQueue,
required this.searchQueue,
required this.recognizeFacesQueue,
required this.sidecarQueue,
required this.thumbnailGeneration,
required this.metadataExtraction,
required this.videoConversion,
required this.objectTagging,
required this.clipEncoding,
required this.storageTemplateMigration,
required this.backgroundTask,
required this.search,
required this.recognizeFaces,
required this.sidecar,
});
JobStatusDto thumbnailGenerationQueue;
JobStatusDto thumbnailGeneration;
JobStatusDto metadataExtractionQueue;
JobStatusDto metadataExtraction;
JobStatusDto videoConversionQueue;
JobStatusDto videoConversion;
JobStatusDto objectTaggingQueue;
JobStatusDto objectTagging;
JobStatusDto clipEncodingQueue;
JobStatusDto clipEncoding;
JobStatusDto storageTemplateMigrationQueue;
JobStatusDto storageTemplateMigration;
JobStatusDto backgroundTaskQueue;
JobStatusDto backgroundTask;
JobStatusDto searchQueue;
JobStatusDto search;
JobStatusDto recognizeFacesQueue;
JobStatusDto recognizeFaces;
JobStatusDto sidecarQueue;
JobStatusDto sidecar;
@override
bool operator ==(Object other) => identical(this, other) || other is AllJobStatusResponseDto &&
other.thumbnailGenerationQueue == thumbnailGenerationQueue &&
other.metadataExtractionQueue == metadataExtractionQueue &&
other.videoConversionQueue == videoConversionQueue &&
other.objectTaggingQueue == objectTaggingQueue &&
other.clipEncodingQueue == clipEncodingQueue &&
other.storageTemplateMigrationQueue == storageTemplateMigrationQueue &&
other.backgroundTaskQueue == backgroundTaskQueue &&
other.searchQueue == searchQueue &&
other.recognizeFacesQueue == recognizeFacesQueue &&
other.sidecarQueue == sidecarQueue;
other.thumbnailGeneration == thumbnailGeneration &&
other.metadataExtraction == metadataExtraction &&
other.videoConversion == videoConversion &&
other.objectTagging == objectTagging &&
other.clipEncoding == clipEncoding &&
other.storageTemplateMigration == storageTemplateMigration &&
other.backgroundTask == backgroundTask &&
other.search == search &&
other.recognizeFaces == recognizeFaces &&
other.sidecar == sidecar;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(thumbnailGenerationQueue.hashCode) +
(metadataExtractionQueue.hashCode) +
(videoConversionQueue.hashCode) +
(objectTaggingQueue.hashCode) +
(clipEncodingQueue.hashCode) +
(storageTemplateMigrationQueue.hashCode) +
(backgroundTaskQueue.hashCode) +
(searchQueue.hashCode) +
(recognizeFacesQueue.hashCode) +
(sidecarQueue.hashCode);
(thumbnailGeneration.hashCode) +
(metadataExtraction.hashCode) +
(videoConversion.hashCode) +
(objectTagging.hashCode) +
(clipEncoding.hashCode) +
(storageTemplateMigration.hashCode) +
(backgroundTask.hashCode) +
(search.hashCode) +
(recognizeFaces.hashCode) +
(sidecar.hashCode);
@override
String toString() => 'AllJobStatusResponseDto[thumbnailGenerationQueue=$thumbnailGenerationQueue, metadataExtractionQueue=$metadataExtractionQueue, videoConversionQueue=$videoConversionQueue, objectTaggingQueue=$objectTaggingQueue, clipEncodingQueue=$clipEncodingQueue, storageTemplateMigrationQueue=$storageTemplateMigrationQueue, backgroundTaskQueue=$backgroundTaskQueue, searchQueue=$searchQueue, recognizeFacesQueue=$recognizeFacesQueue, sidecarQueue=$sidecarQueue]';
String toString() => 'AllJobStatusResponseDto[thumbnailGeneration=$thumbnailGeneration, metadataExtraction=$metadataExtraction, videoConversion=$videoConversion, objectTagging=$objectTagging, clipEncoding=$clipEncoding, storageTemplateMigration=$storageTemplateMigration, backgroundTask=$backgroundTask, search=$search, recognizeFaces=$recognizeFaces, sidecar=$sidecar]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'thumbnail-generation-queue'] = this.thumbnailGenerationQueue;
json[r'metadata-extraction-queue'] = this.metadataExtractionQueue;
json[r'video-conversion-queue'] = this.videoConversionQueue;
json[r'object-tagging-queue'] = this.objectTaggingQueue;
json[r'clip-encoding-queue'] = this.clipEncodingQueue;
json[r'storage-template-migration-queue'] = this.storageTemplateMigrationQueue;
json[r'background-task-queue'] = this.backgroundTaskQueue;
json[r'search-queue'] = this.searchQueue;
json[r'recognize-faces-queue'] = this.recognizeFacesQueue;
json[r'sidecar-queue'] = this.sidecarQueue;
json[r'thumbnailGeneration'] = this.thumbnailGeneration;
json[r'metadataExtraction'] = this.metadataExtraction;
json[r'videoConversion'] = this.videoConversion;
json[r'objectTagging'] = this.objectTagging;
json[r'clipEncoding'] = this.clipEncoding;
json[r'storageTemplateMigration'] = this.storageTemplateMigration;
json[r'backgroundTask'] = this.backgroundTask;
json[r'search'] = this.search;
json[r'recognizeFaces'] = this.recognizeFaces;
json[r'sidecar'] = this.sidecar;
return json;
}
@@ -109,16 +109,16 @@ class AllJobStatusResponseDto {
}());
return AllJobStatusResponseDto(
thumbnailGenerationQueue: JobStatusDto.fromJson(json[r'thumbnail-generation-queue'])!,
metadataExtractionQueue: JobStatusDto.fromJson(json[r'metadata-extraction-queue'])!,
videoConversionQueue: JobStatusDto.fromJson(json[r'video-conversion-queue'])!,
objectTaggingQueue: JobStatusDto.fromJson(json[r'object-tagging-queue'])!,
clipEncodingQueue: JobStatusDto.fromJson(json[r'clip-encoding-queue'])!,
storageTemplateMigrationQueue: JobStatusDto.fromJson(json[r'storage-template-migration-queue'])!,
backgroundTaskQueue: JobStatusDto.fromJson(json[r'background-task-queue'])!,
searchQueue: JobStatusDto.fromJson(json[r'search-queue'])!,
recognizeFacesQueue: JobStatusDto.fromJson(json[r'recognize-faces-queue'])!,
sidecarQueue: JobStatusDto.fromJson(json[r'sidecar-queue'])!,
thumbnailGeneration: JobStatusDto.fromJson(json[r'thumbnailGeneration'])!,
metadataExtraction: JobStatusDto.fromJson(json[r'metadataExtraction'])!,
videoConversion: JobStatusDto.fromJson(json[r'videoConversion'])!,
objectTagging: JobStatusDto.fromJson(json[r'objectTagging'])!,
clipEncoding: JobStatusDto.fromJson(json[r'clipEncoding'])!,
storageTemplateMigration: JobStatusDto.fromJson(json[r'storageTemplateMigration'])!,
backgroundTask: JobStatusDto.fromJson(json[r'backgroundTask'])!,
search: JobStatusDto.fromJson(json[r'search'])!,
recognizeFaces: JobStatusDto.fromJson(json[r'recognizeFaces'])!,
sidecar: JobStatusDto.fromJson(json[r'sidecar'])!,
);
}
return null;
@@ -166,16 +166,16 @@ class AllJobStatusResponseDto {
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'thumbnail-generation-queue',
'metadata-extraction-queue',
'video-conversion-queue',
'object-tagging-queue',
'clip-encoding-queue',
'storage-template-migration-queue',
'background-task-queue',
'search-queue',
'recognize-faces-queue',
'sidecar-queue',
'thumbnailGeneration',
'metadataExtraction',
'videoConversion',
'objectTagging',
'clipEncoding',
'storageTemplateMigration',
'backgroundTask',
'search',
'recognizeFaces',
'sidecar',
};
}

View File

@@ -0,0 +1,111 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class AssetIdsDto {
/// Returns a new [AssetIdsDto] instance.
AssetIdsDto({
this.assetIds = const [],
});
List<String> assetIds;
@override
bool operator ==(Object other) => identical(this, other) || other is AssetIdsDto &&
other.assetIds == assetIds;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(assetIds.hashCode);
@override
String toString() => 'AssetIdsDto[assetIds=$assetIds]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'assetIds'] = this.assetIds;
return json;
}
/// Returns a new [AssetIdsDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static AssetIdsDto? fromJson(dynamic value) {
if (value is Map) {
final json = value.cast<String, dynamic>();
// Ensure that the map contains the required keys.
// Note 1: the values aren't checked for validity beyond being non-null.
// Note 2: this code is stripped in release mode!
assert(() {
requiredKeys.forEach((key) {
assert(json.containsKey(key), 'Required key "AssetIdsDto[$key]" is missing from JSON.');
assert(json[key] != null, 'Required key "AssetIdsDto[$key]" has a null value in JSON.');
});
return true;
}());
return AssetIdsDto(
assetIds: json[r'assetIds'] is Iterable
? (json[r'assetIds'] as Iterable).cast<String>().toList(growable: false)
: const [],
);
}
return null;
}
static List<AssetIdsDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AssetIdsDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = AssetIdsDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, AssetIdsDto> mapFromJson(dynamic json) {
final map = <String, AssetIdsDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = AssetIdsDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of AssetIdsDto-objects as value to a dart map
static Map<String, List<AssetIdsDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<AssetIdsDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = AssetIdsDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'assetIds',
};
}

View File

@@ -0,0 +1,205 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class AssetIdsResponseDto {
/// Returns a new [AssetIdsResponseDto] instance.
AssetIdsResponseDto({
required this.assetId,
required this.success,
this.error,
});
String assetId;
bool success;
AssetIdsResponseDtoErrorEnum? error;
@override
bool operator ==(Object other) => identical(this, other) || other is AssetIdsResponseDto &&
other.assetId == assetId &&
other.success == success &&
other.error == error;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(assetId.hashCode) +
(success.hashCode) +
(error == null ? 0 : error!.hashCode);
@override
String toString() => 'AssetIdsResponseDto[assetId=$assetId, success=$success, error=$error]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'assetId'] = this.assetId;
json[r'success'] = this.success;
if (this.error != null) {
json[r'error'] = this.error;
} else {
// json[r'error'] = null;
}
return json;
}
/// Returns a new [AssetIdsResponseDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static AssetIdsResponseDto? fromJson(dynamic value) {
if (value is Map) {
final json = value.cast<String, dynamic>();
// Ensure that the map contains the required keys.
// Note 1: the values aren't checked for validity beyond being non-null.
// Note 2: this code is stripped in release mode!
assert(() {
requiredKeys.forEach((key) {
assert(json.containsKey(key), 'Required key "AssetIdsResponseDto[$key]" is missing from JSON.');
assert(json[key] != null, 'Required key "AssetIdsResponseDto[$key]" has a null value in JSON.');
});
return true;
}());
return AssetIdsResponseDto(
assetId: mapValueOfType<String>(json, r'assetId')!,
success: mapValueOfType<bool>(json, r'success')!,
error: AssetIdsResponseDtoErrorEnum.fromJson(json[r'error']),
);
}
return null;
}
static List<AssetIdsResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AssetIdsResponseDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = AssetIdsResponseDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, AssetIdsResponseDto> mapFromJson(dynamic json) {
final map = <String, AssetIdsResponseDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = AssetIdsResponseDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of AssetIdsResponseDto-objects as value to a dart map
static Map<String, List<AssetIdsResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<AssetIdsResponseDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = AssetIdsResponseDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'assetId',
'success',
};
}
class AssetIdsResponseDtoErrorEnum {
/// Instantiate a new enum with the provided [value].
const AssetIdsResponseDtoErrorEnum._(this.value);
/// The underlying value of this enum member.
final String value;
@override
String toString() => value;
String toJson() => value;
static const duplicate = AssetIdsResponseDtoErrorEnum._(r'duplicate');
static const noPermission = AssetIdsResponseDtoErrorEnum._(r'no_permission');
static const notFound = AssetIdsResponseDtoErrorEnum._(r'not_found');
/// List of all possible values in this [enum][AssetIdsResponseDtoErrorEnum].
static const values = <AssetIdsResponseDtoErrorEnum>[
duplicate,
noPermission,
notFound,
];
static AssetIdsResponseDtoErrorEnum? fromJson(dynamic value) => AssetIdsResponseDtoErrorEnumTypeTransformer().decode(value);
static List<AssetIdsResponseDtoErrorEnum>? listFromJson(dynamic json, {bool growable = false,}) {
final result = <AssetIdsResponseDtoErrorEnum>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = AssetIdsResponseDtoErrorEnum.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
}
/// Transformation class that can [encode] an instance of [AssetIdsResponseDtoErrorEnum] to String,
/// and [decode] dynamic data back to [AssetIdsResponseDtoErrorEnum].
class AssetIdsResponseDtoErrorEnumTypeTransformer {
factory AssetIdsResponseDtoErrorEnumTypeTransformer() => _instance ??= const AssetIdsResponseDtoErrorEnumTypeTransformer._();
const AssetIdsResponseDtoErrorEnumTypeTransformer._();
String encode(AssetIdsResponseDtoErrorEnum data) => data.value;
/// Decodes a [dynamic value][data] to a AssetIdsResponseDtoErrorEnum.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
///
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
AssetIdsResponseDtoErrorEnum? decode(dynamic data, {bool allowNull = true}) {
if (data != null) {
switch (data) {
case r'duplicate': return AssetIdsResponseDtoErrorEnum.duplicate;
case r'no_permission': return AssetIdsResponseDtoErrorEnum.noPermission;
case r'not_found': return AssetIdsResponseDtoErrorEnum.notFound;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');
}
}
}
return null;
}
/// Singleton [AssetIdsResponseDtoErrorEnumTypeTransformer] instance.
static AssetIdsResponseDtoErrorEnumTypeTransformer? _instance;
}

View File

@@ -21,6 +21,7 @@ class AssetResponseDto {
required this.originalPath,
required this.originalFileName,
required this.resized,
required this.thumbhash,
required this.fileCreatedAt,
required this.fileModifiedAt,
required this.updatedAt,
@@ -52,6 +53,9 @@ class AssetResponseDto {
bool resized;
/// base64 encoded thumbhash
String? thumbhash;
DateTime fileCreatedAt;
DateTime fileModifiedAt;
@@ -101,6 +105,7 @@ class AssetResponseDto {
other.originalPath == originalPath &&
other.originalFileName == originalFileName &&
other.resized == resized &&
other.thumbhash == thumbhash &&
other.fileCreatedAt == fileCreatedAt &&
other.fileModifiedAt == fileModifiedAt &&
other.updatedAt == updatedAt &&
@@ -126,6 +131,7 @@ class AssetResponseDto {
(originalPath.hashCode) +
(originalFileName.hashCode) +
(resized.hashCode) +
(thumbhash == null ? 0 : thumbhash!.hashCode) +
(fileCreatedAt.hashCode) +
(fileModifiedAt.hashCode) +
(updatedAt.hashCode) +
@@ -141,7 +147,7 @@ class AssetResponseDto {
(checksum.hashCode);
@override
String toString() => 'AssetResponseDto[type=$type, id=$id, deviceAssetId=$deviceAssetId, ownerId=$ownerId, deviceId=$deviceId, originalPath=$originalPath, originalFileName=$originalFileName, resized=$resized, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, updatedAt=$updatedAt, isFavorite=$isFavorite, isArchived=$isArchived, mimeType=$mimeType, duration=$duration, exifInfo=$exifInfo, smartInfo=$smartInfo, livePhotoVideoId=$livePhotoVideoId, tags=$tags, people=$people, checksum=$checksum]';
String toString() => 'AssetResponseDto[type=$type, id=$id, deviceAssetId=$deviceAssetId, ownerId=$ownerId, deviceId=$deviceId, originalPath=$originalPath, originalFileName=$originalFileName, resized=$resized, thumbhash=$thumbhash, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, updatedAt=$updatedAt, isFavorite=$isFavorite, isArchived=$isArchived, mimeType=$mimeType, duration=$duration, exifInfo=$exifInfo, smartInfo=$smartInfo, livePhotoVideoId=$livePhotoVideoId, tags=$tags, people=$people, checksum=$checksum]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
@@ -153,6 +159,11 @@ class AssetResponseDto {
json[r'originalPath'] = this.originalPath;
json[r'originalFileName'] = this.originalFileName;
json[r'resized'] = this.resized;
if (this.thumbhash != null) {
json[r'thumbhash'] = this.thumbhash;
} else {
// json[r'thumbhash'] = null;
}
json[r'fileCreatedAt'] = this.fileCreatedAt.toUtc().toIso8601String();
json[r'fileModifiedAt'] = this.fileModifiedAt.toUtc().toIso8601String();
json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String();
@@ -212,6 +223,7 @@ class AssetResponseDto {
originalPath: mapValueOfType<String>(json, r'originalPath')!,
originalFileName: mapValueOfType<String>(json, r'originalFileName')!,
resized: mapValueOfType<bool>(json, r'resized')!,
thumbhash: mapValueOfType<String>(json, r'thumbhash'),
fileCreatedAt: mapDateTime(json, r'fileCreatedAt', '')!,
fileModifiedAt: mapDateTime(json, r'fileModifiedAt', '')!,
updatedAt: mapDateTime(json, r'updatedAt', '')!,
@@ -280,6 +292,7 @@ class AssetResponseDto {
'originalPath',
'originalFileName',
'resized',
'thumbhash',
'fileCreatedAt',
'fileModifiedAt',
'updatedAt',

View File

@@ -15,6 +15,7 @@ class GetAssetCountByTimeBucketDto {
GetAssetCountByTimeBucketDto({
required this.timeGroup,
this.userId,
this.withoutThumbs,
});
TimeGroupEnum timeGroup;
@@ -27,19 +28,30 @@ class GetAssetCountByTimeBucketDto {
///
String? userId;
/// Include assets without thumbnails
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? withoutThumbs;
@override
bool operator ==(Object other) => identical(this, other) || other is GetAssetCountByTimeBucketDto &&
other.timeGroup == timeGroup &&
other.userId == userId;
other.userId == userId &&
other.withoutThumbs == withoutThumbs;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(timeGroup.hashCode) +
(userId == null ? 0 : userId!.hashCode);
(userId == null ? 0 : userId!.hashCode) +
(withoutThumbs == null ? 0 : withoutThumbs!.hashCode);
@override
String toString() => 'GetAssetCountByTimeBucketDto[timeGroup=$timeGroup, userId=$userId]';
String toString() => 'GetAssetCountByTimeBucketDto[timeGroup=$timeGroup, userId=$userId, withoutThumbs=$withoutThumbs]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
@@ -49,6 +61,11 @@ class GetAssetCountByTimeBucketDto {
} else {
// json[r'userId'] = null;
}
if (this.withoutThumbs != null) {
json[r'withoutThumbs'] = this.withoutThumbs;
} else {
// json[r'withoutThumbs'] = null;
}
return json;
}
@@ -73,6 +90,7 @@ class GetAssetCountByTimeBucketDto {
return GetAssetCountByTimeBucketDto(
timeGroup: TimeGroupEnum.fromJson(json[r'timeGroup'])!,
userId: mapValueOfType<String>(json, r'userId'),
withoutThumbs: mapValueOfType<bool>(json, r'withoutThumbs'),
);
}
return null;

View File

@@ -23,29 +23,29 @@ class JobName {
String toJson() => value;
static const thumbnailGenerationQueue = JobName._(r'thumbnail-generation-queue');
static const metadataExtractionQueue = JobName._(r'metadata-extraction-queue');
static const videoConversionQueue = JobName._(r'video-conversion-queue');
static const objectTaggingQueue = JobName._(r'object-tagging-queue');
static const recognizeFacesQueue = JobName._(r'recognize-faces-queue');
static const clipEncodingQueue = JobName._(r'clip-encoding-queue');
static const backgroundTaskQueue = JobName._(r'background-task-queue');
static const storageTemplateMigrationQueue = JobName._(r'storage-template-migration-queue');
static const searchQueue = JobName._(r'search-queue');
static const sidecarQueue = JobName._(r'sidecar-queue');
static const thumbnailGeneration = JobName._(r'thumbnailGeneration');
static const metadataExtraction = JobName._(r'metadataExtraction');
static const videoConversion = JobName._(r'videoConversion');
static const objectTagging = JobName._(r'objectTagging');
static const recognizeFaces = JobName._(r'recognizeFaces');
static const clipEncoding = JobName._(r'clipEncoding');
static const backgroundTask = JobName._(r'backgroundTask');
static const storageTemplateMigration = JobName._(r'storageTemplateMigration');
static const search = JobName._(r'search');
static const sidecar = JobName._(r'sidecar');
/// List of all possible values in this [enum][JobName].
static const values = <JobName>[
thumbnailGenerationQueue,
metadataExtractionQueue,
videoConversionQueue,
objectTaggingQueue,
recognizeFacesQueue,
clipEncodingQueue,
backgroundTaskQueue,
storageTemplateMigrationQueue,
searchQueue,
sidecarQueue,
thumbnailGeneration,
metadataExtraction,
videoConversion,
objectTagging,
recognizeFaces,
clipEncoding,
backgroundTask,
storageTemplateMigration,
search,
sidecar,
];
static JobName? fromJson(dynamic value) => JobNameTypeTransformer().decode(value);
@@ -84,16 +84,16 @@ class JobNameTypeTransformer {
JobName? decode(dynamic data, {bool allowNull = true}) {
if (data != null) {
switch (data) {
case r'thumbnail-generation-queue': return JobName.thumbnailGenerationQueue;
case r'metadata-extraction-queue': return JobName.metadataExtractionQueue;
case r'video-conversion-queue': return JobName.videoConversionQueue;
case r'object-tagging-queue': return JobName.objectTaggingQueue;
case r'recognize-faces-queue': return JobName.recognizeFacesQueue;
case r'clip-encoding-queue': return JobName.clipEncodingQueue;
case r'background-task-queue': return JobName.backgroundTaskQueue;
case r'storage-template-migration-queue': return JobName.storageTemplateMigrationQueue;
case r'search-queue': return JobName.searchQueue;
case r'sidecar-queue': return JobName.sidecarQueue;
case r'thumbnailGeneration': return JobName.thumbnailGeneration;
case r'metadataExtraction': return JobName.metadataExtraction;
case r'videoConversion': return JobName.videoConversion;
case r'objectTagging': return JobName.objectTagging;
case r'recognizeFaces': return JobName.recognizeFaces;
case r'clipEncoding': return JobName.clipEncoding;
case r'backgroundTask': return JobName.backgroundTask;
case r'storageTemplateMigration': return JobName.storageTemplateMigration;
case r'search': return JobName.search;
case r'sidecar': return JobName.sidecar;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');

View File

@@ -0,0 +1,109 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class JobSettingsDto {
/// Returns a new [JobSettingsDto] instance.
JobSettingsDto({
required this.concurrency,
});
int concurrency;
@override
bool operator ==(Object other) => identical(this, other) || other is JobSettingsDto &&
other.concurrency == concurrency;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(concurrency.hashCode);
@override
String toString() => 'JobSettingsDto[concurrency=$concurrency]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'concurrency'] = this.concurrency;
return json;
}
/// Returns a new [JobSettingsDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static JobSettingsDto? fromJson(dynamic value) {
if (value is Map) {
final json = value.cast<String, dynamic>();
// Ensure that the map contains the required keys.
// Note 1: the values aren't checked for validity beyond being non-null.
// Note 2: this code is stripped in release mode!
assert(() {
requiredKeys.forEach((key) {
assert(json.containsKey(key), 'Required key "JobSettingsDto[$key]" is missing from JSON.');
assert(json[key] != null, 'Required key "JobSettingsDto[$key]" has a null value in JSON.');
});
return true;
}());
return JobSettingsDto(
concurrency: mapValueOfType<int>(json, r'concurrency')!,
);
}
return null;
}
static List<JobSettingsDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <JobSettingsDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = JobSettingsDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, JobSettingsDto> mapFromJson(dynamic json) {
final map = <String, JobSettingsDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = JobSettingsDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of JobSettingsDto-objects as value to a dart map
static Map<String, List<JobSettingsDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<JobSettingsDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = JobSettingsDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'concurrency',
};
}

View File

@@ -0,0 +1,117 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class MemoryLaneResponseDto {
/// Returns a new [MemoryLaneResponseDto] instance.
MemoryLaneResponseDto({
required this.title,
this.assets = const [],
});
String title;
List<AssetResponseDto> assets;
@override
bool operator ==(Object other) => identical(this, other) || other is MemoryLaneResponseDto &&
other.title == title &&
other.assets == assets;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(title.hashCode) +
(assets.hashCode);
@override
String toString() => 'MemoryLaneResponseDto[title=$title, assets=$assets]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'title'] = this.title;
json[r'assets'] = this.assets;
return json;
}
/// Returns a new [MemoryLaneResponseDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static MemoryLaneResponseDto? fromJson(dynamic value) {
if (value is Map) {
final json = value.cast<String, dynamic>();
// Ensure that the map contains the required keys.
// Note 1: the values aren't checked for validity beyond being non-null.
// Note 2: this code is stripped in release mode!
assert(() {
requiredKeys.forEach((key) {
assert(json.containsKey(key), 'Required key "MemoryLaneResponseDto[$key]" is missing from JSON.');
assert(json[key] != null, 'Required key "MemoryLaneResponseDto[$key]" has a null value in JSON.');
});
return true;
}());
return MemoryLaneResponseDto(
title: mapValueOfType<String>(json, r'title')!,
assets: AssetResponseDto.listFromJson(json[r'assets']),
);
}
return null;
}
static List<MemoryLaneResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <MemoryLaneResponseDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = MemoryLaneResponseDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, MemoryLaneResponseDto> mapFromJson(dynamic json) {
final map = <String, MemoryLaneResponseDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = MemoryLaneResponseDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of MemoryLaneResponseDto-objects as value to a dart map
static Map<String, List<MemoryLaneResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<MemoryLaneResponseDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = MemoryLaneResponseDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'title',
'assets',
};
}

View File

@@ -17,6 +17,7 @@ class SystemConfigDto {
required this.oauth,
required this.passwordLogin,
required this.storageTemplate,
required this.job,
});
SystemConfigFFmpegDto ffmpeg;
@@ -27,12 +28,15 @@ class SystemConfigDto {
SystemConfigStorageTemplateDto storageTemplate;
SystemConfigJobDto job;
@override
bool operator ==(Object other) => identical(this, other) || other is SystemConfigDto &&
other.ffmpeg == ffmpeg &&
other.oauth == oauth &&
other.passwordLogin == passwordLogin &&
other.storageTemplate == storageTemplate;
other.storageTemplate == storageTemplate &&
other.job == job;
@override
int get hashCode =>
@@ -40,10 +44,11 @@ class SystemConfigDto {
(ffmpeg.hashCode) +
(oauth.hashCode) +
(passwordLogin.hashCode) +
(storageTemplate.hashCode);
(storageTemplate.hashCode) +
(job.hashCode);
@override
String toString() => 'SystemConfigDto[ffmpeg=$ffmpeg, oauth=$oauth, passwordLogin=$passwordLogin, storageTemplate=$storageTemplate]';
String toString() => 'SystemConfigDto[ffmpeg=$ffmpeg, oauth=$oauth, passwordLogin=$passwordLogin, storageTemplate=$storageTemplate, job=$job]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
@@ -51,6 +56,7 @@ class SystemConfigDto {
json[r'oauth'] = this.oauth;
json[r'passwordLogin'] = this.passwordLogin;
json[r'storageTemplate'] = this.storageTemplate;
json[r'job'] = this.job;
return json;
}
@@ -77,6 +83,7 @@ class SystemConfigDto {
oauth: SystemConfigOAuthDto.fromJson(json[r'oauth'])!,
passwordLogin: SystemConfigPasswordLoginDto.fromJson(json[r'passwordLogin'])!,
storageTemplate: SystemConfigStorageTemplateDto.fromJson(json[r'storageTemplate'])!,
job: SystemConfigJobDto.fromJson(json[r'job'])!,
);
}
return null;
@@ -128,6 +135,7 @@ class SystemConfigDto {
'oauth',
'passwordLogin',
'storageTemplate',
'job',
};
}

View File

@@ -0,0 +1,181 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class SystemConfigJobDto {
/// Returns a new [SystemConfigJobDto] instance.
SystemConfigJobDto({
required this.thumbnailGeneration,
required this.metadataExtraction,
required this.videoConversion,
required this.objectTagging,
required this.clipEncoding,
required this.storageTemplateMigration,
required this.backgroundTask,
required this.search,
required this.recognizeFaces,
required this.sidecar,
});
JobSettingsDto thumbnailGeneration;
JobSettingsDto metadataExtraction;
JobSettingsDto videoConversion;
JobSettingsDto objectTagging;
JobSettingsDto clipEncoding;
JobSettingsDto storageTemplateMigration;
JobSettingsDto backgroundTask;
JobSettingsDto search;
JobSettingsDto recognizeFaces;
JobSettingsDto sidecar;
@override
bool operator ==(Object other) => identical(this, other) || other is SystemConfigJobDto &&
other.thumbnailGeneration == thumbnailGeneration &&
other.metadataExtraction == metadataExtraction &&
other.videoConversion == videoConversion &&
other.objectTagging == objectTagging &&
other.clipEncoding == clipEncoding &&
other.storageTemplateMigration == storageTemplateMigration &&
other.backgroundTask == backgroundTask &&
other.search == search &&
other.recognizeFaces == recognizeFaces &&
other.sidecar == sidecar;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(thumbnailGeneration.hashCode) +
(metadataExtraction.hashCode) +
(videoConversion.hashCode) +
(objectTagging.hashCode) +
(clipEncoding.hashCode) +
(storageTemplateMigration.hashCode) +
(backgroundTask.hashCode) +
(search.hashCode) +
(recognizeFaces.hashCode) +
(sidecar.hashCode);
@override
String toString() => 'SystemConfigJobDto[thumbnailGeneration=$thumbnailGeneration, metadataExtraction=$metadataExtraction, videoConversion=$videoConversion, objectTagging=$objectTagging, clipEncoding=$clipEncoding, storageTemplateMigration=$storageTemplateMigration, backgroundTask=$backgroundTask, search=$search, recognizeFaces=$recognizeFaces, sidecar=$sidecar]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'thumbnailGeneration'] = this.thumbnailGeneration;
json[r'metadataExtraction'] = this.metadataExtraction;
json[r'videoConversion'] = this.videoConversion;
json[r'objectTagging'] = this.objectTagging;
json[r'clipEncoding'] = this.clipEncoding;
json[r'storageTemplateMigration'] = this.storageTemplateMigration;
json[r'backgroundTask'] = this.backgroundTask;
json[r'search'] = this.search;
json[r'recognizeFaces'] = this.recognizeFaces;
json[r'sidecar'] = this.sidecar;
return json;
}
/// Returns a new [SystemConfigJobDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static SystemConfigJobDto? fromJson(dynamic value) {
if (value is Map) {
final json = value.cast<String, dynamic>();
// Ensure that the map contains the required keys.
// Note 1: the values aren't checked for validity beyond being non-null.
// Note 2: this code is stripped in release mode!
assert(() {
requiredKeys.forEach((key) {
assert(json.containsKey(key), 'Required key "SystemConfigJobDto[$key]" is missing from JSON.');
assert(json[key] != null, 'Required key "SystemConfigJobDto[$key]" has a null value in JSON.');
});
return true;
}());
return SystemConfigJobDto(
thumbnailGeneration: JobSettingsDto.fromJson(json[r'thumbnailGeneration'])!,
metadataExtraction: JobSettingsDto.fromJson(json[r'metadataExtraction'])!,
videoConversion: JobSettingsDto.fromJson(json[r'videoConversion'])!,
objectTagging: JobSettingsDto.fromJson(json[r'objectTagging'])!,
clipEncoding: JobSettingsDto.fromJson(json[r'clipEncoding'])!,
storageTemplateMigration: JobSettingsDto.fromJson(json[r'storageTemplateMigration'])!,
backgroundTask: JobSettingsDto.fromJson(json[r'backgroundTask'])!,
search: JobSettingsDto.fromJson(json[r'search'])!,
recognizeFaces: JobSettingsDto.fromJson(json[r'recognizeFaces'])!,
sidecar: JobSettingsDto.fromJson(json[r'sidecar'])!,
);
}
return null;
}
static List<SystemConfigJobDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <SystemConfigJobDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = SystemConfigJobDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, SystemConfigJobDto> mapFromJson(dynamic json) {
final map = <String, SystemConfigJobDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = SystemConfigJobDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of SystemConfigJobDto-objects as value to a dart map
static Map<String, List<SystemConfigJobDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<SystemConfigJobDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = SystemConfigJobDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'thumbnailGeneration',
'metadataExtraction',
'videoConversion',
'objectTagging',
'clipEncoding',
'storageTemplateMigration',
'backgroundTask',
'search',
'recognizeFaces',
'sidecar',
};
}

View File

@@ -13,54 +13,44 @@ part of openapi.api;
class TagResponseDto {
/// Returns a new [TagResponseDto] instance.
TagResponseDto({
required this.id,
required this.type,
required this.id,
required this.name,
required this.userId,
this.renameTagId,
});
String id;
TagTypeEnum type;
String id;
String name;
String userId;
String? renameTagId;
@override
bool operator ==(Object other) => identical(this, other) || other is TagResponseDto &&
other.id == id &&
other.type == type &&
other.id == id &&
other.name == name &&
other.userId == userId &&
other.renameTagId == renameTagId;
other.userId == userId;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(id.hashCode) +
(type.hashCode) +
(id.hashCode) +
(name.hashCode) +
(userId.hashCode) +
(renameTagId == null ? 0 : renameTagId!.hashCode);
(userId.hashCode);
@override
String toString() => 'TagResponseDto[id=$id, type=$type, name=$name, userId=$userId, renameTagId=$renameTagId]';
String toString() => 'TagResponseDto[type=$type, id=$id, name=$name, userId=$userId]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'id'] = this.id;
json[r'type'] = this.type;
json[r'id'] = this.id;
json[r'name'] = this.name;
json[r'userId'] = this.userId;
if (this.renameTagId != null) {
json[r'renameTagId'] = this.renameTagId;
} else {
// json[r'renameTagId'] = null;
}
return json;
}
@@ -83,11 +73,10 @@ class TagResponseDto {
}());
return TagResponseDto(
id: mapValueOfType<String>(json, r'id')!,
type: TagTypeEnum.fromJson(json[r'type'])!,
id: mapValueOfType<String>(json, r'id')!,
name: mapValueOfType<String>(json, r'name')!,
userId: mapValueOfType<String>(json, r'userId')!,
renameTagId: mapValueOfType<String>(json, r'renameTagId'),
);
}
return null;
@@ -135,8 +124,8 @@ class TagResponseDto {
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'id',
'type',
'id',
'name',
'userId',
};

View File

@@ -14,7 +14,6 @@ class UpdateTagDto {
/// Returns a new [UpdateTagDto] instance.
UpdateTagDto({
this.name,
this.renameTagId,
});
///
@@ -25,27 +24,17 @@ class UpdateTagDto {
///
String? name;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
String? renameTagId;
@override
bool operator ==(Object other) => identical(this, other) || other is UpdateTagDto &&
other.name == name &&
other.renameTagId == renameTagId;
other.name == name;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(name == null ? 0 : name!.hashCode) +
(renameTagId == null ? 0 : renameTagId!.hashCode);
(name == null ? 0 : name!.hashCode);
@override
String toString() => 'UpdateTagDto[name=$name, renameTagId=$renameTagId]';
String toString() => 'UpdateTagDto[name=$name]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
@@ -54,11 +43,6 @@ class UpdateTagDto {
} else {
// json[r'name'] = null;
}
if (this.renameTagId != null) {
json[r'renameTagId'] = this.renameTagId;
} else {
// json[r'renameTagId'] = null;
}
return json;
}
@@ -82,7 +66,6 @@ class UpdateTagDto {
return UpdateTagDto(
name: mapValueOfType<String>(json, r'name'),
renameTagId: mapValueOfType<String>(json, r'renameTagId'),
);
}
return null;

View File

@@ -47,8 +47,8 @@ void main() {
// TODO
});
//Future<AlbumCountResponseDto> getAlbumCountByUserId() async
test('test getAlbumCountByUserId', () async {
//Future<AlbumCountResponseDto> getAlbumCount() async
test('test getAlbumCount', () async {
// TODO
});

View File

@@ -26,8 +26,8 @@ void main() {
// TODO
});
// int sharing
test('to test the property `sharing`', () async {
// int notShared
test('to test the property `notShared`', () async {
// TODO
});

View File

@@ -16,53 +16,53 @@ void main() {
// final instance = AllJobStatusResponseDto();
group('test AllJobStatusResponseDto', () {
// JobStatusDto thumbnailGenerationQueue
test('to test the property `thumbnailGenerationQueue`', () async {
// JobStatusDto thumbnailGeneration
test('to test the property `thumbnailGeneration`', () async {
// TODO
});
// JobStatusDto metadataExtractionQueue
test('to test the property `metadataExtractionQueue`', () async {
// JobStatusDto metadataExtraction
test('to test the property `metadataExtraction`', () async {
// TODO
});
// JobStatusDto videoConversionQueue
test('to test the property `videoConversionQueue`', () async {
// JobStatusDto videoConversion
test('to test the property `videoConversion`', () async {
// TODO
});
// JobStatusDto objectTaggingQueue
test('to test the property `objectTaggingQueue`', () async {
// JobStatusDto objectTagging
test('to test the property `objectTagging`', () async {
// TODO
});
// JobStatusDto clipEncodingQueue
test('to test the property `clipEncodingQueue`', () async {
// JobStatusDto clipEncoding
test('to test the property `clipEncoding`', () async {
// TODO
});
// JobStatusDto storageTemplateMigrationQueue
test('to test the property `storageTemplateMigrationQueue`', () async {
// JobStatusDto storageTemplateMigration
test('to test the property `storageTemplateMigration`', () async {
// TODO
});
// JobStatusDto backgroundTaskQueue
test('to test the property `backgroundTaskQueue`', () async {
// JobStatusDto backgroundTask
test('to test the property `backgroundTask`', () async {
// TODO
});
// JobStatusDto searchQueue
test('to test the property `searchQueue`', () async {
// JobStatusDto search
test('to test the property `search`', () async {
// TODO
});
// JobStatusDto recognizeFacesQueue
test('to test the property `recognizeFacesQueue`', () async {
// JobStatusDto recognizeFaces
test('to test the property `recognizeFaces`', () async {
// TODO
});
// JobStatusDto sidecarQueue
test('to test the property `sidecarQueue`', () async {
// JobStatusDto sidecar
test('to test the property `sidecar`', () async {
// TODO
});

View File

@@ -53,7 +53,7 @@ void main() {
// TODO
});
//Future<MultipartFile> downloadFile(String assetId, { String key }) async
//Future<MultipartFile> downloadFile(String id, { String key }) async
test('test downloadFile', () async {
// TODO
});
@@ -72,7 +72,7 @@ void main() {
// Get all AssetEntity belong to the user
//
//Future<List<AssetResponseDto>> getAllAssets({ String userId, bool isFavorite, bool isArchived, num skip, String ifNoneMatch }) async
//Future<List<AssetResponseDto>> getAllAssets({ String userId, bool isFavorite, bool isArchived, bool withoutThumbs, num skip, String ifNoneMatch }) async
test('test getAllAssets', () async {
// TODO
});
@@ -84,7 +84,7 @@ void main() {
// Get a single asset's information
//
//Future<AssetResponseDto> getAssetById(String assetId, { String key }) async
//Future<AssetResponseDto> getAssetById(String id, { String key }) async
test('test getAssetById', () async {
// TODO
});
@@ -109,7 +109,7 @@ void main() {
// TODO
});
//Future<MultipartFile> getAssetThumbnail(String assetId, { ThumbnailFormat format, String key }) async
//Future<MultipartFile> getAssetThumbnail(String id, { ThumbnailFormat format, String key }) async
test('test getAssetThumbnail', () async {
// TODO
});
@@ -129,6 +129,11 @@ void main() {
// TODO
});
//Future<List<MemoryLaneResponseDto>> getMemoryLane(DateTime timestamp) async
test('test getMemoryLane', () async {
// TODO
});
// Get all asset of a device that are in the database, ID only.
//
//Future<List<String>> getUserAssetsByDeviceId(String deviceId) async
@@ -146,14 +151,14 @@ void main() {
// TODO
});
//Future<MultipartFile> serveFile(String assetId, { bool isThumb, bool isWeb, String key }) async
//Future<MultipartFile> serveFile(String id, { bool isThumb, bool isWeb, String key }) async
test('test serveFile', () async {
// TODO
});
// Update an asset
//
//Future<AssetResponseDto> updateAsset(String assetId, UpdateAssetDto updateAssetDto) async
//Future<AssetResponseDto> updateAsset(String id, UpdateAssetDto updateAssetDto) async
test('test updateAsset', () async {
// TODO
});

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