Compare commits

...

22 Commits

Author SHA1 Message Date
Alex The Bot
86f5ceb80e Version v1.79.0 2023-09-21 14:17:00 +00:00
martin
06959a9ea5 Revert "fix(web): Assigns description text area with asset description if it exists #4073 (#4125)" (#4160)
This reverts commit b6c6a7e403.
2023-09-21 17:57:59 +07:00
Jonathan Jogenfors
acdc66413c feat(server,web): libraries (#3124)
* feat: libraries

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
Co-authored-by: Alex <alex.tran1502@gmail.com>
2023-09-20 13:16:33 +02:00
Louis-Marie Michelin
816db700e1 feat(web): cancel pending requests on image change (#4145) 2023-09-19 23:16:53 -04:00
Mert
9030b1f89f chore(deps): upgrade sharp and libvips (#4140)
* upgrade libvips

* upgrade sharp
2023-09-19 15:11:25 -04:00
Alex
2e0c7abd65 fix(mobile): fix background upload regression on Android (#4139) 2023-09-19 16:39:54 +07:00
Daniel Dietzler
1a633f3fca chore(server): Use access core for person permissions (#4138)
* use access core for all person methods

* minor fixes, feedback

* reorder assignments

* remove unnecessary permission requirement

* unify naming of tests

* reorder variables
2023-09-18 21:22:44 +00:00
Jason Rasmussen
dda735ec51 feat(server): add m4v format (#4135) 2023-09-18 19:39:42 +02:00
Daniel Dietzler
f1c98ac9e6 fix(server): Error when loading album with deleted owner (#4086)
* soft delete albums when user gets soft deleted

* fix wrong intl openapi version

* fix tests

* ability to restore albums, automatically restore when user restored

* (e2e) tests for shared albums via link and with user

* (e2e) test deletion of users and linked albums

* (e2e) fix share album with owner test

* fix: deletedAt

* chore: fix restore order

* fix: use timezone date column

* chore: cleanup e2e tests

* (e2e) fix user delete test

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-09-18 11:56:50 -04:00
Daniel Dietzler
7d07aaeba3 chore(docs): Some SQL queries to copy paste for advanced debugging (#4074)
* enable sql code highlighting, first queries

* remove tabs

* something with moons...

* feat: more queries

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-09-18 14:25:15 +00:00
martin
a0163d8df0 feat(web): swap between people when merging faces (#4089)
* feat: swap between people when merging faces

* rename

* fix: remove url parameter when closing

* chore: handler naming

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-09-18 13:24:31 +00:00
Thomas
49ef86173f fix(server): use exiftool decoded values and unify metadata extraction (#2908)
* refactor(server): metadata extraction

* chore: upgrade exiftool

* chore: remove log

* fix: other rotation cases

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-09-18 09:06:03 -04:00
Russell Tan
b6c6a7e403 fix(web): Assigns description text area with asset description if it exists #4073 (#4125) 2023-09-18 14:21:28 +07:00
Ploonet
672560f55b fix(web): accordion arrow was not anchored to the right (#4129)
Co-authored-by: Antony Mota <amota.confluenceconseil@boiron.fr>
2023-09-18 11:06:11 +07:00
GenericGuy
94cbbf3c4b feat(web): add setting for minimum face count for face detection (#4128)
* feat: add setting for minimum face count for face detection

Adds the minimum face count setting to the web interface to
circumvent detection of strangers and random background people
if desired.

* fix: codestyle, remove max for face count
2023-09-18 11:05:35 +07:00
Daniel Dietzler
40b802a5a9 fix(web): context menu underflowing (#4127) 2023-09-18 11:03:18 +07:00
bo0tzz
a63f027bf7 chore(docs): Fix incorrect header (#4123) 2023-09-18 11:02:28 +07:00
Fynn Petersen-Frey
1c02e1dadf feat(mobile): image caching & viewer improvements (#4095) 2023-09-18 10:57:05 +07:00
Ploonet
63b6a71ebd feat(web): make settings accordion click area larger (#4119)
* feat: make settings accordion click area larger

* fix: svelte check & prettier

---------

Co-authored-by: Antony Mota <amota.confluenceconseil@boiron.fr>
2023-09-17 20:36:34 +07:00
Mert
0a9b632e48 fix(server): use libopus for transcoding (#4102)
* updated audio codec enum

* added migration

* updated api

* fixed enum

* formatting

* simplified migration
2023-09-16 00:52:45 +00:00
martin
7fcc5a5417 feat(web): hide face from detail page (#4098) 2023-09-15 22:58:14 +07:00
Alex
9cec6aaf46 chore: post release tasks 2023-09-14 22:16:15 +07:00
205 changed files with 12384 additions and 1183 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.78.1
* The version of the OpenAPI document: 1.79.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

View File

@@ -4,7 +4,7 @@
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.78.1
* The version of the OpenAPI document: 1.79.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

View File

@@ -4,7 +4,7 @@
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.78.1
* The version of the OpenAPI document: 1.79.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

View File

@@ -4,7 +4,7 @@
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.78.1
* The version of the OpenAPI document: 1.79.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

View File

@@ -56,8 +56,6 @@ The API key can be obtained in the user setting panel on the web interface.
---
## Uploading existing libraries
### Run via Docker
You can run the CLI inside of a docker container to avoid needing to install anything.

View File

@@ -0,0 +1,148 @@
# Libraries
## Overview
Immich supports the creation of libraries which is a top-level asset container. Currently, there are two types of libraries: traditional upload libraries that can sync with a mobile device, and external libraries, that keeps up to date with files on disk. Libraries are different from albums in that an asset can belong to multiple albums but only one library, and deleting a library deletes all assets contained within. As of August 2023, this is a new feature and libraries have a lot of potential for future development beyond what is documented here. This document attempts to describe the current state of libraries.
## The Upload Library
Immich comes preconfigured with an upload library for each user. All assets uploaded to Immich are added to this library. This library can be renamed, but not deleted. The upload library is the only library that can be synced with a mobile device. No items in an upload library is allowed to have the same sha1 hash as another item in the same library in order to prevent duplicates.
## External Libraries
External libraries tracks assets stored outside of immich, i.e. in the file system. Immich will only read data from the files, and will not modify them in any way. Therefore, the delete button is disabled for external assets. When the external library is scanned, immich will read the metadata from the file and create an asset in the library for each image or video file. These items will then be shown in the main timeline, and they will look and behave like any other asset, including viewing on the map, adding to albums, etc.
If a file is modified outside of Immich, the changes will not be reflected in immich until the library is scanned again. There are different ways to scan a library depending on the use case:
- Scan Library Files: This is the default scan method and also the quickest. It will scan all files in the library and add new files to the library. It will notice if any files are missing (see below) but not check existing assets
- Scan All Library Files: Same as above, but will check each existing asset to see if the modification time has changed. If it has, the asset will be updated. Since it has to check each asset, this is slower than Scan Library Files.
- Force Scan All Library Files: Same as above, but will read each asset from disk no matter the modification time. This is useful in some cases where an asset has been modified externally but the modification time has not changed. This is the slowest way to scan because it reads each asset from disk.
:::caution
Due to aggressive caching it can take some time for a refreshed asset to appear correctly in the web view. You need to clear the cache in your browser to see the changes. This is a known issue and will be fixed in a future release. In Chrome, you need to open the developer console with F12, then reload the page with F5, and finally right click on the reload button and select "Empty Cache and Hard Reload".
:::
In external libraries, the file path is used for duplicate detection. This means that if a file is moved to a different location, it will be added as a new asset. If the file is moved back to its original location, it will be added as a new asset. In contrast to upload libraries, two identical files can be uploaded if they are in different locations. This is a deliberate design choice to make Immich reflect the file system as closely as possible. Remember that duplication detection is only done within the same library, so if you have multiple external libraries, the same file can be added to multiple libraries.
:::caution
If you add assets from an external library to an album and then move the asset to another location within the library, the asset will be removed from the album upon rescan. This is because the asset is considered a new asset after the move. This is a known issue and will be fixed in a future release.
:::
### Deleted External Assets
In all above scan methods, Immich will check if any files are missing. This can happen if files are deleted, or if they are on a storage location that is currently unavailable, like a network drive that is not mounted, or a USB drive that has been unplugged. In order to prevent accidental deletion of assets, Immich will not immediately delete an asset from the library if the file is missing. Instead, the asset will be internally marked as offline and will still be visible in the main timeline. If the file is moved back to its original location and the library is scanned again, the asset will be restored.
Finally, files can be deleted from Immich via the `Remove Offline Files` job. Any assets marked as offline will then be removed from Immich. Run this job whenever files have been deleted from the file system and you want to remove them from Immich. Note that a library scan must be performed first to mark the assets as offline.
### Import Paths
External libraries use import paths to determine which files to scan. Each library can have multiple import paths so that files from different locations can be added to the same library. Import paths are scanned recursively, and if a file is in multiple import paths, it will only be added once. If the import paths are edited in a way that an external file is no longer in any import path, it will be removed from the library in the same way a deleted file would. If the file is moved back to an import path, it will be added again as if it was a new file.
### Security Considerations
For security purposes, each Immich user is disallowed to add external files by default. This is to prevent devastating [path traversal attacks](https://owasp.org/www-community/attacks/Path_Traversal). An admin can allow individual users to use external path feature via the `external path` setting found in the admin panel. Without the external path restriction, a user can add any image or video file on the Immich host filesystem to be imported into Immich, potentially allowing sensitive data to be accessed. If you are running Immich as root in your Docker setup (which is the default), all external file reads are done with root privileges. This is particularly dangerous if the Immich host is a shared server.
With the `external path` set, a user is restricted to accessing external files to files or directories within that path. The Immich admin should still be careful not set the external path too generously. For example, `user1` wants to read their photos in to `/home/user1`. A lazy admin sets that user's external path to `/home/` since it "gets the job done". However, that user will then be able to read all photos in `/home/user2/private-photos`, too! Please set the external path as specific as possible. If multiple folders must be added, do this using the docker volume mount feature described below.
### Exclusion Patterns and Scan Settings
By default, all files in the import paths will be added to the library. If there are files that should not be added, exclusion patterns can be used to exclude them. Exclusion patterns are glob patterns are matched against the full file path. If a file matches an exclusion pattern, it will not be added to the library. Exclusion patterns can be added in the Scan Settings page for each library. Under the hood, Immich uses the [glob](https://www.npmjs.com/package/glob) package to match patterns, so please refer to [their documentation](https://github.com/isaacs/node-glob#glob-primer) to see what patterns are supported.
Some basic examples:
- `*.tif` will exclude all files with the extension `.tif`
- `hidden.jpg` will exclude all files named `hidden.jpg`
- `**/Raw/**` will exclude all files in any directory named `Raw`
- `*.(tif,jpg)` will exclude all files with the extension `.tif` or `.jpg`
## Usage
Let's show a concrete example where we add an existing gallery to Immich. Here, we have the following folders we want to add:
- `/home/user/old-pics`: a folder contining childhood photos.
- `/mnt/nas/christmas-trip`: photos from a christmas trip. The subfolder `/mnt/nas/christmas-trip/Raw` contains the raw files directly from the DSLR. We don't want to import the raw files to Immich
- `/mnt/media/videos`: Videos from the same christmas trip.
First, we need to plan how we want to organize the libraries. The christmas trip photos should belong to its own library since we want to exclude the raw files. The videos and old photos can be in the same library since we want to import all files. We could also add all three folders to the same library if there are no files matching the Raw exclusion pattern in the other folders.
### Mount Docker Volumes
`immich-server` and `immich-microservices` containers will need access to the gallery. Modify your docker compose file as follows
```diff title="docker-compose.yml"
immich-server:
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
+ - /mnt/nas/christmas-trip:/mnt/media/christmas-trip:ro
+ - /home/user/old-pics:/mnt/media/old-pics:ro
+ - /mnt/media/videos:/mnt/media/videos:ro
immich-microservices:
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
+ - /mnt/nas/christmas-trip:/mnt/media/christmas-trip:ro
+ - /home/user/old-pics:/mnt/media/old-pics:ro
+ - /mnt/media/videos:/mnt/media/videos:ro
```
:::tip
The `ro` flag at the end only gives read-only access to the volumes. While Immich does not modify files, it's a good practice to mount read-only.
:::
_Remember to bring the container down/up to register the changes. Make sure you can see the mounted path in the container._
### Set External Path
Only an admin can do this.
- Navigate to `Administration > Users` page on the web.
- Click on the user edit button.
- Set `/mnt/media` to be the external path. This folder will only contain the three folders that we want to import, so nothing else can be accessed.
### Create External Libraries
- Click on your user name in the top right corner -> Account Settings
- Click on Libraries
- Click on Create External Library
- Click the drop-down menu on the newly created library
- Click on Rename Library and rename it to "Christmas Trip"
- Click Edit Import Paths
- Click on Add Path
- Enter `/mnt/media/christmas-trip` then click Add
NOTE: We have to use the `/mnt/media/christmas-trip` path and not the `/mnt/nas/christmas-trip` path since all paths have to be what the Docker containers see.
Next, we'll add an exclusion pattern to filter out raw files.
- Click the drop-down menu on the newly christmas library
- Click on Manage
- Click on Scan Settings
- Click on Add Exclusion Pattern
- Enter `**/Raw/**` and click save.
- Click save
- Click the drop-down menu on the newly created library
- Click on Scan Library Files
The christmas trip library will now be scanned in the background. In the meantime, let's add the videos and old photos to another library.
- Click on Create External Library.
:::info Note
If you get an error here, please rename the other external library to something else. This is a bug that will be fixed in a future release.
:::
- Click the drop-down menu on the newly created library
- Click Edit Import Paths
- Click on Add Path
- Enter `/mnt/media/old-pics` then click Add
- Click on Add Path
- Enter `/mnt/media/videos` then click Add
- Click Save
- Click on Scan Library Files
Within seconds, the assets from the old-pics and videos folders should show up in the main timeline.

View File

@@ -0,0 +1,81 @@
# Database Queries
:::danger
Keep in mind that mucking around in the database might set the moon on fire. Avoid modifying the database directly when possible, and always have current backups.
:::
:::tip
Run `docker exec -it immich_postgres psql immich <DB_USERNAME>` to connect to the database via the container directly.
(Replace `<DB_USERNAME>` wit the value from your [`.env` file](/docs/install/environment-variables#database)).
:::
## Assets
:::note
The `"originalFileName"` column is the name of the uploaded file _without_ the extension.
:::
```sql title="Find by original filename"
SELECT * FROM "assets" WHERE "originalFileName" = 'PXL_20230903_232542848';
SELECT * FROM "assets" WHERE "originalFileName" LIKE 'PXL_%'; -- all files starting with PXL_
SELECT * FROM "assets" WHERE "originalFileName" LIKE '%_2023_%'; -- all files with _2023_ in the middle
```
```sql title="Find by path"
SELECT * FROM "assets" WHERE "originalPath" = 'upload/library/admin/2023/2023-09-03/PXL_20230903_232542848.jpg';
SELECT * FROM "assets" WHERE "originalPath" LIKE 'upload/library/admin/2023/%';
```
```sql title="Find by checksum" (sha1)
SELECT encode("checksum", 'hex') FROM "assets";
SELECT * FROM "assets" WHERE "checksum" = decode('69de19c87658c4c15d9cacb9967b8e033bf74dd1', 'hex');
```
```sql title="Live photos"
SELECT * FROM "assets" where "livePhotoVideoId" IS NOT NULL;
```
```sql title="Without metadata"
SELECT "assets".* FROM "exif" LEFT JOIN "assets" ON "assets"."id" = "exif"."assetId" WHERE "exif"."assetId" IS NULL;
```
```sql title="Without thumbnails"
SELECT * FROM "assets" WHERE "assets"."resizePath" IS NULL OR "assets"."webpPath" IS NULL;
```
```sql title="By type"
SELECT * FROM "assets" WHERE "assets"."type" = 'VIDEO';
SELECT * FROM "assets" WHERE "assets"."type" = 'IMAGE';
```
```sql title="Count by type"
SELECT "assets"."type", count(*) FROM "assets" GROUP BY "assets"."type";
```
```sql title="Count by type (per user)"
SELECT
"users"."email", "assets"."type", COUNT(*)
FROM
"assets"
JOIN
"users" ON "assets"."ownerId" = "users"."id"
GROUP BY
"assets"."type", "users"."email"
ORDER BY
"users"."email";
```
## Users
```sql title="List"
SELECT * FROM "users";
```
## System Config
```sql title="Custom settings"
SELECT "key", "value" FROM "system_config";
```
(Only used when not using the [config file](/docs/install/config-file))

View File

@@ -1,7 +1,3 @@
---
sidebar_position: 1
---
# Docker Help
## Containers

View File

@@ -1,4 +1,4 @@
# Hosting the machine-learning service on another system
# Remote Machine Learning
To alleviate [performance issues on low-memory systems](/docs/FAQ.md#why-is-immich-slow-on-low-memory-systems-like-the-raspberry-pi) like the Raspberry Pi, you may also host Immich's machine-learning container on a more powerful system (e.g. your laptop or desktop computer):

View File

@@ -70,7 +70,8 @@ The default configuration looks like this:
"enabled": true,
"modelName": "buffalo_l",
"minScore": 0.7,
"maxDistance": 0.6
"maxDistance": 0.6,
"minFaces": 1
}
},
"oauth": {

View File

@@ -161,6 +161,7 @@ const config = {
prism: {
theme: lightCodeTheme,
darkTheme: darkCodeTheme,
additionalLanguages: ['sql'],
},
image: 'overview/img/feature-panel.png',
}),

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "machine-learning"
version = "1.78.1"
version = "1.79.0"
description = ""
authors = ["Hau Tran <alex.tran1502@gmail.com>"]
readme = "README.md"

View File

@@ -35,8 +35,8 @@ platform :android do
task: 'bundle',
build_type: 'Release',
properties: {
"android.injected.version.code" => 102,
"android.injected.version.name" => "1.78.1",
"android.injected.version.code" => 103,
"android.injected.version.name" => "1.79.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

@@ -5,17 +5,17 @@
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000272">
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000269">
</testcase>
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="7.037177">
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="81.160108">
</testcase>
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="38.375024">
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="39.176668">
</testcase>

View File

@@ -379,7 +379,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 117;
CURRENT_PROJECT_VERSION = 118;
DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@@ -515,7 +515,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 117;
CURRENT_PROJECT_VERSION = 118;
DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@@ -543,7 +543,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 117;
CURRENT_PROJECT_VERSION = 118;
DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;

View File

@@ -59,11 +59,11 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.78.0</string>
<string>1.78.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>117</string>
<string>118</string>
<key>FLTEnableImpeller</key>
<true />
<key>ITSAppUsesNonExemptEncryption</key>

View File

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

View File

@@ -5,32 +5,32 @@
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000273">
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000256">
</testcase>
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="2.39399">
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="7.645306">
</testcase>
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="4.519187">
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="4.669798">
</testcase>
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="2.043276">
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="2.218788">
</testcase>
<testcase classname="fastlane.lanes" name="4: build_app" time="115.309967">
<testcase classname="fastlane.lanes" name="4: build_app" time="97.596654">
</testcase>
<testcase classname="fastlane.lanes" name="5: upload_to_testflight" time="89.848118">
<testcase classname="fastlane.lanes" name="5: upload_to_testflight" time="89.490906">
</testcase>

View File

@@ -13,6 +13,7 @@ import 'package:immich_mobile/modules/backup/models/backup_album.model.dart';
import 'package:immich_mobile/modules/backup/models/duplicated_asset.model.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/routing/tab_navigation_observer.dart';
import 'package:immich_mobile/shared/cache/widgets_binding.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';
@@ -37,7 +38,7 @@ import 'package:logging/logging.dart';
import 'package:path_provider/path_provider.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
ImmichWidgetsBinding();
final db = await loadDb();
await initApp();

View File

@@ -18,6 +18,7 @@ import 'package:immich_mobile/modules/asset_viewer/ui/top_control_app_bar.dart';
import 'package:immich_mobile/modules/asset_viewer/views/video_viewer_page.dart';
import 'package:immich_mobile/modules/backup/providers/manual_upload.provider.dart';
import 'package:immich_mobile/modules/home/ui/upload_dialog.dart';
import 'package:immich_mobile/shared/cache/original_image_provider.dart';
import 'package:immich_mobile/shared/models/store.dart';
import 'package:immich_mobile/modules/home/ui/delete_dialog.dart';
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
@@ -31,8 +32,7 @@ import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/providers/asset.provider.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:immich_mobile/utils/image_url_builder.dart';
import 'package:photo_manager/photo_manager.dart';
import 'package:openapi/api.dart' as api;
import 'package:openapi/api.dart' show ThumbnailFormat;
// ignore: must_be_immutable
class GalleryViewerPage extends HookConsumerWidget {
@@ -51,6 +51,9 @@ class GalleryViewerPage extends HookConsumerWidget {
final PageController controller;
static const jpeg = ThumbnailFormat.JPEG;
static const webp = ThumbnailFormat.WEBP;
@override
Widget build(BuildContext context, WidgetRef ref) {
final settings = ref.watch(appSettingsServiceProvider);
@@ -59,9 +62,9 @@ class GalleryViewerPage extends HookConsumerWidget {
final isZoomed = useState<bool>(false);
final isPlayingMotionVideo = useState(false);
final isPlayingVideo = useState(false);
final progressValue = useState(0.0);
Offset? localPosition;
final authToken = 'Bearer ${Store.get(StoreKey.accessToken)}';
final header = {"Authorization": authToken};
final currentIndex = useState(initialIndex);
final currentAsset = loadAsset(currentIndex.value);
@@ -83,93 +86,52 @@ class GalleryViewerPage extends HookConsumerWidget {
.watch(assetProvider.notifier)
.toggleFavorite([asset], !asset.isFavorite);
/// Thumbnail image of a remote asset. Required asset.isRemote
ImageProvider remoteThumbnailImageProvider(
Asset asset,
api.ThumbnailFormat type,
) {
return CachedNetworkImageProvider(
getThumbnailUrl(
asset,
type: type,
),
cacheKey: getThumbnailCacheKey(
asset,
type: type,
),
headers: {"Authorization": authToken},
);
}
/// Original (large) image of a remote asset. Required asset.isRemote
ImageProvider originalImageProvider(Asset asset) {
return CachedNetworkImageProvider(
getImageUrl(asset),
cacheKey: getImageCacheKey(asset),
headers: {"Authorization": authToken},
);
}
/// Thumbnail image of a local asset. Required asset.isLocal
ImageProvider localThumbnailImageProvider(Asset asset) {
return AssetEntityImageProvider(
asset.local!,
isOriginal: false,
thumbnailSize: ThumbnailSize(
MediaQuery.of(context).size.width.floor(),
MediaQuery.of(context).size.height.floor(),
),
);
}
ImageProvider remoteOriginalProvider(Asset asset) =>
CachedNetworkImageProvider(
getImageUrl(asset),
cacheKey: getImageCacheKey(asset),
headers: header,
);
/// Original (large) image of a local asset. Required asset.isLocal
ImageProvider localImageProvider(Asset asset) {
return AssetEntityImageProvider(
isOriginal: true,
asset.local!,
);
ImageProvider localOriginalProvider(Asset asset) =>
OriginalImageProvider(asset);
ImageProvider finalImageProvider(Asset asset) {
if (ImmichImage.useLocal(asset)) {
return localOriginalProvider(asset);
} else if (isLoadOriginal.value) {
return remoteOriginalProvider(asset);
} else if (isLoadPreview.value) {
return ImmichImage.remoteThumbnailProvider(asset, jpeg, header);
}
return ImmichImage.remoteThumbnailProvider(asset, webp, header);
}
Iterable<ImageProvider> allImageProviders(Asset asset) sync* {
if (ImmichImage.useLocal(asset)) {
yield ImmichImage.localThumbnailProvider(asset);
yield localOriginalProvider(asset);
} else {
yield ImmichImage.remoteThumbnailProvider(asset, webp, header);
if (isLoadPreview.value) {
yield ImmichImage.remoteThumbnailProvider(asset, jpeg, header);
}
if (isLoadOriginal.value) {
yield remoteOriginalProvider(asset);
}
}
}
void precacheNextImage(int index) {
void onError(Object exception, StackTrace? stackTrace) {
// swallow error silently
}
if (index < totalAssets && index >= 0) {
final asset = loadAsset(index);
if (!asset.isRemote ||
asset.isLocal && !Store.get(StoreKey.preferRemoteImage, false)) {
// Preload the local asset
precacheImage(localImageProvider(asset), context);
} else {
onError(Object exception, StackTrace? stackTrace) {
// swallow error silently
}
// Probably load WEBP either way
precacheImage(
remoteThumbnailImageProvider(
asset,
api.ThumbnailFormat.WEBP,
),
context,
onError: onError,
);
if (isLoadPreview.value) {
// Precache the JPEG thumbnail
precacheImage(
remoteThumbnailImageProvider(
asset,
api.ThumbnailFormat.JPEG,
),
context,
onError: onError,
);
}
if (isLoadOriginal.value) {
// Preload the original asset
precacheImage(
originalImageProvider(asset),
context,
onError: onError,
);
}
for (final imageProvider in allImageProviders(asset)) {
precacheImage(imageProvider, context, onError: onError);
}
}
}
@@ -346,7 +308,6 @@ class GalleryViewerPage extends HookConsumerWidget {
activeColor: Colors.white,
inactiveColor: Colors.white.withOpacity(0.75),
onChanged: (position) {
progressValue.value = position;
ref.read(videoPlayerControlsProvider.notifier).position = position;
},
),
@@ -485,27 +446,6 @@ class GalleryViewerPage extends HookConsumerWidget {
}
});
ImageProvider imageProvider(Asset asset) {
if (!asset.isRemote ||
asset.isLocal && !Store.get(StoreKey.preferRemoteImage, false)) {
return localImageProvider(asset);
} else {
if (isLoadOriginal.value) {
return originalImageProvider(asset);
} else if (isLoadPreview.value) {
return remoteThumbnailImageProvider(
asset,
api.ThumbnailFormat.JPEG,
);
} else {
return remoteThumbnailImageProvider(
asset,
api.ThumbnailFormat.WEBP,
);
}
}
}
return Scaffold(
backgroundColor: Colors.black,
body: WillPopScope(
@@ -531,79 +471,51 @@ class GalleryViewerPage extends HookConsumerWidget {
itemCount: totalAssets,
scrollDirection: Axis.horizontal,
onPageChanged: (value) {
// Precache image
if (currentIndex.value < value) {
// Moving forwards, so precache the next asset
precacheNextImage(value + 1);
} else {
// Moving backwards, so precache previous asset
precacheNextImage(value - 1);
}
final next = currentIndex.value < value ? value + 1 : value - 1;
precacheNextImage(next);
currentIndex.value = value;
progressValue.value = 0.0;
HapticFeedback.selectionClick();
},
loadingBuilder: isLoadPreview.value
? (context, event) {
final a = asset();
if (!a.isLocal ||
(a.isRemote &&
Store.get(StoreKey.preferRemoteImage, false))) {
// Use the WEBP Thumbnail as a placeholder for the JPEG thumbnail to achieve
// Three-Stage Loading (WEBP -> JPEG -> Original)
final webPThumbnail = CachedNetworkImage(
imageUrl: getThumbnailUrl(
a,
type: api.ThumbnailFormat.WEBP,
),
cacheKey: getThumbnailCacheKey(
a,
type: api.ThumbnailFormat.WEBP,
),
httpHeaders: {'Authorization': authToken},
progressIndicatorBuilder: (_, __, ___) =>
const Center(
child: ImmichLoadingIndicator(),
),
fadeInDuration: const Duration(milliseconds: 0),
fit: BoxFit.contain,
errorWidget: (context, url, error) =>
const Icon(Icons.image_not_supported_outlined),
);
loadingBuilder: (context, event, index) {
final a = loadAsset(index);
if (ImmichImage.useLocal(a)) {
return Image(
image: ImmichImage.localThumbnailProvider(a),
fit: BoxFit.contain,
);
}
// Use the WEBP Thumbnail as a placeholder for the JPEG thumbnail to achieve
// Three-Stage Loading (WEBP -> JPEG -> Original)
final webPThumbnail = CachedNetworkImage(
imageUrl: getThumbnailUrl(a, type: webp),
cacheKey: getThumbnailCacheKey(a, type: webp),
httpHeaders: header,
progressIndicatorBuilder: (_, __, ___) => const Center(
child: ImmichLoadingIndicator(),
),
fadeInDuration: const Duration(milliseconds: 0),
fit: BoxFit.contain,
errorWidget: (context, url, error) =>
const Icon(Icons.image_not_supported_outlined),
);
if (isLoadOriginal.value) {
// loading the preview in the loadingBuilder only
// makes sense if the original is loaded in the builder
return CachedNetworkImage(
imageUrl: getThumbnailUrl(
a,
type: api.ThumbnailFormat.JPEG,
),
cacheKey: getThumbnailCacheKey(
a,
type: api.ThumbnailFormat.JPEG,
),
httpHeaders: {'Authorization': authToken},
fit: BoxFit.contain,
fadeInDuration: const Duration(milliseconds: 0),
placeholder: (_, __) => webPThumbnail,
errorWidget: (_, __, ___) => webPThumbnail,
);
} else {
return webPThumbnail;
}
} else {
return Image(
image: localThumbnailImageProvider(a),
fit: BoxFit.contain,
);
}
}
: null,
// loading the preview in the loadingBuilder only
// makes sense if the original is loaded in the builder
return isLoadPreview.value && isLoadOriginal.value
? CachedNetworkImage(
imageUrl: getThumbnailUrl(a, type: jpeg),
cacheKey: getThumbnailCacheKey(a, type: jpeg),
httpHeaders: header,
fit: BoxFit.contain,
fadeInDuration: const Duration(milliseconds: 0),
placeholder: (_, __) => webPThumbnail,
errorWidget: (_, __, ___) => webPThumbnail,
)
: webPThumbnail;
},
builder: (context, index) {
final asset = loadAsset(index);
final ImageProvider provider = imageProvider(asset);
final ImageProvider provider = finalImageProvider(asset);
if (asset.isImage && !isPlayingMotionVideo.value) {
return PhotoViewGalleryPageOptions(

View File

@@ -220,7 +220,9 @@ class BackupService {
final List<String> duplicatedAssetIds = [];
// DON'T KNOW WHY BUT THIS HELPS BACKGROUND BACKUP TO WORK ON IOS
await PhotoManager.requestPermissionExtend();
if (Platform.isIOS) {
await PhotoManager.requestPermissionExtend();
}
List<AssetEntity> assetsToUpload = sortAssets
// Upload images before video assets

View File

@@ -0,0 +1,69 @@
import 'package:flutter/painting.dart';
import 'original_image_provider.dart';
/// [ImageCache] that uses two caches for small and large images
/// so that a single large image does not evict all small iamges
final class CustomImageCache implements ImageCache {
final _small = ImageCache();
final _large = ImageCache();
@override
int get maximumSize => _small.maximumSize + _large.maximumSize;
@override
int get maximumSizeBytes => _small.maximumSizeBytes + _large.maximumSizeBytes;
@override
set maximumSize(int value) => _small.maximumSize = value;
@override
set maximumSizeBytes(int value) => _small.maximumSize = value;
@override
void clear() {
_small.clear();
_large.clear();
}
@override
void clearLiveImages() {
_small.clearLiveImages();
_large.clearLiveImages();
}
@override
bool containsKey(Object key) =>
(key is OriginalImageProvider ? _large : _small).containsKey(key);
@override
int get currentSize => _small.currentSize + _large.currentSize;
@override
int get currentSizeBytes => _small.currentSizeBytes + _large.currentSizeBytes;
@override
bool evict(Object key, {bool includeLive = true}) =>
(key is OriginalImageProvider ? _large : _small)
.evict(key, includeLive: includeLive);
@override
int get liveImageCount => _small.liveImageCount + _large.liveImageCount;
@override
int get pendingImageCount =>
_small.pendingImageCount + _large.pendingImageCount;
@override
ImageStreamCompleter? putIfAbsent(
Object key,
ImageStreamCompleter Function() loader, {
ImageErrorListener? onError,
}) =>
(key is OriginalImageProvider ? _large : _small)
.putIfAbsent(key, loader, onError: onError);
@override
ImageCacheStatus statusForKey(Object key) =>
(key is OriginalImageProvider ? _large : _small).statusForKey(key);
}

View File

@@ -0,0 +1,73 @@
import 'dart:async';
import 'dart:io';
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:immich_mobile/shared/models/asset.dart';
/// Loads the original image for local assets
@immutable
final class OriginalImageProvider extends ImageProvider<OriginalImageProvider> {
final Asset asset;
const OriginalImageProvider(this.asset);
@override
Future<OriginalImageProvider> obtainKey(ImageConfiguration configuration) =>
SynchronousFuture<OriginalImageProvider>(this);
@override
ImageStreamCompleter loadImage(
OriginalImageProvider key,
ImageDecoderCallback decode,
) =>
MultiFrameImageStreamCompleter(
codec: _loadAsync(key, decode),
scale: 1.0,
informationCollector: () sync* {
yield ErrorDescription(asset.fileName);
},
);
Future<ui.Codec> _loadAsync(
OriginalImageProvider key,
ImageDecoderCallback decode,
) async {
final ui.ImmutableBuffer buffer;
if (asset.isImage) {
final File? file = await asset.local?.originFile;
if (file == null) {
throw StateError("Opening file for asset ${asset.fileName} failed");
}
try {
buffer = await ui.ImmutableBuffer.fromFilePath(file.path);
} catch (error) {
throw StateError("Loading asset ${asset.fileName} failed");
}
} else {
final thumbBytes = await asset.local?.thumbnailData;
if (thumbBytes == null) {
throw StateError("Loading thumb for video ${asset.fileName} failed");
}
buffer = await ui.ImmutableBuffer.fromUint8List(thumbBytes);
}
try {
final codec = await decode(buffer);
debugPrint("Decoded image ${asset.fileName}");
return codec;
} catch (error) {
throw StateError("Decoding asset ${asset.fileName} failed");
}
}
@override
bool operator ==(Object other) {
if (other is! OriginalImageProvider) return false;
if (identical(this, other)) return true;
return asset == other.asset;
}
@override
int get hashCode => asset.hashCode;
}

View File

@@ -0,0 +1,8 @@
import 'package:flutter/widgets.dart';
import 'custom_image_cache.dart';
final class ImmichWidgetsBinding extends WidgetsFlutterBinding {
@override
ImageCache createImageCache() => CustomImageCache();
}

View File

@@ -178,6 +178,7 @@ class Asset {
@override
bool operator ==(other) {
if (other is! Asset) return false;
if (identical(this, other)) return true;
return id == other.id &&
checksum == other.checksum &&
remoteId == other.remoteId &&

View File

@@ -45,14 +45,9 @@ class ImmichImage extends StatelessWidget {
);
}
final Asset asset = this.asset!;
if (!asset.isRemote ||
(asset.isLocal && !Store.get(StoreKey.preferRemoteImage, false))) {
if (useLocal(asset)) {
return Image(
image: AssetEntityImageProvider(
asset.local!,
isOriginal: false,
thumbnailSize: const ThumbnailSize.square(250), // like server thumbs
),
image: localThumbnailProvider(asset),
width: width,
height: height,
fit: fit,
@@ -148,45 +143,44 @@ class ImmichImage extends StatelessWidget {
);
}
static AssetEntityImageProvider localThumbnailProvider(Asset asset) =>
AssetEntityImageProvider(
asset.local!,
isOriginal: false,
thumbnailSize: const ThumbnailSize.square(250),
);
static CachedNetworkImageProvider remoteThumbnailProvider(
Asset asset,
api.ThumbnailFormat type,
Map<String, String> authHeader,
) =>
CachedNetworkImageProvider(
getThumbnailUrl(asset, type: type),
cacheKey: getThumbnailCacheKey(asset, type: type),
headers: authHeader,
);
/// Precaches this asset for instant load the next time it is shown
static Future<void> precacheAsset(
Asset asset,
BuildContext context, {
type = api.ThumbnailFormat.WEBP,
}) {
final authToken = 'Bearer ${Store.get(StoreKey.accessToken)}';
if (type == api.ThumbnailFormat.WEBP) {
final thumbnailUrl = getThumbnailUrl(asset);
final thumbnailCacheKey = getThumbnailCacheKey(asset);
final thumbnailProvider = CachedNetworkImageProvider(
thumbnailUrl,
cacheKey: thumbnailCacheKey,
headers: {"Authorization": authToken},
);
return precacheImage(thumbnailProvider, context);
}
// Precache the local image
if (!asset.isRemote &&
(asset.isLocal || !Store.get(StoreKey.preferRemoteImage, false))) {
final provider = AssetEntityImageProvider(
asset.local!,
isOriginal: false,
thumbnailSize: const ThumbnailSize.square(250), // like server thumbs
);
return precacheImage(provider, context);
if (useLocal(asset)) {
// Precache the local image
return precacheImage(localThumbnailProvider(asset), context);
} else {
final authToken = 'Bearer ${Store.get(StoreKey.accessToken)}';
// Precache the remote image since we are not using local images
final url = getThumbnailUrl(asset, type: api.ThumbnailFormat.JPEG);
final cacheKey =
getThumbnailCacheKey(asset, type: api.ThumbnailFormat.JPEG);
final provider = CachedNetworkImageProvider(
url,
cacheKey: cacheKey,
headers: {"Authorization": authToken},
return precacheImage(
remoteThumbnailProvider(asset, type, {"Authorization": authToken}),
context,
);
return precacheImage(provider, context);
}
}
static bool useLocal(Asset asset) =>
!asset.isRemote ||
asset.isLocal && !Store.get(StoreKey.preferRemoteImage, false);
}

View File

@@ -235,6 +235,7 @@ class PhotoView extends StatefulWidget {
const PhotoView({
Key? key,
required this.imageProvider,
required this.index,
this.loadingBuilder,
this.backgroundDecoration,
this.wantKeepAlive = false,
@@ -304,6 +305,7 @@ class PhotoView extends StatefulWidget {
imageProvider = null,
gaplessPlayback = false,
loadingBuilder = null,
index = 0,
super(key: key);
/// Given a [imageProvider] it resolves into an zoomable image widget using. It
@@ -419,6 +421,8 @@ class PhotoView extends StatefulWidget {
/// Useful when you want to drag a widget without restrictions.
final bool? enablePanAlways;
final int index;
bool get _isCustomChild {
return child != null;
}
@@ -571,6 +575,7 @@ class _PhotoViewState extends State<PhotoView>
disableGestures: widget.disableGestures,
errorBuilder: widget.errorBuilder,
enablePanAlways: widget.enablePanAlways,
index: widget.index,
);
},
);
@@ -625,7 +630,7 @@ typedef PhotoViewImageDragStartCallback = Function(
PhotoViewControllerValue controllerValue,
);
/// A type definition for a callback when the user drags
/// A type definition for a callback when the user drags
typedef PhotoViewImageDragUpdateCallback = Function(
BuildContext context,
DragUpdateDetails details,
@@ -650,4 +655,5 @@ typedef PhotoViewImageScaleEndCallback = Function(
typedef LoadingBuilder = Widget Function(
BuildContext context,
ImageChunkEvent? event,
int index,
);

View File

@@ -281,6 +281,7 @@ class _PhotoViewGalleryState extends State<PhotoViewGallery> {
)
: PhotoView(
key: ObjectKey(index),
index: index,
imageProvider: pageOption.imageProvider,
loadingBuilder: widget.loadingBuilder,
backgroundDecoration: widget.backgroundDecoration,
@@ -315,7 +316,10 @@ class _PhotoViewGalleryState extends State<PhotoViewGallery> {
);
}
PhotoViewGalleryPageOptions _buildPageOption(BuildContext context, int index) {
PhotoViewGalleryPageOptions _buildPageOption(
BuildContext context,
int index,
) {
if (widget._isBuilder) {
return widget.builder!(context, index);
}

View File

@@ -35,6 +35,7 @@ class ImageWrapper extends StatefulWidget {
required this.disableGestures,
required this.errorBuilder,
required this.enablePanAlways,
required this.index,
}) : super(key: key);
final ImageProvider imageProvider;
@@ -64,6 +65,7 @@ class ImageWrapper extends StatefulWidget {
final FilterQuality? filterQuality;
final bool? disableGestures;
final bool? enablePanAlways;
final int index;
@override
createState() => _ImageWrapperState();
@@ -128,6 +130,7 @@ class _ImageWrapperState extends State<ImageWrapper> {
_lastException = null;
_lastStack = null;
}
synchronousCall ? setupCB() : setState(setupCB);
}
@@ -212,7 +215,7 @@ class _ImageWrapperState extends State<ImageWrapper> {
Widget _buildLoading(BuildContext context) {
if (widget.loadingBuilder != null) {
return widget.loadingBuilder!(context, _loadingProgress);
return widget.loadingBuilder!(context, _loadingProgress, widget.index);
}
return PhotoViewDefaultLoading(

View File

@@ -46,6 +46,7 @@ doc/CheckExistingAssetsResponseDto.md
doc/ClassificationConfig.md
doc/Colorspace.md
doc/CreateAlbumDto.md
doc/CreateLibraryDto.md
doc/CreateProfileImageResponseDto.md
doc/CreateTagDto.md
doc/CreateUserDto.md
@@ -67,6 +68,10 @@ doc/JobCountsDto.md
doc/JobName.md
doc/JobSettingsDto.md
doc/JobStatusDto.md
doc/LibraryApi.md
doc/LibraryResponseDto.md
doc/LibraryStatsResponseDto.md
doc/LibraryType.md
doc/LoginCredentialDto.md
doc/LoginResponseDto.md
doc/LogoutResponseDto.md
@@ -88,6 +93,7 @@ doc/PersonResponseDto.md
doc/PersonUpdateDto.md
doc/QueueStatusDto.md
doc/RecognitionConfig.md
doc/ScanLibraryDto.md
doc/SearchAlbumResponseDto.md
doc/SearchApi.md
doc/SearchAssetDto.md
@@ -134,6 +140,7 @@ doc/TranscodeHWAccel.md
doc/TranscodePolicy.md
doc/UpdateAlbumDto.md
doc/UpdateAssetDto.md
doc/UpdateLibraryDto.md
doc/UpdateTagDto.md
doc/UpdateUserDto.md
doc/UsageByUserDto.md
@@ -150,6 +157,7 @@ lib/api/asset_api.dart
lib/api/audit_api.dart
lib/api/authentication_api.dart
lib/api/job_api.dart
lib/api/library_api.dart
lib/api/o_auth_api.dart
lib/api/partner_api.dart
lib/api/person_api.dart
@@ -205,6 +213,7 @@ lib/model/clip_mode.dart
lib/model/colorspace.dart
lib/model/cq_mode.dart
lib/model/create_album_dto.dart
lib/model/create_library_dto.dart
lib/model/create_profile_image_response_dto.dart
lib/model/create_tag_dto.dart
lib/model/create_user_dto.dart
@@ -225,6 +234,9 @@ 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/library_response_dto.dart
lib/model/library_stats_response_dto.dart
lib/model/library_type.dart
lib/model/login_credential_dto.dart
lib/model/login_response_dto.dart
lib/model/logout_response_dto.dart
@@ -243,6 +255,7 @@ lib/model/person_response_dto.dart
lib/model/person_update_dto.dart
lib/model/queue_status_dto.dart
lib/model/recognition_config.dart
lib/model/scan_library_dto.dart
lib/model/search_album_response_dto.dart
lib/model/search_asset_dto.dart
lib/model/search_asset_response_dto.dart
@@ -284,6 +297,7 @@ lib/model/transcode_hw_accel.dart
lib/model/transcode_policy.dart
lib/model/update_album_dto.dart
lib/model/update_asset_dto.dart
lib/model/update_library_dto.dart
lib/model/update_tag_dto.dart
lib/model/update_user_dto.dart
lib/model/usage_by_user_dto.dart
@@ -335,6 +349,7 @@ test/clip_mode_test.dart
test/colorspace_test.dart
test/cq_mode_test.dart
test/create_album_dto_test.dart
test/create_library_dto_test.dart
test/create_profile_image_response_dto_test.dart
test/create_tag_dto_test.dart
test/create_user_dto_test.dart
@@ -356,6 +371,10 @@ test/job_counts_dto_test.dart
test/job_name_test.dart
test/job_settings_dto_test.dart
test/job_status_dto_test.dart
test/library_api_test.dart
test/library_response_dto_test.dart
test/library_stats_response_dto_test.dart
test/library_type_test.dart
test/login_credential_dto_test.dart
test/login_response_dto_test.dart
test/logout_response_dto_test.dart
@@ -377,6 +396,7 @@ test/person_response_dto_test.dart
test/person_update_dto_test.dart
test/queue_status_dto_test.dart
test/recognition_config_test.dart
test/scan_library_dto_test.dart
test/search_album_response_dto_test.dart
test/search_api_test.dart
test/search_asset_dto_test.dart
@@ -423,6 +443,7 @@ test/transcode_hw_accel_test.dart
test/transcode_policy_test.dart
test/update_album_dto_test.dart
test/update_asset_dto_test.dart
test/update_library_dto_test.dart
test/update_tag_dto_test.dart
test/update_user_dto_test.dart
test/usage_by_user_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.78.1
- API version: 1.79.0
- Build package: org.openapitools.codegen.languages.DartClientCodegen
## Requirements
@@ -124,6 +124,14 @@ Class | Method | HTTP request | Description
*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/{id} |
*LibraryApi* | [**createLibrary**](doc//LibraryApi.md#createlibrary) | **POST** /library |
*LibraryApi* | [**deleteLibrary**](doc//LibraryApi.md#deletelibrary) | **DELETE** /library/{id} |
*LibraryApi* | [**getAllForUser**](doc//LibraryApi.md#getallforuser) | **GET** /library |
*LibraryApi* | [**getLibraryInfo**](doc//LibraryApi.md#getlibraryinfo) | **GET** /library/{id} |
*LibraryApi* | [**getLibraryStatistics**](doc//LibraryApi.md#getlibrarystatistics) | **GET** /library/{id}/statistics |
*LibraryApi* | [**removeOfflineFiles**](doc//LibraryApi.md#removeofflinefiles) | **POST** /library/{id}/removeOffline |
*LibraryApi* | [**scanLibrary**](doc//LibraryApi.md#scanlibrary) | **POST** /library/{id}/scan |
*LibraryApi* | [**updateLibrary**](doc//LibraryApi.md#updatelibrary) | **PUT** /library/{id} |
*OAuthApi* | [**authorizeOAuth**](doc//OAuthApi.md#authorizeoauth) | **POST** /oauth/authorize |
*OAuthApi* | [**callback**](doc//OAuthApi.md#callback) | **POST** /oauth/callback |
*OAuthApi* | [**generateConfig**](doc//OAuthApi.md#generateconfig) | **POST** /oauth/config |
@@ -221,6 +229,7 @@ Class | Method | HTTP request | Description
- [ClassificationConfig](doc//ClassificationConfig.md)
- [Colorspace](doc//Colorspace.md)
- [CreateAlbumDto](doc//CreateAlbumDto.md)
- [CreateLibraryDto](doc//CreateLibraryDto.md)
- [CreateProfileImageResponseDto](doc//CreateProfileImageResponseDto.md)
- [CreateTagDto](doc//CreateTagDto.md)
- [CreateUserDto](doc//CreateUserDto.md)
@@ -241,6 +250,9 @@ Class | Method | HTTP request | Description
- [JobName](doc//JobName.md)
- [JobSettingsDto](doc//JobSettingsDto.md)
- [JobStatusDto](doc//JobStatusDto.md)
- [LibraryResponseDto](doc//LibraryResponseDto.md)
- [LibraryStatsResponseDto](doc//LibraryStatsResponseDto.md)
- [LibraryType](doc//LibraryType.md)
- [LoginCredentialDto](doc//LoginCredentialDto.md)
- [LoginResponseDto](doc//LoginResponseDto.md)
- [LogoutResponseDto](doc//LogoutResponseDto.md)
@@ -259,6 +271,7 @@ Class | Method | HTTP request | Description
- [PersonUpdateDto](doc//PersonUpdateDto.md)
- [QueueStatusDto](doc//QueueStatusDto.md)
- [RecognitionConfig](doc//RecognitionConfig.md)
- [ScanLibraryDto](doc//ScanLibraryDto.md)
- [SearchAlbumResponseDto](doc//SearchAlbumResponseDto.md)
- [SearchAssetDto](doc//SearchAssetDto.md)
- [SearchAssetResponseDto](doc//SearchAssetResponseDto.md)
@@ -300,6 +313,7 @@ Class | Method | HTTP request | Description
- [TranscodePolicy](doc//TranscodePolicy.md)
- [UpdateAlbumDto](doc//UpdateAlbumDto.md)
- [UpdateAssetDto](doc//UpdateAssetDto.md)
- [UpdateLibraryDto](doc//UpdateLibraryDto.md)
- [UpdateTagDto](doc//UpdateTagDto.md)
- [UpdateUserDto](doc//UpdateUserDto.md)
- [UsageByUserDto](doc//UsageByUserDto.md)

View File

@@ -10,6 +10,7 @@ Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**backgroundTask** | [**JobStatusDto**](JobStatusDto.md) | |
**clipEncoding** | [**JobStatusDto**](JobStatusDto.md) | |
**library_** | [**JobStatusDto**](JobStatusDto.md) | |
**metadataExtraction** | [**JobStatusDto**](JobStatusDto.md) | |
**objectTagging** | [**JobStatusDto**](JobStatusDto.md) | |
**recognizeFaces** | [**JobStatusDto**](JobStatusDto.md) | |

View File

@@ -1475,7 +1475,7 @@ 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)
# **uploadFile**
> AssetFileUploadResponseDto uploadFile(assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, key, duration, isArchived, isReadOnly, isVisible, livePhotoData, sidecarData)
> AssetFileUploadResponseDto uploadFile(assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, key, duration, isArchived, isExternal, isOffline, isReadOnly, isVisible, libraryId, livePhotoData, sidecarData)
@@ -1507,13 +1507,16 @@ final isFavorite = true; // bool |
final key = key_example; // String |
final duration = duration_example; // String |
final isArchived = true; // bool |
final isExternal = true; // bool |
final isOffline = true; // bool |
final isReadOnly = true; // bool |
final isVisible = true; // bool |
final libraryId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final livePhotoData = BINARY_DATA_HERE; // MultipartFile |
final sidecarData = BINARY_DATA_HERE; // MultipartFile |
try {
final result = api_instance.uploadFile(assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, key, duration, isArchived, isReadOnly, isVisible, livePhotoData, sidecarData);
final result = api_instance.uploadFile(assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, key, duration, isArchived, isExternal, isOffline, isReadOnly, isVisible, libraryId, livePhotoData, sidecarData);
print(result);
} catch (e) {
print('Exception when calling AssetApi->uploadFile: $e\n');
@@ -1533,8 +1536,11 @@ Name | Type | Description | Notes
**key** | **String**| | [optional]
**duration** | **String**| | [optional]
**isArchived** | **bool**| | [optional]
**isReadOnly** | **bool**| | [optional] [default to false]
**isExternal** | **bool**| | [optional]
**isOffline** | **bool**| | [optional]
**isReadOnly** | **bool**| | [optional]
**isVisible** | **bool**| | [optional]
**libraryId** | **String**| | [optional]
**livePhotoData** | **MultipartFile**| | [optional]
**sidecarData** | **MultipartFile**| | [optional]

View File

@@ -17,7 +17,11 @@ Name | Type | Description | Notes
**fileModifiedAt** | [**DateTime**](DateTime.md) | |
**id** | **String** | |
**isArchived** | **bool** | |
**isExternal** | **bool** | |
**isFavorite** | **bool** | |
**isOffline** | **bool** | |
**isReadOnly** | **bool** | |
**libraryId** | **String** | |
**livePhotoVideoId** | **String** | | [optional]
**originalFileName** | **String** | |
**originalPath** | **String** | |

19
mobile/openapi/doc/CreateLibraryDto.md generated Normal file
View File

@@ -0,0 +1,19 @@
# openapi.model.CreateLibraryDto
## Load the model package
```dart
import 'package:openapi/api.dart';
```
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**exclusionPatterns** | **List<String>** | | [optional] [default to const []]
**importPaths** | **List<String>** | | [optional] [default to const []]
**isVisible** | **bool** | | [optional]
**name** | **String** | | [optional]
**type** | [**LibraryType**](LibraryType.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,9 +15,12 @@ Name | Type | Description | Notes
**fileCreatedAt** | [**DateTime**](DateTime.md) | |
**fileModifiedAt** | [**DateTime**](DateTime.md) | |
**isArchived** | **bool** | | [optional]
**isExternal** | **bool** | | [optional]
**isFavorite** | **bool** | |
**isOffline** | **bool** | | [optional]
**isReadOnly** | **bool** | | [optional] [default to true]
**isVisible** | **bool** | | [optional]
**libraryId** | **String** | | [optional]
**sidecarPath** | **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)

458
mobile/openapi/doc/LibraryApi.md generated Normal file
View File

@@ -0,0 +1,458 @@
# openapi.api.LibraryApi
## Load the API package
```dart
import 'package:openapi/api.dart';
```
All URIs are relative to */api*
Method | HTTP request | Description
------------- | ------------- | -------------
[**createLibrary**](LibraryApi.md#createlibrary) | **POST** /library |
[**deleteLibrary**](LibraryApi.md#deletelibrary) | **DELETE** /library/{id} |
[**getAllForUser**](LibraryApi.md#getallforuser) | **GET** /library |
[**getLibraryInfo**](LibraryApi.md#getlibraryinfo) | **GET** /library/{id} |
[**getLibraryStatistics**](LibraryApi.md#getlibrarystatistics) | **GET** /library/{id}/statistics |
[**removeOfflineFiles**](LibraryApi.md#removeofflinefiles) | **POST** /library/{id}/removeOffline |
[**scanLibrary**](LibraryApi.md#scanlibrary) | **POST** /library/{id}/scan |
[**updateLibrary**](LibraryApi.md#updatelibrary) | **PUT** /library/{id} |
# **createLibrary**
> LibraryResponseDto createLibrary(createLibraryDto)
### 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 = LibraryApi();
final createLibraryDto = CreateLibraryDto(); // CreateLibraryDto |
try {
final result = api_instance.createLibrary(createLibraryDto);
print(result);
} catch (e) {
print('Exception when calling LibraryApi->createLibrary: $e\n');
}
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**createLibraryDto** | [**CreateLibraryDto**](CreateLibraryDto.md)| |
### Return type
[**LibraryResponseDto**](LibraryResponseDto.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)
# **deleteLibrary**
> deleteLibrary(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 = LibraryApi();
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
try {
api_instance.deleteLibrary(id);
} catch (e) {
print('Exception when calling LibraryApi->deleteLibrary: $e\n');
}
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**id** | **String**| |
### Return type
void (empty response body)
### Authorization
[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer)
### HTTP request headers
- **Content-Type**: Not defined
- **Accept**: Not defined
[[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)
# **getAllForUser**
> List<LibraryResponseDto> getAllForUser()
### 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 = LibraryApi();
try {
final result = api_instance.getAllForUser();
print(result);
} catch (e) {
print('Exception when calling LibraryApi->getAllForUser: $e\n');
}
```
### Parameters
This endpoint does not need any parameter.
### Return type
[**List<LibraryResponseDto>**](LibraryResponseDto.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)
# **getLibraryInfo**
> LibraryResponseDto getLibraryInfo(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 = LibraryApi();
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
try {
final result = api_instance.getLibraryInfo(id);
print(result);
} catch (e) {
print('Exception when calling LibraryApi->getLibraryInfo: $e\n');
}
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**id** | **String**| |
### Return type
[**LibraryResponseDto**](LibraryResponseDto.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)
# **getLibraryStatistics**
> LibraryStatsResponseDto getLibraryStatistics(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 = LibraryApi();
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
try {
final result = api_instance.getLibraryStatistics(id);
print(result);
} catch (e) {
print('Exception when calling LibraryApi->getLibraryStatistics: $e\n');
}
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**id** | **String**| |
### Return type
[**LibraryStatsResponseDto**](LibraryStatsResponseDto.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)
# **removeOfflineFiles**
> removeOfflineFiles(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 = LibraryApi();
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
try {
api_instance.removeOfflineFiles(id);
} catch (e) {
print('Exception when calling LibraryApi->removeOfflineFiles: $e\n');
}
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**id** | **String**| |
### Return type
void (empty response body)
### Authorization
[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer)
### HTTP request headers
- **Content-Type**: Not defined
- **Accept**: Not defined
[[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)
# **scanLibrary**
> scanLibrary(id, scanLibraryDto)
### 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 = LibraryApi();
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final scanLibraryDto = ScanLibraryDto(); // ScanLibraryDto |
try {
api_instance.scanLibrary(id, scanLibraryDto);
} catch (e) {
print('Exception when calling LibraryApi->scanLibrary: $e\n');
}
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**id** | **String**| |
**scanLibraryDto** | [**ScanLibraryDto**](ScanLibraryDto.md)| |
### Return type
void (empty response body)
### Authorization
[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer)
### HTTP request headers
- **Content-Type**: application/json
- **Accept**: Not defined
[[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)
# **updateLibrary**
> LibraryResponseDto updateLibrary(id, updateLibraryDto)
### 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 = LibraryApi();
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final updateLibraryDto = UpdateLibraryDto(); // UpdateLibraryDto |
try {
final result = api_instance.updateLibrary(id, updateLibraryDto);
print(result);
} catch (e) {
print('Exception when calling LibraryApi->updateLibrary: $e\n');
}
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**id** | **String**| |
**updateLibraryDto** | [**UpdateLibraryDto**](UpdateLibraryDto.md)| |
### Return type
[**LibraryResponseDto**](LibraryResponseDto.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)

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

@@ -0,0 +1,24 @@
# openapi.model.LibraryResponseDto
## Load the model package
```dart
import 'package:openapi/api.dart';
```
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**assetCount** | **int** | |
**createdAt** | [**DateTime**](DateTime.md) | |
**exclusionPatterns** | **List<String>** | | [default to const []]
**id** | **String** | |
**importPaths** | **List<String>** | | [default to const []]
**name** | **String** | |
**ownerId** | **String** | |
**refreshedAt** | [**DateTime**](DateTime.md) | |
**type** | [**LibraryType**](LibraryType.md) | |
**updatedAt** | [**DateTime**](DateTime.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

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

14
mobile/openapi/doc/LibraryType.md generated Normal file
View File

@@ -0,0 +1,14 @@
# openapi.model.LibraryType
## Load the model package
```dart
import 'package:openapi/api.dart';
```
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
[[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,6 +10,7 @@ Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**enabled** | **bool** | |
**maxDistance** | **int** | |
**minFaces** | **int** | |
**minScore** | **int** | |
**modelName** | **String** | |
**modelType** | [**ModelType**](ModelType.md) | | [optional]

16
mobile/openapi/doc/ScanLibraryDto.md generated Normal file
View File

@@ -0,0 +1,16 @@
# openapi.model.ScanLibraryDto
## Load the model package
```dart
import 'package:openapi/api.dart';
```
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**refreshAllFiles** | **bool** | | [optional] [default to false]
**refreshModifiedFiles** | **bool** | | [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,6 +10,7 @@ Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**backgroundTask** | [**JobSettingsDto**](JobSettingsDto.md) | |
**clipEncoding** | [**JobSettingsDto**](JobSettingsDto.md) | |
**library_** | [**JobSettingsDto**](JobSettingsDto.md) | |
**metadataExtraction** | [**JobSettingsDto**](JobSettingsDto.md) | |
**objectTagging** | [**JobSettingsDto**](JobSettingsDto.md) | |
**recognizeFaces** | [**JobSettingsDto**](JobSettingsDto.md) | |

18
mobile/openapi/doc/UpdateLibraryDto.md generated Normal file
View File

@@ -0,0 +1,18 @@
# openapi.model.UpdateLibraryDto
## Load the model package
```dart
import 'package:openapi/api.dart';
```
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**exclusionPatterns** | **List<String>** | | [optional] [default to const []]
**importPaths** | **List<String>** | | [optional] [default to const []]
**isVisible** | **bool** | | [optional]
**name** | **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

@@ -34,6 +34,7 @@ part 'api/asset_api.dart';
part 'api/audit_api.dart';
part 'api/authentication_api.dart';
part 'api/job_api.dart';
part 'api/library_api.dart';
part 'api/o_auth_api.dart';
part 'api/partner_api.dart';
part 'api/person_api.dart';
@@ -82,6 +83,7 @@ part 'model/check_existing_assets_response_dto.dart';
part 'model/classification_config.dart';
part 'model/colorspace.dart';
part 'model/create_album_dto.dart';
part 'model/create_library_dto.dart';
part 'model/create_profile_image_response_dto.dart';
part 'model/create_tag_dto.dart';
part 'model/create_user_dto.dart';
@@ -102,6 +104,9 @@ 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/library_response_dto.dart';
part 'model/library_stats_response_dto.dart';
part 'model/library_type.dart';
part 'model/login_credential_dto.dart';
part 'model/login_response_dto.dart';
part 'model/logout_response_dto.dart';
@@ -120,6 +125,7 @@ part 'model/person_response_dto.dart';
part 'model/person_update_dto.dart';
part 'model/queue_status_dto.dart';
part 'model/recognition_config.dart';
part 'model/scan_library_dto.dart';
part 'model/search_album_response_dto.dart';
part 'model/search_asset_dto.dart';
part 'model/search_asset_response_dto.dart';
@@ -161,6 +167,7 @@ part 'model/transcode_hw_accel.dart';
part 'model/transcode_policy.dart';
part 'model/update_album_dto.dart';
part 'model/update_asset_dto.dart';
part 'model/update_library_dto.dart';
part 'model/update_tag_dto.dart';
part 'model/update_user_dto.dart';
part 'model/usage_by_user_dto.dart';

View File

@@ -1496,14 +1496,20 @@ class AssetApi {
///
/// * [bool] isArchived:
///
/// * [bool] isExternal:
///
/// * [bool] isOffline:
///
/// * [bool] isReadOnly:
///
/// * [bool] isVisible:
///
/// * [String] libraryId:
///
/// * [MultipartFile] livePhotoData:
///
/// * [MultipartFile] sidecarData:
Future<Response> uploadFileWithHttpInfo(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, bool isFavorite, { String? key, String? duration, bool? isArchived, bool? isReadOnly, bool? isVisible, MultipartFile? livePhotoData, MultipartFile? sidecarData, }) async {
Future<Response> uploadFileWithHttpInfo(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, bool isFavorite, { String? key, String? duration, bool? isArchived, bool? isExternal, bool? isOffline, bool? isReadOnly, bool? isVisible, String? libraryId, MultipartFile? livePhotoData, MultipartFile? sidecarData, }) async {
// ignore: prefer_const_declarations
final path = r'/asset/upload';
@@ -1551,10 +1557,18 @@ class AssetApi {
hasFields = true;
mp.fields[r'isArchived'] = parameterToString(isArchived);
}
if (isExternal != null) {
hasFields = true;
mp.fields[r'isExternal'] = parameterToString(isExternal);
}
if (isFavorite != null) {
hasFields = true;
mp.fields[r'isFavorite'] = parameterToString(isFavorite);
}
if (isOffline != null) {
hasFields = true;
mp.fields[r'isOffline'] = parameterToString(isOffline);
}
if (isReadOnly != null) {
hasFields = true;
mp.fields[r'isReadOnly'] = parameterToString(isReadOnly);
@@ -1563,6 +1577,10 @@ class AssetApi {
hasFields = true;
mp.fields[r'isVisible'] = parameterToString(isVisible);
}
if (libraryId != null) {
hasFields = true;
mp.fields[r'libraryId'] = parameterToString(libraryId);
}
if (livePhotoData != null) {
hasFields = true;
mp.fields[r'livePhotoData'] = livePhotoData.field;
@@ -1608,15 +1626,21 @@ class AssetApi {
///
/// * [bool] isArchived:
///
/// * [bool] isExternal:
///
/// * [bool] isOffline:
///
/// * [bool] isReadOnly:
///
/// * [bool] isVisible:
///
/// * [String] libraryId:
///
/// * [MultipartFile] livePhotoData:
///
/// * [MultipartFile] sidecarData:
Future<AssetFileUploadResponseDto?> uploadFile(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, bool isFavorite, { String? key, String? duration, bool? isArchived, bool? isReadOnly, bool? isVisible, MultipartFile? livePhotoData, MultipartFile? sidecarData, }) async {
final response = await uploadFileWithHttpInfo(assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, key: key, duration: duration, isArchived: isArchived, isReadOnly: isReadOnly, isVisible: isVisible, livePhotoData: livePhotoData, sidecarData: sidecarData, );
Future<AssetFileUploadResponseDto?> uploadFile(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, bool isFavorite, { String? key, String? duration, bool? isArchived, bool? isExternal, bool? isOffline, bool? isReadOnly, bool? isVisible, String? libraryId, MultipartFile? livePhotoData, MultipartFile? sidecarData, }) async {
final response = await uploadFileWithHttpInfo(assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, key: key, duration: duration, isArchived: isArchived, isExternal: isExternal, isOffline: isOffline, isReadOnly: isReadOnly, isVisible: isVisible, libraryId: libraryId, livePhotoData: livePhotoData, sidecarData: sidecarData, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}

381
mobile/openapi/lib/api/library_api.dart generated Normal file
View File

@@ -0,0 +1,381 @@
//
// 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 LibraryApi {
LibraryApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient;
final ApiClient apiClient;
/// Performs an HTTP 'POST /library' operation and returns the [Response].
/// Parameters:
///
/// * [CreateLibraryDto] createLibraryDto (required):
Future<Response> createLibraryWithHttpInfo(CreateLibraryDto createLibraryDto,) async {
// ignore: prefer_const_declarations
final path = r'/library';
// ignore: prefer_final_locals
Object? postBody = createLibraryDto;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>['application/json'];
return apiClient.invokeAPI(
path,
'POST',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Parameters:
///
/// * [CreateLibraryDto] createLibraryDto (required):
Future<LibraryResponseDto?> createLibrary(CreateLibraryDto createLibraryDto,) async {
final response = await createLibraryWithHttpInfo(createLibraryDto,);
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), 'LibraryResponseDto',) as LibraryResponseDto;
}
return null;
}
/// Performs an HTTP 'DELETE /library/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
Future<Response> deleteLibraryWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final path = r'/library/{id}'
.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,
'DELETE',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Parameters:
///
/// * [String] id (required):
Future<void> deleteLibrary(String id,) async {
final response = await deleteLibraryWithHttpInfo(id,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
}
/// Performs an HTTP 'GET /library' operation and returns the [Response].
Future<Response> getAllForUserWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/library';
// 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,
);
}
Future<List<LibraryResponseDto>?> getAllForUser() async {
final response = await getAllForUserWithHttpInfo();
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<LibraryResponseDto>') as List)
.cast<LibraryResponseDto>()
.toList();
}
return null;
}
/// Performs an HTTP 'GET /library/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
Future<Response> getLibraryInfoWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final path = r'/library/{id}'
.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<LibraryResponseDto?> getLibraryInfo(String id,) async {
final response = await getLibraryInfoWithHttpInfo(id,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'LibraryResponseDto',) as LibraryResponseDto;
}
return null;
}
/// Performs an HTTP 'GET /library/{id}/statistics' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
Future<Response> getLibraryStatisticsWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final path = r'/library/{id}/statistics'
.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<LibraryStatsResponseDto?> getLibraryStatistics(String id,) async {
final response = await getLibraryStatisticsWithHttpInfo(id,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'LibraryStatsResponseDto',) as LibraryStatsResponseDto;
}
return null;
}
/// Performs an HTTP 'POST /library/{id}/removeOffline' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
Future<Response> removeOfflineFilesWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final path = r'/library/{id}/removeOffline'
.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,
'POST',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Parameters:
///
/// * [String] id (required):
Future<void> removeOfflineFiles(String id,) async {
final response = await removeOfflineFilesWithHttpInfo(id,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
}
/// Performs an HTTP 'POST /library/{id}/scan' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
///
/// * [ScanLibraryDto] scanLibraryDto (required):
Future<Response> scanLibraryWithHttpInfo(String id, ScanLibraryDto scanLibraryDto,) async {
// ignore: prefer_const_declarations
final path = r'/library/{id}/scan'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
Object? postBody = scanLibraryDto;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>['application/json'];
return apiClient.invokeAPI(
path,
'POST',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Parameters:
///
/// * [String] id (required):
///
/// * [ScanLibraryDto] scanLibraryDto (required):
Future<void> scanLibrary(String id, ScanLibraryDto scanLibraryDto,) async {
final response = await scanLibraryWithHttpInfo(id, scanLibraryDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
}
/// Performs an HTTP 'PUT /library/{id}' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
///
/// * [UpdateLibraryDto] updateLibraryDto (required):
Future<Response> updateLibraryWithHttpInfo(String id, UpdateLibraryDto updateLibraryDto,) async {
// ignore: prefer_const_declarations
final path = r'/library/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
Object? postBody = updateLibraryDto;
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):
///
/// * [UpdateLibraryDto] updateLibraryDto (required):
Future<LibraryResponseDto?> updateLibrary(String id, UpdateLibraryDto updateLibraryDto,) async {
final response = await updateLibraryWithHttpInfo(id, updateLibraryDto,);
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), 'LibraryResponseDto',) as LibraryResponseDto;
}
return null;
}
}

View File

@@ -257,6 +257,8 @@ class ApiClient {
return ColorspaceTypeTransformer().decode(value);
case 'CreateAlbumDto':
return CreateAlbumDto.fromJson(value);
case 'CreateLibraryDto':
return CreateLibraryDto.fromJson(value);
case 'CreateProfileImageResponseDto':
return CreateProfileImageResponseDto.fromJson(value);
case 'CreateTagDto':
@@ -297,6 +299,12 @@ class ApiClient {
return JobSettingsDto.fromJson(value);
case 'JobStatusDto':
return JobStatusDto.fromJson(value);
case 'LibraryResponseDto':
return LibraryResponseDto.fromJson(value);
case 'LibraryStatsResponseDto':
return LibraryStatsResponseDto.fromJson(value);
case 'LibraryType':
return LibraryTypeTypeTransformer().decode(value);
case 'LoginCredentialDto':
return LoginCredentialDto.fromJson(value);
case 'LoginResponseDto':
@@ -333,6 +341,8 @@ class ApiClient {
return QueueStatusDto.fromJson(value);
case 'RecognitionConfig':
return RecognitionConfig.fromJson(value);
case 'ScanLibraryDto':
return ScanLibraryDto.fromJson(value);
case 'SearchAlbumResponseDto':
return SearchAlbumResponseDto.fromJson(value);
case 'SearchAssetDto':
@@ -415,6 +425,8 @@ class ApiClient {
return UpdateAlbumDto.fromJson(value);
case 'UpdateAssetDto':
return UpdateAssetDto.fromJson(value);
case 'UpdateLibraryDto':
return UpdateLibraryDto.fromJson(value);
case 'UpdateTagDto':
return UpdateTagDto.fromJson(value);
case 'UpdateUserDto':

View File

@@ -85,6 +85,9 @@ String parameterToString(dynamic value) {
if (value is JobName) {
return JobNameTypeTransformer().encode(value).toString();
}
if (value is LibraryType) {
return LibraryTypeTypeTransformer().encode(value).toString();
}
if (value is ModelType) {
return ModelTypeTypeTransformer().encode(value).toString();
}

View File

@@ -15,6 +15,7 @@ class AllJobStatusResponseDto {
AllJobStatusResponseDto({
required this.backgroundTask,
required this.clipEncoding,
required this.library_,
required this.metadataExtraction,
required this.objectTagging,
required this.recognizeFaces,
@@ -29,6 +30,8 @@ class AllJobStatusResponseDto {
JobStatusDto clipEncoding;
JobStatusDto library_;
JobStatusDto metadataExtraction;
JobStatusDto objectTagging;
@@ -49,6 +52,7 @@ class AllJobStatusResponseDto {
bool operator ==(Object other) => identical(this, other) || other is AllJobStatusResponseDto &&
other.backgroundTask == backgroundTask &&
other.clipEncoding == clipEncoding &&
other.library_ == library_ &&
other.metadataExtraction == metadataExtraction &&
other.objectTagging == objectTagging &&
other.recognizeFaces == recognizeFaces &&
@@ -63,6 +67,7 @@ class AllJobStatusResponseDto {
// ignore: unnecessary_parenthesis
(backgroundTask.hashCode) +
(clipEncoding.hashCode) +
(library_.hashCode) +
(metadataExtraction.hashCode) +
(objectTagging.hashCode) +
(recognizeFaces.hashCode) +
@@ -73,12 +78,13 @@ class AllJobStatusResponseDto {
(videoConversion.hashCode);
@override
String toString() => 'AllJobStatusResponseDto[backgroundTask=$backgroundTask, clipEncoding=$clipEncoding, metadataExtraction=$metadataExtraction, objectTagging=$objectTagging, recognizeFaces=$recognizeFaces, search=$search, sidecar=$sidecar, storageTemplateMigration=$storageTemplateMigration, thumbnailGeneration=$thumbnailGeneration, videoConversion=$videoConversion]';
String toString() => 'AllJobStatusResponseDto[backgroundTask=$backgroundTask, clipEncoding=$clipEncoding, library_=$library_, metadataExtraction=$metadataExtraction, objectTagging=$objectTagging, recognizeFaces=$recognizeFaces, search=$search, sidecar=$sidecar, storageTemplateMigration=$storageTemplateMigration, thumbnailGeneration=$thumbnailGeneration, videoConversion=$videoConversion]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'backgroundTask'] = this.backgroundTask;
json[r'clipEncoding'] = this.clipEncoding;
json[r'library'] = this.library_;
json[r'metadataExtraction'] = this.metadataExtraction;
json[r'objectTagging'] = this.objectTagging;
json[r'recognizeFaces'] = this.recognizeFaces;
@@ -100,6 +106,7 @@ class AllJobStatusResponseDto {
return AllJobStatusResponseDto(
backgroundTask: JobStatusDto.fromJson(json[r'backgroundTask'])!,
clipEncoding: JobStatusDto.fromJson(json[r'clipEncoding'])!,
library_: JobStatusDto.fromJson(json[r'library'])!,
metadataExtraction: JobStatusDto.fromJson(json[r'metadataExtraction'])!,
objectTagging: JobStatusDto.fromJson(json[r'objectTagging'])!,
recognizeFaces: JobStatusDto.fromJson(json[r'recognizeFaces'])!,
@@ -157,6 +164,7 @@ class AllJobStatusResponseDto {
static const requiredKeys = <String>{
'backgroundTask',
'clipEncoding',
'library',
'metadataExtraction',
'objectTagging',
'recognizeFaces',

View File

@@ -22,7 +22,11 @@ class AssetResponseDto {
required this.fileModifiedAt,
required this.id,
required this.isArchived,
required this.isExternal,
required this.isFavorite,
required this.isOffline,
required this.isReadOnly,
required this.libraryId,
this.livePhotoVideoId,
required this.originalFileName,
required this.originalPath,
@@ -62,8 +66,16 @@ class AssetResponseDto {
bool isArchived;
bool isExternal;
bool isFavorite;
bool isOffline;
bool isReadOnly;
String libraryId;
String? livePhotoVideoId;
String originalFileName;
@@ -112,7 +124,11 @@ class AssetResponseDto {
other.fileModifiedAt == fileModifiedAt &&
other.id == id &&
other.isArchived == isArchived &&
other.isExternal == isExternal &&
other.isFavorite == isFavorite &&
other.isOffline == isOffline &&
other.isReadOnly == isReadOnly &&
other.libraryId == libraryId &&
other.livePhotoVideoId == livePhotoVideoId &&
other.originalFileName == originalFileName &&
other.originalPath == originalPath &&
@@ -138,7 +154,11 @@ class AssetResponseDto {
(fileModifiedAt.hashCode) +
(id.hashCode) +
(isArchived.hashCode) +
(isExternal.hashCode) +
(isFavorite.hashCode) +
(isOffline.hashCode) +
(isReadOnly.hashCode) +
(libraryId.hashCode) +
(livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) +
(originalFileName.hashCode) +
(originalPath.hashCode) +
@@ -153,7 +173,7 @@ class AssetResponseDto {
(updatedAt.hashCode);
@override
String toString() => 'AssetResponseDto[checksum=$checksum, deviceAssetId=$deviceAssetId, deviceId=$deviceId, duration=$duration, exifInfo=$exifInfo, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, id=$id, isArchived=$isArchived, isFavorite=$isFavorite, livePhotoVideoId=$livePhotoVideoId, originalFileName=$originalFileName, originalPath=$originalPath, owner=$owner, ownerId=$ownerId, people=$people, resized=$resized, smartInfo=$smartInfo, tags=$tags, thumbhash=$thumbhash, type=$type, updatedAt=$updatedAt]';
String toString() => 'AssetResponseDto[checksum=$checksum, deviceAssetId=$deviceAssetId, deviceId=$deviceId, duration=$duration, exifInfo=$exifInfo, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, id=$id, isArchived=$isArchived, isExternal=$isExternal, isFavorite=$isFavorite, isOffline=$isOffline, isReadOnly=$isReadOnly, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, originalFileName=$originalFileName, originalPath=$originalPath, owner=$owner, ownerId=$ownerId, people=$people, resized=$resized, smartInfo=$smartInfo, tags=$tags, thumbhash=$thumbhash, type=$type, updatedAt=$updatedAt]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
@@ -170,7 +190,11 @@ class AssetResponseDto {
json[r'fileModifiedAt'] = this.fileModifiedAt.toUtc().toIso8601String();
json[r'id'] = this.id;
json[r'isArchived'] = this.isArchived;
json[r'isExternal'] = this.isExternal;
json[r'isFavorite'] = this.isFavorite;
json[r'isOffline'] = this.isOffline;
json[r'isReadOnly'] = this.isReadOnly;
json[r'libraryId'] = this.libraryId;
if (this.livePhotoVideoId != null) {
json[r'livePhotoVideoId'] = this.livePhotoVideoId;
} else {
@@ -219,7 +243,11 @@ class AssetResponseDto {
fileModifiedAt: mapDateTime(json, r'fileModifiedAt', '')!,
id: mapValueOfType<String>(json, r'id')!,
isArchived: mapValueOfType<bool>(json, r'isArchived')!,
isExternal: mapValueOfType<bool>(json, r'isExternal')!,
isFavorite: mapValueOfType<bool>(json, r'isFavorite')!,
isOffline: mapValueOfType<bool>(json, r'isOffline')!,
isReadOnly: mapValueOfType<bool>(json, r'isReadOnly')!,
libraryId: mapValueOfType<String>(json, r'libraryId')!,
livePhotoVideoId: mapValueOfType<String>(json, r'livePhotoVideoId'),
originalFileName: mapValueOfType<String>(json, r'originalFileName')!,
originalPath: mapValueOfType<String>(json, r'originalPath')!,
@@ -287,7 +315,11 @@ class AssetResponseDto {
'fileModifiedAt',
'id',
'isArchived',
'isExternal',
'isFavorite',
'isOffline',
'isReadOnly',
'libraryId',
'originalFileName',
'originalPath',
'ownerId',

View File

@@ -25,13 +25,13 @@ class AudioCodec {
static const mp3 = AudioCodec._(r'mp3');
static const aac = AudioCodec._(r'aac');
static const opus = AudioCodec._(r'opus');
static const libopus = AudioCodec._(r'libopus');
/// List of all possible values in this [enum][AudioCodec].
static const values = <AudioCodec>[
mp3,
aac,
opus,
libopus,
];
static AudioCodec? fromJson(dynamic value) => AudioCodecTypeTransformer().decode(value);
@@ -72,7 +72,7 @@ class AudioCodecTypeTransformer {
switch (data) {
case r'mp3': return AudioCodec.mp3;
case r'aac': return AudioCodec.aac;
case r'opus': return AudioCodec.opus;
case r'libopus': return AudioCodec.libopus;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');

View File

@@ -0,0 +1,150 @@
//
// 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 CreateLibraryDto {
/// Returns a new [CreateLibraryDto] instance.
CreateLibraryDto({
this.exclusionPatterns = const [],
this.importPaths = const [],
this.isVisible,
this.name,
required this.type,
});
List<String> exclusionPatterns;
List<String> importPaths;
///
/// 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? isVisible;
///
/// 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? name;
LibraryType type;
@override
bool operator ==(Object other) => identical(this, other) || other is CreateLibraryDto &&
other.exclusionPatterns == exclusionPatterns &&
other.importPaths == importPaths &&
other.isVisible == isVisible &&
other.name == name &&
other.type == type;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(exclusionPatterns.hashCode) +
(importPaths.hashCode) +
(isVisible == null ? 0 : isVisible!.hashCode) +
(name == null ? 0 : name!.hashCode) +
(type.hashCode);
@override
String toString() => 'CreateLibraryDto[exclusionPatterns=$exclusionPatterns, importPaths=$importPaths, isVisible=$isVisible, name=$name, type=$type]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'exclusionPatterns'] = this.exclusionPatterns;
json[r'importPaths'] = this.importPaths;
if (this.isVisible != null) {
json[r'isVisible'] = this.isVisible;
} else {
// json[r'isVisible'] = null;
}
if (this.name != null) {
json[r'name'] = this.name;
} else {
// json[r'name'] = null;
}
json[r'type'] = this.type;
return json;
}
/// Returns a new [CreateLibraryDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static CreateLibraryDto? fromJson(dynamic value) {
if (value is Map) {
final json = value.cast<String, dynamic>();
return CreateLibraryDto(
exclusionPatterns: json[r'exclusionPatterns'] is List
? (json[r'exclusionPatterns'] as List).cast<String>()
: const [],
importPaths: json[r'importPaths'] is List
? (json[r'importPaths'] as List).cast<String>()
: const [],
isVisible: mapValueOfType<bool>(json, r'isVisible'),
name: mapValueOfType<String>(json, r'name'),
type: LibraryType.fromJson(json[r'type'])!,
);
}
return null;
}
static List<CreateLibraryDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <CreateLibraryDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = CreateLibraryDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, CreateLibraryDto> mapFromJson(dynamic json) {
final map = <String, CreateLibraryDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = CreateLibraryDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of CreateLibraryDto-objects as value to a dart map
static Map<String, List<CreateLibraryDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<CreateLibraryDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = CreateLibraryDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'type',
};
}

View File

@@ -20,9 +20,12 @@ class ImportAssetDto {
required this.fileCreatedAt,
required this.fileModifiedAt,
this.isArchived,
this.isExternal,
required this.isFavorite,
this.isOffline,
this.isReadOnly = true,
this.isVisible,
this.libraryId,
this.sidecarPath,
});
@@ -52,8 +55,24 @@ class ImportAssetDto {
///
bool? isArchived;
///
/// 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? isExternal;
bool isFavorite;
///
/// 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? isOffline;
bool isReadOnly;
///
@@ -64,6 +83,14 @@ class ImportAssetDto {
///
bool? isVisible;
///
/// 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? libraryId;
///
/// 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
@@ -81,9 +108,12 @@ class ImportAssetDto {
other.fileCreatedAt == fileCreatedAt &&
other.fileModifiedAt == fileModifiedAt &&
other.isArchived == isArchived &&
other.isExternal == isExternal &&
other.isFavorite == isFavorite &&
other.isOffline == isOffline &&
other.isReadOnly == isReadOnly &&
other.isVisible == isVisible &&
other.libraryId == libraryId &&
other.sidecarPath == sidecarPath;
@override
@@ -96,13 +126,16 @@ class ImportAssetDto {
(fileCreatedAt.hashCode) +
(fileModifiedAt.hashCode) +
(isArchived == null ? 0 : isArchived!.hashCode) +
(isExternal == null ? 0 : isExternal!.hashCode) +
(isFavorite.hashCode) +
(isOffline == null ? 0 : isOffline!.hashCode) +
(isReadOnly.hashCode) +
(isVisible == null ? 0 : isVisible!.hashCode) +
(libraryId == null ? 0 : libraryId!.hashCode) +
(sidecarPath == null ? 0 : sidecarPath!.hashCode);
@override
String toString() => 'ImportAssetDto[assetPath=$assetPath, deviceAssetId=$deviceAssetId, deviceId=$deviceId, duration=$duration, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, isArchived=$isArchived, isFavorite=$isFavorite, isReadOnly=$isReadOnly, isVisible=$isVisible, sidecarPath=$sidecarPath]';
String toString() => 'ImportAssetDto[assetPath=$assetPath, deviceAssetId=$deviceAssetId, deviceId=$deviceId, duration=$duration, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, isArchived=$isArchived, isExternal=$isExternal, isFavorite=$isFavorite, isOffline=$isOffline, isReadOnly=$isReadOnly, isVisible=$isVisible, libraryId=$libraryId, sidecarPath=$sidecarPath]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
@@ -120,14 +153,29 @@ class ImportAssetDto {
json[r'isArchived'] = this.isArchived;
} else {
// json[r'isArchived'] = null;
}
if (this.isExternal != null) {
json[r'isExternal'] = this.isExternal;
} else {
// json[r'isExternal'] = null;
}
json[r'isFavorite'] = this.isFavorite;
if (this.isOffline != null) {
json[r'isOffline'] = this.isOffline;
} else {
// json[r'isOffline'] = null;
}
json[r'isReadOnly'] = this.isReadOnly;
if (this.isVisible != null) {
json[r'isVisible'] = this.isVisible;
} else {
// json[r'isVisible'] = null;
}
if (this.libraryId != null) {
json[r'libraryId'] = this.libraryId;
} else {
// json[r'libraryId'] = null;
}
if (this.sidecarPath != null) {
json[r'sidecarPath'] = this.sidecarPath;
} else {
@@ -151,9 +199,12 @@ class ImportAssetDto {
fileCreatedAt: mapDateTime(json, r'fileCreatedAt', '')!,
fileModifiedAt: mapDateTime(json, r'fileModifiedAt', '')!,
isArchived: mapValueOfType<bool>(json, r'isArchived'),
isExternal: mapValueOfType<bool>(json, r'isExternal'),
isFavorite: mapValueOfType<bool>(json, r'isFavorite')!,
isOffline: mapValueOfType<bool>(json, r'isOffline'),
isReadOnly: mapValueOfType<bool>(json, r'isReadOnly') ?? true,
isVisible: mapValueOfType<bool>(json, r'isVisible'),
libraryId: mapValueOfType<String>(json, r'libraryId'),
sidecarPath: mapValueOfType<String>(json, r'sidecarPath'),
);
}

View File

@@ -33,6 +33,7 @@ class JobName {
static const storageTemplateMigration = JobName._(r'storageTemplateMigration');
static const search = JobName._(r'search');
static const sidecar = JobName._(r'sidecar');
static const library_ = JobName._(r'library');
/// List of all possible values in this [enum][JobName].
static const values = <JobName>[
@@ -46,6 +47,7 @@ class JobName {
storageTemplateMigration,
search,
sidecar,
library_,
];
static JobName? fromJson(dynamic value) => JobNameTypeTransformer().decode(value);
@@ -94,6 +96,7 @@ class JobNameTypeTransformer {
case r'storageTemplateMigration': return JobName.storageTemplateMigration;
case r'search': return JobName.search;
case r'sidecar': return JobName.sidecar;
case r'library': return JobName.library_;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');

View File

@@ -0,0 +1,178 @@
//
// 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 LibraryResponseDto {
/// Returns a new [LibraryResponseDto] instance.
LibraryResponseDto({
required this.assetCount,
required this.createdAt,
this.exclusionPatterns = const [],
required this.id,
this.importPaths = const [],
required this.name,
required this.ownerId,
required this.refreshedAt,
required this.type,
required this.updatedAt,
});
int assetCount;
DateTime createdAt;
List<String> exclusionPatterns;
String id;
List<String> importPaths;
String name;
String ownerId;
DateTime? refreshedAt;
LibraryType type;
DateTime updatedAt;
@override
bool operator ==(Object other) => identical(this, other) || other is LibraryResponseDto &&
other.assetCount == assetCount &&
other.createdAt == createdAt &&
other.exclusionPatterns == exclusionPatterns &&
other.id == id &&
other.importPaths == importPaths &&
other.name == name &&
other.ownerId == ownerId &&
other.refreshedAt == refreshedAt &&
other.type == type &&
other.updatedAt == updatedAt;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(assetCount.hashCode) +
(createdAt.hashCode) +
(exclusionPatterns.hashCode) +
(id.hashCode) +
(importPaths.hashCode) +
(name.hashCode) +
(ownerId.hashCode) +
(refreshedAt == null ? 0 : refreshedAt!.hashCode) +
(type.hashCode) +
(updatedAt.hashCode);
@override
String toString() => 'LibraryResponseDto[assetCount=$assetCount, createdAt=$createdAt, exclusionPatterns=$exclusionPatterns, id=$id, importPaths=$importPaths, name=$name, ownerId=$ownerId, refreshedAt=$refreshedAt, type=$type, updatedAt=$updatedAt]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'assetCount'] = this.assetCount;
json[r'createdAt'] = this.createdAt.toUtc().toIso8601String();
json[r'exclusionPatterns'] = this.exclusionPatterns;
json[r'id'] = this.id;
json[r'importPaths'] = this.importPaths;
json[r'name'] = this.name;
json[r'ownerId'] = this.ownerId;
if (this.refreshedAt != null) {
json[r'refreshedAt'] = this.refreshedAt!.toUtc().toIso8601String();
} else {
// json[r'refreshedAt'] = null;
}
json[r'type'] = this.type;
json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String();
return json;
}
/// Returns a new [LibraryResponseDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static LibraryResponseDto? fromJson(dynamic value) {
if (value is Map) {
final json = value.cast<String, dynamic>();
return LibraryResponseDto(
assetCount: mapValueOfType<int>(json, r'assetCount')!,
createdAt: mapDateTime(json, r'createdAt', '')!,
exclusionPatterns: json[r'exclusionPatterns'] is List
? (json[r'exclusionPatterns'] as List).cast<String>()
: const [],
id: mapValueOfType<String>(json, r'id')!,
importPaths: json[r'importPaths'] is List
? (json[r'importPaths'] as List).cast<String>()
: const [],
name: mapValueOfType<String>(json, r'name')!,
ownerId: mapValueOfType<String>(json, r'ownerId')!,
refreshedAt: mapDateTime(json, r'refreshedAt', ''),
type: LibraryType.fromJson(json[r'type'])!,
updatedAt: mapDateTime(json, r'updatedAt', '')!,
);
}
return null;
}
static List<LibraryResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <LibraryResponseDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = LibraryResponseDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, LibraryResponseDto> mapFromJson(dynamic json) {
final map = <String, LibraryResponseDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = LibraryResponseDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of LibraryResponseDto-objects as value to a dart map
static Map<String, List<LibraryResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<LibraryResponseDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = LibraryResponseDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'assetCount',
'createdAt',
'exclusionPatterns',
'id',
'importPaths',
'name',
'ownerId',
'refreshedAt',
'type',
'updatedAt',
};
}

View File

@@ -0,0 +1,122 @@
//
// 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 LibraryStatsResponseDto {
/// Returns a new [LibraryStatsResponseDto] instance.
LibraryStatsResponseDto({
this.photos = 0,
this.total = 0,
this.usage = 0,
this.videos = 0,
});
int photos;
int total;
int usage;
int videos;
@override
bool operator ==(Object other) => identical(this, other) || other is LibraryStatsResponseDto &&
other.photos == photos &&
other.total == total &&
other.usage == usage &&
other.videos == videos;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(photos.hashCode) +
(total.hashCode) +
(usage.hashCode) +
(videos.hashCode);
@override
String toString() => 'LibraryStatsResponseDto[photos=$photos, total=$total, usage=$usage, videos=$videos]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'photos'] = this.photos;
json[r'total'] = this.total;
json[r'usage'] = this.usage;
json[r'videos'] = this.videos;
return json;
}
/// Returns a new [LibraryStatsResponseDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static LibraryStatsResponseDto? fromJson(dynamic value) {
if (value is Map) {
final json = value.cast<String, dynamic>();
return LibraryStatsResponseDto(
photos: mapValueOfType<int>(json, r'photos')!,
total: mapValueOfType<int>(json, r'total')!,
usage: mapValueOfType<int>(json, r'usage')!,
videos: mapValueOfType<int>(json, r'videos')!,
);
}
return null;
}
static List<LibraryStatsResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <LibraryStatsResponseDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = LibraryStatsResponseDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, LibraryStatsResponseDto> mapFromJson(dynamic json) {
final map = <String, LibraryStatsResponseDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = LibraryStatsResponseDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of LibraryStatsResponseDto-objects as value to a dart map
static Map<String, List<LibraryStatsResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<LibraryStatsResponseDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = LibraryStatsResponseDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'photos',
'total',
'usage',
'videos',
};
}

View File

@@ -0,0 +1,85 @@
//
// 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 LibraryType {
/// Instantiate a new enum with the provided [value].
const LibraryType._(this.value);
/// The underlying value of this enum member.
final String value;
@override
String toString() => value;
String toJson() => value;
static const UPLOAD = LibraryType._(r'UPLOAD');
static const EXTERNAL = LibraryType._(r'EXTERNAL');
/// List of all possible values in this [enum][LibraryType].
static const values = <LibraryType>[
UPLOAD,
EXTERNAL,
];
static LibraryType? fromJson(dynamic value) => LibraryTypeTypeTransformer().decode(value);
static List<LibraryType>? listFromJson(dynamic json, {bool growable = false,}) {
final result = <LibraryType>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = LibraryType.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
}
/// Transformation class that can [encode] an instance of [LibraryType] to String,
/// and [decode] dynamic data back to [LibraryType].
class LibraryTypeTypeTransformer {
factory LibraryTypeTypeTransformer() => _instance ??= const LibraryTypeTypeTransformer._();
const LibraryTypeTypeTransformer._();
String encode(LibraryType data) => data.value;
/// Decodes a [dynamic value][data] to a LibraryType.
///
/// 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.
LibraryType? decode(dynamic data, {bool allowNull = true}) {
if (data != null) {
switch (data) {
case r'UPLOAD': return LibraryType.UPLOAD;
case r'EXTERNAL': return LibraryType.EXTERNAL;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');
}
}
}
return null;
}
/// Singleton [LibraryTypeTypeTransformer] instance.
static LibraryTypeTypeTransformer? _instance;
}

View File

@@ -15,6 +15,7 @@ class RecognitionConfig {
RecognitionConfig({
required this.enabled,
required this.maxDistance,
required this.minFaces,
required this.minScore,
required this.modelName,
this.modelType,
@@ -24,6 +25,8 @@ class RecognitionConfig {
int maxDistance;
int minFaces;
int minScore;
String modelName;
@@ -40,6 +43,7 @@ class RecognitionConfig {
bool operator ==(Object other) => identical(this, other) || other is RecognitionConfig &&
other.enabled == enabled &&
other.maxDistance == maxDistance &&
other.minFaces == minFaces &&
other.minScore == minScore &&
other.modelName == modelName &&
other.modelType == modelType;
@@ -49,17 +53,19 @@ class RecognitionConfig {
// ignore: unnecessary_parenthesis
(enabled.hashCode) +
(maxDistance.hashCode) +
(minFaces.hashCode) +
(minScore.hashCode) +
(modelName.hashCode) +
(modelType == null ? 0 : modelType!.hashCode);
@override
String toString() => 'RecognitionConfig[enabled=$enabled, maxDistance=$maxDistance, minScore=$minScore, modelName=$modelName, modelType=$modelType]';
String toString() => 'RecognitionConfig[enabled=$enabled, maxDistance=$maxDistance, minFaces=$minFaces, minScore=$minScore, modelName=$modelName, modelType=$modelType]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'enabled'] = this.enabled;
json[r'maxDistance'] = this.maxDistance;
json[r'minFaces'] = this.minFaces;
json[r'minScore'] = this.minScore;
json[r'modelName'] = this.modelName;
if (this.modelType != null) {
@@ -80,6 +86,7 @@ class RecognitionConfig {
return RecognitionConfig(
enabled: mapValueOfType<bool>(json, r'enabled')!,
maxDistance: mapValueOfType<int>(json, r'maxDistance')!,
minFaces: mapValueOfType<int>(json, r'minFaces')!,
minScore: mapValueOfType<int>(json, r'minScore')!,
modelName: mapValueOfType<String>(json, r'modelName')!,
modelType: ModelType.fromJson(json[r'modelType']),
@@ -132,6 +139,7 @@ class RecognitionConfig {
static const requiredKeys = <String>{
'enabled',
'maxDistance',
'minFaces',
'minScore',
'modelName',
};

View File

@@ -0,0 +1,114 @@
//
// 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 ScanLibraryDto {
/// Returns a new [ScanLibraryDto] instance.
ScanLibraryDto({
this.refreshAllFiles = false,
this.refreshModifiedFiles,
});
bool refreshAllFiles;
///
/// 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? refreshModifiedFiles;
@override
bool operator ==(Object other) => identical(this, other) || other is ScanLibraryDto &&
other.refreshAllFiles == refreshAllFiles &&
other.refreshModifiedFiles == refreshModifiedFiles;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(refreshAllFiles.hashCode) +
(refreshModifiedFiles == null ? 0 : refreshModifiedFiles!.hashCode);
@override
String toString() => 'ScanLibraryDto[refreshAllFiles=$refreshAllFiles, refreshModifiedFiles=$refreshModifiedFiles]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'refreshAllFiles'] = this.refreshAllFiles;
if (this.refreshModifiedFiles != null) {
json[r'refreshModifiedFiles'] = this.refreshModifiedFiles;
} else {
// json[r'refreshModifiedFiles'] = null;
}
return json;
}
/// Returns a new [ScanLibraryDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static ScanLibraryDto? fromJson(dynamic value) {
if (value is Map) {
final json = value.cast<String, dynamic>();
return ScanLibraryDto(
refreshAllFiles: mapValueOfType<bool>(json, r'refreshAllFiles') ?? false,
refreshModifiedFiles: mapValueOfType<bool>(json, r'refreshModifiedFiles'),
);
}
return null;
}
static List<ScanLibraryDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <ScanLibraryDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = ScanLibraryDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, ScanLibraryDto> mapFromJson(dynamic json) {
final map = <String, ScanLibraryDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = ScanLibraryDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of ScanLibraryDto-objects as value to a dart map
static Map<String, List<ScanLibraryDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<ScanLibraryDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = ScanLibraryDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
};
}

View File

@@ -15,6 +15,7 @@ class SystemConfigJobDto {
SystemConfigJobDto({
required this.backgroundTask,
required this.clipEncoding,
required this.library_,
required this.metadataExtraction,
required this.objectTagging,
required this.recognizeFaces,
@@ -29,6 +30,8 @@ class SystemConfigJobDto {
JobSettingsDto clipEncoding;
JobSettingsDto library_;
JobSettingsDto metadataExtraction;
JobSettingsDto objectTagging;
@@ -49,6 +52,7 @@ class SystemConfigJobDto {
bool operator ==(Object other) => identical(this, other) || other is SystemConfigJobDto &&
other.backgroundTask == backgroundTask &&
other.clipEncoding == clipEncoding &&
other.library_ == library_ &&
other.metadataExtraction == metadataExtraction &&
other.objectTagging == objectTagging &&
other.recognizeFaces == recognizeFaces &&
@@ -63,6 +67,7 @@ class SystemConfigJobDto {
// ignore: unnecessary_parenthesis
(backgroundTask.hashCode) +
(clipEncoding.hashCode) +
(library_.hashCode) +
(metadataExtraction.hashCode) +
(objectTagging.hashCode) +
(recognizeFaces.hashCode) +
@@ -73,12 +78,13 @@ class SystemConfigJobDto {
(videoConversion.hashCode);
@override
String toString() => 'SystemConfigJobDto[backgroundTask=$backgroundTask, clipEncoding=$clipEncoding, metadataExtraction=$metadataExtraction, objectTagging=$objectTagging, recognizeFaces=$recognizeFaces, search=$search, sidecar=$sidecar, storageTemplateMigration=$storageTemplateMigration, thumbnailGeneration=$thumbnailGeneration, videoConversion=$videoConversion]';
String toString() => 'SystemConfigJobDto[backgroundTask=$backgroundTask, clipEncoding=$clipEncoding, library_=$library_, metadataExtraction=$metadataExtraction, objectTagging=$objectTagging, recognizeFaces=$recognizeFaces, search=$search, sidecar=$sidecar, storageTemplateMigration=$storageTemplateMigration, thumbnailGeneration=$thumbnailGeneration, videoConversion=$videoConversion]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'backgroundTask'] = this.backgroundTask;
json[r'clipEncoding'] = this.clipEncoding;
json[r'library'] = this.library_;
json[r'metadataExtraction'] = this.metadataExtraction;
json[r'objectTagging'] = this.objectTagging;
json[r'recognizeFaces'] = this.recognizeFaces;
@@ -100,6 +106,7 @@ class SystemConfigJobDto {
return SystemConfigJobDto(
backgroundTask: JobSettingsDto.fromJson(json[r'backgroundTask'])!,
clipEncoding: JobSettingsDto.fromJson(json[r'clipEncoding'])!,
library_: JobSettingsDto.fromJson(json[r'library'])!,
metadataExtraction: JobSettingsDto.fromJson(json[r'metadataExtraction'])!,
objectTagging: JobSettingsDto.fromJson(json[r'objectTagging'])!,
recognizeFaces: JobSettingsDto.fromJson(json[r'recognizeFaces'])!,
@@ -157,6 +164,7 @@ class SystemConfigJobDto {
static const requiredKeys = <String>{
'backgroundTask',
'clipEncoding',
'library',
'metadataExtraction',
'objectTagging',
'recognizeFaces',

View File

@@ -0,0 +1,142 @@
//
// 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 UpdateLibraryDto {
/// Returns a new [UpdateLibraryDto] instance.
UpdateLibraryDto({
this.exclusionPatterns = const [],
this.importPaths = const [],
this.isVisible,
this.name,
});
List<String> exclusionPatterns;
List<String> importPaths;
///
/// 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? isVisible;
///
/// 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? name;
@override
bool operator ==(Object other) => identical(this, other) || other is UpdateLibraryDto &&
other.exclusionPatterns == exclusionPatterns &&
other.importPaths == importPaths &&
other.isVisible == isVisible &&
other.name == name;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(exclusionPatterns.hashCode) +
(importPaths.hashCode) +
(isVisible == null ? 0 : isVisible!.hashCode) +
(name == null ? 0 : name!.hashCode);
@override
String toString() => 'UpdateLibraryDto[exclusionPatterns=$exclusionPatterns, importPaths=$importPaths, isVisible=$isVisible, name=$name]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'exclusionPatterns'] = this.exclusionPatterns;
json[r'importPaths'] = this.importPaths;
if (this.isVisible != null) {
json[r'isVisible'] = this.isVisible;
} else {
// json[r'isVisible'] = null;
}
if (this.name != null) {
json[r'name'] = this.name;
} else {
// json[r'name'] = null;
}
return json;
}
/// Returns a new [UpdateLibraryDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static UpdateLibraryDto? fromJson(dynamic value) {
if (value is Map) {
final json = value.cast<String, dynamic>();
return UpdateLibraryDto(
exclusionPatterns: json[r'exclusionPatterns'] is List
? (json[r'exclusionPatterns'] as List).cast<String>()
: const [],
importPaths: json[r'importPaths'] is List
? (json[r'importPaths'] as List).cast<String>()
: const [],
isVisible: mapValueOfType<bool>(json, r'isVisible'),
name: mapValueOfType<String>(json, r'name'),
);
}
return null;
}
static List<UpdateLibraryDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <UpdateLibraryDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = UpdateLibraryDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, UpdateLibraryDto> mapFromJson(dynamic json) {
final map = <String, UpdateLibraryDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = UpdateLibraryDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of UpdateLibraryDto-objects as value to a dart map
static Map<String, List<UpdateLibraryDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<UpdateLibraryDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = UpdateLibraryDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
};
}

View File

@@ -26,6 +26,11 @@ void main() {
// TODO
});
// JobStatusDto library_
test('to test the property `library_`', () async {
// TODO
});
// JobStatusDto metadataExtraction
test('to test the property `metadataExtraction`', () async {
// TODO

View File

@@ -154,7 +154,7 @@ void main() {
// TODO
});
//Future<AssetFileUploadResponseDto> uploadFile(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, bool isFavorite, { String key, String duration, bool isArchived, bool isReadOnly, bool isVisible, MultipartFile livePhotoData, MultipartFile sidecarData }) async
//Future<AssetFileUploadResponseDto> uploadFile(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, bool isFavorite, { String key, String duration, bool isArchived, bool isExternal, bool isOffline, bool isReadOnly, bool isVisible, String libraryId, MultipartFile livePhotoData, MultipartFile sidecarData }) async
test('test uploadFile', () async {
// TODO
});

View File

@@ -62,11 +62,31 @@ void main() {
// TODO
});
// bool isExternal
test('to test the property `isExternal`', () async {
// TODO
});
// bool isFavorite
test('to test the property `isFavorite`', () async {
// TODO
});
// bool isOffline
test('to test the property `isOffline`', () async {
// TODO
});
// bool isReadOnly
test('to test the property `isReadOnly`', () async {
// TODO
});
// String libraryId
test('to test the property `libraryId`', () async {
// TODO
});
// String livePhotoVideoId
test('to test the property `livePhotoVideoId`', () async {
// TODO

View File

@@ -0,0 +1,47 @@
//
// 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
import 'package:openapi/api.dart';
import 'package:test/test.dart';
// tests for CreateLibraryDto
void main() {
// final instance = CreateLibraryDto();
group('test CreateLibraryDto', () {
// List<String> exclusionPatterns (default value: const [])
test('to test the property `exclusionPatterns`', () async {
// TODO
});
// List<String> importPaths (default value: const [])
test('to test the property `importPaths`', () async {
// TODO
});
// bool isVisible
test('to test the property `isVisible`', () async {
// TODO
});
// String name
test('to test the property `name`', () async {
// TODO
});
// LibraryType type
test('to test the property `type`', () async {
// TODO
});
});
}

View File

@@ -51,11 +51,21 @@ void main() {
// TODO
});
// bool isExternal
test('to test the property `isExternal`', () async {
// TODO
});
// bool isFavorite
test('to test the property `isFavorite`', () async {
// TODO
});
// bool isOffline
test('to test the property `isOffline`', () async {
// TODO
});
// bool isReadOnly (default value: true)
test('to test the property `isReadOnly`', () async {
// TODO
@@ -66,6 +76,11 @@ void main() {
// TODO
});
// String libraryId
test('to test the property `libraryId`', () async {
// TODO
});
// String sidecarPath
test('to test the property `sidecarPath`', () async {
// TODO

View File

@@ -0,0 +1,61 @@
//
// 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
import 'package:openapi/api.dart';
import 'package:test/test.dart';
/// tests for LibraryApi
void main() {
// final instance = LibraryApi();
group('tests for LibraryApi', () {
//Future<LibraryResponseDto> createLibrary(CreateLibraryDto createLibraryDto) async
test('test createLibrary', () async {
// TODO
});
//Future deleteLibrary(String id) async
test('test deleteLibrary', () async {
// TODO
});
//Future<List<LibraryResponseDto>> getAllForUser() async
test('test getAllForUser', () async {
// TODO
});
//Future<LibraryResponseDto> getLibraryInfo(String id) async
test('test getLibraryInfo', () async {
// TODO
});
//Future<LibraryStatsResponseDto> getLibraryStatistics(String id) async
test('test getLibraryStatistics', () async {
// TODO
});
//Future removeOfflineFiles(String id) async
test('test removeOfflineFiles', () async {
// TODO
});
//Future scanLibrary(String id, ScanLibraryDto scanLibraryDto) async
test('test scanLibrary', () async {
// TODO
});
//Future<LibraryResponseDto> updateLibrary(String id, UpdateLibraryDto updateLibraryDto) async
test('test updateLibrary', () async {
// TODO
});
});
}

View File

@@ -0,0 +1,72 @@
//
// 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
import 'package:openapi/api.dart';
import 'package:test/test.dart';
// tests for LibraryResponseDto
void main() {
// final instance = LibraryResponseDto();
group('test LibraryResponseDto', () {
// int assetCount
test('to test the property `assetCount`', () async {
// TODO
});
// DateTime createdAt
test('to test the property `createdAt`', () async {
// TODO
});
// List<String> exclusionPatterns (default value: const [])
test('to test the property `exclusionPatterns`', () async {
// TODO
});
// String id
test('to test the property `id`', () async {
// TODO
});
// List<String> importPaths (default value: const [])
test('to test the property `importPaths`', () async {
// TODO
});
// String name
test('to test the property `name`', () async {
// TODO
});
// String ownerId
test('to test the property `ownerId`', () async {
// TODO
});
// DateTime refreshedAt
test('to test the property `refreshedAt`', () async {
// TODO
});
// LibraryType type
test('to test the property `type`', () async {
// TODO
});
// DateTime updatedAt
test('to test the property `updatedAt`', () async {
// TODO
});
});
}

View File

@@ -0,0 +1,42 @@
//
// 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
import 'package:openapi/api.dart';
import 'package:test/test.dart';
// tests for LibraryStatsResponseDto
void main() {
// final instance = LibraryStatsResponseDto();
group('test LibraryStatsResponseDto', () {
// int photos (default value: 0)
test('to test the property `photos`', () async {
// TODO
});
// int total (default value: 0)
test('to test the property `total`', () async {
// TODO
});
// int usage (default value: 0)
test('to test the property `usage`', () async {
// TODO
});
// int videos (default value: 0)
test('to test the property `videos`', () async {
// TODO
});
});
}

View File

@@ -0,0 +1,21 @@
//
// 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
import 'package:openapi/api.dart';
import 'package:test/test.dart';
// tests for LibraryType
void main() {
group('test LibraryType', () {
});
}

View File

@@ -26,6 +26,11 @@ void main() {
// TODO
});
// int minFaces
test('to test the property `minFaces`', () async {
// TODO
});
// int minScore
test('to test the property `minScore`', () async {
// TODO

View File

@@ -0,0 +1,32 @@
//
// 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
import 'package:openapi/api.dart';
import 'package:test/test.dart';
// tests for ScanLibraryDto
void main() {
// final instance = ScanLibraryDto();
group('test ScanLibraryDto', () {
// bool refreshAllFiles (default value: false)
test('to test the property `refreshAllFiles`', () async {
// TODO
});
// bool refreshModifiedFiles
test('to test the property `refreshModifiedFiles`', () async {
// TODO
});
});
}

View File

@@ -26,6 +26,11 @@ void main() {
// TODO
});
// JobSettingsDto library_
test('to test the property `library_`', () async {
// TODO
});
// JobSettingsDto metadataExtraction
test('to test the property `metadataExtraction`', () async {
// TODO

View File

@@ -0,0 +1,42 @@
//
// 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
import 'package:openapi/api.dart';
import 'package:test/test.dart';
// tests for UpdateLibraryDto
void main() {
// final instance = UpdateLibraryDto();
group('test UpdateLibraryDto', () {
// List<String> exclusionPatterns (default value: const [])
test('to test the property `exclusionPatterns`', () async {
// TODO
});
// List<String> importPaths (default value: const [])
test('to test the property `importPaths`', () async {
// TODO
});
// bool isVisible
test('to test the property `isVisible`', () async {
// TODO
});
// String name
test('to test the property `name`', () async {
// TODO
});
});
}

View File

@@ -2,7 +2,7 @@ name: immich_mobile
description: Immich - selfhosted backup media file on mobile phone
publish_to: "none"
version: 1.78.1+102
version: 1.79.0+103
isar_version: &isar_version 3.1.0+1
environment:

View File

@@ -12,8 +12,8 @@
},
{
"name": "libvips",
"version": "8.14.3",
"sha256": "f884d61a6b54c99cdae855001c8b9523e13b4982be7e76cac03faccb91be105c"
"version": "8.14.5",
"sha256": "90374e9f6fbd5657b5faf306cacda20658d6144d385316b59b865bc1a487b68d"
},
{
"name": "ffmpeg",

View File

@@ -2476,6 +2476,328 @@
]
}
},
"/library": {
"get": {
"operationId": "getAllForUser",
"parameters": [],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/LibraryResponseDto"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Library"
]
},
"post": {
"operationId": "createLibrary",
"parameters": [],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateLibraryDto"
}
}
},
"required": true
},
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LibraryResponseDto"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Library"
]
}
},
"/library/{id}": {
"delete": {
"operationId": "deleteLibrary",
"parameters": [
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Library"
]
},
"get": {
"operationId": "getLibraryInfo",
"parameters": [
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LibraryResponseDto"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Library"
]
},
"put": {
"operationId": "updateLibrary",
"parameters": [
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateLibraryDto"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LibraryResponseDto"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Library"
]
}
},
"/library/{id}/removeOffline": {
"post": {
"operationId": "removeOfflineFiles",
"parameters": [
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"201": {
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Library"
]
}
},
"/library/{id}/scan": {
"post": {
"operationId": "scanLibrary",
"parameters": [
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScanLibraryDto"
}
}
},
"required": true
},
"responses": {
"201": {
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Library"
]
}
},
"/library/{id}/statistics": {
"get": {
"operationId": "getLibraryStatistics",
"parameters": [
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LibraryStatsResponseDto"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Library"
]
}
},
"/oauth/authorize": {
"post": {
"operationId": "authorizeOAuth",
@@ -4733,7 +5055,7 @@
"info": {
"title": "Immich",
"description": "Immich API",
"version": "1.78.1",
"version": "1.79.0",
"contact": {}
},
"tags": [],
@@ -4971,6 +5293,9 @@
"clipEncoding": {
"$ref": "#/components/schemas/JobStatusDto"
},
"library": {
"$ref": "#/components/schemas/JobStatusDto"
},
"metadataExtraction": {
"$ref": "#/components/schemas/JobStatusDto"
},
@@ -5006,7 +5331,8 @@
"backgroundTask",
"search",
"recognizeFaces",
"sidecar"
"sidecar",
"library"
],
"type": "object"
},
@@ -5216,9 +5542,21 @@
"isArchived": {
"type": "boolean"
},
"isExternal": {
"type": "boolean"
},
"isFavorite": {
"type": "boolean"
},
"isOffline": {
"type": "boolean"
},
"isReadOnly": {
"type": "boolean"
},
"libraryId": {
"type": "string"
},
"livePhotoVideoId": {
"nullable": true,
"type": "string"
@@ -5272,6 +5610,7 @@
"deviceAssetId",
"deviceId",
"ownerId",
"libraryId",
"originalPath",
"originalFileName",
"resized",
@@ -5281,6 +5620,9 @@
"updatedAt",
"isFavorite",
"isArchived",
"isOffline",
"isExternal",
"isReadOnly",
"duration",
"checksum"
],
@@ -5318,7 +5660,7 @@
"enum": [
"mp3",
"aac",
"opus"
"libopus"
],
"type": "string"
},
@@ -5607,16 +5949,25 @@
"isArchived": {
"type": "boolean"
},
"isExternal": {
"type": "boolean"
},
"isFavorite": {
"type": "boolean"
},
"isOffline": {
"type": "boolean"
},
"isReadOnly": {
"default": false,
"type": "boolean"
},
"isVisible": {
"type": "boolean"
},
"libraryId": {
"format": "uuid",
"type": "string"
},
"livePhotoData": {
"format": "binary",
"type": "string"
@@ -5636,6 +5987,35 @@
],
"type": "object"
},
"CreateLibraryDto": {
"properties": {
"exclusionPatterns": {
"items": {
"type": "string"
},
"type": "array"
},
"importPaths": {
"items": {
"type": "string"
},
"type": "array"
},
"isVisible": {
"type": "boolean"
},
"name": {
"type": "string"
},
"type": {
"$ref": "#/components/schemas/LibraryType"
}
},
"required": [
"type"
],
"type": "object"
},
"CreateProfileImageDto": {
"properties": {
"file": {
@@ -6012,9 +6392,15 @@
"isArchived": {
"type": "boolean"
},
"isExternal": {
"type": "boolean"
},
"isFavorite": {
"type": "boolean"
},
"isOffline": {
"type": "boolean"
},
"isReadOnly": {
"default": true,
"type": "boolean"
@@ -6022,6 +6408,10 @@
"isVisible": {
"type": "boolean"
},
"libraryId": {
"format": "uuid",
"type": "string"
},
"sidecarPath": {
"type": "string"
}
@@ -6102,7 +6492,8 @@
"backgroundTask",
"storageTemplateMigration",
"search",
"sidecar"
"sidecar",
"library"
],
"type": "string"
},
@@ -6132,6 +6523,98 @@
],
"type": "object"
},
"LibraryResponseDto": {
"properties": {
"assetCount": {
"type": "integer"
},
"createdAt": {
"format": "date-time",
"type": "string"
},
"exclusionPatterns": {
"items": {
"type": "string"
},
"type": "array"
},
"id": {
"type": "string"
},
"importPaths": {
"items": {
"type": "string"
},
"type": "array"
},
"name": {
"type": "string"
},
"ownerId": {
"type": "string"
},
"refreshedAt": {
"format": "date-time",
"nullable": true,
"type": "string"
},
"type": {
"$ref": "#/components/schemas/LibraryType"
},
"updatedAt": {
"format": "date-time",
"type": "string"
}
},
"required": [
"type",
"assetCount",
"id",
"ownerId",
"name",
"importPaths",
"exclusionPatterns",
"createdAt",
"updatedAt",
"refreshedAt"
],
"type": "object"
},
"LibraryStatsResponseDto": {
"properties": {
"photos": {
"default": 0,
"type": "integer"
},
"total": {
"default": 0,
"type": "integer"
},
"usage": {
"default": 0,
"format": "int64",
"type": "integer"
},
"videos": {
"default": 0,
"type": "integer"
}
},
"required": [
"photos",
"videos",
"total",
"usage"
],
"type": "object"
},
"LibraryType": {
"enum": [
"UPLOAD",
"EXTERNAL"
],
"type": "string"
},
"LoginCredentialDto": {
"properties": {
"email": {
@@ -6471,6 +6954,9 @@
"maxDistance": {
"type": "integer"
},
"minFaces": {
"type": "integer"
},
"minScore": {
"type": "integer"
},
@@ -6484,11 +6970,24 @@
"required": [
"minScore",
"maxDistance",
"minFaces",
"enabled",
"modelName"
],
"type": "object"
},
"ScanLibraryDto": {
"properties": {
"refreshAllFiles": {
"default": false,
"type": "boolean"
},
"refreshModifiedFiles": {
"type": "boolean"
}
},
"type": "object"
},
"SearchAlbumResponseDto": {
"properties": {
"count": {
@@ -7144,6 +7643,9 @@
"clipEncoding": {
"$ref": "#/components/schemas/JobSettingsDto"
},
"library": {
"$ref": "#/components/schemas/JobSettingsDto"
},
"metadataExtraction": {
"$ref": "#/components/schemas/JobSettingsDto"
},
@@ -7179,7 +7681,8 @@
"backgroundTask",
"search",
"recognizeFaces",
"sidecar"
"sidecar",
"library"
],
"type": "object"
},
@@ -7493,6 +7996,29 @@
},
"type": "object"
},
"UpdateLibraryDto": {
"properties": {
"exclusionPatterns": {
"items": {
"type": "string"
},
"type": "array"
},
"importPaths": {
"items": {
"type": "string"
},
"type": "array"
},
"isVisible": {
"type": "boolean"
},
"name": {
"type": "string"
}
},
"type": "object"
},
"UpdateTagDto": {
"properties": {
"name": {

1755
server/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "immich",
"version": "1.78.1",
"version": "1.79.0",
"description": "",
"author": "",
"private": true,
@@ -58,9 +58,11 @@
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"cookie-parser": "^1.4.6",
"exiftool-vendored": "^22.0.0",
"exiftool-vendored": "^23.0.0",
"exiftool-vendored.pl": "^12.62.0",
"fluent-ffmpeg": "^2.1.2",
"glob": "^10.3.3",
"geo-tz": "^7.0.7",
"handlebars": "^4.7.8",
"i18n-iso-countries": "^7.6.0",
"immich": "^0.41.0",
@@ -76,7 +78,7 @@
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.1",
"sanitize-filename": "^1.6.3",
"sharp": "^0.31.3",
"sharp": "^0.32.6",
"thumbhash": "^0.1.1",
"typeorm": "^0.3.17",
"typesense": "^1.7.1",
@@ -98,6 +100,7 @@
"@types/jest": "29.5.4",
"@types/jest-when": "^3.5.2",
"@types/lodash": "^4.14.197",
"@types/mock-fs": "^4.13.1",
"@types/multer": "^1.4.7",
"@types/mv": "^2.1.2",
"@types/node": "^20.5.7",
@@ -112,6 +115,7 @@
"eslint-plugin-prettier": "^5.0.0",
"jest": "^29.6.4",
"jest-when": "^3.6.0",
"mock-fs": "^5.2.0",
"prettier": "^3.0.2",
"prettier-plugin-organize-imports": "^3.2.3",
"rimraf": "^5.0.1",

View File

@@ -21,8 +21,18 @@ export enum Permission {
ARCHIVE_READ = 'archive.read',
TIMELINE_READ = 'timeline.read',
TIMELINE_DOWNLOAD = 'timeline.download',
LIBRARY_CREATE = 'library.create',
LIBRARY_READ = 'library.read',
LIBRARY_UPDATE = 'library.update',
LIBRARY_DELETE = 'library.delete',
LIBRARY_DOWNLOAD = 'library.download',
PERSON_READ = 'person.read',
PERSON_WRITE = 'person.write',
PERSON_MERGE = 'person.merge',
}
export class AccessCore {
@@ -161,12 +171,33 @@ export class AccessCore {
case Permission.ARCHIVE_READ:
return authUser.id === id;
case Permission.LIBRARY_READ:
return authUser.id === id || (await this.repository.library.hasPartnerAccess(authUser.id, id));
case Permission.TIMELINE_READ:
return authUser.id === id || (await this.repository.timeline.hasPartnerAccess(authUser.id, id));
case Permission.LIBRARY_DOWNLOAD:
case Permission.TIMELINE_DOWNLOAD:
return authUser.id === id;
case Permission.LIBRARY_READ:
return (
(await this.repository.library.hasOwnerAccess(authUser.id, id)) ||
(await this.repository.library.hasPartnerAccess(authUser.id, id))
);
case Permission.LIBRARY_UPDATE:
return this.repository.library.hasOwnerAccess(authUser.id, id);
case Permission.LIBRARY_DELETE:
return this.repository.library.hasOwnerAccess(authUser.id, id);
case Permission.PERSON_READ:
return this.repository.person.hasOwnerAccess(authUser.id, id);
case Permission.PERSON_WRITE:
return this.repository.person.hasOwnerAccess(authUser.id, id);
case Permission.PERSON_MERGE:
return this.repository.person.hasOwnerAccess(authUser.id, id);
default:
return false;
}

View File

@@ -15,6 +15,15 @@ export interface IAccessRepository {
};
library: {
hasOwnerAccess(userId: string, libraryId: string): Promise<boolean>;
hasPartnerAccess(userId: string, partnerId: string): Promise<boolean>;
};
timeline: {
hasPartnerAccess(userId: string, partnerId: string): Promise<boolean>;
};
person: {
hasOwnerAccess(userId: string, personId: string): Promise<boolean>;
};
}

View File

@@ -23,6 +23,8 @@ export interface IAlbumRepository {
getOwned(ownerId: string): Promise<AlbumEntity[]>;
getShared(ownerId: string): Promise<AlbumEntity[]>;
getNotShared(ownerId: string): Promise<AlbumEntity[]>;
restoreAll(userId: string): Promise<void>;
softDeleteAll(userId: string): Promise<void>;
deleteAll(userId: string): Promise<void>;
getAll(): Promise<AlbumEntity[]>;
create(album: Partial<AlbumEntity>): Promise<AlbumEntity>;

View File

@@ -45,6 +45,7 @@ export enum WithoutProperty {
export enum WithProperty {
SIDECAR = 'sidecar',
IS_OFFLINE = 'isOffline',
}
export enum TimeBucketSize {
@@ -69,15 +70,18 @@ export interface TimeBucketItem {
export const IAssetRepository = 'IAssetRepository';
export interface IAssetRepository {
create(asset: Partial<AssetEntity>): Promise<AssetEntity>;
getByDate(ownerId: string, date: Date): Promise<AssetEntity[]>;
getByIds(ids: string[]): Promise<AssetEntity[]>;
getByChecksum(userId: string, checksum: Buffer): Promise<AssetEntity | null>;
getByAlbumId(pagination: PaginationOptions, albumId: string): Paginated<AssetEntity>;
getByUserId(pagination: PaginationOptions, userId: string): Paginated<AssetEntity>;
getWithout(pagination: PaginationOptions, property: WithoutProperty): Paginated<AssetEntity>;
getWith(pagination: PaginationOptions, property: WithProperty): Paginated<AssetEntity>;
getWith(pagination: PaginationOptions, property: WithProperty, libraryId?: string): Paginated<AssetEntity>;
getFirstAssetForAlbumId(albumId: string): Promise<AssetEntity | null>;
getLastUpdatedAssetForAlbumId(albumId: string): Promise<AssetEntity | null>;
getByLibraryId(libraryIds: string[]): Promise<AssetEntity[]>;
getByLibraryIdAndOriginalPath(libraryId: string, originalPath: string): Promise<AssetEntity | null>;
deleteAll(ownerId: string): Promise<void>;
getAll(pagination: PaginationOptions, options?: AssetSearchOptions): Paginated<AssetEntity>;
updateAll(ids: string[], options: Partial<AssetEntity>): Promise<void>;
@@ -87,5 +91,7 @@ export interface IAssetRepository {
getStatistics(ownerId: string, options: AssetStatsOptions): Promise<AssetStats>;
getTimeBuckets(options: TimeBucketOptions): Promise<TimeBucketItem[]>;
getByTimeBucket(timeBucket: string, options: TimeBucketOptions): Promise<AssetEntity[]>;
remove(asset: AssetEntity): Promise<AssetEntity>;
getById(assetId: string): Promise<AssetEntity>;
upsertExif(exif: Partial<ExifEntity>): Promise<void>;
}

View File

@@ -501,6 +501,7 @@ describe(AssetService.name, () => {
});
it('should return a list of archives (userId)', async () => {
accessMock.library.hasOwnerAccess.mockResolvedValue(true);
assetMock.getByUserId.mockResolvedValue({
items: [assetStub.image, assetStub.video],
hasNextPage: false,
@@ -514,6 +515,8 @@ describe(AssetService.name, () => {
});
it('should split archives by size', async () => {
accessMock.library.hasOwnerAccess.mockResolvedValue(true);
assetMock.getByUserId.mockResolvedValue({
items: [
{ ...assetStub.image, id: 'asset-1' },

View File

@@ -162,7 +162,7 @@ export class AssetService {
if (dto.isArchived !== false) {
await this.access.requirePermission(authUser, Permission.ARCHIVE_READ, [dto.userId]);
}
await this.access.requirePermission(authUser, Permission.LIBRARY_READ, [dto.userId]);
await this.access.requirePermission(authUser, Permission.TIMELINE_READ, [dto.userId]);
} else {
dto.userId = authUser.id;
}
@@ -187,6 +187,10 @@ export class AssetService {
throw new BadRequestException('Asset not found');
}
if (asset.isOffline) {
throw new BadRequestException('Asset is offline');
}
return this.storageRepository.createReadStream(asset.originalPath, mimeTypes.lookup(asset.originalPath));
}
@@ -268,7 +272,7 @@ export class AssetService {
if (dto.userId) {
const userId = dto.userId;
await this.access.requirePermission(authUser, Permission.LIBRARY_DOWNLOAD, userId);
await this.access.requirePermission(authUser, Permission.TIMELINE_DOWNLOAD, userId);
return usePagination(PAGINATION_SIZE, (pagination) => this.assetRepository.getByUserId(pagination, userId));
}

View File

@@ -12,6 +12,7 @@ export class AssetResponseDto {
deviceId!: string;
ownerId!: string;
owner?: UserResponseDto;
libraryId!: string;
@ApiProperty({ enumName: 'AssetTypeEnum', enum: AssetType })
type!: AssetType;
@@ -25,6 +26,9 @@ export class AssetResponseDto {
updatedAt!: Date;
isFavorite!: boolean;
isArchived!: boolean;
isOffline!: boolean;
isExternal!: boolean;
isReadOnly!: boolean;
duration!: string;
exifInfo?: ExifResponseDto;
smartInfo?: SmartInfoResponseDto;
@@ -42,6 +46,7 @@ function _map(entity: AssetEntity, withExif: boolean): AssetResponseDto {
ownerId: entity.ownerId,
owner: entity.owner ? mapUser(entity.owner) : undefined,
deviceId: entity.deviceId,
libraryId: entity.libraryId,
type: entity.type,
originalPath: entity.originalPath,
originalFileName: entity.originalFileName,
@@ -59,6 +64,9 @@ function _map(entity: AssetEntity, withExif: boolean): AssetResponseDto {
tags: entity.tags?.map(mapTag),
people: entity.faces?.map(mapFace).filter((person) => !person.isHidden),
checksum: entity.checksum.toString('base64'),
isExternal: entity.isExternal,
isOffline: entity.isOffline,
isReadOnly: entity.isReadOnly,
};
}

View File

@@ -25,7 +25,7 @@ export class AuditService {
async getDeletes(authUser: AuthUserDto, dto: AuditDeletesDto): Promise<AuditDeletesResponseDto> {
const userId = dto.userId || authUser.id;
await this.access.requirePermission(authUser, Permission.LIBRARY_READ, userId);
await this.access.requirePermission(authUser, Permission.TIMELINE_READ, userId);
const audits = await this.repository.getAfter(dto.after, {
ownerId: userId,

View File

@@ -6,6 +6,7 @@ import {
loginResponseStub,
newCryptoRepositoryMock,
newKeyRepositoryMock,
newLibraryRepositoryMock,
newSharedLinkRepositoryMock,
newSystemConfigRepositoryMock,
newUserRepositoryMock,
@@ -20,6 +21,7 @@ import { Issuer, generators } from 'openid-client';
import { Socket } from 'socket.io';
import { IKeyRepository } from '../api-key';
import { ICryptoRepository } from '../crypto/crypto.repository';
import { ILibraryRepository } from '../library';
import { ISharedLinkRepository } from '../shared-link';
import { ISystemConfigRepository } from '../system-config';
import { IUserRepository } from '../user';
@@ -50,6 +52,7 @@ describe('AuthService', () => {
let sut: AuthService;
let cryptoMock: jest.Mocked<ICryptoRepository>;
let userMock: jest.Mocked<IUserRepository>;
let libraryMock: jest.Mocked<ILibraryRepository>;
let configMock: jest.Mocked<ISystemConfigRepository>;
let userTokenMock: jest.Mocked<IUserTokenRepository>;
let shareMock: jest.Mocked<ISharedLinkRepository>;
@@ -81,12 +84,13 @@ describe('AuthService', () => {
cryptoMock = newCryptoRepositoryMock();
userMock = newUserRepositoryMock();
libraryMock = newLibraryRepositoryMock();
configMock = newSystemConfigRepositoryMock();
userTokenMock = newUserTokenRepositoryMock();
shareMock = newSharedLinkRepositoryMock();
keyMock = newKeyRepositoryMock();
sut = new AuthService(cryptoMock, configMock, userMock, userTokenMock, shareMock, keyMock);
sut = new AuthService(cryptoMock, configMock, userMock, userTokenMock, libraryMock, shareMock, keyMock);
});
it('should be defined', () => {

View File

@@ -13,6 +13,7 @@ import { DateTime } from 'luxon';
import { ClientMetadata, Issuer, UserinfoResponse, custom, generators } from 'openid-client';
import { IKeyRepository } from '../api-key';
import { ICryptoRepository } from '../crypto/crypto.repository';
import { ILibraryRepository } from '../library';
import { ISharedLinkRepository } from '../shared-link';
import { ISystemConfigRepository } from '../system-config';
import { SystemConfigCore } from '../system-config/system-config.core';
@@ -66,11 +67,12 @@ export class AuthService {
@Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository,
@Inject(IUserRepository) userRepository: IUserRepository,
@Inject(IUserTokenRepository) private userTokenRepository: IUserTokenRepository,
@Inject(ILibraryRepository) libraryRepository: ILibraryRepository,
@Inject(ISharedLinkRepository) private sharedLinkRepository: ISharedLinkRepository,
@Inject(IKeyRepository) private keyRepository: IKeyRepository,
) {
this.configCore = new SystemConfigCore(configRepository);
this.userCore = new UserCore(userRepository, cryptoRepository);
this.userCore = new UserCore(userRepository, libraryRepository, cryptoRepository);
custom.setHttpOptionsDefaults({ timeout: 30000 });
}

View File

@@ -76,6 +76,7 @@ const video: Record<string, string[]> = {
'.flv': ['video/x-flv'],
'.insv': ['video/mp4'],
'.m2ts': ['video/mp2t'],
'.m4v': ['video/x-m4v'],
'.mkv': ['video/x-matroska'],
'.mov': ['video/quicktime'],
'.mp4': ['video/mp4'],
@@ -101,6 +102,7 @@ export const mimeTypes = {
video,
isAsset: (filename: string) => isType(filename, image) || isType(filename, video),
isImage: (filename: string) => isType(filename, image),
isProfile: (filename: string) => isType(filename, profile),
isSidecar: (filename: string) => isType(filename, sidecar),
isVideo: (filename: string) => isType(filename, video),
@@ -114,4 +116,5 @@ export const mimeTypes = {
}
return AssetType.OTHER;
},
getSupportedFileExtensions: () => Object.keys(image).concat(Object.keys(video)),
};

View File

@@ -6,6 +6,7 @@ import { AuditService } from './audit';
import { AuthService } from './auth';
import { FacialRecognitionService } from './facial-recognition';
import { JobService } from './job';
import { LibraryService } from './library';
import { MediaService } from './media';
import { MetadataService } from './metadata';
import { PartnerService } from './partner';
@@ -30,6 +31,7 @@ const providers: Provider[] = [
JobService,
MediaService,
MetadataService,
LibraryService,
PersonService,
PartnerService,
SearchService,

View File

@@ -205,6 +205,7 @@ describe(FacialRecognitionService.name, () => {
enabled: true,
maxDistance: 0.6,
minScore: 0.7,
minFaces: 1,
modelName: 'buffalo_l',
},
);

View File

@@ -12,6 +12,7 @@ export * from './domain.module';
export * from './domain.util';
export * from './facial-recognition';
export * from './job';
export * from './library';
export * from './media';
export * from './metadata';
export * from './partner';

View File

@@ -9,6 +9,7 @@ export enum QueueName {
STORAGE_TEMPLATE_MIGRATION = 'storageTemplateMigration',
SEARCH = 'search',
SIDECAR = 'sidecar',
LIBRARY = 'library',
}
export enum JobCommand {
@@ -53,6 +54,15 @@ export enum JobName {
RECOGNIZE_FACES = 'recognize-faces',
PERSON_CLEANUP = 'person-cleanup',
// library managment
LIBRARY_SCAN = 'library-refresh',
LIBRARY_SCAN_ASSET = 'library-refresh-asset',
LIBRARY_REMOVE_OFFLINE = 'library-remove-offline',
LIBRARY_MARK_ASSET_OFFLINE = 'library-mark-asset-offline',
LIBRARY_DELETE = 'library-delete',
LIBRARY_QUEUE_SCAN_ALL = 'library-queue-all-refresh',
LIBRARY_QUEUE_CLEANUP = 'library-queue-cleanup',
// cleanup
DELETE_FILES = 'delete-files',
CLEAN_OLD_AUDIT_LOGS = 'clean-old-audit-logs',
@@ -140,4 +150,13 @@ export const JOBS_TO_QUEUE: Record<JobName, QueueName> = {
[JobName.QUEUE_SIDECAR]: QueueName.SIDECAR,
[JobName.SIDECAR_DISCOVERY]: QueueName.SIDECAR,
[JobName.SIDECAR_SYNC]: QueueName.SIDECAR,
// Library managment
[JobName.LIBRARY_SCAN_ASSET]: QueueName.LIBRARY,
[JobName.LIBRARY_MARK_ASSET_OFFLINE]: QueueName.LIBRARY,
[JobName.LIBRARY_SCAN]: QueueName.LIBRARY,
[JobName.LIBRARY_DELETE]: QueueName.LIBRARY,
[JobName.LIBRARY_REMOVE_OFFLINE]: QueueName.LIBRARY,
[JobName.LIBRARY_QUEUE_SCAN_ALL]: QueueName.LIBRARY,
[JobName.LIBRARY_QUEUE_CLEANUP]: QueueName.LIBRARY,
};

View File

@@ -79,4 +79,7 @@ export class AllJobStatusResponseDto implements Record<QueueName, JobStatusDto>
@ApiProperty({ type: JobStatusDto })
[QueueName.SIDECAR]!: JobStatusDto;
@ApiProperty({ type: JobStatusDto })
[QueueName.LIBRARY]!: JobStatusDto;
}

View File

@@ -22,6 +22,21 @@ export interface IEntityJob extends IBaseJob {
source?: 'upload';
}
export interface IOfflineLibraryFileJob extends IEntityJob {
assetPath: string;
}
export interface ILibraryFileJob extends IEntityJob {
ownerId: string;
assetPath: string;
forceRefresh: boolean;
}
export interface ILibraryRefreshJob extends IEntityJob {
refreshModifiedFiles: boolean;
refreshAllFiles: boolean;
}
export interface IBulkEntityJob extends IBaseJob {
ids: string[];
}

View File

@@ -1,4 +1,5 @@
import { JobName, QueueName } from './job.constants';
import {
IAssetFaceJob,
IBaseJob,
@@ -6,6 +7,9 @@ import {
IDeleteFilesJob,
IEntityJob,
IFaceThumbnailJob,
ILibraryFileJob,
ILibraryRefreshJob,
IOfflineLibraryFileJob,
} from './job.interface';
export interface JobCounts {
@@ -74,6 +78,15 @@ export type JobItem =
// Asset Deletion
| { name: JobName.PERSON_CLEANUP; data?: IBaseJob }
// Library Managment
| { name: JobName.LIBRARY_SCAN_ASSET; data: ILibraryFileJob }
| { name: JobName.LIBRARY_MARK_ASSET_OFFLINE; data: IOfflineLibraryFileJob }
| { name: JobName.LIBRARY_SCAN; data: ILibraryRefreshJob }
| { name: JobName.LIBRARY_REMOVE_OFFLINE; data: IEntityJob }
| { name: JobName.LIBRARY_DELETE; data: IEntityJob }
| { name: JobName.LIBRARY_QUEUE_SCAN_ALL; data: IBaseJob }
| { name: JobName.LIBRARY_QUEUE_CLEANUP; data: IBaseJob }
// Search
| { name: JobName.SEARCH_INDEX_ASSETS; data?: IBaseJob }
| { name: JobName.SEARCH_INDEX_ASSET; data: IBulkEntityJob }

View File

@@ -52,6 +52,7 @@ describe(JobService.name, () => {
[{ name: JobName.PERSON_CLEANUP }],
[{ name: JobName.QUEUE_GENERATE_THUMBNAILS, data: { force: false } }],
[{ name: JobName.CLEAN_OLD_AUDIT_LOGS }],
[{ name: JobName.LIBRARY_QUEUE_SCAN_ALL, data: { force: false } }],
]);
});
});
@@ -97,6 +98,7 @@ describe(JobService.name, () => {
[QueueName.VIDEO_CONVERSION]: expectedJobStatus,
[QueueName.RECOGNIZE_FACES]: expectedJobStatus,
[QueueName.SIDECAR]: expectedJobStatus,
[QueueName.LIBRARY]: expectedJobStatus,
});
});
});
@@ -225,6 +227,7 @@ describe(JobService.name, () => {
[QueueName.RECOGNIZE_FACES]: { concurrency: 10 },
[QueueName.SEARCH]: { concurrency: 10 },
[QueueName.SIDECAR]: { concurrency: 10 },
[QueueName.LIBRARY]: { concurrency: 10 },
[QueueName.STORAGE_TEMPLATE_MIGRATION]: { concurrency: 10 },
[QueueName.THUMBNAIL_GENERATION]: { concurrency: 10 },
[QueueName.VIDEO_CONVERSION]: { concurrency: 10 },
@@ -237,6 +240,7 @@ describe(JobService.name, () => {
expect(jobMock.setConcurrency).toHaveBeenCalledWith(QueueName.OBJECT_TAGGING, 10);
expect(jobMock.setConcurrency).toHaveBeenCalledWith(QueueName.RECOGNIZE_FACES, 10);
expect(jobMock.setConcurrency).toHaveBeenCalledWith(QueueName.SIDECAR, 10);
expect(jobMock.setConcurrency).toHaveBeenCalledWith(QueueName.LIBRARY, 10);
expect(jobMock.setConcurrency).toHaveBeenCalledWith(QueueName.STORAGE_TEMPLATE_MIGRATION, 10);
expect(jobMock.setConcurrency).toHaveBeenCalledWith(QueueName.THUMBNAIL_GENERATION, 10);
expect(jobMock.setConcurrency).toHaveBeenCalledWith(QueueName.VIDEO_CONVERSION, 10);

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