mirror of
				https://github.com/KevinMidboe/immich.git
				synced 2025-10-29 17:40:28 +00:00 
			
		
		
		
	feat(server/web) Add manual job trigger mechanism to the web (#767)
This commit is contained in:
		| @@ -8,6 +8,7 @@ doc/AdminSignupResponseDto.md | |||||||
| doc/AlbumApi.md | doc/AlbumApi.md | ||||||
| doc/AlbumCountResponseDto.md | doc/AlbumCountResponseDto.md | ||||||
| doc/AlbumResponseDto.md | doc/AlbumResponseDto.md | ||||||
|  | doc/AllJobStatusResponseDto.md | ||||||
| doc/AssetApi.md | doc/AssetApi.md | ||||||
| doc/AssetCountByTimeBucket.md | doc/AssetCountByTimeBucket.md | ||||||
| doc/AssetCountByTimeBucketResponseDto.md | doc/AssetCountByTimeBucketResponseDto.md | ||||||
| @@ -33,6 +34,12 @@ doc/DeviceTypeEnum.md | |||||||
| doc/ExifResponseDto.md | doc/ExifResponseDto.md | ||||||
| doc/GetAssetByTimeBucketDto.md | doc/GetAssetByTimeBucketDto.md | ||||||
| doc/GetAssetCountByTimeBucketDto.md | doc/GetAssetCountByTimeBucketDto.md | ||||||
|  | doc/JobApi.md | ||||||
|  | doc/JobCommand.md | ||||||
|  | doc/JobCommandDto.md | ||||||
|  | doc/JobCounts.md | ||||||
|  | doc/JobId.md | ||||||
|  | doc/JobStatusResponseDto.md | ||||||
| doc/LoginCredentialDto.md | doc/LoginCredentialDto.md | ||||||
| doc/LoginResponseDto.md | doc/LoginResponseDto.md | ||||||
| doc/LogoutResponseDto.md | doc/LogoutResponseDto.md | ||||||
| @@ -59,6 +66,7 @@ lib/api/album_api.dart | |||||||
| lib/api/asset_api.dart | lib/api/asset_api.dart | ||||||
| lib/api/authentication_api.dart | lib/api/authentication_api.dart | ||||||
| lib/api/device_info_api.dart | lib/api/device_info_api.dart | ||||||
|  | lib/api/job_api.dart | ||||||
| lib/api/server_info_api.dart | lib/api/server_info_api.dart | ||||||
| lib/api/user_api.dart | lib/api/user_api.dart | ||||||
| lib/api_client.dart | lib/api_client.dart | ||||||
| @@ -74,6 +82,7 @@ lib/model/add_users_dto.dart | |||||||
| lib/model/admin_signup_response_dto.dart | lib/model/admin_signup_response_dto.dart | ||||||
| lib/model/album_count_response_dto.dart | lib/model/album_count_response_dto.dart | ||||||
| lib/model/album_response_dto.dart | lib/model/album_response_dto.dart | ||||||
|  | lib/model/all_job_status_response_dto.dart | ||||||
| lib/model/asset_count_by_time_bucket.dart | lib/model/asset_count_by_time_bucket.dart | ||||||
| lib/model/asset_count_by_time_bucket_response_dto.dart | lib/model/asset_count_by_time_bucket_response_dto.dart | ||||||
| lib/model/asset_count_by_user_id_response_dto.dart | lib/model/asset_count_by_user_id_response_dto.dart | ||||||
| @@ -96,6 +105,11 @@ lib/model/device_type_enum.dart | |||||||
| lib/model/exif_response_dto.dart | lib/model/exif_response_dto.dart | ||||||
| lib/model/get_asset_by_time_bucket_dto.dart | lib/model/get_asset_by_time_bucket_dto.dart | ||||||
| lib/model/get_asset_count_by_time_bucket_dto.dart | lib/model/get_asset_count_by_time_bucket_dto.dart | ||||||
|  | lib/model/job_command.dart | ||||||
|  | lib/model/job_command_dto.dart | ||||||
|  | lib/model/job_counts.dart | ||||||
|  | lib/model/job_id.dart | ||||||
|  | lib/model/job_status_response_dto.dart | ||||||
| lib/model/login_credential_dto.dart | lib/model/login_credential_dto.dart | ||||||
| lib/model/login_response_dto.dart | lib/model/login_response_dto.dart | ||||||
| lib/model/logout_response_dto.dart | lib/model/logout_response_dto.dart | ||||||
|   | |||||||
| @@ -97,6 +97,9 @@ Class | Method | HTTP request | Description | |||||||
| *AuthenticationApi* | [**validateAccessToken**](doc//AuthenticationApi.md#validateaccesstoken) | **POST** /auth/validateToken |  | *AuthenticationApi* | [**validateAccessToken**](doc//AuthenticationApi.md#validateaccesstoken) | **POST** /auth/validateToken |  | ||||||
| *DeviceInfoApi* | [**createDeviceInfo**](doc//DeviceInfoApi.md#createdeviceinfo) | **POST** /device-info |  | *DeviceInfoApi* | [**createDeviceInfo**](doc//DeviceInfoApi.md#createdeviceinfo) | **POST** /device-info |  | ||||||
| *DeviceInfoApi* | [**updateDeviceInfo**](doc//DeviceInfoApi.md#updatedeviceinfo) | **PATCH** /device-info |  | *DeviceInfoApi* | [**updateDeviceInfo**](doc//DeviceInfoApi.md#updatedeviceinfo) | **PATCH** /device-info |  | ||||||
|  | *JobApi* | [**getAllJobsStatus**](doc//JobApi.md#getalljobsstatus) | **GET** /jobs |  | ||||||
|  | *JobApi* | [**getJobStatus**](doc//JobApi.md#getjobstatus) | **GET** /jobs/{jobId} |  | ||||||
|  | *JobApi* | [**sendJobCommand**](doc//JobApi.md#sendjobcommand) | **PUT** /jobs/{jobId} |  | ||||||
| *ServerInfoApi* | [**getServerInfo**](doc//ServerInfoApi.md#getserverinfo) | **GET** /server-info |  | *ServerInfoApi* | [**getServerInfo**](doc//ServerInfoApi.md#getserverinfo) | **GET** /server-info |  | ||||||
| *ServerInfoApi* | [**getServerVersion**](doc//ServerInfoApi.md#getserverversion) | **GET** /server-info/version |  | *ServerInfoApi* | [**getServerVersion**](doc//ServerInfoApi.md#getserverversion) | **GET** /server-info/version |  | ||||||
| *ServerInfoApi* | [**pingServer**](doc//ServerInfoApi.md#pingserver) | **GET** /server-info/ping |  | *ServerInfoApi* | [**pingServer**](doc//ServerInfoApi.md#pingserver) | **GET** /server-info/ping |  | ||||||
| @@ -117,6 +120,7 @@ Class | Method | HTTP request | Description | |||||||
|  - [AdminSignupResponseDto](doc//AdminSignupResponseDto.md) |  - [AdminSignupResponseDto](doc//AdminSignupResponseDto.md) | ||||||
|  - [AlbumCountResponseDto](doc//AlbumCountResponseDto.md) |  - [AlbumCountResponseDto](doc//AlbumCountResponseDto.md) | ||||||
|  - [AlbumResponseDto](doc//AlbumResponseDto.md) |  - [AlbumResponseDto](doc//AlbumResponseDto.md) | ||||||
|  |  - [AllJobStatusResponseDto](doc//AllJobStatusResponseDto.md) | ||||||
|  - [AssetCountByTimeBucket](doc//AssetCountByTimeBucket.md) |  - [AssetCountByTimeBucket](doc//AssetCountByTimeBucket.md) | ||||||
|  - [AssetCountByTimeBucketResponseDto](doc//AssetCountByTimeBucketResponseDto.md) |  - [AssetCountByTimeBucketResponseDto](doc//AssetCountByTimeBucketResponseDto.md) | ||||||
|  - [AssetCountByUserIdResponseDto](doc//AssetCountByUserIdResponseDto.md) |  - [AssetCountByUserIdResponseDto](doc//AssetCountByUserIdResponseDto.md) | ||||||
| @@ -139,6 +143,11 @@ Class | Method | HTTP request | Description | |||||||
|  - [ExifResponseDto](doc//ExifResponseDto.md) |  - [ExifResponseDto](doc//ExifResponseDto.md) | ||||||
|  - [GetAssetByTimeBucketDto](doc//GetAssetByTimeBucketDto.md) |  - [GetAssetByTimeBucketDto](doc//GetAssetByTimeBucketDto.md) | ||||||
|  - [GetAssetCountByTimeBucketDto](doc//GetAssetCountByTimeBucketDto.md) |  - [GetAssetCountByTimeBucketDto](doc//GetAssetCountByTimeBucketDto.md) | ||||||
|  |  - [JobCommand](doc//JobCommand.md) | ||||||
|  |  - [JobCommandDto](doc//JobCommandDto.md) | ||||||
|  |  - [JobCounts](doc//JobCounts.md) | ||||||
|  |  - [JobId](doc//JobId.md) | ||||||
|  |  - [JobStatusResponseDto](doc//JobStatusResponseDto.md) | ||||||
|  - [LoginCredentialDto](doc//LoginCredentialDto.md) |  - [LoginCredentialDto](doc//LoginCredentialDto.md) | ||||||
|  - [LoginResponseDto](doc//LoginResponseDto.md) |  - [LoginResponseDto](doc//LoginResponseDto.md) | ||||||
|  - [LogoutResponseDto](doc//LogoutResponseDto.md) |  - [LogoutResponseDto](doc//LogoutResponseDto.md) | ||||||
|   | |||||||
							
								
								
									
										22
									
								
								mobile/openapi/doc/AllJobStatusResponseDto.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								mobile/openapi/doc/AllJobStatusResponseDto.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | # openapi.model.AllJobStatusResponseDto | ||||||
|  |  | ||||||
|  | ## Load the model package | ||||||
|  | ```dart | ||||||
|  | import 'package:openapi/api.dart'; | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **thumbnailGenerationQueueCount** | [**JobCounts**](JobCounts.md) |  |  | ||||||
|  | **metadataExtractionQueueCount** | [**JobCounts**](JobCounts.md) |  |  | ||||||
|  | **videoConversionQueueCount** | [**JobCounts**](JobCounts.md) |  |  | ||||||
|  | **machineLearningQueueCount** | [**JobCounts**](JobCounts.md) |  |  | ||||||
|  | **isThumbnailGenerationActive** | **bool** |  |  | ||||||
|  | **isMetadataExtractionActive** | **bool** |  |  | ||||||
|  | **isVideoConversionActive** | **bool** |  |  | ||||||
|  | **isMachineLearningActive** | **bool** |  |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										15
									
								
								mobile/openapi/doc/CreateJobDto.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								mobile/openapi/doc/CreateJobDto.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | # openapi.model.CreateJobDto | ||||||
|  |  | ||||||
|  | ## Load the model package | ||||||
|  | ```dart | ||||||
|  | import 'package:openapi/api.dart'; | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **jobType** | [**JobType**](JobType.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) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -8,13 +8,13 @@ import 'package:openapi/api.dart'; | |||||||
| ## Properties | ## Properties | ||||||
| Name | Type | Description | Notes | Name | Type | Description | Notes | ||||||
| ------------ | ------------- | ------------- | ------------- | ------------ | ------------- | ------------- | ------------- | ||||||
| **id** | **String** |  | [optional]  | **id** | **int** |  | [optional]  | ||||||
|  | **fileSizeInByte** | **int** |  | [optional]  | ||||||
| **make** | **String** |  | [optional]  | **make** | **String** |  | [optional]  | ||||||
| **model** | **String** |  | [optional]  | **model** | **String** |  | [optional]  | ||||||
| **imageName** | **String** |  | [optional]  | **imageName** | **String** |  | [optional]  | ||||||
| **exifImageWidth** | **num** |  | [optional]  | **exifImageWidth** | **num** |  | [optional]  | ||||||
| **exifImageHeight** | **num** |  | [optional]  | **exifImageHeight** | **num** |  | [optional]  | ||||||
| **fileSizeInByte** | **num** |  | [optional]  |  | ||||||
| **orientation** | **String** |  | [optional]  | **orientation** | **String** |  | [optional]  | ||||||
| **dateTimeOriginal** | [**DateTime**](DateTime.md) |  | [optional]  | **dateTimeOriginal** | [**DateTime**](DateTime.md) |  | [optional]  | ||||||
| **modifyDate** | [**DateTime**](DateTime.md) |  | [optional]  | **modifyDate** | [**DateTime**](DateTime.md) |  | [optional]  | ||||||
|   | |||||||
							
								
								
									
										155
									
								
								mobile/openapi/doc/JobApi.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								mobile/openapi/doc/JobApi.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,155 @@ | |||||||
|  | # openapi.api.JobApi | ||||||
|  |  | ||||||
|  | ## Load the API package | ||||||
|  | ```dart | ||||||
|  | import 'package:openapi/api.dart'; | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | All URIs are relative to */api* | ||||||
|  |  | ||||||
|  | Method | HTTP request | Description | ||||||
|  | ------------- | ------------- | ------------- | ||||||
|  | [**getAllJobsStatus**](JobApi.md#getalljobsstatus) | **GET** /jobs |  | ||||||
|  | [**getJobStatus**](JobApi.md#getjobstatus) | **GET** /jobs/{jobId} |  | ||||||
|  | [**sendJobCommand**](JobApi.md#sendjobcommand) | **PUT** /jobs/{jobId} |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # **getAllJobsStatus** | ||||||
|  | > AllJobStatusResponseDto getAllJobsStatus() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Example | ||||||
|  | ```dart | ||||||
|  | import 'package:openapi/api.dart'; | ||||||
|  | // 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 = JobApi(); | ||||||
|  |  | ||||||
|  | try { | ||||||
|  |     final result = api_instance.getAllJobsStatus(); | ||||||
|  |     print(result); | ||||||
|  | } catch (e) { | ||||||
|  |     print('Exception when calling JobApi->getAllJobsStatus: $e\n'); | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### Parameters | ||||||
|  | This endpoint does not need any parameter. | ||||||
|  |  | ||||||
|  | ### Return type | ||||||
|  |  | ||||||
|  | [**AllJobStatusResponseDto**](AllJobStatusResponseDto.md) | ||||||
|  |  | ||||||
|  | ### Authorization | ||||||
|  |  | ||||||
|  | [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) | ||||||
|  |  | ||||||
|  | # **getJobStatus** | ||||||
|  | > JobStatusResponseDto getJobStatus(jobId) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Example | ||||||
|  | ```dart | ||||||
|  | import 'package:openapi/api.dart'; | ||||||
|  | // 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 = JobApi(); | ||||||
|  | final jobId = ; // JobId |  | ||||||
|  |  | ||||||
|  | try { | ||||||
|  |     final result = api_instance.getJobStatus(jobId); | ||||||
|  |     print(result); | ||||||
|  | } catch (e) { | ||||||
|  |     print('Exception when calling JobApi->getJobStatus: $e\n'); | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### Parameters | ||||||
|  |  | ||||||
|  | Name | Type | Description  | Notes | ||||||
|  | ------------- | ------------- | ------------- | ------------- | ||||||
|  |  **jobId** | [**JobId**](.md)|  |  | ||||||
|  |  | ||||||
|  | ### Return type | ||||||
|  |  | ||||||
|  | [**JobStatusResponseDto**](JobStatusResponseDto.md) | ||||||
|  |  | ||||||
|  | ### Authorization | ||||||
|  |  | ||||||
|  | [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) | ||||||
|  |  | ||||||
|  | # **sendJobCommand** | ||||||
|  | > num sendJobCommand(jobId, jobCommandDto) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Example | ||||||
|  | ```dart | ||||||
|  | import 'package:openapi/api.dart'; | ||||||
|  | // 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 = JobApi(); | ||||||
|  | final jobId = ; // JobId |  | ||||||
|  | final jobCommandDto = JobCommandDto(); // JobCommandDto |  | ||||||
|  |  | ||||||
|  | try { | ||||||
|  |     final result = api_instance.sendJobCommand(jobId, jobCommandDto); | ||||||
|  |     print(result); | ||||||
|  | } catch (e) { | ||||||
|  |     print('Exception when calling JobApi->sendJobCommand: $e\n'); | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### Parameters | ||||||
|  |  | ||||||
|  | Name | Type | Description  | Notes | ||||||
|  | ------------- | ------------- | ------------- | ------------- | ||||||
|  |  **jobId** | [**JobId**](.md)|  |  | ||||||
|  |  **jobCommandDto** | [**JobCommandDto**](JobCommandDto.md)|  |  | ||||||
|  |  | ||||||
|  | ### Return type | ||||||
|  |  | ||||||
|  | **num** | ||||||
|  |  | ||||||
|  | ### Authorization | ||||||
|  |  | ||||||
|  | [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) | ||||||
|  |  | ||||||
							
								
								
									
										14
									
								
								mobile/openapi/doc/JobCommand.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								mobile/openapi/doc/JobCommand.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | # openapi.model.JobCommand | ||||||
|  |  | ||||||
|  | ## 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) | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										15
									
								
								mobile/openapi/doc/JobCommandDto.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								mobile/openapi/doc/JobCommandDto.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | # openapi.model.JobCommandDto | ||||||
|  |  | ||||||
|  | ## Load the model package | ||||||
|  | ```dart | ||||||
|  | import 'package:openapi/api.dart'; | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **command** | [**JobCommand**](JobCommand.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) | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										19
									
								
								mobile/openapi/doc/JobCounts.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								mobile/openapi/doc/JobCounts.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | # openapi.model.JobCounts | ||||||
|  |  | ||||||
|  | ## Load the model package | ||||||
|  | ```dart | ||||||
|  | import 'package:openapi/api.dart'; | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **active** | **num** |  |  | ||||||
|  | **completed** | **num** |  |  | ||||||
|  | **failed** | **num** |  |  | ||||||
|  | **delayed** | **num** |  |  | ||||||
|  | **waiting** | **num** |  |  | ||||||
|  |  | ||||||
|  | [[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/JobId.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								mobile/openapi/doc/JobId.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | # openapi.model.JobId | ||||||
|  |  | ||||||
|  | ## 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) | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										16
									
								
								mobile/openapi/doc/JobStatusResponseDto.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								mobile/openapi/doc/JobStatusResponseDto.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | # openapi.model.JobStatusResponseDto | ||||||
|  |  | ||||||
|  | ## Load the model package | ||||||
|  | ```dart | ||||||
|  | import 'package:openapi/api.dart'; | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **isActive** | **bool** |  |  | ||||||
|  | **queueCount** | [**Object**](.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) | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										14
									
								
								mobile/openapi/doc/JobType.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								mobile/openapi/doc/JobType.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | # openapi.model.JobType | ||||||
|  |  | ||||||
|  | ## 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) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -31,6 +31,7 @@ part 'api/album_api.dart'; | |||||||
| part 'api/asset_api.dart'; | part 'api/asset_api.dart'; | ||||||
| part 'api/authentication_api.dart'; | part 'api/authentication_api.dart'; | ||||||
| part 'api/device_info_api.dart'; | part 'api/device_info_api.dart'; | ||||||
|  | part 'api/job_api.dart'; | ||||||
| part 'api/server_info_api.dart'; | part 'api/server_info_api.dart'; | ||||||
| part 'api/user_api.dart'; | part 'api/user_api.dart'; | ||||||
|  |  | ||||||
| @@ -39,6 +40,7 @@ part 'model/add_users_dto.dart'; | |||||||
| part 'model/admin_signup_response_dto.dart'; | part 'model/admin_signup_response_dto.dart'; | ||||||
| part 'model/album_count_response_dto.dart'; | part 'model/album_count_response_dto.dart'; | ||||||
| part 'model/album_response_dto.dart'; | part 'model/album_response_dto.dart'; | ||||||
|  | part 'model/all_job_status_response_dto.dart'; | ||||||
| part 'model/asset_count_by_time_bucket.dart'; | part 'model/asset_count_by_time_bucket.dart'; | ||||||
| part 'model/asset_count_by_time_bucket_response_dto.dart'; | part 'model/asset_count_by_time_bucket_response_dto.dart'; | ||||||
| part 'model/asset_count_by_user_id_response_dto.dart'; | part 'model/asset_count_by_user_id_response_dto.dart'; | ||||||
| @@ -61,6 +63,11 @@ part 'model/device_type_enum.dart'; | |||||||
| part 'model/exif_response_dto.dart'; | part 'model/exif_response_dto.dart'; | ||||||
| part 'model/get_asset_by_time_bucket_dto.dart'; | part 'model/get_asset_by_time_bucket_dto.dart'; | ||||||
| part 'model/get_asset_count_by_time_bucket_dto.dart'; | part 'model/get_asset_count_by_time_bucket_dto.dart'; | ||||||
|  | part 'model/job_command.dart'; | ||||||
|  | part 'model/job_command_dto.dart'; | ||||||
|  | part 'model/job_counts.dart'; | ||||||
|  | part 'model/job_id.dart'; | ||||||
|  | part 'model/job_status_response_dto.dart'; | ||||||
| part 'model/login_credential_dto.dart'; | part 'model/login_credential_dto.dart'; | ||||||
| part 'model/login_response_dto.dart'; | part 'model/login_response_dto.dart'; | ||||||
| part 'model/logout_response_dto.dart'; | part 'model/logout_response_dto.dart'; | ||||||
|   | |||||||
							
								
								
									
										159
									
								
								mobile/openapi/lib/api/job_api.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								mobile/openapi/lib/api/job_api.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,159 @@ | |||||||
|  | // | ||||||
|  | // 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 JobApi { | ||||||
|  |   JobApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; | ||||||
|  |  | ||||||
|  |   final ApiClient apiClient; | ||||||
|  |  | ||||||
|  |   /// Performs an HTTP 'GET /jobs' operation and returns the [Response]. | ||||||
|  |   Future<Response> getAllJobsStatusWithHttpInfo() async { | ||||||
|  |     // ignore: prefer_const_declarations | ||||||
|  |     final path = r'/jobs'; | ||||||
|  |  | ||||||
|  |     // 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<AllJobStatusResponseDto?> getAllJobsStatus() async { | ||||||
|  |     final response = await getAllJobsStatusWithHttpInfo(); | ||||||
|  |     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), 'AllJobStatusResponseDto',) as AllJobStatusResponseDto; | ||||||
|  |      | ||||||
|  |     } | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /// Performs an HTTP 'GET /jobs/{jobId}' operation and returns the [Response]. | ||||||
|  |   /// Parameters: | ||||||
|  |   /// | ||||||
|  |   /// * [JobId] jobId (required): | ||||||
|  |   Future<Response> getJobStatusWithHttpInfo(JobId jobId,) async { | ||||||
|  |     // ignore: prefer_const_declarations | ||||||
|  |     final path = r'/jobs/{jobId}' | ||||||
|  |       .replaceAll('{jobId}', jobId.toString()); | ||||||
|  |  | ||||||
|  |     // 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: | ||||||
|  |   /// | ||||||
|  |   /// * [JobId] jobId (required): | ||||||
|  |   Future<JobStatusResponseDto?> getJobStatus(JobId jobId,) async { | ||||||
|  |     final response = await getJobStatusWithHttpInfo(jobId,); | ||||||
|  |     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), 'JobStatusResponseDto',) as JobStatusResponseDto; | ||||||
|  |      | ||||||
|  |     } | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /// Performs an HTTP 'PUT /jobs/{jobId}' operation and returns the [Response]. | ||||||
|  |   /// Parameters: | ||||||
|  |   /// | ||||||
|  |   /// * [JobId] jobId (required): | ||||||
|  |   /// | ||||||
|  |   /// * [JobCommandDto] jobCommandDto (required): | ||||||
|  |   Future<Response> sendJobCommandWithHttpInfo(JobId jobId, JobCommandDto jobCommandDto,) async { | ||||||
|  |     // ignore: prefer_const_declarations | ||||||
|  |     final path = r'/jobs/{jobId}' | ||||||
|  |       .replaceAll('{jobId}', jobId.toString()); | ||||||
|  |  | ||||||
|  |     // ignore: prefer_final_locals | ||||||
|  |     Object? postBody = jobCommandDto; | ||||||
|  |  | ||||||
|  |     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: | ||||||
|  |   /// | ||||||
|  |   /// * [JobId] jobId (required): | ||||||
|  |   /// | ||||||
|  |   /// * [JobCommandDto] jobCommandDto (required): | ||||||
|  |   Future<num?> sendJobCommand(JobId jobId, JobCommandDto jobCommandDto,) async { | ||||||
|  |     final response = await sendJobCommandWithHttpInfo(jobId, jobCommandDto,); | ||||||
|  |     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), 'num',) as num; | ||||||
|  |      | ||||||
|  |     } | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -202,6 +202,8 @@ class ApiClient { | |||||||
|           return AlbumCountResponseDto.fromJson(value); |           return AlbumCountResponseDto.fromJson(value); | ||||||
|         case 'AlbumResponseDto': |         case 'AlbumResponseDto': | ||||||
|           return AlbumResponseDto.fromJson(value); |           return AlbumResponseDto.fromJson(value); | ||||||
|  |         case 'AllJobStatusResponseDto': | ||||||
|  |           return AllJobStatusResponseDto.fromJson(value); | ||||||
|         case 'AssetCountByTimeBucket': |         case 'AssetCountByTimeBucket': | ||||||
|           return AssetCountByTimeBucket.fromJson(value); |           return AssetCountByTimeBucket.fromJson(value); | ||||||
|         case 'AssetCountByTimeBucketResponseDto': |         case 'AssetCountByTimeBucketResponseDto': | ||||||
| @@ -246,6 +248,16 @@ class ApiClient { | |||||||
|           return GetAssetByTimeBucketDto.fromJson(value); |           return GetAssetByTimeBucketDto.fromJson(value); | ||||||
|         case 'GetAssetCountByTimeBucketDto': |         case 'GetAssetCountByTimeBucketDto': | ||||||
|           return GetAssetCountByTimeBucketDto.fromJson(value); |           return GetAssetCountByTimeBucketDto.fromJson(value); | ||||||
|  |         case 'JobCommand': | ||||||
|  |           return JobCommandTypeTransformer().decode(value); | ||||||
|  |         case 'JobCommandDto': | ||||||
|  |           return JobCommandDto.fromJson(value); | ||||||
|  |         case 'JobCounts': | ||||||
|  |           return JobCounts.fromJson(value); | ||||||
|  |         case 'JobId': | ||||||
|  |           return JobIdTypeTransformer().decode(value); | ||||||
|  |         case 'JobStatusResponseDto': | ||||||
|  |           return JobStatusResponseDto.fromJson(value); | ||||||
|         case 'LoginCredentialDto': |         case 'LoginCredentialDto': | ||||||
|           return LoginCredentialDto.fromJson(value); |           return LoginCredentialDto.fromJson(value); | ||||||
|         case 'LoginResponseDto': |         case 'LoginResponseDto': | ||||||
|   | |||||||
| @@ -64,6 +64,12 @@ String parameterToString(dynamic value) { | |||||||
|   if (value is DeviceTypeEnum) { |   if (value is DeviceTypeEnum) { | ||||||
|     return DeviceTypeEnumTypeTransformer().encode(value).toString(); |     return DeviceTypeEnumTypeTransformer().encode(value).toString(); | ||||||
|   } |   } | ||||||
|  |   if (value is JobCommand) { | ||||||
|  |     return JobCommandTypeTransformer().encode(value).toString(); | ||||||
|  |   } | ||||||
|  |   if (value is JobId) { | ||||||
|  |     return JobIdTypeTransformer().encode(value).toString(); | ||||||
|  |   } | ||||||
|   if (value is ThumbnailFormat) { |   if (value is ThumbnailFormat) { | ||||||
|     return ThumbnailFormatTypeTransformer().encode(value).toString(); |     return ThumbnailFormatTypeTransformer().encode(value).toString(); | ||||||
|   } |   } | ||||||
|   | |||||||
							
								
								
									
										167
									
								
								mobile/openapi/lib/model/all_job_status_response_dto.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								mobile/openapi/lib/model/all_job_status_response_dto.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,167 @@ | |||||||
|  | // | ||||||
|  | // 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 AllJobStatusResponseDto { | ||||||
|  |   /// Returns a new [AllJobStatusResponseDto] instance. | ||||||
|  |   AllJobStatusResponseDto({ | ||||||
|  |     required this.thumbnailGenerationQueueCount, | ||||||
|  |     required this.metadataExtractionQueueCount, | ||||||
|  |     required this.videoConversionQueueCount, | ||||||
|  |     required this.machineLearningQueueCount, | ||||||
|  |     required this.isThumbnailGenerationActive, | ||||||
|  |     required this.isMetadataExtractionActive, | ||||||
|  |     required this.isVideoConversionActive, | ||||||
|  |     required this.isMachineLearningActive, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   JobCounts thumbnailGenerationQueueCount; | ||||||
|  |  | ||||||
|  |   JobCounts metadataExtractionQueueCount; | ||||||
|  |  | ||||||
|  |   JobCounts videoConversionQueueCount; | ||||||
|  |  | ||||||
|  |   JobCounts machineLearningQueueCount; | ||||||
|  |  | ||||||
|  |   bool isThumbnailGenerationActive; | ||||||
|  |  | ||||||
|  |   bool isMetadataExtractionActive; | ||||||
|  |  | ||||||
|  |   bool isVideoConversionActive; | ||||||
|  |  | ||||||
|  |   bool isMachineLearningActive; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) => identical(this, other) || other is AllJobStatusResponseDto && | ||||||
|  |      other.thumbnailGenerationQueueCount == thumbnailGenerationQueueCount && | ||||||
|  |      other.metadataExtractionQueueCount == metadataExtractionQueueCount && | ||||||
|  |      other.videoConversionQueueCount == videoConversionQueueCount && | ||||||
|  |      other.machineLearningQueueCount == machineLearningQueueCount && | ||||||
|  |      other.isThumbnailGenerationActive == isThumbnailGenerationActive && | ||||||
|  |      other.isMetadataExtractionActive == isMetadataExtractionActive && | ||||||
|  |      other.isVideoConversionActive == isVideoConversionActive && | ||||||
|  |      other.isMachineLearningActive == isMachineLearningActive; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode => | ||||||
|  |     // ignore: unnecessary_parenthesis | ||||||
|  |     (thumbnailGenerationQueueCount.hashCode) + | ||||||
|  |     (metadataExtractionQueueCount.hashCode) + | ||||||
|  |     (videoConversionQueueCount.hashCode) + | ||||||
|  |     (machineLearningQueueCount.hashCode) + | ||||||
|  |     (isThumbnailGenerationActive.hashCode) + | ||||||
|  |     (isMetadataExtractionActive.hashCode) + | ||||||
|  |     (isVideoConversionActive.hashCode) + | ||||||
|  |     (isMachineLearningActive.hashCode); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String toString() => 'AllJobStatusResponseDto[thumbnailGenerationQueueCount=$thumbnailGenerationQueueCount, metadataExtractionQueueCount=$metadataExtractionQueueCount, videoConversionQueueCount=$videoConversionQueueCount, machineLearningQueueCount=$machineLearningQueueCount, isThumbnailGenerationActive=$isThumbnailGenerationActive, isMetadataExtractionActive=$isMetadataExtractionActive, isVideoConversionActive=$isVideoConversionActive, isMachineLearningActive=$isMachineLearningActive]'; | ||||||
|  |  | ||||||
|  |   Map<String, dynamic> toJson() { | ||||||
|  |     final _json = <String, dynamic>{}; | ||||||
|  |       _json[r'thumbnailGenerationQueueCount'] = thumbnailGenerationQueueCount; | ||||||
|  |       _json[r'metadataExtractionQueueCount'] = metadataExtractionQueueCount; | ||||||
|  |       _json[r'videoConversionQueueCount'] = videoConversionQueueCount; | ||||||
|  |       _json[r'machineLearningQueueCount'] = machineLearningQueueCount; | ||||||
|  |       _json[r'isThumbnailGenerationActive'] = isThumbnailGenerationActive; | ||||||
|  |       _json[r'isMetadataExtractionActive'] = isMetadataExtractionActive; | ||||||
|  |       _json[r'isVideoConversionActive'] = isVideoConversionActive; | ||||||
|  |       _json[r'isMachineLearningActive'] = isMachineLearningActive; | ||||||
|  |     return _json; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /// Returns a new [AllJobStatusResponseDto] instance and imports its values from | ||||||
|  |   /// [value] if it's a [Map], null otherwise. | ||||||
|  |   // ignore: prefer_constructors_over_static_methods | ||||||
|  |   static AllJobStatusResponseDto? fromJson(dynamic value) { | ||||||
|  |     if (value is Map) { | ||||||
|  |       final json = value.cast<String, dynamic>(); | ||||||
|  |  | ||||||
|  |       // Ensure that the map contains the required keys. | ||||||
|  |       // Note 1: the values aren't checked for validity beyond being non-null. | ||||||
|  |       // Note 2: this code is stripped in release mode! | ||||||
|  |       assert(() { | ||||||
|  |         requiredKeys.forEach((key) { | ||||||
|  |           assert(json.containsKey(key), 'Required key "AllJobStatusResponseDto[$key]" is missing from JSON.'); | ||||||
|  |           assert(json[key] != null, 'Required key "AllJobStatusResponseDto[$key]" has a null value in JSON.'); | ||||||
|  |         }); | ||||||
|  |         return true; | ||||||
|  |       }()); | ||||||
|  |  | ||||||
|  |       return AllJobStatusResponseDto( | ||||||
|  |         thumbnailGenerationQueueCount: JobCounts.fromJson(json[r'thumbnailGenerationQueueCount'])!, | ||||||
|  |         metadataExtractionQueueCount: JobCounts.fromJson(json[r'metadataExtractionQueueCount'])!, | ||||||
|  |         videoConversionQueueCount: JobCounts.fromJson(json[r'videoConversionQueueCount'])!, | ||||||
|  |         machineLearningQueueCount: JobCounts.fromJson(json[r'machineLearningQueueCount'])!, | ||||||
|  |         isThumbnailGenerationActive: mapValueOfType<bool>(json, r'isThumbnailGenerationActive')!, | ||||||
|  |         isMetadataExtractionActive: mapValueOfType<bool>(json, r'isMetadataExtractionActive')!, | ||||||
|  |         isVideoConversionActive: mapValueOfType<bool>(json, r'isVideoConversionActive')!, | ||||||
|  |         isMachineLearningActive: mapValueOfType<bool>(json, r'isMachineLearningActive')!, | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static List<AllJobStatusResponseDto>? listFromJson(dynamic json, {bool growable = false,}) { | ||||||
|  |     final result = <AllJobStatusResponseDto>[]; | ||||||
|  |     if (json is List && json.isNotEmpty) { | ||||||
|  |       for (final row in json) { | ||||||
|  |         final value = AllJobStatusResponseDto.fromJson(row); | ||||||
|  |         if (value != null) { | ||||||
|  |           result.add(value); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return result.toList(growable: growable); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static Map<String, AllJobStatusResponseDto> mapFromJson(dynamic json) { | ||||||
|  |     final map = <String, AllJobStatusResponseDto>{}; | ||||||
|  |     if (json is Map && json.isNotEmpty) { | ||||||
|  |       json = json.cast<String, dynamic>(); // ignore: parameter_assignments | ||||||
|  |       for (final entry in json.entries) { | ||||||
|  |         final value = AllJobStatusResponseDto.fromJson(entry.value); | ||||||
|  |         if (value != null) { | ||||||
|  |           map[entry.key] = value; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return map; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // maps a json object with a list of AllJobStatusResponseDto-objects as value to a dart map | ||||||
|  |   static Map<String, List<AllJobStatusResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) { | ||||||
|  |     final map = <String, List<AllJobStatusResponseDto>>{}; | ||||||
|  |     if (json is Map && json.isNotEmpty) { | ||||||
|  |       json = json.cast<String, dynamic>(); // ignore: parameter_assignments | ||||||
|  |       for (final entry in json.entries) { | ||||||
|  |         final value = AllJobStatusResponseDto.listFromJson(entry.value, growable: growable,); | ||||||
|  |         if (value != null) { | ||||||
|  |           map[entry.key] = value; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return map; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /// The list of required keys that must be present in a JSON. | ||||||
|  |   static const requiredKeys = <String>{ | ||||||
|  |     'thumbnailGenerationQueueCount', | ||||||
|  |     'metadataExtractionQueueCount', | ||||||
|  |     'videoConversionQueueCount', | ||||||
|  |     'machineLearningQueueCount', | ||||||
|  |     'isThumbnailGenerationActive', | ||||||
|  |     'isMetadataExtractionActive', | ||||||
|  |     'isVideoConversionActive', | ||||||
|  |     'isMachineLearningActive', | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  |  | ||||||
| @@ -76,72 +76,69 @@ class AssetResponseDto { | |||||||
|   SmartInfoResponseDto? smartInfo; |   SmartInfoResponseDto? smartInfo; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   bool operator ==(Object other) => |   bool operator ==(Object other) => identical(this, other) || other is AssetResponseDto && | ||||||
|       identical(this, other) || |      other.type == type && | ||||||
|       other is AssetResponseDto && |      other.id == id && | ||||||
|           other.type == type && |      other.deviceAssetId == deviceAssetId && | ||||||
|           other.id == id && |      other.ownerId == ownerId && | ||||||
|           other.deviceAssetId == deviceAssetId && |      other.deviceId == deviceId && | ||||||
|           other.ownerId == ownerId && |      other.originalPath == originalPath && | ||||||
|           other.deviceId == deviceId && |      other.resizePath == resizePath && | ||||||
|           other.originalPath == originalPath && |      other.createdAt == createdAt && | ||||||
|           other.resizePath == resizePath && |      other.modifiedAt == modifiedAt && | ||||||
|           other.createdAt == createdAt && |      other.isFavorite == isFavorite && | ||||||
|           other.modifiedAt == modifiedAt && |      other.mimeType == mimeType && | ||||||
|           other.isFavorite == isFavorite && |      other.duration == duration && | ||||||
|           other.mimeType == mimeType && |      other.webpPath == webpPath && | ||||||
|           other.duration == duration && |      other.encodedVideoPath == encodedVideoPath && | ||||||
|           other.webpPath == webpPath && |      other.exifInfo == exifInfo && | ||||||
|           other.encodedVideoPath == encodedVideoPath && |      other.smartInfo == smartInfo; | ||||||
|           other.exifInfo == exifInfo && |  | ||||||
|           other.smartInfo == smartInfo; |  | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   int get hashCode => |   int get hashCode => | ||||||
|       // ignore: unnecessary_parenthesis |     // ignore: unnecessary_parenthesis | ||||||
|       (type.hashCode) + |     (type.hashCode) + | ||||||
|       (id.hashCode) + |     (id.hashCode) + | ||||||
|       (deviceAssetId.hashCode) + |     (deviceAssetId.hashCode) + | ||||||
|       (ownerId.hashCode) + |     (ownerId.hashCode) + | ||||||
|       (deviceId.hashCode) + |     (deviceId.hashCode) + | ||||||
|       (originalPath.hashCode) + |     (originalPath.hashCode) + | ||||||
|       (resizePath == null ? 0 : resizePath!.hashCode) + |     (resizePath == null ? 0 : resizePath!.hashCode) + | ||||||
|       (createdAt.hashCode) + |     (createdAt.hashCode) + | ||||||
|       (modifiedAt.hashCode) + |     (modifiedAt.hashCode) + | ||||||
|       (isFavorite.hashCode) + |     (isFavorite.hashCode) + | ||||||
|       (mimeType == null ? 0 : mimeType!.hashCode) + |     (mimeType == null ? 0 : mimeType!.hashCode) + | ||||||
|       (duration.hashCode) + |     (duration.hashCode) + | ||||||
|       (webpPath == null ? 0 : webpPath!.hashCode) + |     (webpPath == null ? 0 : webpPath!.hashCode) + | ||||||
|       (encodedVideoPath == null ? 0 : encodedVideoPath!.hashCode) + |     (encodedVideoPath == null ? 0 : encodedVideoPath!.hashCode) + | ||||||
|       (exifInfo == null ? 0 : exifInfo!.hashCode) + |     (exifInfo == null ? 0 : exifInfo!.hashCode) + | ||||||
|       (smartInfo == null ? 0 : smartInfo!.hashCode); |     (smartInfo == null ? 0 : smartInfo!.hashCode); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   String toString() => |   String toString() => 'AssetResponseDto[type=$type, id=$id, deviceAssetId=$deviceAssetId, ownerId=$ownerId, deviceId=$deviceId, originalPath=$originalPath, resizePath=$resizePath, createdAt=$createdAt, modifiedAt=$modifiedAt, isFavorite=$isFavorite, mimeType=$mimeType, duration=$duration, webpPath=$webpPath, encodedVideoPath=$encodedVideoPath, exifInfo=$exifInfo, smartInfo=$smartInfo]'; | ||||||
|       'AssetResponseDto[type=$type, id=$id, deviceAssetId=$deviceAssetId, ownerId=$ownerId, deviceId=$deviceId, originalPath=$originalPath, resizePath=$resizePath, createdAt=$createdAt, modifiedAt=$modifiedAt, isFavorite=$isFavorite, mimeType=$mimeType, duration=$duration, webpPath=$webpPath, encodedVideoPath=$encodedVideoPath, exifInfo=$exifInfo, smartInfo=$smartInfo]'; |  | ||||||
|  |  | ||||||
|   Map<String, dynamic> toJson() { |   Map<String, dynamic> toJson() { | ||||||
|     final _json = <String, dynamic>{}; |     final _json = <String, dynamic>{}; | ||||||
|     _json[r'type'] = type; |       _json[r'type'] = type; | ||||||
|     _json[r'id'] = id; |       _json[r'id'] = id; | ||||||
|     _json[r'deviceAssetId'] = deviceAssetId; |       _json[r'deviceAssetId'] = deviceAssetId; | ||||||
|     _json[r'ownerId'] = ownerId; |       _json[r'ownerId'] = ownerId; | ||||||
|     _json[r'deviceId'] = deviceId; |       _json[r'deviceId'] = deviceId; | ||||||
|     _json[r'originalPath'] = originalPath; |       _json[r'originalPath'] = originalPath; | ||||||
|     if (resizePath != null) { |     if (resizePath != null) { | ||||||
|       _json[r'resizePath'] = resizePath; |       _json[r'resizePath'] = resizePath; | ||||||
|     } else { |     } else { | ||||||
|       _json[r'resizePath'] = null; |       _json[r'resizePath'] = null; | ||||||
|     } |     } | ||||||
|     _json[r'createdAt'] = createdAt; |       _json[r'createdAt'] = createdAt; | ||||||
|     _json[r'modifiedAt'] = modifiedAt; |       _json[r'modifiedAt'] = modifiedAt; | ||||||
|     _json[r'isFavorite'] = isFavorite; |       _json[r'isFavorite'] = isFavorite; | ||||||
|     if (mimeType != null) { |     if (mimeType != null) { | ||||||
|       _json[r'mimeType'] = mimeType; |       _json[r'mimeType'] = mimeType; | ||||||
|     } else { |     } else { | ||||||
|       _json[r'mimeType'] = null; |       _json[r'mimeType'] = null; | ||||||
|     } |     } | ||||||
|     _json[r'duration'] = duration; |       _json[r'duration'] = duration; | ||||||
|     if (webpPath != null) { |     if (webpPath != null) { | ||||||
|       _json[r'webpPath'] = webpPath; |       _json[r'webpPath'] = webpPath; | ||||||
|     } else { |     } else { | ||||||
| @@ -175,13 +172,13 @@ class AssetResponseDto { | |||||||
|       // Ensure that the map contains the required keys. |       // Ensure that the map contains the required keys. | ||||||
|       // Note 1: the values aren't checked for validity beyond being non-null. |       // Note 1: the values aren't checked for validity beyond being non-null. | ||||||
|       // Note 2: this code is stripped in release mode! |       // Note 2: this code is stripped in release mode! | ||||||
|       // assert(() { |       assert(() { | ||||||
|       //   requiredKeys.forEach((key) { |         requiredKeys.forEach((key) { | ||||||
|       //     assert(json.containsKey(key), 'Required key "AssetResponseDto[$key]" is missing from JSON.'); |           assert(json.containsKey(key), 'Required key "AssetResponseDto[$key]" is missing from JSON.'); | ||||||
|       //     assert(json[key] != null, 'Required key "AssetResponseDto[$key]" has a null value in JSON.'); |           assert(json[key] != null, 'Required key "AssetResponseDto[$key]" has a null value in JSON.'); | ||||||
|       //   }); |         }); | ||||||
|       //   return true; |         return true; | ||||||
|       // }()); |       }()); | ||||||
|  |  | ||||||
|       return AssetResponseDto( |       return AssetResponseDto( | ||||||
|         type: AssetTypeEnum.fromJson(json[r'type'])!, |         type: AssetTypeEnum.fromJson(json[r'type'])!, | ||||||
| @@ -205,10 +202,7 @@ class AssetResponseDto { | |||||||
|     return null; |     return null; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   static List<AssetResponseDto>? listFromJson( |   static List<AssetResponseDto>? listFromJson(dynamic json, {bool growable = false,}) { | ||||||
|     dynamic json, { |  | ||||||
|     bool growable = false, |  | ||||||
|   }) { |  | ||||||
|     final result = <AssetResponseDto>[]; |     final result = <AssetResponseDto>[]; | ||||||
|     if (json is List && json.isNotEmpty) { |     if (json is List && json.isNotEmpty) { | ||||||
|       for (final row in json) { |       for (final row in json) { | ||||||
| @@ -236,18 +230,12 @@ class AssetResponseDto { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   // maps a json object with a list of AssetResponseDto-objects as value to a dart map |   // maps a json object with a list of AssetResponseDto-objects as value to a dart map | ||||||
|   static Map<String, List<AssetResponseDto>> mapListFromJson( |   static Map<String, List<AssetResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) { | ||||||
|     dynamic json, { |  | ||||||
|     bool growable = false, |  | ||||||
|   }) { |  | ||||||
|     final map = <String, List<AssetResponseDto>>{}; |     final map = <String, List<AssetResponseDto>>{}; | ||||||
|     if (json is Map && json.isNotEmpty) { |     if (json is Map && json.isNotEmpty) { | ||||||
|       json = json.cast<String, dynamic>(); // ignore: parameter_assignments |       json = json.cast<String, dynamic>(); // ignore: parameter_assignments | ||||||
|       for (final entry in json.entries) { |       for (final entry in json.entries) { | ||||||
|         final value = AssetResponseDto.listFromJson( |         final value = AssetResponseDto.listFromJson(entry.value, growable: growable,); | ||||||
|           entry.value, |  | ||||||
|           growable: growable, |  | ||||||
|         ); |  | ||||||
|         if (value != null) { |         if (value != null) { | ||||||
|           map[entry.key] = value; |           map[entry.key] = value; | ||||||
|         } |         } | ||||||
| @@ -274,3 +262,4 @@ class AssetResponseDto { | |||||||
|     'encodedVideoPath', |     'encodedVideoPath', | ||||||
|   }; |   }; | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										111
									
								
								mobile/openapi/lib/model/create_job_dto.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								mobile/openapi/lib/model/create_job_dto.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,111 @@ | |||||||
|  | // | ||||||
|  | // AUTO-GENERATED FILE, DO NOT MODIFY! | ||||||
|  | // | ||||||
|  | // @dart=2.12 | ||||||
|  |  | ||||||
|  | // ignore_for_file: unused_element, unused_import | ||||||
|  | // ignore_for_file: always_put_required_named_parameters_first | ||||||
|  | // ignore_for_file: constant_identifier_names | ||||||
|  | // ignore_for_file: lines_longer_than_80_chars | ||||||
|  |  | ||||||
|  | part of openapi.api; | ||||||
|  |  | ||||||
|  | class CreateJobDto { | ||||||
|  |   /// Returns a new [CreateJobDto] instance. | ||||||
|  |   CreateJobDto({ | ||||||
|  |     required this.jobType, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   JobType jobType; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) => identical(this, other) || other is CreateJobDto && | ||||||
|  |      other.jobType == jobType; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode => | ||||||
|  |     // ignore: unnecessary_parenthesis | ||||||
|  |     (jobType.hashCode); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String toString() => 'CreateJobDto[jobType=$jobType]'; | ||||||
|  |  | ||||||
|  |   Map<String, dynamic> toJson() { | ||||||
|  |     final _json = <String, dynamic>{}; | ||||||
|  |       _json[r'jobType'] = jobType; | ||||||
|  |     return _json; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /// Returns a new [CreateJobDto] instance and imports its values from | ||||||
|  |   /// [value] if it's a [Map], null otherwise. | ||||||
|  |   // ignore: prefer_constructors_over_static_methods | ||||||
|  |   static CreateJobDto? fromJson(dynamic value) { | ||||||
|  |     if (value is Map) { | ||||||
|  |       final json = value.cast<String, dynamic>(); | ||||||
|  |  | ||||||
|  |       // Ensure that the map contains the required keys. | ||||||
|  |       // Note 1: the values aren't checked for validity beyond being non-null. | ||||||
|  |       // Note 2: this code is stripped in release mode! | ||||||
|  |       assert(() { | ||||||
|  |         requiredKeys.forEach((key) { | ||||||
|  |           assert(json.containsKey(key), 'Required key "CreateJobDto[$key]" is missing from JSON.'); | ||||||
|  |           assert(json[key] != null, 'Required key "CreateJobDto[$key]" has a null value in JSON.'); | ||||||
|  |         }); | ||||||
|  |         return true; | ||||||
|  |       }()); | ||||||
|  |  | ||||||
|  |       return CreateJobDto( | ||||||
|  |         jobType: JobType.fromJson(json[r'jobType'])!, | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static List<CreateJobDto>? listFromJson(dynamic json, {bool growable = false,}) { | ||||||
|  |     final result = <CreateJobDto>[]; | ||||||
|  |     if (json is List && json.isNotEmpty) { | ||||||
|  |       for (final row in json) { | ||||||
|  |         final value = CreateJobDto.fromJson(row); | ||||||
|  |         if (value != null) { | ||||||
|  |           result.add(value); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return result.toList(growable: growable); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static Map<String, CreateJobDto> mapFromJson(dynamic json) { | ||||||
|  |     final map = <String, CreateJobDto>{}; | ||||||
|  |     if (json is Map && json.isNotEmpty) { | ||||||
|  |       json = json.cast<String, dynamic>(); // ignore: parameter_assignments | ||||||
|  |       for (final entry in json.entries) { | ||||||
|  |         final value = CreateJobDto.fromJson(entry.value); | ||||||
|  |         if (value != null) { | ||||||
|  |           map[entry.key] = value; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return map; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // maps a json object with a list of CreateJobDto-objects as value to a dart map | ||||||
|  |   static Map<String, List<CreateJobDto>> mapListFromJson(dynamic json, {bool growable = false,}) { | ||||||
|  |     final map = <String, List<CreateJobDto>>{}; | ||||||
|  |     if (json is Map && json.isNotEmpty) { | ||||||
|  |       json = json.cast<String, dynamic>(); // ignore: parameter_assignments | ||||||
|  |       for (final entry in json.entries) { | ||||||
|  |         final value = CreateJobDto.listFromJson(entry.value, growable: growable,); | ||||||
|  |         if (value != null) { | ||||||
|  |           map[entry.key] = value; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return map; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /// The list of required keys that must be present in a JSON. | ||||||
|  |   static const requiredKeys = <String>{ | ||||||
|  |     'jobType', | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  |  | ||||||
| @@ -14,12 +14,12 @@ class ExifResponseDto { | |||||||
|   /// Returns a new [ExifResponseDto] instance. |   /// Returns a new [ExifResponseDto] instance. | ||||||
|   ExifResponseDto({ |   ExifResponseDto({ | ||||||
|     this.id, |     this.id, | ||||||
|  |     this.fileSizeInByte, | ||||||
|     this.make, |     this.make, | ||||||
|     this.model, |     this.model, | ||||||
|     this.imageName, |     this.imageName, | ||||||
|     this.exifImageWidth, |     this.exifImageWidth, | ||||||
|     this.exifImageHeight, |     this.exifImageHeight, | ||||||
|     this.fileSizeInByte, |  | ||||||
|     this.orientation, |     this.orientation, | ||||||
|     this.dateTimeOriginal, |     this.dateTimeOriginal, | ||||||
|     this.modifyDate, |     this.modifyDate, | ||||||
| @@ -35,7 +35,9 @@ class ExifResponseDto { | |||||||
|     this.country, |     this.country, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   String? id; |   int? id; | ||||||
|  |  | ||||||
|  |   int? fileSizeInByte; | ||||||
|  |  | ||||||
|   String? make; |   String? make; | ||||||
|  |  | ||||||
| @@ -47,8 +49,6 @@ class ExifResponseDto { | |||||||
|  |  | ||||||
|   num? exifImageHeight; |   num? exifImageHeight; | ||||||
|  |  | ||||||
|   num? fileSizeInByte; |  | ||||||
|  |  | ||||||
|   String? orientation; |   String? orientation; | ||||||
|  |  | ||||||
|   DateTime? dateTimeOriginal; |   DateTime? dateTimeOriginal; | ||||||
| @@ -78,12 +78,12 @@ class ExifResponseDto { | |||||||
|   @override |   @override | ||||||
|   bool operator ==(Object other) => identical(this, other) || other is ExifResponseDto && |   bool operator ==(Object other) => identical(this, other) || other is ExifResponseDto && | ||||||
|      other.id == id && |      other.id == id && | ||||||
|  |      other.fileSizeInByte == fileSizeInByte && | ||||||
|      other.make == make && |      other.make == make && | ||||||
|      other.model == model && |      other.model == model && | ||||||
|      other.imageName == imageName && |      other.imageName == imageName && | ||||||
|      other.exifImageWidth == exifImageWidth && |      other.exifImageWidth == exifImageWidth && | ||||||
|      other.exifImageHeight == exifImageHeight && |      other.exifImageHeight == exifImageHeight && | ||||||
|      other.fileSizeInByte == fileSizeInByte && |  | ||||||
|      other.orientation == orientation && |      other.orientation == orientation && | ||||||
|      other.dateTimeOriginal == dateTimeOriginal && |      other.dateTimeOriginal == dateTimeOriginal && | ||||||
|      other.modifyDate == modifyDate && |      other.modifyDate == modifyDate && | ||||||
| @@ -102,12 +102,12 @@ class ExifResponseDto { | |||||||
|   int get hashCode => |   int get hashCode => | ||||||
|     // ignore: unnecessary_parenthesis |     // ignore: unnecessary_parenthesis | ||||||
|     (id == null ? 0 : id!.hashCode) + |     (id == null ? 0 : id!.hashCode) + | ||||||
|  |     (fileSizeInByte == null ? 0 : fileSizeInByte!.hashCode) + | ||||||
|     (make == null ? 0 : make!.hashCode) + |     (make == null ? 0 : make!.hashCode) + | ||||||
|     (model == null ? 0 : model!.hashCode) + |     (model == null ? 0 : model!.hashCode) + | ||||||
|     (imageName == null ? 0 : imageName!.hashCode) + |     (imageName == null ? 0 : imageName!.hashCode) + | ||||||
|     (exifImageWidth == null ? 0 : exifImageWidth!.hashCode) + |     (exifImageWidth == null ? 0 : exifImageWidth!.hashCode) + | ||||||
|     (exifImageHeight == null ? 0 : exifImageHeight!.hashCode) + |     (exifImageHeight == null ? 0 : exifImageHeight!.hashCode) + | ||||||
|     (fileSizeInByte == null ? 0 : fileSizeInByte!.hashCode) + |  | ||||||
|     (orientation == null ? 0 : orientation!.hashCode) + |     (orientation == null ? 0 : orientation!.hashCode) + | ||||||
|     (dateTimeOriginal == null ? 0 : dateTimeOriginal!.hashCode) + |     (dateTimeOriginal == null ? 0 : dateTimeOriginal!.hashCode) + | ||||||
|     (modifyDate == null ? 0 : modifyDate!.hashCode) + |     (modifyDate == null ? 0 : modifyDate!.hashCode) + | ||||||
| @@ -123,7 +123,7 @@ class ExifResponseDto { | |||||||
|     (country == null ? 0 : country!.hashCode); |     (country == null ? 0 : country!.hashCode); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   String toString() => 'ExifResponseDto[id=$id, make=$make, model=$model, imageName=$imageName, exifImageWidth=$exifImageWidth, exifImageHeight=$exifImageHeight, fileSizeInByte=$fileSizeInByte, orientation=$orientation, dateTimeOriginal=$dateTimeOriginal, modifyDate=$modifyDate, lensModel=$lensModel, fNumber=$fNumber, focalLength=$focalLength, iso=$iso, exposureTime=$exposureTime, latitude=$latitude, longitude=$longitude, city=$city, state=$state, country=$country]'; |   String toString() => 'ExifResponseDto[id=$id, fileSizeInByte=$fileSizeInByte, make=$make, model=$model, imageName=$imageName, exifImageWidth=$exifImageWidth, exifImageHeight=$exifImageHeight, orientation=$orientation, dateTimeOriginal=$dateTimeOriginal, modifyDate=$modifyDate, lensModel=$lensModel, fNumber=$fNumber, focalLength=$focalLength, iso=$iso, exposureTime=$exposureTime, latitude=$latitude, longitude=$longitude, city=$city, state=$state, country=$country]'; | ||||||
|  |  | ||||||
|   Map<String, dynamic> toJson() { |   Map<String, dynamic> toJson() { | ||||||
|     final _json = <String, dynamic>{}; |     final _json = <String, dynamic>{}; | ||||||
| @@ -132,6 +132,11 @@ class ExifResponseDto { | |||||||
|     } else { |     } else { | ||||||
|       _json[r'id'] = null; |       _json[r'id'] = null; | ||||||
|     } |     } | ||||||
|  |     if (fileSizeInByte != null) { | ||||||
|  |       _json[r'fileSizeInByte'] = fileSizeInByte; | ||||||
|  |     } else { | ||||||
|  |       _json[r'fileSizeInByte'] = null; | ||||||
|  |     } | ||||||
|     if (make != null) { |     if (make != null) { | ||||||
|       _json[r'make'] = make; |       _json[r'make'] = make; | ||||||
|     } else { |     } else { | ||||||
| @@ -157,11 +162,6 @@ class ExifResponseDto { | |||||||
|     } else { |     } else { | ||||||
|       _json[r'exifImageHeight'] = null; |       _json[r'exifImageHeight'] = null; | ||||||
|     } |     } | ||||||
|     if (fileSizeInByte != null) { |  | ||||||
|       _json[r'fileSizeInByte'] = fileSizeInByte; |  | ||||||
|     } else { |  | ||||||
|       _json[r'fileSizeInByte'] = null; |  | ||||||
|     } |  | ||||||
|     if (orientation != null) { |     if (orientation != null) { | ||||||
|       _json[r'orientation'] = orientation; |       _json[r'orientation'] = orientation; | ||||||
|     } else { |     } else { | ||||||
| @@ -249,7 +249,8 @@ class ExifResponseDto { | |||||||
|       }()); |       }()); | ||||||
|  |  | ||||||
|       return ExifResponseDto( |       return ExifResponseDto( | ||||||
|         id: mapValueOfType<String>(json, r'id'), |         id: mapValueOfType<int>(json, r'id'), | ||||||
|  |         fileSizeInByte: mapValueOfType<int>(json, r'fileSizeInByte'), | ||||||
|         make: mapValueOfType<String>(json, r'make'), |         make: mapValueOfType<String>(json, r'make'), | ||||||
|         model: mapValueOfType<String>(json, r'model'), |         model: mapValueOfType<String>(json, r'model'), | ||||||
|         imageName: mapValueOfType<String>(json, r'imageName'), |         imageName: mapValueOfType<String>(json, r'imageName'), | ||||||
| @@ -259,9 +260,6 @@ class ExifResponseDto { | |||||||
|         exifImageHeight: json[r'exifImageHeight'] == null |         exifImageHeight: json[r'exifImageHeight'] == null | ||||||
|             ? null |             ? null | ||||||
|             : num.parse(json[r'exifImageHeight'].toString()), |             : num.parse(json[r'exifImageHeight'].toString()), | ||||||
|         fileSizeInByte: json[r'fileSizeInByte'] == null |  | ||||||
|             ? null |  | ||||||
|             : num.parse(json[r'fileSizeInByte'].toString()), |  | ||||||
|         orientation: mapValueOfType<String>(json, r'orientation'), |         orientation: mapValueOfType<String>(json, r'orientation'), | ||||||
|         dateTimeOriginal: mapDateTime(json, r'dateTimeOriginal', ''), |         dateTimeOriginal: mapDateTime(json, r'dateTimeOriginal', ''), | ||||||
|         modifyDate: mapDateTime(json, r'modifyDate', ''), |         modifyDate: mapDateTime(json, r'modifyDate', ''), | ||||||
|   | |||||||
							
								
								
									
										85
									
								
								mobile/openapi/lib/model/job_command.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								mobile/openapi/lib/model/job_command.dart
									
									
									
									
									
										Normal 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 JobCommand { | ||||||
|  |   /// Instantiate a new enum with the provided [value]. | ||||||
|  |   const JobCommand._(this.value); | ||||||
|  |  | ||||||
|  |   /// The underlying value of this enum member. | ||||||
|  |   final String value; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String toString() => value; | ||||||
|  |  | ||||||
|  |   String toJson() => value; | ||||||
|  |  | ||||||
|  |   static const start = JobCommand._(r'start'); | ||||||
|  |   static const stop = JobCommand._(r'stop'); | ||||||
|  |  | ||||||
|  |   /// List of all possible values in this [enum][JobCommand]. | ||||||
|  |   static const values = <JobCommand>[ | ||||||
|  |     start, | ||||||
|  |     stop, | ||||||
|  |   ]; | ||||||
|  |  | ||||||
|  |   static JobCommand? fromJson(dynamic value) => JobCommandTypeTransformer().decode(value); | ||||||
|  |  | ||||||
|  |   static List<JobCommand>? listFromJson(dynamic json, {bool growable = false,}) { | ||||||
|  |     final result = <JobCommand>[]; | ||||||
|  |     if (json is List && json.isNotEmpty) { | ||||||
|  |       for (final row in json) { | ||||||
|  |         final value = JobCommand.fromJson(row); | ||||||
|  |         if (value != null) { | ||||||
|  |           result.add(value); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return result.toList(growable: growable); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Transformation class that can [encode] an instance of [JobCommand] to String, | ||||||
|  | /// and [decode] dynamic data back to [JobCommand]. | ||||||
|  | class JobCommandTypeTransformer { | ||||||
|  |   factory JobCommandTypeTransformer() => _instance ??= const JobCommandTypeTransformer._(); | ||||||
|  |  | ||||||
|  |   const JobCommandTypeTransformer._(); | ||||||
|  |  | ||||||
|  |   String encode(JobCommand data) => data.value; | ||||||
|  |  | ||||||
|  |   /// Decodes a [dynamic value][data] to a JobCommand. | ||||||
|  |   /// | ||||||
|  |   /// 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. | ||||||
|  |   JobCommand? decode(dynamic data, {bool allowNull = true}) { | ||||||
|  |     if (data != null) { | ||||||
|  |       switch (data.toString()) { | ||||||
|  |         case r'start': return JobCommand.start; | ||||||
|  |         case r'stop': return JobCommand.stop; | ||||||
|  |         default: | ||||||
|  |           if (!allowNull) { | ||||||
|  |             throw ArgumentError('Unknown enum value to decode: $data'); | ||||||
|  |           } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /// Singleton [JobCommandTypeTransformer] instance. | ||||||
|  |   static JobCommandTypeTransformer? _instance; | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										111
									
								
								mobile/openapi/lib/model/job_command_dto.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								mobile/openapi/lib/model/job_command_dto.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,111 @@ | |||||||
|  | // | ||||||
|  | // AUTO-GENERATED FILE, DO NOT MODIFY! | ||||||
|  | // | ||||||
|  | // @dart=2.12 | ||||||
|  |  | ||||||
|  | // ignore_for_file: unused_element, unused_import | ||||||
|  | // ignore_for_file: always_put_required_named_parameters_first | ||||||
|  | // ignore_for_file: constant_identifier_names | ||||||
|  | // ignore_for_file: lines_longer_than_80_chars | ||||||
|  |  | ||||||
|  | part of openapi.api; | ||||||
|  |  | ||||||
|  | class JobCommandDto { | ||||||
|  |   /// Returns a new [JobCommandDto] instance. | ||||||
|  |   JobCommandDto({ | ||||||
|  |     required this.command, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   JobCommand command; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) => identical(this, other) || other is JobCommandDto && | ||||||
|  |      other.command == command; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode => | ||||||
|  |     // ignore: unnecessary_parenthesis | ||||||
|  |     (command.hashCode); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String toString() => 'JobCommandDto[command=$command]'; | ||||||
|  |  | ||||||
|  |   Map<String, dynamic> toJson() { | ||||||
|  |     final _json = <String, dynamic>{}; | ||||||
|  |       _json[r'command'] = command; | ||||||
|  |     return _json; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /// Returns a new [JobCommandDto] instance and imports its values from | ||||||
|  |   /// [value] if it's a [Map], null otherwise. | ||||||
|  |   // ignore: prefer_constructors_over_static_methods | ||||||
|  |   static JobCommandDto? fromJson(dynamic value) { | ||||||
|  |     if (value is Map) { | ||||||
|  |       final json = value.cast<String, dynamic>(); | ||||||
|  |  | ||||||
|  |       // Ensure that the map contains the required keys. | ||||||
|  |       // Note 1: the values aren't checked for validity beyond being non-null. | ||||||
|  |       // Note 2: this code is stripped in release mode! | ||||||
|  |       assert(() { | ||||||
|  |         requiredKeys.forEach((key) { | ||||||
|  |           assert(json.containsKey(key), 'Required key "JobCommandDto[$key]" is missing from JSON.'); | ||||||
|  |           assert(json[key] != null, 'Required key "JobCommandDto[$key]" has a null value in JSON.'); | ||||||
|  |         }); | ||||||
|  |         return true; | ||||||
|  |       }()); | ||||||
|  |  | ||||||
|  |       return JobCommandDto( | ||||||
|  |         command: JobCommand.fromJson(json[r'command'])!, | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static List<JobCommandDto>? listFromJson(dynamic json, {bool growable = false,}) { | ||||||
|  |     final result = <JobCommandDto>[]; | ||||||
|  |     if (json is List && json.isNotEmpty) { | ||||||
|  |       for (final row in json) { | ||||||
|  |         final value = JobCommandDto.fromJson(row); | ||||||
|  |         if (value != null) { | ||||||
|  |           result.add(value); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return result.toList(growable: growable); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static Map<String, JobCommandDto> mapFromJson(dynamic json) { | ||||||
|  |     final map = <String, JobCommandDto>{}; | ||||||
|  |     if (json is Map && json.isNotEmpty) { | ||||||
|  |       json = json.cast<String, dynamic>(); // ignore: parameter_assignments | ||||||
|  |       for (final entry in json.entries) { | ||||||
|  |         final value = JobCommandDto.fromJson(entry.value); | ||||||
|  |         if (value != null) { | ||||||
|  |           map[entry.key] = value; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return map; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // maps a json object with a list of JobCommandDto-objects as value to a dart map | ||||||
|  |   static Map<String, List<JobCommandDto>> mapListFromJson(dynamic json, {bool growable = false,}) { | ||||||
|  |     final map = <String, List<JobCommandDto>>{}; | ||||||
|  |     if (json is Map && json.isNotEmpty) { | ||||||
|  |       json = json.cast<String, dynamic>(); // ignore: parameter_assignments | ||||||
|  |       for (final entry in json.entries) { | ||||||
|  |         final value = JobCommandDto.listFromJson(entry.value, growable: growable,); | ||||||
|  |         if (value != null) { | ||||||
|  |           map[entry.key] = value; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return map; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /// The list of required keys that must be present in a JSON. | ||||||
|  |   static const requiredKeys = <String>{ | ||||||
|  |     'command', | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										153
									
								
								mobile/openapi/lib/model/job_counts.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								mobile/openapi/lib/model/job_counts.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,153 @@ | |||||||
|  | // | ||||||
|  | // 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 JobCounts { | ||||||
|  |   /// Returns a new [JobCounts] instance. | ||||||
|  |   JobCounts({ | ||||||
|  |     required this.active, | ||||||
|  |     required this.completed, | ||||||
|  |     required this.failed, | ||||||
|  |     required this.delayed, | ||||||
|  |     required this.waiting, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   num active; | ||||||
|  |  | ||||||
|  |   num completed; | ||||||
|  |  | ||||||
|  |   num failed; | ||||||
|  |  | ||||||
|  |   num delayed; | ||||||
|  |  | ||||||
|  |   num waiting; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) => identical(this, other) || other is JobCounts && | ||||||
|  |      other.active == active && | ||||||
|  |      other.completed == completed && | ||||||
|  |      other.failed == failed && | ||||||
|  |      other.delayed == delayed && | ||||||
|  |      other.waiting == waiting; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode => | ||||||
|  |     // ignore: unnecessary_parenthesis | ||||||
|  |     (active.hashCode) + | ||||||
|  |     (completed.hashCode) + | ||||||
|  |     (failed.hashCode) + | ||||||
|  |     (delayed.hashCode) + | ||||||
|  |     (waiting.hashCode); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String toString() => 'JobCounts[active=$active, completed=$completed, failed=$failed, delayed=$delayed, waiting=$waiting]'; | ||||||
|  |  | ||||||
|  |   Map<String, dynamic> toJson() { | ||||||
|  |     final _json = <String, dynamic>{}; | ||||||
|  |       _json[r'active'] = active; | ||||||
|  |       _json[r'completed'] = completed; | ||||||
|  |       _json[r'failed'] = failed; | ||||||
|  |       _json[r'delayed'] = delayed; | ||||||
|  |       _json[r'waiting'] = waiting; | ||||||
|  |     return _json; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /// Returns a new [JobCounts] instance and imports its values from | ||||||
|  |   /// [value] if it's a [Map], null otherwise. | ||||||
|  |   // ignore: prefer_constructors_over_static_methods | ||||||
|  |   static JobCounts? fromJson(dynamic value) { | ||||||
|  |     if (value is Map) { | ||||||
|  |       final json = value.cast<String, dynamic>(); | ||||||
|  |  | ||||||
|  |       // Ensure that the map contains the required keys. | ||||||
|  |       // Note 1: the values aren't checked for validity beyond being non-null. | ||||||
|  |       // Note 2: this code is stripped in release mode! | ||||||
|  |       assert(() { | ||||||
|  |         requiredKeys.forEach((key) { | ||||||
|  |           assert(json.containsKey(key), 'Required key "JobCounts[$key]" is missing from JSON.'); | ||||||
|  |           assert(json[key] != null, 'Required key "JobCounts[$key]" has a null value in JSON.'); | ||||||
|  |         }); | ||||||
|  |         return true; | ||||||
|  |       }()); | ||||||
|  |  | ||||||
|  |       return JobCounts( | ||||||
|  |         active: json[r'active'] == null | ||||||
|  |             ? null | ||||||
|  |             : num.parse(json[r'active'].toString()), | ||||||
|  |         completed: json[r'completed'] == null | ||||||
|  |             ? null | ||||||
|  |             : num.parse(json[r'completed'].toString()), | ||||||
|  |         failed: json[r'failed'] == null | ||||||
|  |             ? null | ||||||
|  |             : num.parse(json[r'failed'].toString()), | ||||||
|  |         delayed: json[r'delayed'] == null | ||||||
|  |             ? null | ||||||
|  |             : num.parse(json[r'delayed'].toString()), | ||||||
|  |         waiting: json[r'waiting'] == null | ||||||
|  |             ? null | ||||||
|  |             : num.parse(json[r'waiting'].toString()), | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static List<JobCounts>? listFromJson(dynamic json, {bool growable = false,}) { | ||||||
|  |     final result = <JobCounts>[]; | ||||||
|  |     if (json is List && json.isNotEmpty) { | ||||||
|  |       for (final row in json) { | ||||||
|  |         final value = JobCounts.fromJson(row); | ||||||
|  |         if (value != null) { | ||||||
|  |           result.add(value); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return result.toList(growable: growable); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static Map<String, JobCounts> mapFromJson(dynamic json) { | ||||||
|  |     final map = <String, JobCounts>{}; | ||||||
|  |     if (json is Map && json.isNotEmpty) { | ||||||
|  |       json = json.cast<String, dynamic>(); // ignore: parameter_assignments | ||||||
|  |       for (final entry in json.entries) { | ||||||
|  |         final value = JobCounts.fromJson(entry.value); | ||||||
|  |         if (value != null) { | ||||||
|  |           map[entry.key] = value; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return map; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // maps a json object with a list of JobCounts-objects as value to a dart map | ||||||
|  |   static Map<String, List<JobCounts>> mapListFromJson(dynamic json, {bool growable = false,}) { | ||||||
|  |     final map = <String, List<JobCounts>>{}; | ||||||
|  |     if (json is Map && json.isNotEmpty) { | ||||||
|  |       json = json.cast<String, dynamic>(); // ignore: parameter_assignments | ||||||
|  |       for (final entry in json.entries) { | ||||||
|  |         final value = JobCounts.listFromJson(entry.value, growable: growable,); | ||||||
|  |         if (value != null) { | ||||||
|  |           map[entry.key] = value; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return map; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /// The list of required keys that must be present in a JSON. | ||||||
|  |   static const requiredKeys = <String>{ | ||||||
|  |     'active', | ||||||
|  |     'completed', | ||||||
|  |     'failed', | ||||||
|  |     'delayed', | ||||||
|  |     'waiting', | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										91
									
								
								mobile/openapi/lib/model/job_id.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								mobile/openapi/lib/model/job_id.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,91 @@ | |||||||
|  | // | ||||||
|  | // 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 JobId { | ||||||
|  |   /// Instantiate a new enum with the provided [value]. | ||||||
|  |   const JobId._(this.value); | ||||||
|  |  | ||||||
|  |   /// The underlying value of this enum member. | ||||||
|  |   final String value; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String toString() => value; | ||||||
|  |  | ||||||
|  |   String toJson() => value; | ||||||
|  |  | ||||||
|  |   static const thumbnailGeneration = JobId._(r'thumbnail-generation'); | ||||||
|  |   static const metadataExtraction = JobId._(r'metadata-extraction'); | ||||||
|  |   static const videoConversion = JobId._(r'video-conversion'); | ||||||
|  |   static const machineLearning = JobId._(r'machine-learning'); | ||||||
|  |  | ||||||
|  |   /// List of all possible values in this [enum][JobId]. | ||||||
|  |   static const values = <JobId>[ | ||||||
|  |     thumbnailGeneration, | ||||||
|  |     metadataExtraction, | ||||||
|  |     videoConversion, | ||||||
|  |     machineLearning, | ||||||
|  |   ]; | ||||||
|  |  | ||||||
|  |   static JobId? fromJson(dynamic value) => JobIdTypeTransformer().decode(value); | ||||||
|  |  | ||||||
|  |   static List<JobId>? listFromJson(dynamic json, {bool growable = false,}) { | ||||||
|  |     final result = <JobId>[]; | ||||||
|  |     if (json is List && json.isNotEmpty) { | ||||||
|  |       for (final row in json) { | ||||||
|  |         final value = JobId.fromJson(row); | ||||||
|  |         if (value != null) { | ||||||
|  |           result.add(value); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return result.toList(growable: growable); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Transformation class that can [encode] an instance of [JobId] to String, | ||||||
|  | /// and [decode] dynamic data back to [JobId]. | ||||||
|  | class JobIdTypeTransformer { | ||||||
|  |   factory JobIdTypeTransformer() => _instance ??= const JobIdTypeTransformer._(); | ||||||
|  |  | ||||||
|  |   const JobIdTypeTransformer._(); | ||||||
|  |  | ||||||
|  |   String encode(JobId data) => data.value; | ||||||
|  |  | ||||||
|  |   /// Decodes a [dynamic value][data] to a JobId. | ||||||
|  |   /// | ||||||
|  |   /// 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. | ||||||
|  |   JobId? decode(dynamic data, {bool allowNull = true}) { | ||||||
|  |     if (data != null) { | ||||||
|  |       switch (data.toString()) { | ||||||
|  |         case r'thumbnail-generation': return JobId.thumbnailGeneration; | ||||||
|  |         case r'metadata-extraction': return JobId.metadataExtraction; | ||||||
|  |         case r'video-conversion': return JobId.videoConversion; | ||||||
|  |         case r'machine-learning': return JobId.machineLearning; | ||||||
|  |         default: | ||||||
|  |           if (!allowNull) { | ||||||
|  |             throw ArgumentError('Unknown enum value to decode: $data'); | ||||||
|  |           } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /// Singleton [JobIdTypeTransformer] instance. | ||||||
|  |   static JobIdTypeTransformer? _instance; | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										119
									
								
								mobile/openapi/lib/model/job_status_response_dto.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								mobile/openapi/lib/model/job_status_response_dto.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,119 @@ | |||||||
|  | // | ||||||
|  | // 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 JobStatusResponseDto { | ||||||
|  |   /// Returns a new [JobStatusResponseDto] instance. | ||||||
|  |   JobStatusResponseDto({ | ||||||
|  |     required this.isActive, | ||||||
|  |     required this.queueCount, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   bool isActive; | ||||||
|  |  | ||||||
|  |   Object queueCount; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) => identical(this, other) || other is JobStatusResponseDto && | ||||||
|  |      other.isActive == isActive && | ||||||
|  |      other.queueCount == queueCount; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode => | ||||||
|  |     // ignore: unnecessary_parenthesis | ||||||
|  |     (isActive.hashCode) + | ||||||
|  |     (queueCount.hashCode); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String toString() => 'JobStatusResponseDto[isActive=$isActive, queueCount=$queueCount]'; | ||||||
|  |  | ||||||
|  |   Map<String, dynamic> toJson() { | ||||||
|  |     final _json = <String, dynamic>{}; | ||||||
|  |       _json[r'isActive'] = isActive; | ||||||
|  |       _json[r'queueCount'] = queueCount; | ||||||
|  |     return _json; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /// Returns a new [JobStatusResponseDto] instance and imports its values from | ||||||
|  |   /// [value] if it's a [Map], null otherwise. | ||||||
|  |   // ignore: prefer_constructors_over_static_methods | ||||||
|  |   static JobStatusResponseDto? fromJson(dynamic value) { | ||||||
|  |     if (value is Map) { | ||||||
|  |       final json = value.cast<String, dynamic>(); | ||||||
|  |  | ||||||
|  |       // Ensure that the map contains the required keys. | ||||||
|  |       // Note 1: the values aren't checked for validity beyond being non-null. | ||||||
|  |       // Note 2: this code is stripped in release mode! | ||||||
|  |       assert(() { | ||||||
|  |         requiredKeys.forEach((key) { | ||||||
|  |           assert(json.containsKey(key), 'Required key "JobStatusResponseDto[$key]" is missing from JSON.'); | ||||||
|  |           assert(json[key] != null, 'Required key "JobStatusResponseDto[$key]" has a null value in JSON.'); | ||||||
|  |         }); | ||||||
|  |         return true; | ||||||
|  |       }()); | ||||||
|  |  | ||||||
|  |       return JobStatusResponseDto( | ||||||
|  |         isActive: mapValueOfType<bool>(json, r'isActive')!, | ||||||
|  |         queueCount: mapValueOfType<Object>(json, r'queueCount')!, | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static List<JobStatusResponseDto>? listFromJson(dynamic json, {bool growable = false,}) { | ||||||
|  |     final result = <JobStatusResponseDto>[]; | ||||||
|  |     if (json is List && json.isNotEmpty) { | ||||||
|  |       for (final row in json) { | ||||||
|  |         final value = JobStatusResponseDto.fromJson(row); | ||||||
|  |         if (value != null) { | ||||||
|  |           result.add(value); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return result.toList(growable: growable); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static Map<String, JobStatusResponseDto> mapFromJson(dynamic json) { | ||||||
|  |     final map = <String, JobStatusResponseDto>{}; | ||||||
|  |     if (json is Map && json.isNotEmpty) { | ||||||
|  |       json = json.cast<String, dynamic>(); // ignore: parameter_assignments | ||||||
|  |       for (final entry in json.entries) { | ||||||
|  |         final value = JobStatusResponseDto.fromJson(entry.value); | ||||||
|  |         if (value != null) { | ||||||
|  |           map[entry.key] = value; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return map; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // maps a json object with a list of JobStatusResponseDto-objects as value to a dart map | ||||||
|  |   static Map<String, List<JobStatusResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) { | ||||||
|  |     final map = <String, List<JobStatusResponseDto>>{}; | ||||||
|  |     if (json is Map && json.isNotEmpty) { | ||||||
|  |       json = json.cast<String, dynamic>(); // ignore: parameter_assignments | ||||||
|  |       for (final entry in json.entries) { | ||||||
|  |         final value = JobStatusResponseDto.listFromJson(entry.value, growable: growable,); | ||||||
|  |         if (value != null) { | ||||||
|  |           map[entry.key] = value; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return map; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /// The list of required keys that must be present in a JSON. | ||||||
|  |   static const requiredKeys = <String>{ | ||||||
|  |     'isActive', | ||||||
|  |     'queueCount', | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										91
									
								
								mobile/openapi/lib/model/job_type.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								mobile/openapi/lib/model/job_type.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,91 @@ | |||||||
|  | // | ||||||
|  | // 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 JobType { | ||||||
|  |   /// Instantiate a new enum with the provided [value]. | ||||||
|  |   const JobType._(this.value); | ||||||
|  |  | ||||||
|  |   /// The underlying value of this enum member. | ||||||
|  |   final String value; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String toString() => value; | ||||||
|  |  | ||||||
|  |   String toJson() => value; | ||||||
|  |  | ||||||
|  |   static const THUMBNAIL_GENERATION = JobType._(r'THUMBNAIL_GENERATION'); | ||||||
|  |   static const METADATA_EXTRACTION = JobType._(r'METADATA_EXTRACTION'); | ||||||
|  |   static const VIDEO_CONVERSION = JobType._(r'VIDEO_CONVERSION'); | ||||||
|  |   static const CHECKSUM_GENERATION = JobType._(r'CHECKSUM_GENERATION'); | ||||||
|  |  | ||||||
|  |   /// List of all possible values in this [enum][JobType]. | ||||||
|  |   static const values = <JobType>[ | ||||||
|  |     THUMBNAIL_GENERATION, | ||||||
|  |     METADATA_EXTRACTION, | ||||||
|  |     VIDEO_CONVERSION, | ||||||
|  |     CHECKSUM_GENERATION, | ||||||
|  |   ]; | ||||||
|  |  | ||||||
|  |   static JobType? fromJson(dynamic value) => JobTypeTypeTransformer().decode(value); | ||||||
|  |  | ||||||
|  |   static List<JobType>? listFromJson(dynamic json, {bool growable = false,}) { | ||||||
|  |     final result = <JobType>[]; | ||||||
|  |     if (json is List && json.isNotEmpty) { | ||||||
|  |       for (final row in json) { | ||||||
|  |         final value = JobType.fromJson(row); | ||||||
|  |         if (value != null) { | ||||||
|  |           result.add(value); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return result.toList(growable: growable); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Transformation class that can [encode] an instance of [JobType] to String, | ||||||
|  | /// and [decode] dynamic data back to [JobType]. | ||||||
|  | class JobTypeTypeTransformer { | ||||||
|  |   factory JobTypeTypeTransformer() => _instance ??= const JobTypeTypeTransformer._(); | ||||||
|  |  | ||||||
|  |   const JobTypeTypeTransformer._(); | ||||||
|  |  | ||||||
|  |   String encode(JobType data) => data.value; | ||||||
|  |  | ||||||
|  |   /// Decodes a [dynamic value][data] to a JobType. | ||||||
|  |   /// | ||||||
|  |   /// 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. | ||||||
|  |   JobType? decode(dynamic data, {bool allowNull = true}) { | ||||||
|  |     if (data != null) { | ||||||
|  |       switch (data.toString()) { | ||||||
|  |         case r'THUMBNAIL_GENERATION': return JobType.THUMBNAIL_GENERATION; | ||||||
|  |         case r'METADATA_EXTRACTION': return JobType.METADATA_EXTRACTION; | ||||||
|  |         case r'VIDEO_CONVERSION': return JobType.VIDEO_CONVERSION; | ||||||
|  |         case r'CHECKSUM_GENERATION': return JobType.CHECKSUM_GENERATION; | ||||||
|  |         default: | ||||||
|  |           if (!allowNull) { | ||||||
|  |             throw ArgumentError('Unknown enum value to decode: $data'); | ||||||
|  |           } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /// Singleton [JobTypeTypeTransformer] instance. | ||||||
|  |   static JobTypeTypeTransformer? _instance; | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										52
									
								
								mobile/openapi/test/all_job_status_response_dto_test.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								mobile/openapi/test/all_job_status_response_dto_test.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | |||||||
|  | // | ||||||
|  | // 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 AllJobStatusResponseDto | ||||||
|  | void main() { | ||||||
|  |   // final instance = AllJobStatusResponseDto(); | ||||||
|  |  | ||||||
|  |   group('test AllJobStatusResponseDto', () { | ||||||
|  |     // bool isThumbnailGenerationActive | ||||||
|  |     test('to test the property `isThumbnailGenerationActive`', () async { | ||||||
|  |       // TODO | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     // Object thumbnailGenerationQueueCount | ||||||
|  |     test('to test the property `thumbnailGenerationQueueCount`', () async { | ||||||
|  |       // TODO | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     // bool isMetadataExtractionActive | ||||||
|  |     test('to test the property `isMetadataExtractionActive`', () async { | ||||||
|  |       // TODO | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     // Object metadataExtractionQueueCount | ||||||
|  |     test('to test the property `metadataExtractionQueueCount`', () async { | ||||||
|  |       // TODO | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     // bool isVideoConversionActive | ||||||
|  |     test('to test the property `isVideoConversionActive`', () async { | ||||||
|  |       // TODO | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     // Object videoConversionQueueCount | ||||||
|  |     test('to test the property `videoConversionQueueCount`', () async { | ||||||
|  |       // TODO | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										27
									
								
								mobile/openapi/test/create_job_dto_test.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								mobile/openapi/test/create_job_dto_test.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | // | ||||||
|  | // 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 CreateJobDto | ||||||
|  | void main() { | ||||||
|  |   // final instance = CreateJobDto(); | ||||||
|  |  | ||||||
|  |   group('test CreateJobDto', () { | ||||||
|  |     // JobType jobType | ||||||
|  |     test('to test the property `jobType`', () async { | ||||||
|  |       // TODO | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										41
									
								
								mobile/openapi/test/job_api_test.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								mobile/openapi/test/job_api_test.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  | // | ||||||
|  | // 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 JobApi | ||||||
|  | void main() { | ||||||
|  |   // final instance = JobApi(); | ||||||
|  |  | ||||||
|  |   group('tests for JobApi', () { | ||||||
|  |     //Future<Object> create(CreateJobDto createJobDto) async | ||||||
|  |     test('test create', () async { | ||||||
|  |       // TODO | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     //Future<AllJobStatusResponseDto> getAllJobsStatus() async | ||||||
|  |     test('test getAllJobsStatus', () async { | ||||||
|  |       // TODO | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     //Future<JobStatusResponseDto> getJobStatus(JobType jobType) async | ||||||
|  |     test('test getJobStatus', () async { | ||||||
|  |       // TODO | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     //Future<JobStatusResponseDto> stopJob(JobType jobType) async | ||||||
|  |     test('test stopJob', () async { | ||||||
|  |       // TODO | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |   }); | ||||||
|  | } | ||||||
							
								
								
									
										27
									
								
								mobile/openapi/test/job_command_dto_test.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								mobile/openapi/test/job_command_dto_test.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | // | ||||||
|  | // 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 JobCommandDto | ||||||
|  | void main() { | ||||||
|  |   // final instance = JobCommandDto(); | ||||||
|  |  | ||||||
|  |   group('test JobCommandDto', () { | ||||||
|  |     // JobCommand command | ||||||
|  |     test('to test the property `command`', () async { | ||||||
|  |       // TODO | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										21
									
								
								mobile/openapi/test/job_command_test.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								mobile/openapi/test/job_command_test.dart
									
									
									
									
									
										Normal 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 JobCommand | ||||||
|  | void main() { | ||||||
|  |  | ||||||
|  |   group('test JobCommand', () { | ||||||
|  |  | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										47
									
								
								mobile/openapi/test/job_counts_test.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								mobile/openapi/test/job_counts_test.dart
									
									
									
									
									
										Normal 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 JobCounts | ||||||
|  | void main() { | ||||||
|  |   // final instance = JobCounts(); | ||||||
|  |  | ||||||
|  |   group('test JobCounts', () { | ||||||
|  |     // num active | ||||||
|  |     test('to test the property `active`', () async { | ||||||
|  |       // TODO | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     // num completed | ||||||
|  |     test('to test the property `completed`', () async { | ||||||
|  |       // TODO | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     // num failed | ||||||
|  |     test('to test the property `failed`', () async { | ||||||
|  |       // TODO | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     // num delayed | ||||||
|  |     test('to test the property `delayed`', () async { | ||||||
|  |       // TODO | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     // num waiting | ||||||
|  |     test('to test the property `waiting`', () async { | ||||||
|  |       // TODO | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										21
									
								
								mobile/openapi/test/job_id_test.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								mobile/openapi/test/job_id_test.dart
									
									
									
									
									
										Normal 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 JobId | ||||||
|  | void main() { | ||||||
|  |  | ||||||
|  |   group('test JobId', () { | ||||||
|  |  | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										32
									
								
								mobile/openapi/test/job_status_response_dto_test.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								mobile/openapi/test/job_status_response_dto_test.dart
									
									
									
									
									
										Normal 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 JobStatusResponseDto | ||||||
|  | void main() { | ||||||
|  |   // final instance = JobStatusResponseDto(); | ||||||
|  |  | ||||||
|  |   group('test JobStatusResponseDto', () { | ||||||
|  |     // bool isActive | ||||||
|  |     test('to test the property `isActive`', () async { | ||||||
|  |       // TODO | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     // Object queueCount | ||||||
|  |     test('to test the property `queueCount`', () async { | ||||||
|  |       // TODO | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										21
									
								
								mobile/openapi/test/job_type_test.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								mobile/openapi/test/job_type_test.dart
									
									
									
									
									
										Normal 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 JobType | ||||||
|  | void main() { | ||||||
|  |  | ||||||
|  |   group('test JobType', () { | ||||||
|  |  | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| node_modules/ | node_modules/ | ||||||
| upload/ | upload/ | ||||||
| dist/ | dist/ | ||||||
|  | .reverse-geocoding-dump | ||||||
|   | |||||||
| @@ -134,6 +134,9 @@ describe('Album service', () => { | |||||||
|       getAssetByTimeBucket: jest.fn(), |       getAssetByTimeBucket: jest.fn(), | ||||||
|       getAssetByChecksum: jest.fn(), |       getAssetByChecksum: jest.fn(), | ||||||
|       getAssetCountByUserId: jest.fn(), |       getAssetCountByUserId: jest.fn(), | ||||||
|  |       getAssetWithNoEXIF: jest.fn(), | ||||||
|  |       getAssetWithNoThumbnail: jest.fn(), | ||||||
|  |       getAssetWithNoSmartInfo: jest.fn(), | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     sut = new AlbumService(albumRepositoryMock, assetRepositoryMock); |     sut = new AlbumService(albumRepositoryMock, assetRepositoryMock); | ||||||
|   | |||||||
| @@ -29,6 +29,9 @@ export interface IAssetRepository { | |||||||
|   getAssetCountByUserId(userId: string): Promise<AssetCountByUserIdResponseDto>; |   getAssetCountByUserId(userId: string): Promise<AssetCountByUserIdResponseDto>; | ||||||
|   getAssetByTimeBucket(userId: string, getAssetByTimeBucketDto: GetAssetByTimeBucketDto): Promise<AssetEntity[]>; |   getAssetByTimeBucket(userId: string, getAssetByTimeBucketDto: GetAssetByTimeBucketDto): Promise<AssetEntity[]>; | ||||||
|   getAssetByChecksum(userId: string, checksum: Buffer): Promise<AssetEntity>; |   getAssetByChecksum(userId: string, checksum: Buffer): Promise<AssetEntity>; | ||||||
|  |   getAssetWithNoThumbnail(): Promise<AssetEntity[]>; | ||||||
|  |   getAssetWithNoEXIF(): Promise<AssetEntity[]>; | ||||||
|  |   getAssetWithNoSmartInfo(): Promise<AssetEntity[]>; | ||||||
| } | } | ||||||
|  |  | ||||||
| export const ASSET_REPOSITORY = 'ASSET_REPOSITORY'; | export const ASSET_REPOSITORY = 'ASSET_REPOSITORY'; | ||||||
| @@ -40,6 +43,33 @@ export class AssetRepository implements IAssetRepository { | |||||||
|     private assetRepository: Repository<AssetEntity>, |     private assetRepository: Repository<AssetEntity>, | ||||||
|   ) {} |   ) {} | ||||||
|  |  | ||||||
|  |   async getAssetWithNoSmartInfo(): Promise<AssetEntity[]> { | ||||||
|  |     return await this.assetRepository | ||||||
|  |       .createQueryBuilder('asset') | ||||||
|  |       .leftJoinAndSelect('asset.smartInfo', 'si') | ||||||
|  |       .where('asset.resizePath IS NOT NULL') | ||||||
|  |       .andWhere('si.id IS NULL') | ||||||
|  |       .getMany(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   async getAssetWithNoThumbnail(): Promise<AssetEntity[]> { | ||||||
|  |     return await this.assetRepository | ||||||
|  |       .createQueryBuilder('asset') | ||||||
|  |       .where('asset.resizePath IS NULL') | ||||||
|  |       .orWhere('asset.resizePath = :resizePath', { resizePath: '' }) | ||||||
|  |       .orWhere('asset.webpPath IS NULL') | ||||||
|  |       .orWhere('asset.webpPath = :webpPath', { webpPath: '' }) | ||||||
|  |       .getMany(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   async getAssetWithNoEXIF(): Promise<AssetEntity[]> { | ||||||
|  |     return await this.assetRepository | ||||||
|  |       .createQueryBuilder('asset') | ||||||
|  |       .leftJoinAndSelect('asset.exifInfo', 'ei') | ||||||
|  |       .where('ei."assetId" IS NULL') | ||||||
|  |       .getMany(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   async getAssetCountByUserId(userId: string): Promise<AssetCountByUserIdResponseDto> { |   async getAssetCountByUserId(userId: string): Promise<AssetCountByUserIdResponseDto> { | ||||||
|     // Get asset count by AssetType |     // Get asset count by AssetType | ||||||
|     const res = await this.assetRepository |     const res = await this.assetRepository | ||||||
|   | |||||||
| @@ -30,7 +30,7 @@ import { CommunicationGateway } from '../communication/communication.gateway'; | |||||||
| import { InjectQueue } from '@nestjs/bull'; | import { InjectQueue } from '@nestjs/bull'; | ||||||
| import { Queue } from 'bull'; | import { Queue } from 'bull'; | ||||||
| import { IAssetUploadedJob } from '@app/job/index'; | import { IAssetUploadedJob } from '@app/job/index'; | ||||||
| import { assetUploadedQueueName } from '@app/job/constants/queue-name.constant'; | import { QueueNameEnum } from '@app/job/constants/queue-name.constant'; | ||||||
| import { assetUploadedProcessorName } from '@app/job/constants/job-name.constant'; | import { assetUploadedProcessorName } from '@app/job/constants/job-name.constant'; | ||||||
| import { CheckDuplicateAssetDto } from './dto/check-duplicate-asset.dto'; | import { CheckDuplicateAssetDto } from './dto/check-duplicate-asset.dto'; | ||||||
| import { ApiBearerAuth, ApiBody, ApiConsumes, ApiTags } from '@nestjs/swagger'; | import { ApiBearerAuth, ApiBody, ApiConsumes, ApiTags } from '@nestjs/swagger'; | ||||||
| @@ -59,7 +59,7 @@ export class AssetController { | |||||||
|     private assetService: AssetService, |     private assetService: AssetService, | ||||||
|     private backgroundTaskService: BackgroundTaskService, |     private backgroundTaskService: BackgroundTaskService, | ||||||
|  |  | ||||||
|     @InjectQueue(assetUploadedQueueName) |     @InjectQueue(QueueNameEnum.ASSET_UPLOADED) | ||||||
|     private assetUploadedQueue: Queue<IAssetUploadedJob>, |     private assetUploadedQueue: Queue<IAssetUploadedJob>, | ||||||
|   ) {} |   ) {} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ import { BullModule } from '@nestjs/bull'; | |||||||
| import { BackgroundTaskModule } from '../../modules/background-task/background-task.module'; | import { BackgroundTaskModule } from '../../modules/background-task/background-task.module'; | ||||||
| import { BackgroundTaskService } from '../../modules/background-task/background-task.service'; | import { BackgroundTaskService } from '../../modules/background-task/background-task.service'; | ||||||
| import { CommunicationModule } from '../communication/communication.module'; | import { CommunicationModule } from '../communication/communication.module'; | ||||||
| import { assetUploadedQueueName } from '@app/job/constants/queue-name.constant'; | import { QueueNameEnum } from '@app/job/constants/queue-name.constant'; | ||||||
| import { AssetRepository, ASSET_REPOSITORY } from './asset-repository'; | import { AssetRepository, ASSET_REPOSITORY } from './asset-repository'; | ||||||
|  |  | ||||||
| @Module({ | @Module({ | ||||||
| @@ -16,7 +16,7 @@ import { AssetRepository, ASSET_REPOSITORY } from './asset-repository'; | |||||||
|     BackgroundTaskModule, |     BackgroundTaskModule, | ||||||
|     TypeOrmModule.forFeature([AssetEntity]), |     TypeOrmModule.forFeature([AssetEntity]), | ||||||
|     BullModule.registerQueue({ |     BullModule.registerQueue({ | ||||||
|       name: assetUploadedQueueName, |       name: QueueNameEnum.ASSET_UPLOADED, | ||||||
|       defaultJobOptions: { |       defaultJobOptions: { | ||||||
|         attempts: 3, |         attempts: 3, | ||||||
|         removeOnComplete: true, |         removeOnComplete: true, | ||||||
|   | |||||||
| @@ -107,6 +107,9 @@ describe('AssetService', () => { | |||||||
|       getAssetByTimeBucket: jest.fn(), |       getAssetByTimeBucket: jest.fn(), | ||||||
|       getAssetByChecksum: jest.fn(), |       getAssetByChecksum: jest.fn(), | ||||||
|       getAssetCountByUserId: jest.fn(), |       getAssetCountByUserId: jest.fn(), | ||||||
|  |       getAssetWithNoEXIF: jest.fn(), | ||||||
|  |       getAssetWithNoThumbnail: jest.fn(), | ||||||
|  |       getAssetWithNoSmartInfo: jest.fn(), | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     sui = new AssetService(assetRepositoryMock, a); |     sui = new AssetService(assetRepositoryMock, a); | ||||||
|   | |||||||
| @@ -1,12 +1,16 @@ | |||||||
| import { ExifEntity } from '@app/database/entities/exif.entity'; | import { ExifEntity } from '@app/database/entities/exif.entity'; | ||||||
|  | import { ApiProperty } from '@nestjs/swagger'; | ||||||
|  |  | ||||||
| export class ExifResponseDto { | export class ExifResponseDto { | ||||||
|   id?: string | null = null; |   @ApiProperty({ type: 'integer', format: 'int64' }) | ||||||
|  |   id?: number | null = null; | ||||||
|   make?: string | null = null; |   make?: string | null = null; | ||||||
|   model?: string | null = null; |   model?: string | null = null; | ||||||
|   imageName?: string | null = null; |   imageName?: string | null = null; | ||||||
|   exifImageWidth?: number | null = null; |   exifImageWidth?: number | null = null; | ||||||
|   exifImageHeight?: number | null = null; |   exifImageHeight?: number | null = null; | ||||||
|  |  | ||||||
|  |   @ApiProperty({ type: 'integer', format: 'int64' }) | ||||||
|   fileSizeInByte?: number | null = null; |   fileSizeInByte?: number | null = null; | ||||||
|   orientation?: string | null = null; |   orientation?: string | null = null; | ||||||
|   dateTimeOriginal?: Date | null = null; |   dateTimeOriginal?: Date | null = null; | ||||||
| @@ -25,13 +29,13 @@ export class ExifResponseDto { | |||||||
|  |  | ||||||
| export function mapExif(entity: ExifEntity): ExifResponseDto { | export function mapExif(entity: ExifEntity): ExifResponseDto { | ||||||
|   return { |   return { | ||||||
|     id: entity.id, |     id: parseInt(entity.id), | ||||||
|     make: entity.make, |     make: entity.make, | ||||||
|     model: entity.model, |     model: entity.model, | ||||||
|     imageName: entity.imageName, |     imageName: entity.imageName, | ||||||
|     exifImageWidth: entity.exifImageWidth, |     exifImageWidth: entity.exifImageWidth, | ||||||
|     exifImageHeight: entity.exifImageHeight, |     exifImageHeight: entity.exifImageHeight, | ||||||
|     fileSizeInByte: entity.fileSizeInByte, |     fileSizeInByte: entity.fileSizeInByte ? parseInt(entity.fileSizeInByte.toString()) : null, | ||||||
|     orientation: entity.orientation, |     orientation: entity.orientation, | ||||||
|     dateTimeOriginal: entity.dateTimeOriginal, |     dateTimeOriginal: entity.dateTimeOriginal, | ||||||
|     modifyDate: entity.modifyDate, |     modifyDate: entity.modifyDate, | ||||||
|   | |||||||
							
								
								
									
										21
									
								
								server/apps/immich/src/api-v1/job/dto/get-job.dto.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								server/apps/immich/src/api-v1/job/dto/get-job.dto.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | |||||||
|  | import { ApiProperty } from '@nestjs/swagger'; | ||||||
|  | import { IsEnum, IsNotEmpty } from 'class-validator'; | ||||||
|  |  | ||||||
|  | export enum JobId { | ||||||
|  |   THUMBNAIL_GENERATION = 'thumbnail-generation', | ||||||
|  |   METADATA_EXTRACTION = 'metadata-extraction', | ||||||
|  |   VIDEO_CONVERSION = 'video-conversion', | ||||||
|  |   MACHINE_LEARNING = 'machine-learning', | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export class GetJobDto { | ||||||
|  |   @IsNotEmpty() | ||||||
|  |   @IsEnum(JobId, { | ||||||
|  |     message: `params must be one of ${Object.values(JobId).join()}`, | ||||||
|  |   }) | ||||||
|  |   @ApiProperty({ | ||||||
|  |     enum: JobId, | ||||||
|  |     enumName: 'JobId', | ||||||
|  |   }) | ||||||
|  |   jobId!: string; | ||||||
|  | } | ||||||
							
								
								
									
										12
									
								
								server/apps/immich/src/api-v1/job/dto/job-command.dto.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								server/apps/immich/src/api-v1/job/dto/job-command.dto.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | import { ApiProperty } from '@nestjs/swagger'; | ||||||
|  | import { IsIn, IsNotEmpty } from 'class-validator'; | ||||||
|  |  | ||||||
|  | export class JobCommandDto { | ||||||
|  |   @IsNotEmpty() | ||||||
|  |   @IsIn(['start', 'stop']) | ||||||
|  |   @ApiProperty({ | ||||||
|  |     enum: ['start', 'stop'], | ||||||
|  |     enumName: 'JobCommand', | ||||||
|  |   }) | ||||||
|  |   command!: string; | ||||||
|  | } | ||||||
							
								
								
									
										43
									
								
								server/apps/immich/src/api-v1/job/job.controller.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								server/apps/immich/src/api-v1/job/job.controller.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | |||||||
|  | import { Controller, Get, Body, UseGuards, ValidationPipe, Put, Param } from '@nestjs/common'; | ||||||
|  | import { JobService } from './job.service'; | ||||||
|  | import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; | ||||||
|  | import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard'; | ||||||
|  | import { AdminRolesGuard } from '../../middlewares/admin-role-guard.middleware'; | ||||||
|  | import { AllJobStatusResponseDto } from './response-dto/all-job-status-response.dto'; | ||||||
|  | import { GetJobDto } from './dto/get-job.dto'; | ||||||
|  | import { JobStatusResponseDto } from './response-dto/job-status-response.dto'; | ||||||
|  |  | ||||||
|  | import { JobCommandDto } from './dto/job-command.dto'; | ||||||
|  |  | ||||||
|  | @UseGuards(JwtAuthGuard) | ||||||
|  | @UseGuards(AdminRolesGuard) | ||||||
|  | @ApiTags('Job') | ||||||
|  | @ApiBearerAuth() | ||||||
|  | @Controller('jobs') | ||||||
|  | export class JobController { | ||||||
|  |   constructor(private readonly jobService: JobService) {} | ||||||
|  |  | ||||||
|  |   @Get() | ||||||
|  |   getAllJobsStatus(): Promise<AllJobStatusResponseDto> { | ||||||
|  |     return this.jobService.getAllJobsStatus(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @Get('/:jobId') | ||||||
|  |   getJobStatus(@Param(ValidationPipe) params: GetJobDto): Promise<JobStatusResponseDto> { | ||||||
|  |     return this.jobService.getJobStatus(params); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @Put('/:jobId') | ||||||
|  |   async sendJobCommand( | ||||||
|  |     @Param(ValidationPipe) params: GetJobDto, | ||||||
|  |     @Body(ValidationPipe) body: JobCommandDto, | ||||||
|  |   ): Promise<number> { | ||||||
|  |     if (body.command === 'start') { | ||||||
|  |       return await this.jobService.startJob(params); | ||||||
|  |     } | ||||||
|  |     if (body.command === 'stop') { | ||||||
|  |       return await this.jobService.stopJob(params); | ||||||
|  |     } | ||||||
|  |     return 0; | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										82
									
								
								server/apps/immich/src/api-v1/job/job.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								server/apps/immich/src/api-v1/job/job.module.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | |||||||
|  | import { Module } from '@nestjs/common'; | ||||||
|  | import { JobService } from './job.service'; | ||||||
|  | import { JobController } from './job.controller'; | ||||||
|  | import { ImmichJwtService } from '../../modules/immich-jwt/immich-jwt.service'; | ||||||
|  | import { ImmichJwtModule } from '../../modules/immich-jwt/immich-jwt.module'; | ||||||
|  | import { JwtModule } from '@nestjs/jwt'; | ||||||
|  | import { jwtConfig } from '../../config/jwt.config'; | ||||||
|  | import { UserEntity } from '@app/database/entities/user.entity'; | ||||||
|  | import { TypeOrmModule } from '@nestjs/typeorm'; | ||||||
|  | import { BullModule } from '@nestjs/bull'; | ||||||
|  | import { QueueNameEnum } from '@app/job'; | ||||||
|  | import { AssetEntity } from '@app/database/entities/asset.entity'; | ||||||
|  | import { ExifEntity } from '@app/database/entities/exif.entity'; | ||||||
|  | import { AssetRepository, ASSET_REPOSITORY } from '../asset/asset-repository'; | ||||||
|  |  | ||||||
|  | @Module({ | ||||||
|  |   imports: [ | ||||||
|  |     TypeOrmModule.forFeature([UserEntity, AssetEntity, ExifEntity]), | ||||||
|  |     ImmichJwtModule, | ||||||
|  |     JwtModule.register(jwtConfig), | ||||||
|  |     BullModule.registerQueue( | ||||||
|  |       { | ||||||
|  |         name: QueueNameEnum.THUMBNAIL_GENERATION, | ||||||
|  |         defaultJobOptions: { | ||||||
|  |           attempts: 3, | ||||||
|  |           removeOnComplete: true, | ||||||
|  |           removeOnFail: false, | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         name: QueueNameEnum.ASSET_UPLOADED, | ||||||
|  |         defaultJobOptions: { | ||||||
|  |           attempts: 3, | ||||||
|  |           removeOnComplete: true, | ||||||
|  |           removeOnFail: false, | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         name: QueueNameEnum.METADATA_EXTRACTION, | ||||||
|  |         defaultJobOptions: { | ||||||
|  |           attempts: 3, | ||||||
|  |           removeOnComplete: true, | ||||||
|  |           removeOnFail: false, | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         name: QueueNameEnum.VIDEO_CONVERSION, | ||||||
|  |         defaultJobOptions: { | ||||||
|  |           attempts: 3, | ||||||
|  |           removeOnComplete: true, | ||||||
|  |           removeOnFail: false, | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         name: QueueNameEnum.CHECKSUM_GENERATION, | ||||||
|  |         defaultJobOptions: { | ||||||
|  |           attempts: 3, | ||||||
|  |           removeOnComplete: true, | ||||||
|  |           removeOnFail: false, | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         name: QueueNameEnum.MACHINE_LEARNING, | ||||||
|  |         defaultJobOptions: { | ||||||
|  |           attempts: 3, | ||||||
|  |           removeOnComplete: true, | ||||||
|  |           removeOnFail: false, | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |     ), | ||||||
|  |   ], | ||||||
|  |   controllers: [JobController], | ||||||
|  |   providers: [ | ||||||
|  |     JobService, | ||||||
|  |     ImmichJwtService, | ||||||
|  |     { | ||||||
|  |       provide: ASSET_REPOSITORY, | ||||||
|  |       useClass: AssetRepository, | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|  | }) | ||||||
|  | export class JobModule {} | ||||||
							
								
								
									
										180
									
								
								server/apps/immich/src/api-v1/job/job.service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								server/apps/immich/src/api-v1/job/job.service.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,180 @@ | |||||||
|  | import { | ||||||
|  |   exifExtractionProcessorName, | ||||||
|  |   generateJPEGThumbnailProcessorName, | ||||||
|  |   IMetadataExtractionJob, | ||||||
|  |   IThumbnailGenerationJob, | ||||||
|  |   IVideoTranscodeJob, | ||||||
|  |   MachineLearningJobNameEnum, | ||||||
|  |   QueueNameEnum, | ||||||
|  |   videoMetadataExtractionProcessorName, | ||||||
|  | } from '@app/job'; | ||||||
|  | import { InjectQueue } from '@nestjs/bull'; | ||||||
|  | import { Queue } from 'bull'; | ||||||
|  | import { BadRequestException, Inject, Injectable } from '@nestjs/common'; | ||||||
|  | import { AllJobStatusResponseDto } from './response-dto/all-job-status-response.dto'; | ||||||
|  | import { randomUUID } from 'crypto'; | ||||||
|  | import { ASSET_REPOSITORY, IAssetRepository } from '../asset/asset-repository'; | ||||||
|  | import { AssetType } from '@app/database/entities/asset.entity'; | ||||||
|  | import { GetJobDto, JobId } from './dto/get-job.dto'; | ||||||
|  | import { JobStatusResponseDto } from './response-dto/job-status-response.dto'; | ||||||
|  | import { IMachineLearningJob } from '@app/job/interfaces/machine-learning.interface'; | ||||||
|  |  | ||||||
|  | @Injectable() | ||||||
|  | export class JobService { | ||||||
|  |   constructor( | ||||||
|  |     @InjectQueue(QueueNameEnum.THUMBNAIL_GENERATION) | ||||||
|  |     private thumbnailGeneratorQueue: Queue<IThumbnailGenerationJob>, | ||||||
|  |  | ||||||
|  |     @InjectQueue(QueueNameEnum.METADATA_EXTRACTION) | ||||||
|  |     private metadataExtractionQueue: Queue<IMetadataExtractionJob>, | ||||||
|  |  | ||||||
|  |     @InjectQueue(QueueNameEnum.VIDEO_CONVERSION) | ||||||
|  |     private videoConversionQueue: Queue<IVideoTranscodeJob>, | ||||||
|  |  | ||||||
|  |     @InjectQueue(QueueNameEnum.MACHINE_LEARNING) | ||||||
|  |     private machineLearningQueue: Queue<IMachineLearningJob>, | ||||||
|  |  | ||||||
|  |     @Inject(ASSET_REPOSITORY) | ||||||
|  |     private _assetRepository: IAssetRepository, | ||||||
|  |   ) { | ||||||
|  |     this.thumbnailGeneratorQueue.empty(); | ||||||
|  |     this.metadataExtractionQueue.empty(); | ||||||
|  |     this.videoConversionQueue.empty(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   async startJob(jobDto: GetJobDto): Promise<number> { | ||||||
|  |     switch (jobDto.jobId) { | ||||||
|  |       case JobId.THUMBNAIL_GENERATION: | ||||||
|  |         return this.runThumbnailGenerationJob(); | ||||||
|  |       case JobId.METADATA_EXTRACTION: | ||||||
|  |         return this.runMetadataExtractionJob(); | ||||||
|  |       case JobId.VIDEO_CONVERSION: | ||||||
|  |         return 0; | ||||||
|  |       case JobId.MACHINE_LEARNING: | ||||||
|  |         return this.runMachineLearningPipeline(); | ||||||
|  |       default: | ||||||
|  |         throw new BadRequestException('Invalid job id'); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   async getAllJobsStatus(): Promise<AllJobStatusResponseDto> { | ||||||
|  |     const thumbnailGeneratorJobCount = await this.thumbnailGeneratorQueue.getJobCounts(); | ||||||
|  |     const metadataExtractionJobCount = await this.metadataExtractionQueue.getJobCounts(); | ||||||
|  |     const videoConversionJobCount = await this.videoConversionQueue.getJobCounts(); | ||||||
|  |     const machineLearningJobCount = await this.machineLearningQueue.getJobCounts(); | ||||||
|  |  | ||||||
|  |     const response = new AllJobStatusResponseDto(); | ||||||
|  |     response.isThumbnailGenerationActive = Boolean(thumbnailGeneratorJobCount.waiting); | ||||||
|  |     response.thumbnailGenerationQueueCount = thumbnailGeneratorJobCount; | ||||||
|  |     response.isMetadataExtractionActive = Boolean(metadataExtractionJobCount.waiting); | ||||||
|  |     response.metadataExtractionQueueCount = metadataExtractionJobCount; | ||||||
|  |     response.isVideoConversionActive = Boolean(videoConversionJobCount.waiting); | ||||||
|  |     response.videoConversionQueueCount = videoConversionJobCount; | ||||||
|  |     response.isMachineLearningActive = Boolean(machineLearningJobCount.waiting); | ||||||
|  |     response.machineLearningQueueCount = machineLearningJobCount; | ||||||
|  |  | ||||||
|  |     return response; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   async getJobStatus(query: GetJobDto): Promise<JobStatusResponseDto> { | ||||||
|  |     const response = new JobStatusResponseDto(); | ||||||
|  |     if (query.jobId === JobId.THUMBNAIL_GENERATION) { | ||||||
|  |       response.isActive = Boolean((await this.thumbnailGeneratorQueue.getJobCounts()).waiting); | ||||||
|  |       response.queueCount = await this.thumbnailGeneratorQueue.getJobCounts(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (query.jobId === JobId.METADATA_EXTRACTION) { | ||||||
|  |       response.isActive = Boolean((await this.metadataExtractionQueue.getJobCounts()).waiting); | ||||||
|  |       response.queueCount = await this.metadataExtractionQueue.getJobCounts(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (query.jobId === JobId.VIDEO_CONVERSION) { | ||||||
|  |       response.isActive = Boolean((await this.videoConversionQueue.getJobCounts()).waiting); | ||||||
|  |       response.queueCount = await this.videoConversionQueue.getJobCounts(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return response; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   async stopJob(query: GetJobDto): Promise<number> { | ||||||
|  |     switch (query.jobId) { | ||||||
|  |       case JobId.THUMBNAIL_GENERATION: | ||||||
|  |         this.thumbnailGeneratorQueue.empty(); | ||||||
|  |         return 0; | ||||||
|  |       case JobId.METADATA_EXTRACTION: | ||||||
|  |         this.metadataExtractionQueue.empty(); | ||||||
|  |         return 0; | ||||||
|  |       case JobId.VIDEO_CONVERSION: | ||||||
|  |         this.videoConversionQueue.empty(); | ||||||
|  |         return 0; | ||||||
|  |       case JobId.MACHINE_LEARNING: | ||||||
|  |         this.machineLearningQueue.empty(); | ||||||
|  |         return 0; | ||||||
|  |       default: | ||||||
|  |         throw new BadRequestException('Invalid job id'); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private async runThumbnailGenerationJob(): Promise<number> { | ||||||
|  |     const jobCount = await this.thumbnailGeneratorQueue.getJobCounts(); | ||||||
|  |  | ||||||
|  |     if (jobCount.waiting > 0) { | ||||||
|  |       throw new BadRequestException('Thumbnail generation job is already running'); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const assetsWithNoThumbnail = await this._assetRepository.getAssetWithNoThumbnail(); | ||||||
|  |  | ||||||
|  |     for (const asset of assetsWithNoThumbnail) { | ||||||
|  |       await this.thumbnailGeneratorQueue.add(generateJPEGThumbnailProcessorName, { asset }, { jobId: randomUUID() }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return assetsWithNoThumbnail.length; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private async runMetadataExtractionJob(): Promise<number> { | ||||||
|  |     const jobCount = await this.metadataExtractionQueue.getJobCounts(); | ||||||
|  |  | ||||||
|  |     if (jobCount.waiting > 0) { | ||||||
|  |       throw new BadRequestException('Metadata extraction job is already running'); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const assetsWithNoExif = await this._assetRepository.getAssetWithNoEXIF(); | ||||||
|  |     for (const asset of assetsWithNoExif) { | ||||||
|  |       if (asset.type === AssetType.VIDEO) { | ||||||
|  |         await this.metadataExtractionQueue.add( | ||||||
|  |           videoMetadataExtractionProcessorName, | ||||||
|  |           { asset, fileName: asset.id }, | ||||||
|  |           { jobId: randomUUID() }, | ||||||
|  |         ); | ||||||
|  |       } else { | ||||||
|  |         await this.metadataExtractionQueue.add( | ||||||
|  |           exifExtractionProcessorName, | ||||||
|  |           { asset, fileName: asset.id }, | ||||||
|  |           { jobId: randomUUID() }, | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return assetsWithNoExif.length; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private async runMachineLearningPipeline(): Promise<number> { | ||||||
|  |     const jobCount = await this.machineLearningQueue.getJobCounts(); | ||||||
|  |  | ||||||
|  |     if (jobCount.waiting > 0) { | ||||||
|  |       throw new BadRequestException('Metadata extraction job is already running'); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const assetWithNoSmartInfo = await this._assetRepository.getAssetWithNoSmartInfo(); | ||||||
|  |  | ||||||
|  |     for (const asset of assetWithNoSmartInfo) { | ||||||
|  |       await this.machineLearningQueue.add(MachineLearningJobNameEnum.IMAGE_TAGGING, { asset }, { jobId: randomUUID() }); | ||||||
|  |       await this.machineLearningQueue.add( | ||||||
|  |         MachineLearningJobNameEnum.OBJECT_DETECTION, | ||||||
|  |         { asset }, | ||||||
|  |         { jobId: randomUUID() }, | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return assetWithNoSmartInfo.length; | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -0,0 +1,35 @@ | |||||||
|  | import { ApiProperty } from '@nestjs/swagger'; | ||||||
|  |  | ||||||
|  | export class JobCounts { | ||||||
|  |   active!: number; | ||||||
|  |   completed!: number; | ||||||
|  |   failed!: number; | ||||||
|  |   delayed!: number; | ||||||
|  |   waiting!: number; | ||||||
|  | } | ||||||
|  | export class AllJobStatusResponseDto { | ||||||
|  |   isThumbnailGenerationActive!: boolean; | ||||||
|  |   isMetadataExtractionActive!: boolean; | ||||||
|  |   isVideoConversionActive!: boolean; | ||||||
|  |   isMachineLearningActive!: boolean; | ||||||
|  |  | ||||||
|  |   @ApiProperty({ | ||||||
|  |     type: JobCounts, | ||||||
|  |   }) | ||||||
|  |   thumbnailGenerationQueueCount!: JobCounts; | ||||||
|  |  | ||||||
|  |   @ApiProperty({ | ||||||
|  |     type: JobCounts, | ||||||
|  |   }) | ||||||
|  |   metadataExtractionQueueCount!: JobCounts; | ||||||
|  |  | ||||||
|  |   @ApiProperty({ | ||||||
|  |     type: JobCounts, | ||||||
|  |   }) | ||||||
|  |   videoConversionQueueCount!: JobCounts; | ||||||
|  |  | ||||||
|  |   @ApiProperty({ | ||||||
|  |     type: JobCounts, | ||||||
|  |   }) | ||||||
|  |   machineLearningQueueCount!: JobCounts; | ||||||
|  | } | ||||||
| @@ -0,0 +1,6 @@ | |||||||
|  | import Bull from 'bull'; | ||||||
|  |  | ||||||
|  | export class JobStatusResponseDto { | ||||||
|  |   isActive!: boolean; | ||||||
|  |   queueCount!: Bull.JobCounts; | ||||||
|  | } | ||||||
| @@ -5,13 +5,13 @@ export class ServerInfoResponseDto { | |||||||
|   diskUse!: string; |   diskUse!: string; | ||||||
|   diskAvailable!: string; |   diskAvailable!: string; | ||||||
|  |  | ||||||
|   @ApiProperty({ type: 'integer' }) |   @ApiProperty({ type: 'integer', format: 'int64' }) | ||||||
|   diskSizeRaw!: number; |   diskSizeRaw!: number; | ||||||
|  |  | ||||||
|   @ApiProperty({ type: 'integer' }) |   @ApiProperty({ type: 'integer', format: 'int64' }) | ||||||
|   diskUseRaw!: number; |   diskUseRaw!: number; | ||||||
|  |  | ||||||
|   @ApiProperty({ type: 'integer' }) |   @ApiProperty({ type: 'integer', format: 'int64' }) | ||||||
|   diskAvailableRaw!: number; |   diskAvailableRaw!: number; | ||||||
|  |  | ||||||
|   @ApiProperty({ type: 'number', format: 'float' }) |   @ApiProperty({ type: 'number', format: 'float' }) | ||||||
|   | |||||||
| @@ -15,6 +15,7 @@ import { AppController } from './app.controller'; | |||||||
| import { ScheduleModule } from '@nestjs/schedule'; | import { ScheduleModule } from '@nestjs/schedule'; | ||||||
| import { ScheduleTasksModule } from './modules/schedule-tasks/schedule-tasks.module'; | import { ScheduleTasksModule } from './modules/schedule-tasks/schedule-tasks.module'; | ||||||
| import { DatabaseModule } from '@app/database'; | import { DatabaseModule } from '@app/database'; | ||||||
|  | import { JobModule } from './api-v1/job/job.module'; | ||||||
|  |  | ||||||
| @Module({ | @Module({ | ||||||
|   imports: [ |   imports: [ | ||||||
| @@ -55,6 +56,8 @@ import { DatabaseModule } from '@app/database'; | |||||||
|     ScheduleModule.forRoot(), |     ScheduleModule.forRoot(), | ||||||
|  |  | ||||||
|     ScheduleTasksModule, |     ScheduleTasksModule, | ||||||
|  |  | ||||||
|  |     JobModule, | ||||||
|   ], |   ], | ||||||
|   controllers: [AppController], |   controllers: [AppController], | ||||||
|   providers: [], |   providers: [], | ||||||
|   | |||||||
| @@ -3,18 +3,14 @@ import { Module } from '@nestjs/common'; | |||||||
| import { TypeOrmModule } from '@nestjs/typeorm'; | import { TypeOrmModule } from '@nestjs/typeorm'; | ||||||
| import { AssetEntity } from '@app/database/entities/asset.entity'; | import { AssetEntity } from '@app/database/entities/asset.entity'; | ||||||
| import { ScheduleTasksService } from './schedule-tasks.service'; | import { ScheduleTasksService } from './schedule-tasks.service'; | ||||||
| import { | import { QueueNameEnum } from '@app/job/constants/queue-name.constant'; | ||||||
|   metadataExtractionQueueName, |  | ||||||
|   thumbnailGeneratorQueueName, |  | ||||||
|   videoConversionQueueName, |  | ||||||
| } from '@app/job/constants/queue-name.constant'; |  | ||||||
| import { ExifEntity } from '@app/database/entities/exif.entity'; | import { ExifEntity } from '@app/database/entities/exif.entity'; | ||||||
|  |  | ||||||
| @Module({ | @Module({ | ||||||
|   imports: [ |   imports: [ | ||||||
|     TypeOrmModule.forFeature([AssetEntity, ExifEntity]), |     TypeOrmModule.forFeature([AssetEntity, ExifEntity]), | ||||||
|     BullModule.registerQueue({ |     BullModule.registerQueue({ | ||||||
|       name: videoConversionQueueName, |       name: QueueNameEnum.VIDEO_CONVERSION, | ||||||
|       defaultJobOptions: { |       defaultJobOptions: { | ||||||
|         attempts: 3, |         attempts: 3, | ||||||
|         removeOnComplete: true, |         removeOnComplete: true, | ||||||
| @@ -22,7 +18,7 @@ import { ExifEntity } from '@app/database/entities/exif.entity'; | |||||||
|       }, |       }, | ||||||
|     }), |     }), | ||||||
|     BullModule.registerQueue({ |     BullModule.registerQueue({ | ||||||
|       name: thumbnailGeneratorQueueName, |       name: QueueNameEnum.THUMBNAIL_GENERATION, | ||||||
|       defaultJobOptions: { |       defaultJobOptions: { | ||||||
|         attempts: 3, |         attempts: 3, | ||||||
|         removeOnComplete: true, |         removeOnComplete: true, | ||||||
| @@ -31,7 +27,7 @@ import { ExifEntity } from '@app/database/entities/exif.entity'; | |||||||
|     }), |     }), | ||||||
|  |  | ||||||
|     BullModule.registerQueue({ |     BullModule.registerQueue({ | ||||||
|       name: metadataExtractionQueueName, |       name: QueueNameEnum.METADATA_EXTRACTION, | ||||||
|       defaultJobOptions: { |       defaultJobOptions: { | ||||||
|         attempts: 3, |         attempts: 3, | ||||||
|         removeOnComplete: true, |         removeOnComplete: true, | ||||||
|   | |||||||
| @@ -12,11 +12,9 @@ import { | |||||||
|   generateWEBPThumbnailProcessorName, |   generateWEBPThumbnailProcessorName, | ||||||
|   IMetadataExtractionJob, |   IMetadataExtractionJob, | ||||||
|   IVideoTranscodeJob, |   IVideoTranscodeJob, | ||||||
|   metadataExtractionQueueName, |  | ||||||
|   mp4ConversionProcessorName, |   mp4ConversionProcessorName, | ||||||
|  |   QueueNameEnum, | ||||||
|   reverseGeocodingProcessorName, |   reverseGeocodingProcessorName, | ||||||
|   thumbnailGeneratorQueueName, |  | ||||||
|   videoConversionQueueName, |  | ||||||
|   videoMetadataExtractionProcessorName, |   videoMetadataExtractionProcessorName, | ||||||
| } from '@app/job'; | } from '@app/job'; | ||||||
| import { ConfigService } from '@nestjs/config'; | import { ConfigService } from '@nestjs/config'; | ||||||
| @@ -30,13 +28,13 @@ export class ScheduleTasksService { | |||||||
|     @InjectRepository(ExifEntity) |     @InjectRepository(ExifEntity) | ||||||
|     private exifRepository: Repository<ExifEntity>, |     private exifRepository: Repository<ExifEntity>, | ||||||
|  |  | ||||||
|     @InjectQueue(thumbnailGeneratorQueueName) |     @InjectQueue(QueueNameEnum.THUMBNAIL_GENERATION) | ||||||
|     private thumbnailGeneratorQueue: Queue, |     private thumbnailGeneratorQueue: Queue, | ||||||
|  |  | ||||||
|     @InjectQueue(videoConversionQueueName) |     @InjectQueue(QueueNameEnum.VIDEO_CONVERSION) | ||||||
|     private videoConversionQueue: Queue<IVideoTranscodeJob>, |     private videoConversionQueue: Queue<IVideoTranscodeJob>, | ||||||
|  |  | ||||||
|     @InjectQueue(metadataExtractionQueueName) |     @InjectQueue(QueueNameEnum.METADATA_EXTRACTION) | ||||||
|     private metadataExtractionQueue: Queue<IMetadataExtractionJob>, |     private metadataExtractionQueue: Queue<IMetadataExtractionJob>, | ||||||
|  |  | ||||||
|     private configService: ConfigService, |     private configService: ConfigService, | ||||||
| @@ -108,11 +106,11 @@ export class ScheduleTasksService { | |||||||
|  |  | ||||||
|   @Cron(CronExpression.EVERY_DAY_AT_3AM) |   @Cron(CronExpression.EVERY_DAY_AT_3AM) | ||||||
|   async extractExif() { |   async extractExif() { | ||||||
|     const exifAssets = await this.assetRepository.find({ |     const exifAssets = await this.assetRepository | ||||||
|       where: { |       .createQueryBuilder('asset') | ||||||
|         exifInfo: IsNull(), |       .leftJoinAndSelect('asset.exifInfo', 'ei') | ||||||
|       }, |       .where('ei."assetId" IS NULL') | ||||||
|     }); |       .getMany(); | ||||||
|  |  | ||||||
|     for (const asset of exifAssets) { |     for (const asset of exifAssets) { | ||||||
|       if (asset.type === AssetType.VIDEO) { |       if (asset.type === AssetType.VIDEO) { | ||||||
|   | |||||||
| @@ -4,13 +4,7 @@ import { AssetEntity } from '@app/database/entities/asset.entity'; | |||||||
| import { ExifEntity } from '@app/database/entities/exif.entity'; | import { ExifEntity } from '@app/database/entities/exif.entity'; | ||||||
| import { SmartInfoEntity } from '@app/database/entities/smart-info.entity'; | import { SmartInfoEntity } from '@app/database/entities/smart-info.entity'; | ||||||
| import { UserEntity } from '@app/database/entities/user.entity'; | import { UserEntity } from '@app/database/entities/user.entity'; | ||||||
| import { | import { QueueNameEnum } from '@app/job/constants/queue-name.constant'; | ||||||
|   assetUploadedQueueName, |  | ||||||
|   generateChecksumQueueName, |  | ||||||
|   metadataExtractionQueueName, |  | ||||||
|   thumbnailGeneratorQueueName, |  | ||||||
|   videoConversionQueueName, |  | ||||||
| } from '@app/job/constants/queue-name.constant'; |  | ||||||
| import { BullModule } from '@nestjs/bull'; | import { BullModule } from '@nestjs/bull'; | ||||||
| import { Module } from '@nestjs/common'; | import { Module } from '@nestjs/common'; | ||||||
| import { ConfigModule, ConfigService } from '@nestjs/config'; | import { ConfigModule, ConfigService } from '@nestjs/config'; | ||||||
| @@ -19,6 +13,7 @@ import { CommunicationModule } from '../../immich/src/api-v1/communication/commu | |||||||
| import { MicroservicesService } from './microservices.service'; | import { MicroservicesService } from './microservices.service'; | ||||||
| import { AssetUploadedProcessor } from './processors/asset-uploaded.processor'; | import { AssetUploadedProcessor } from './processors/asset-uploaded.processor'; | ||||||
| import { GenerateChecksumProcessor } from './processors/generate-checksum.processor'; | import { GenerateChecksumProcessor } from './processors/generate-checksum.processor'; | ||||||
|  | import { MachineLearningProcessor } from './processors/machine-learning.processor'; | ||||||
| import { MetadataExtractionProcessor } from './processors/metadata-extraction.processor'; | import { MetadataExtractionProcessor } from './processors/metadata-extraction.processor'; | ||||||
| import { ThumbnailGeneratorProcessor } from './processors/thumbnail.processor'; | import { ThumbnailGeneratorProcessor } from './processors/thumbnail.processor'; | ||||||
| import { VideoTranscodeProcessor } from './processors/video-transcode.processor'; | import { VideoTranscodeProcessor } from './processors/video-transcode.processor'; | ||||||
| @@ -42,7 +37,7 @@ import { VideoTranscodeProcessor } from './processors/video-transcode.processor' | |||||||
|     }), |     }), | ||||||
|     BullModule.registerQueue( |     BullModule.registerQueue( | ||||||
|       { |       { | ||||||
|         name: thumbnailGeneratorQueueName, |         name: QueueNameEnum.THUMBNAIL_GENERATION, | ||||||
|         defaultJobOptions: { |         defaultJobOptions: { | ||||||
|           attempts: 3, |           attempts: 3, | ||||||
|           removeOnComplete: true, |           removeOnComplete: true, | ||||||
| @@ -50,7 +45,7 @@ import { VideoTranscodeProcessor } from './processors/video-transcode.processor' | |||||||
|         }, |         }, | ||||||
|       }, |       }, | ||||||
|       { |       { | ||||||
|         name: assetUploadedQueueName, |         name: QueueNameEnum.ASSET_UPLOADED, | ||||||
|         defaultJobOptions: { |         defaultJobOptions: { | ||||||
|           attempts: 3, |           attempts: 3, | ||||||
|           removeOnComplete: true, |           removeOnComplete: true, | ||||||
| @@ -58,7 +53,7 @@ import { VideoTranscodeProcessor } from './processors/video-transcode.processor' | |||||||
|         }, |         }, | ||||||
|       }, |       }, | ||||||
|       { |       { | ||||||
|         name: metadataExtractionQueueName, |         name: QueueNameEnum.METADATA_EXTRACTION, | ||||||
|         defaultJobOptions: { |         defaultJobOptions: { | ||||||
|           attempts: 3, |           attempts: 3, | ||||||
|           removeOnComplete: true, |           removeOnComplete: true, | ||||||
| @@ -66,7 +61,7 @@ import { VideoTranscodeProcessor } from './processors/video-transcode.processor' | |||||||
|         }, |         }, | ||||||
|       }, |       }, | ||||||
|       { |       { | ||||||
|         name: videoConversionQueueName, |         name: QueueNameEnum.VIDEO_CONVERSION, | ||||||
|         defaultJobOptions: { |         defaultJobOptions: { | ||||||
|           attempts: 3, |           attempts: 3, | ||||||
|           removeOnComplete: true, |           removeOnComplete: true, | ||||||
| @@ -74,7 +69,15 @@ import { VideoTranscodeProcessor } from './processors/video-transcode.processor' | |||||||
|         }, |         }, | ||||||
|       }, |       }, | ||||||
|       { |       { | ||||||
|         name: generateChecksumQueueName, |         name: QueueNameEnum.CHECKSUM_GENERATION, | ||||||
|  |         defaultJobOptions: { | ||||||
|  |           attempts: 3, | ||||||
|  |           removeOnComplete: true, | ||||||
|  |           removeOnFail: false, | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         name: QueueNameEnum.MACHINE_LEARNING, | ||||||
|         defaultJobOptions: { |         defaultJobOptions: { | ||||||
|           attempts: 3, |           attempts: 3, | ||||||
|           removeOnComplete: true, |           removeOnComplete: true, | ||||||
| @@ -92,6 +95,7 @@ import { VideoTranscodeProcessor } from './processors/video-transcode.processor' | |||||||
|     MetadataExtractionProcessor, |     MetadataExtractionProcessor, | ||||||
|     VideoTranscodeProcessor, |     VideoTranscodeProcessor, | ||||||
|     GenerateChecksumProcessor, |     GenerateChecksumProcessor, | ||||||
|  |     MachineLearningProcessor, | ||||||
|     ConfigService, |     ConfigService, | ||||||
|   ], |   ], | ||||||
|   exports: [], |   exports: [], | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| import { generateChecksumQueueName } from '@app/job'; | import { QueueNameEnum } from '@app/job'; | ||||||
| import { InjectQueue } from '@nestjs/bull'; | import { InjectQueue } from '@nestjs/bull'; | ||||||
| import { Injectable, OnModuleInit } from '@nestjs/common'; | import { Injectable, OnModuleInit } from '@nestjs/common'; | ||||||
| import { Queue } from 'bull'; | import { Queue } from 'bull'; | ||||||
| @@ -6,14 +6,18 @@ import { randomUUID } from 'node:crypto'; | |||||||
|  |  | ||||||
| @Injectable() | @Injectable() | ||||||
| export class MicroservicesService implements OnModuleInit { | export class MicroservicesService implements OnModuleInit { | ||||||
|   constructor ( |   constructor( | ||||||
|     @InjectQueue(generateChecksumQueueName) |     @InjectQueue(QueueNameEnum.CHECKSUM_GENERATION) | ||||||
|     private generateChecksumQueue: Queue, |     private generateChecksumQueue: Queue, | ||||||
|   ) {} |   ) {} | ||||||
|  |  | ||||||
|   async onModuleInit() { |   async onModuleInit() { | ||||||
|     await this.generateChecksumQueue.add({}, { |     await this.generateChecksumQueue.add( | ||||||
|       jobId: randomUUID(), delay: 10000 // wait for migration |       {}, | ||||||
|     }); |       { | ||||||
|  |         jobId: randomUUID(), | ||||||
|  |         delay: 10000, // wait for migration | ||||||
|  |       }, | ||||||
|  |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -4,30 +4,27 @@ import { | |||||||
|   IMetadataExtractionJob, |   IMetadataExtractionJob, | ||||||
|   IThumbnailGenerationJob, |   IThumbnailGenerationJob, | ||||||
|   IVideoTranscodeJob, |   IVideoTranscodeJob, | ||||||
|   assetUploadedQueueName, |  | ||||||
|   metadataExtractionQueueName, |  | ||||||
|   thumbnailGeneratorQueueName, |  | ||||||
|   videoConversionQueueName, |  | ||||||
|   assetUploadedProcessorName, |   assetUploadedProcessorName, | ||||||
|   exifExtractionProcessorName, |   exifExtractionProcessorName, | ||||||
|   generateJPEGThumbnailProcessorName, |   generateJPEGThumbnailProcessorName, | ||||||
|   mp4ConversionProcessorName, |   mp4ConversionProcessorName, | ||||||
|   videoMetadataExtractionProcessorName, |   videoMetadataExtractionProcessorName, | ||||||
|  |   QueueNameEnum, | ||||||
| } from '@app/job'; | } from '@app/job'; | ||||||
| import { InjectQueue, Process, Processor } from '@nestjs/bull'; | import { InjectQueue, Process, Processor } from '@nestjs/bull'; | ||||||
| import { Job, Queue } from 'bull'; | import { Job, Queue } from 'bull'; | ||||||
| import { randomUUID } from 'crypto'; | import { randomUUID } from 'crypto'; | ||||||
|  |  | ||||||
| @Processor(assetUploadedQueueName) | @Processor(QueueNameEnum.ASSET_UPLOADED) | ||||||
| export class AssetUploadedProcessor { | export class AssetUploadedProcessor { | ||||||
|   constructor( |   constructor( | ||||||
|     @InjectQueue(thumbnailGeneratorQueueName) |     @InjectQueue(QueueNameEnum.THUMBNAIL_GENERATION) | ||||||
|     private thumbnailGeneratorQueue: Queue<IThumbnailGenerationJob>, |     private thumbnailGeneratorQueue: Queue<IThumbnailGenerationJob>, | ||||||
|  |  | ||||||
|     @InjectQueue(metadataExtractionQueueName) |     @InjectQueue(QueueNameEnum.METADATA_EXTRACTION) | ||||||
|     private metadataExtractionQueue: Queue<IMetadataExtractionJob>, |     private metadataExtractionQueue: Queue<IMetadataExtractionJob>, | ||||||
|  |  | ||||||
|     @InjectQueue(videoConversionQueueName) |     @InjectQueue(QueueNameEnum.VIDEO_CONVERSION) | ||||||
|     private videoConversionQueue: Queue<IVideoTranscodeJob>, |     private videoConversionQueue: Queue<IVideoTranscodeJob>, | ||||||
|   ) {} |   ) {} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import { AssetEntity } from '@app/database/entities/asset.entity'; | import { AssetEntity } from '@app/database/entities/asset.entity'; | ||||||
| import { generateChecksumQueueName } from '@app/job'; | import { QueueNameEnum } from '@app/job'; | ||||||
| import { Process, Processor } from '@nestjs/bull'; | import { Process, Processor } from '@nestjs/bull'; | ||||||
| import { Logger } from '@nestjs/common'; | import { Logger } from '@nestjs/common'; | ||||||
| import { InjectRepository } from '@nestjs/typeorm'; | import { InjectRepository } from '@nestjs/typeorm'; | ||||||
| @@ -8,7 +8,7 @@ import fs from 'node:fs'; | |||||||
| import { FindOptionsWhere, IsNull, MoreThan, QueryFailedError, Repository } from 'typeorm'; | import { FindOptionsWhere, IsNull, MoreThan, QueryFailedError, Repository } from 'typeorm'; | ||||||
|  |  | ||||||
| // TODO: just temporary task to generate previous uploaded assets. | // TODO: just temporary task to generate previous uploaded assets. | ||||||
| @Processor(generateChecksumQueueName) | @Processor(QueueNameEnum.CHECKSUM_GENERATION) | ||||||
| export class GenerateChecksumProcessor { | export class GenerateChecksumProcessor { | ||||||
|   constructor( |   constructor( | ||||||
|     @InjectRepository(AssetEntity) |     @InjectRepository(AssetEntity) | ||||||
| @@ -33,7 +33,7 @@ export class GenerateChecksumProcessor { | |||||||
|       const assets = await this.assetRepository.find({ |       const assets = await this.assetRepository.find({ | ||||||
|         where: whereStat, |         where: whereStat, | ||||||
|         take: pageSize, |         take: pageSize, | ||||||
|         order: { id: 'ASC' } |         order: { id: 'ASC' }, | ||||||
|       }); |       }); | ||||||
|  |  | ||||||
|       if (!assets?.length) { |       if (!assets?.length) { | ||||||
|   | |||||||
| @@ -0,0 +1,60 @@ | |||||||
|  | import { AssetEntity } from '@app/database/entities/asset.entity'; | ||||||
|  | import { SmartInfoEntity } from '@app/database/entities/smart-info.entity'; | ||||||
|  | import { MachineLearningJobNameEnum, QueueNameEnum } from '@app/job'; | ||||||
|  | import { IMachineLearningJob } from '@app/job/interfaces/machine-learning.interface'; | ||||||
|  | import { Process, Processor } from '@nestjs/bull'; | ||||||
|  | import { Logger } from '@nestjs/common'; | ||||||
|  | import { InjectRepository } from '@nestjs/typeorm'; | ||||||
|  | import axios from 'axios'; | ||||||
|  | import { Job } from 'bull'; | ||||||
|  | import { Repository } from 'typeorm'; | ||||||
|  |  | ||||||
|  | @Processor(QueueNameEnum.MACHINE_LEARNING) | ||||||
|  | export class MachineLearningProcessor { | ||||||
|  |   constructor( | ||||||
|  |     @InjectRepository(SmartInfoEntity) | ||||||
|  |     private smartInfoRepository: Repository<SmartInfoEntity>, | ||||||
|  |   ) {} | ||||||
|  |  | ||||||
|  |   @Process({ name: MachineLearningJobNameEnum.IMAGE_TAGGING, concurrency: 2 }) | ||||||
|  |   async tagImage(job: Job<IMachineLearningJob>) { | ||||||
|  |     const { asset } = job.data; | ||||||
|  |  | ||||||
|  |     const res = await axios.post('http://immich-machine-learning:3003/image-classifier/tag-image', { | ||||||
|  |       thumbnailPath: asset.resizePath, | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     if (res.status == 201 && res.data.length > 0) { | ||||||
|  |       const smartInfo = new SmartInfoEntity(); | ||||||
|  |       smartInfo.assetId = asset.id; | ||||||
|  |       smartInfo.tags = [...res.data]; | ||||||
|  |  | ||||||
|  |       await this.smartInfoRepository.upsert(smartInfo, { | ||||||
|  |         conflictPaths: ['assetId'], | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @Process({ name: MachineLearningJobNameEnum.OBJECT_DETECTION, concurrency: 2 }) | ||||||
|  |   async detectObject(job: Job<IMachineLearningJob>) { | ||||||
|  |     try { | ||||||
|  |       const { asset }: { asset: AssetEntity } = job.data; | ||||||
|  |  | ||||||
|  |       const res = await axios.post('http://immich-machine-learning:3003/object-detection/detect-object', { | ||||||
|  |         thumbnailPath: asset.resizePath, | ||||||
|  |       }); | ||||||
|  |  | ||||||
|  |       if (res.status == 201 && res.data.length > 0) { | ||||||
|  |         const smartInfo = new SmartInfoEntity(); | ||||||
|  |         smartInfo.assetId = asset.id; | ||||||
|  |         smartInfo.objects = [...res.data]; | ||||||
|  |  | ||||||
|  |         await this.smartInfoRepository.upsert(smartInfo, { | ||||||
|  |           conflictPaths: ['assetId'], | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     } catch (error) { | ||||||
|  |       Logger.error(`Failed to trigger object detection pipe line ${String(error)}`); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -1,23 +1,19 @@ | |||||||
| import { ImmichLogLevel } from '@app/common/constants/log-level.constant'; | import { ImmichLogLevel } from '@app/common/constants/log-level.constant'; | ||||||
| import { AssetEntity } from '@app/database/entities/asset.entity'; | import { AssetEntity } from '@app/database/entities/asset.entity'; | ||||||
| import { ExifEntity } from '@app/database/entities/exif.entity'; | import { ExifEntity } from '@app/database/entities/exif.entity'; | ||||||
| import { SmartInfoEntity } from '@app/database/entities/smart-info.entity'; |  | ||||||
| import { | import { | ||||||
|   IExifExtractionProcessor, |   IExifExtractionProcessor, | ||||||
|   IVideoLengthExtractionProcessor, |   IVideoLengthExtractionProcessor, | ||||||
|   exifExtractionProcessorName, |   exifExtractionProcessorName, | ||||||
|   imageTaggingProcessorName, |  | ||||||
|   objectDetectionProcessorName, |  | ||||||
|   videoMetadataExtractionProcessorName, |   videoMetadataExtractionProcessorName, | ||||||
|   metadataExtractionQueueName, |  | ||||||
|   reverseGeocodingProcessorName, |   reverseGeocodingProcessorName, | ||||||
|   IReverseGeocodingProcessor, |   IReverseGeocodingProcessor, | ||||||
|  |   QueueNameEnum, | ||||||
| } from '@app/job'; | } from '@app/job'; | ||||||
| import { Process, Processor } from '@nestjs/bull'; | import { Process, Processor } from '@nestjs/bull'; | ||||||
| import { Logger } from '@nestjs/common'; | import { Logger } from '@nestjs/common'; | ||||||
| import { ConfigService } from '@nestjs/config'; | import { ConfigService } from '@nestjs/config'; | ||||||
| import { InjectRepository } from '@nestjs/typeorm'; | import { InjectRepository } from '@nestjs/typeorm'; | ||||||
| import axios from 'axios'; |  | ||||||
| import { Job } from 'bull'; | import { Job } from 'bull'; | ||||||
| import exifr from 'exifr'; | import exifr from 'exifr'; | ||||||
| import ffmpeg from 'fluent-ffmpeg'; | import ffmpeg from 'fluent-ffmpeg'; | ||||||
| @@ -79,7 +75,7 @@ export interface GeoData { | |||||||
|   distance: number; |   distance: number; | ||||||
| } | } | ||||||
|  |  | ||||||
| @Processor(metadataExtractionQueueName) | @Processor(QueueNameEnum.METADATA_EXTRACTION) | ||||||
| export class MetadataExtractionProcessor { | export class MetadataExtractionProcessor { | ||||||
|   private isGeocodeInitialized = false; |   private isGeocodeInitialized = false; | ||||||
|   private logLevel: ImmichLogLevel; |   private logLevel: ImmichLogLevel; | ||||||
| @@ -91,9 +87,6 @@ export class MetadataExtractionProcessor { | |||||||
|     @InjectRepository(ExifEntity) |     @InjectRepository(ExifEntity) | ||||||
|     private exifRepository: Repository<ExifEntity>, |     private exifRepository: Repository<ExifEntity>, | ||||||
|  |  | ||||||
|     @InjectRepository(SmartInfoEntity) |  | ||||||
|     private smartInfoRepository: Repository<SmartInfoEntity>, |  | ||||||
|  |  | ||||||
|     private configService: ConfigService, |     private configService: ConfigService, | ||||||
|   ) { |   ) { | ||||||
|     if (!configService.get('DISABLE_REVERSE_GEOCODING')) { |     if (!configService.get('DISABLE_REVERSE_GEOCODING')) { | ||||||
| @@ -109,7 +102,8 @@ export class MetadataExtractionProcessor { | |||||||
|           alternateNames: false, |           alternateNames: false, | ||||||
|         }, |         }, | ||||||
|         countries: [], |         countries: [], | ||||||
|         dumpDirectory: configService.get('REVERSE_GEOCODING_DUMP_DIRECTORY') || (process.cwd() + '/.reverse-geocoding-dump/'), |         dumpDirectory: | ||||||
|  |           configService.get('REVERSE_GEOCODING_DUMP_DIRECTORY') || process.cwd() + '/.reverse-geocoding-dump/', | ||||||
|       }).then(() => { |       }).then(() => { | ||||||
|         this.isGeocodeInitialized = true; |         this.isGeocodeInitialized = true; | ||||||
|         Logger.log('Reverse Geocoding Initialised'); |         Logger.log('Reverse Geocoding Initialised'); | ||||||
| @@ -273,48 +267,6 @@ export class MetadataExtractionProcessor { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @Process({ name: imageTaggingProcessorName, concurrency: 2 }) |  | ||||||
|   async tagImage(job: Job) { |  | ||||||
|     const { asset }: { asset: AssetEntity } = job.data; |  | ||||||
|  |  | ||||||
|     const res = await axios.post('http://immich-machine-learning:3003/image-classifier/tag-image', { |  | ||||||
|       thumbnailPath: asset.resizePath, |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     if (res.status == 201 && res.data.length > 0) { |  | ||||||
|       const smartInfo = new SmartInfoEntity(); |  | ||||||
|       smartInfo.assetId = asset.id; |  | ||||||
|       smartInfo.tags = [...res.data]; |  | ||||||
|  |  | ||||||
|       await this.smartInfoRepository.upsert(smartInfo, { |  | ||||||
|         conflictPaths: ['assetId'], |  | ||||||
|       }); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   @Process({ name: objectDetectionProcessorName, concurrency: 2 }) |  | ||||||
|   async detectObject(job: Job) { |  | ||||||
|     try { |  | ||||||
|       const { asset }: { asset: AssetEntity } = job.data; |  | ||||||
|  |  | ||||||
|       const res = await axios.post('http://immich-machine-learning:3003/object-detection/detect-object', { |  | ||||||
|         thumbnailPath: asset.resizePath, |  | ||||||
|       }); |  | ||||||
|  |  | ||||||
|       if (res.status == 201 && res.data.length > 0) { |  | ||||||
|         const smartInfo = new SmartInfoEntity(); |  | ||||||
|         smartInfo.assetId = asset.id; |  | ||||||
|         smartInfo.objects = [...res.data]; |  | ||||||
|  |  | ||||||
|         await this.smartInfoRepository.upsert(smartInfo, { |  | ||||||
|           conflictPaths: ['assetId'], |  | ||||||
|         }); |  | ||||||
|       } |  | ||||||
|     } catch (error) { |  | ||||||
|       Logger.error(`Failed to trigger object detection pipe line ${String(error)}`); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   @Process({ name: videoMetadataExtractionProcessorName, concurrency: 2 }) |   @Process({ name: videoMetadataExtractionProcessorName, concurrency: 2 }) | ||||||
|   async extractVideoMetadata(job: Job<IVideoLengthExtractionProcessor>) { |   async extractVideoMetadata(job: Job<IVideoLengthExtractionProcessor>) { | ||||||
|     const { asset, fileName } = job.data; |     const { asset, fileName } = job.data; | ||||||
|   | |||||||
| @@ -5,11 +5,9 @@ import { | |||||||
|   WebpGeneratorProcessor, |   WebpGeneratorProcessor, | ||||||
|   generateJPEGThumbnailProcessorName, |   generateJPEGThumbnailProcessorName, | ||||||
|   generateWEBPThumbnailProcessorName, |   generateWEBPThumbnailProcessorName, | ||||||
|   imageTaggingProcessorName, |  | ||||||
|   objectDetectionProcessorName, |  | ||||||
|   metadataExtractionQueueName, |  | ||||||
|   thumbnailGeneratorQueueName, |  | ||||||
|   JpegGeneratorProcessor, |   JpegGeneratorProcessor, | ||||||
|  |   QueueNameEnum, | ||||||
|  |   MachineLearningJobNameEnum, | ||||||
| } from '@app/job'; | } from '@app/job'; | ||||||
| import { InjectQueue, Process, Processor } from '@nestjs/bull'; | import { InjectQueue, Process, Processor } from '@nestjs/bull'; | ||||||
| import { Logger } from '@nestjs/common'; | import { Logger } from '@nestjs/common'; | ||||||
| @@ -25,8 +23,9 @@ import sharp from 'sharp'; | |||||||
| import { Repository } from 'typeorm/repository/Repository'; | import { Repository } from 'typeorm/repository/Repository'; | ||||||
| import { join } from 'path'; | import { join } from 'path'; | ||||||
| import { CommunicationGateway } from 'apps/immich/src/api-v1/communication/communication.gateway'; | import { CommunicationGateway } from 'apps/immich/src/api-v1/communication/communication.gateway'; | ||||||
|  | import { IMachineLearningJob } from '@app/job/interfaces/machine-learning.interface'; | ||||||
|  |  | ||||||
| @Processor(thumbnailGeneratorQueueName) | @Processor(QueueNameEnum.THUMBNAIL_GENERATION) | ||||||
| export class ThumbnailGeneratorProcessor { | export class ThumbnailGeneratorProcessor { | ||||||
|   private logLevel: ImmichLogLevel; |   private logLevel: ImmichLogLevel; | ||||||
|  |  | ||||||
| @@ -34,13 +33,13 @@ export class ThumbnailGeneratorProcessor { | |||||||
|     @InjectRepository(AssetEntity) |     @InjectRepository(AssetEntity) | ||||||
|     private assetRepository: Repository<AssetEntity>, |     private assetRepository: Repository<AssetEntity>, | ||||||
|  |  | ||||||
|     @InjectQueue(thumbnailGeneratorQueueName) |     @InjectQueue(QueueNameEnum.THUMBNAIL_GENERATION) | ||||||
|     private thumbnailGeneratorQueue: Queue, |     private thumbnailGeneratorQueue: Queue, | ||||||
|  |  | ||||||
|     private wsCommunicationGateway: CommunicationGateway, |     private wsCommunicationGateway: CommunicationGateway, | ||||||
|  |  | ||||||
|     @InjectQueue(metadataExtractionQueueName) |     @InjectQueue(QueueNameEnum.MACHINE_LEARNING) | ||||||
|     private metadataExtractionQueue: Queue, |     private machineLearningQueue: Queue<IMachineLearningJob>, | ||||||
|  |  | ||||||
|     private configService: ConfigService, |     private configService: ConfigService, | ||||||
|   ) { |   ) { | ||||||
| @@ -80,8 +79,12 @@ export class ThumbnailGeneratorProcessor { | |||||||
|       asset.resizePath = jpegThumbnailPath; |       asset.resizePath = jpegThumbnailPath; | ||||||
|  |  | ||||||
|       await this.thumbnailGeneratorQueue.add(generateWEBPThumbnailProcessorName, { asset }, { jobId: randomUUID() }); |       await this.thumbnailGeneratorQueue.add(generateWEBPThumbnailProcessorName, { asset }, { jobId: randomUUID() }); | ||||||
|       await this.metadataExtractionQueue.add(imageTaggingProcessorName, { asset }, { jobId: randomUUID() }); |       await this.machineLearningQueue.add(MachineLearningJobNameEnum.IMAGE_TAGGING, { asset }, { jobId: randomUUID() }); | ||||||
|       await this.metadataExtractionQueue.add(objectDetectionProcessorName, { asset }, { jobId: randomUUID() }); |       await this.machineLearningQueue.add( | ||||||
|  |         MachineLearningJobNameEnum.OBJECT_DETECTION, | ||||||
|  |         { asset }, | ||||||
|  |         { jobId: randomUUID() }, | ||||||
|  |       ); | ||||||
|       this.wsCommunicationGateway.server.to(asset.userId).emit('on_upload_success', JSON.stringify(mapAsset(asset))); |       this.wsCommunicationGateway.server.to(asset.userId).emit('on_upload_success', JSON.stringify(mapAsset(asset))); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -110,8 +113,12 @@ export class ThumbnailGeneratorProcessor { | |||||||
|       asset.resizePath = jpegThumbnailPath; |       asset.resizePath = jpegThumbnailPath; | ||||||
|  |  | ||||||
|       await this.thumbnailGeneratorQueue.add(generateWEBPThumbnailProcessorName, { asset }, { jobId: randomUUID() }); |       await this.thumbnailGeneratorQueue.add(generateWEBPThumbnailProcessorName, { asset }, { jobId: randomUUID() }); | ||||||
|       await this.metadataExtractionQueue.add(imageTaggingProcessorName, { asset }, { jobId: randomUUID() }); |       await this.machineLearningQueue.add(MachineLearningJobNameEnum.IMAGE_TAGGING, { asset }, { jobId: randomUUID() }); | ||||||
|       await this.metadataExtractionQueue.add(objectDetectionProcessorName, { asset }, { jobId: randomUUID() }); |       await this.machineLearningQueue.add( | ||||||
|  |         MachineLearningJobNameEnum.OBJECT_DETECTION, | ||||||
|  |         { asset }, | ||||||
|  |         { jobId: randomUUID() }, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|       this.wsCommunicationGateway.server.to(asset.userId).emit('on_upload_success', JSON.stringify(mapAsset(asset))); |       this.wsCommunicationGateway.server.to(asset.userId).emit('on_upload_success', JSON.stringify(mapAsset(asset))); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import { APP_UPLOAD_LOCATION } from '@app/common/constants'; | import { APP_UPLOAD_LOCATION } from '@app/common/constants'; | ||||||
| import { AssetEntity } from '@app/database/entities/asset.entity'; | import { AssetEntity } from '@app/database/entities/asset.entity'; | ||||||
|  | import { QueueNameEnum } from '@app/job'; | ||||||
| import { mp4ConversionProcessorName } from '@app/job/constants/job-name.constant'; | import { mp4ConversionProcessorName } from '@app/job/constants/job-name.constant'; | ||||||
| import { videoConversionQueueName } from '@app/job/constants/queue-name.constant'; |  | ||||||
| import { IMp4ConversionProcessor } from '@app/job/interfaces/video-transcode.interface'; | import { IMp4ConversionProcessor } from '@app/job/interfaces/video-transcode.interface'; | ||||||
| import { Process, Processor } from '@nestjs/bull'; | import { Process, Processor } from '@nestjs/bull'; | ||||||
| import { Logger } from '@nestjs/common'; | import { Logger } from '@nestjs/common'; | ||||||
| @@ -11,7 +11,7 @@ import ffmpeg from 'fluent-ffmpeg'; | |||||||
| import { existsSync, mkdirSync } from 'fs'; | import { existsSync, mkdirSync } from 'fs'; | ||||||
| import { Repository } from 'typeorm'; | import { Repository } from 'typeorm'; | ||||||
|  |  | ||||||
| @Processor(videoConversionQueueName) | @Processor(QueueNameEnum.VIDEO_CONVERSION) | ||||||
| export class VideoTranscodeProcessor { | export class VideoTranscodeProcessor { | ||||||
|   constructor( |   constructor( | ||||||
|     @InjectRepository(AssetEntity) |     @InjectRepository(AssetEntity) | ||||||
|   | |||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -20,5 +20,12 @@ export const generateWEBPThumbnailProcessorName = 'generate-webp-thumbnail'; | |||||||
| export const exifExtractionProcessorName = 'exif-extraction'; | export const exifExtractionProcessorName = 'exif-extraction'; | ||||||
| export const videoMetadataExtractionProcessorName = 'extract-video-metadata'; | export const videoMetadataExtractionProcessorName = 'extract-video-metadata'; | ||||||
| export const reverseGeocodingProcessorName = 'reverse-geocoding'; | export const reverseGeocodingProcessorName = 'reverse-geocoding'; | ||||||
| export const objectDetectionProcessorName = 'detect-object'; |  | ||||||
| export const imageTaggingProcessorName = 'tag-image'; | /** | ||||||
|  |  * Machine learning Queue Jobs | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | export enum MachineLearningJobNameEnum { | ||||||
|  |   OBJECT_DETECTION = 'detect-object', | ||||||
|  |   IMAGE_TAGGING = 'tag-image', | ||||||
|  | } | ||||||
|   | |||||||
| @@ -1,5 +1,8 @@ | |||||||
| export const thumbnailGeneratorQueueName = 'thumbnail-generator-queue'; | export enum QueueNameEnum { | ||||||
| export const assetUploadedQueueName = 'asset-uploaded-queue'; |   THUMBNAIL_GENERATION = 'thumbnail-generation-queue', | ||||||
| export const metadataExtractionQueueName = 'metadata-extraction-queue'; |   METADATA_EXTRACTION = 'metadata-extraction-queue', | ||||||
| export const videoConversionQueueName = 'video-conversion-queue'; |   VIDEO_CONVERSION = 'video-conversion-queue', | ||||||
| export const generateChecksumQueueName = 'generate-checksum-queue'; |   CHECKSUM_GENERATION = 'generate-checksum-queue', | ||||||
|  |   ASSET_UPLOADED = 'asset-uploaded-queue', | ||||||
|  |   MACHINE_LEARNING = 'machine-learning-queue', | ||||||
|  | } | ||||||
|   | |||||||
| @@ -0,0 +1,8 @@ | |||||||
|  | import { AssetEntity } from '@app/database/entities/asset.entity'; | ||||||
|  |  | ||||||
|  | export interface IMachineLearningJob { | ||||||
|  |   /** | ||||||
|  |    * The Asset entity that was saved in the database | ||||||
|  |    */ | ||||||
|  |   asset: AssetEntity; | ||||||
|  | } | ||||||
							
								
								
									
										69
									
								
								server/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										69
									
								
								server/package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -59,7 +59,7 @@ | |||||||
|         "@nestjs/testing": "^8.4.7", |         "@nestjs/testing": "^8.4.7", | ||||||
|         "@openapitools/openapi-generator-cli": "2.5.1", |         "@openapitools/openapi-generator-cli": "2.5.1", | ||||||
|         "@types/bcrypt": "^5.0.0", |         "@types/bcrypt": "^5.0.0", | ||||||
|         "@types/bull": "^3.15.7", |         "@types/bull": "^3.15.9", | ||||||
|         "@types/cookie-parser": "^1.4.3", |         "@types/cookie-parser": "^1.4.3", | ||||||
|         "@types/cron": "^2.0.0", |         "@types/cron": "^2.0.0", | ||||||
|         "@types/express": "^4.17.13", |         "@types/express": "^4.17.13", | ||||||
| @@ -2339,9 +2339,9 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "node_modules/@types/bull": { |     "node_modules/@types/bull": { | ||||||
|       "version": "3.15.7", |       "version": "3.15.9", | ||||||
|       "resolved": "https://registry.npmjs.org/@types/bull/-/bull-3.15.7.tgz", |       "resolved": "https://registry.npmjs.org/@types/bull/-/bull-3.15.9.tgz", | ||||||
|       "integrity": "sha512-7NC7XN5NoS0A+leJ/dR69ZfKaegOlCZaii/xGgKnCyh1UYisRncibImb7VMwrc3OdJcbDJt6+4om70TeNl3J7g==", |       "integrity": "sha512-MPUcyPPQauAmynoO3ezHAmCOhbB0pWmYyijr/5ctaCqhbKWsjW0YCod38ZcLzUBprosfZ9dPqfYIcfdKjk7RNQ==", | ||||||
|       "dev": true, |       "dev": true, | ||||||
|       "dependencies": { |       "dependencies": { | ||||||
|         "@types/ioredis": "*", |         "@types/ioredis": "*", | ||||||
| @@ -3764,6 +3764,27 @@ | |||||||
|         "node": ">= 0.8" |         "node": ">= 0.8" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "node_modules/cache-manager": { | ||||||
|  |       "version": "5.0.0", | ||||||
|  |       "resolved": "https://registry.npmjs.org/cache-manager/-/cache-manager-5.0.0.tgz", | ||||||
|  |       "integrity": "sha512-1qKdoeoJKmrf95Zvhr3NpBVAgBESt4TuZomBzn4N2gCFZvHjuUXBK1H8EDVsJdba6/grIgi6WGYb/ncJj+wjtg==", | ||||||
|  |       "optional": true, | ||||||
|  |       "peer": true, | ||||||
|  |       "dependencies": { | ||||||
|  |         "lodash.clonedeep": "^4.5.0", | ||||||
|  |         "lru-cache": "^7.14.0" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "node_modules/cache-manager/node_modules/lru-cache": { | ||||||
|  |       "version": "7.14.0", | ||||||
|  |       "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.0.tgz", | ||||||
|  |       "integrity": "sha512-EIRtP1GrSJny0dqb50QXRUNBxHJhcpxHC++M5tD7RYbvLLn5KVWKsbyswSSqDuU15UFi3bgTQIY8nhDMeF6aDQ==", | ||||||
|  |       "optional": true, | ||||||
|  |       "peer": true, | ||||||
|  |       "engines": { | ||||||
|  |         "node": ">=12" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "node_modules/call-bind": { |     "node_modules/call-bind": { | ||||||
|       "version": "1.0.2", |       "version": "1.0.2", | ||||||
|       "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", |       "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", | ||||||
| @@ -7674,6 +7695,13 @@ | |||||||
|       "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", |       "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", | ||||||
|       "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" |       "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" | ||||||
|     }, |     }, | ||||||
|  |     "node_modules/lodash.clonedeep": { | ||||||
|  |       "version": "4.5.0", | ||||||
|  |       "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", | ||||||
|  |       "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", | ||||||
|  |       "optional": true, | ||||||
|  |       "peer": true | ||||||
|  |     }, | ||||||
|     "node_modules/lodash.defaults": { |     "node_modules/lodash.defaults": { | ||||||
|       "version": "4.2.0", |       "version": "4.2.0", | ||||||
|       "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", |       "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", | ||||||
| @@ -12900,9 +12928,9 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "@types/bull": { |     "@types/bull": { | ||||||
|       "version": "3.15.7", |       "version": "3.15.9", | ||||||
|       "resolved": "https://registry.npmjs.org/@types/bull/-/bull-3.15.7.tgz", |       "resolved": "https://registry.npmjs.org/@types/bull/-/bull-3.15.9.tgz", | ||||||
|       "integrity": "sha512-7NC7XN5NoS0A+leJ/dR69ZfKaegOlCZaii/xGgKnCyh1UYisRncibImb7VMwrc3OdJcbDJt6+4om70TeNl3J7g==", |       "integrity": "sha512-MPUcyPPQauAmynoO3ezHAmCOhbB0pWmYyijr/5ctaCqhbKWsjW0YCod38ZcLzUBprosfZ9dPqfYIcfdKjk7RNQ==", | ||||||
|       "dev": true, |       "dev": true, | ||||||
|       "requires": { |       "requires": { | ||||||
|         "@types/ioredis": "*", |         "@types/ioredis": "*", | ||||||
| @@ -14073,6 +14101,26 @@ | |||||||
|       "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", |       "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", | ||||||
|       "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" |       "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" | ||||||
|     }, |     }, | ||||||
|  |     "cache-manager": { | ||||||
|  |       "version": "5.0.0", | ||||||
|  |       "resolved": "https://registry.npmjs.org/cache-manager/-/cache-manager-5.0.0.tgz", | ||||||
|  |       "integrity": "sha512-1qKdoeoJKmrf95Zvhr3NpBVAgBESt4TuZomBzn4N2gCFZvHjuUXBK1H8EDVsJdba6/grIgi6WGYb/ncJj+wjtg==", | ||||||
|  |       "optional": true, | ||||||
|  |       "peer": true, | ||||||
|  |       "requires": { | ||||||
|  |         "lodash.clonedeep": "^4.5.0", | ||||||
|  |         "lru-cache": "^7.14.0" | ||||||
|  |       }, | ||||||
|  |       "dependencies": { | ||||||
|  |         "lru-cache": { | ||||||
|  |           "version": "7.14.0", | ||||||
|  |           "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.0.tgz", | ||||||
|  |           "integrity": "sha512-EIRtP1GrSJny0dqb50QXRUNBxHJhcpxHC++M5tD7RYbvLLn5KVWKsbyswSSqDuU15UFi3bgTQIY8nhDMeF6aDQ==", | ||||||
|  |           "optional": true, | ||||||
|  |           "peer": true | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "call-bind": { |     "call-bind": { | ||||||
|       "version": "1.0.2", |       "version": "1.0.2", | ||||||
|       "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", |       "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", | ||||||
| @@ -17088,6 +17136,13 @@ | |||||||
|       "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", |       "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", | ||||||
|       "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" |       "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" | ||||||
|     }, |     }, | ||||||
|  |     "lodash.clonedeep": { | ||||||
|  |       "version": "4.5.0", | ||||||
|  |       "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", | ||||||
|  |       "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", | ||||||
|  |       "optional": true, | ||||||
|  |       "peer": true | ||||||
|  |     }, | ||||||
|     "lodash.defaults": { |     "lodash.defaults": { | ||||||
|       "version": "4.2.0", |       "version": "4.2.0", | ||||||
|       "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", |       "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", | ||||||
|   | |||||||
| @@ -78,7 +78,7 @@ | |||||||
|     "@nestjs/testing": "^8.4.7", |     "@nestjs/testing": "^8.4.7", | ||||||
|     "@openapitools/openapi-generator-cli": "2.5.1", |     "@openapitools/openapi-generator-cli": "2.5.1", | ||||||
|     "@types/bcrypt": "^5.0.0", |     "@types/bcrypt": "^5.0.0", | ||||||
|     "@types/bull": "^3.15.7", |     "@types/bull": "^3.15.9", | ||||||
|     "@types/cookie-parser": "^1.4.3", |     "@types/cookie-parser": "^1.4.3", | ||||||
|     "@types/cron": "^2.0.0", |     "@types/cron": "^2.0.0", | ||||||
|     "@types/express": "^4.17.13", |     "@types/express": "^4.17.13", | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ import { | |||||||
| 	AuthenticationApi, | 	AuthenticationApi, | ||||||
| 	Configuration, | 	Configuration, | ||||||
| 	DeviceInfoApi, | 	DeviceInfoApi, | ||||||
|  | 	JobApi, | ||||||
| 	ServerInfoApi, | 	ServerInfoApi, | ||||||
| 	UserApi | 	UserApi | ||||||
| } from './open-api'; | } from './open-api'; | ||||||
| @@ -15,6 +16,8 @@ class ImmichApi { | |||||||
| 	public authenticationApi: AuthenticationApi; | 	public authenticationApi: AuthenticationApi; | ||||||
| 	public deviceInfoApi: DeviceInfoApi; | 	public deviceInfoApi: DeviceInfoApi; | ||||||
| 	public serverInfoApi: ServerInfoApi; | 	public serverInfoApi: ServerInfoApi; | ||||||
|  | 	public jobApi: JobApi; | ||||||
|  |  | ||||||
| 	private config = new Configuration({ basePath: '/api' }); | 	private config = new Configuration({ basePath: '/api' }); | ||||||
|  |  | ||||||
| 	constructor() { | 	constructor() { | ||||||
| @@ -24,6 +27,7 @@ class ImmichApi { | |||||||
| 		this.authenticationApi = new AuthenticationApi(this.config); | 		this.authenticationApi = new AuthenticationApi(this.config); | ||||||
| 		this.deviceInfoApi = new DeviceInfoApi(this.config); | 		this.deviceInfoApi = new DeviceInfoApi(this.config); | ||||||
| 		this.serverInfoApi = new ServerInfoApi(this.config); | 		this.serverInfoApi = new ServerInfoApi(this.config); | ||||||
|  | 		this.jobApi = new JobApi(this.config); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	public setAccessToken(accessToken: string) { | 	public setAccessToken(accessToken: string) { | ||||||
|   | |||||||
| @@ -170,6 +170,61 @@ export interface AlbumResponseDto { | |||||||
|      */ |      */ | ||||||
|     'assets': Array<AssetResponseDto>; |     'assets': Array<AssetResponseDto>; | ||||||
| } | } | ||||||
|  | /** | ||||||
|  |  *  | ||||||
|  |  * @export | ||||||
|  |  * @interface AllJobStatusResponseDto | ||||||
|  |  */ | ||||||
|  | export interface AllJobStatusResponseDto { | ||||||
|  |     /** | ||||||
|  |      *  | ||||||
|  |      * @type {JobCounts} | ||||||
|  |      * @memberof AllJobStatusResponseDto | ||||||
|  |      */ | ||||||
|  |     'thumbnailGenerationQueueCount': JobCounts; | ||||||
|  |     /** | ||||||
|  |      *  | ||||||
|  |      * @type {JobCounts} | ||||||
|  |      * @memberof AllJobStatusResponseDto | ||||||
|  |      */ | ||||||
|  |     'metadataExtractionQueueCount': JobCounts; | ||||||
|  |     /** | ||||||
|  |      *  | ||||||
|  |      * @type {JobCounts} | ||||||
|  |      * @memberof AllJobStatusResponseDto | ||||||
|  |      */ | ||||||
|  |     'videoConversionQueueCount': JobCounts; | ||||||
|  |     /** | ||||||
|  |      *  | ||||||
|  |      * @type {JobCounts} | ||||||
|  |      * @memberof AllJobStatusResponseDto | ||||||
|  |      */ | ||||||
|  |     'machineLearningQueueCount': JobCounts; | ||||||
|  |     /** | ||||||
|  |      *  | ||||||
|  |      * @type {boolean} | ||||||
|  |      * @memberof AllJobStatusResponseDto | ||||||
|  |      */ | ||||||
|  |     'isThumbnailGenerationActive': boolean; | ||||||
|  |     /** | ||||||
|  |      *  | ||||||
|  |      * @type {boolean} | ||||||
|  |      * @memberof AllJobStatusResponseDto | ||||||
|  |      */ | ||||||
|  |     'isMetadataExtractionActive': boolean; | ||||||
|  |     /** | ||||||
|  |      *  | ||||||
|  |      * @type {boolean} | ||||||
|  |      * @memberof AllJobStatusResponseDto | ||||||
|  |      */ | ||||||
|  |     'isVideoConversionActive': boolean; | ||||||
|  |     /** | ||||||
|  |      *  | ||||||
|  |      * @type {boolean} | ||||||
|  |      * @memberof AllJobStatusResponseDto | ||||||
|  |      */ | ||||||
|  |     'isMachineLearningActive': boolean; | ||||||
|  | } | ||||||
| /** | /** | ||||||
|  *  |  *  | ||||||
|  * @export |  * @export | ||||||
| @@ -683,10 +738,16 @@ export type DeviceTypeEnum = typeof DeviceTypeEnum[keyof typeof DeviceTypeEnum]; | |||||||
| export interface ExifResponseDto { | export interface ExifResponseDto { | ||||||
|     /** |     /** | ||||||
|      *  |      *  | ||||||
|      * @type {string} |      * @type {number} | ||||||
|      * @memberof ExifResponseDto |      * @memberof ExifResponseDto | ||||||
|      */ |      */ | ||||||
|     'id'?: string | null; |     'id'?: number | null; | ||||||
|  |     /** | ||||||
|  |      *  | ||||||
|  |      * @type {number} | ||||||
|  |      * @memberof ExifResponseDto | ||||||
|  |      */ | ||||||
|  |     'fileSizeInByte'?: number | null; | ||||||
|     /** |     /** | ||||||
|      *  |      *  | ||||||
|      * @type {string} |      * @type {string} | ||||||
| @@ -717,12 +778,6 @@ export interface ExifResponseDto { | |||||||
|      * @memberof ExifResponseDto |      * @memberof ExifResponseDto | ||||||
|      */ |      */ | ||||||
|     'exifImageHeight'?: number | null; |     'exifImageHeight'?: number | null; | ||||||
|     /** |  | ||||||
|      *  |  | ||||||
|      * @type {number} |  | ||||||
|      * @memberof ExifResponseDto |  | ||||||
|      */ |  | ||||||
|     'fileSizeInByte'?: number | null; |  | ||||||
|     /** |     /** | ||||||
|      *  |      *  | ||||||
|      * @type {string} |      * @type {string} | ||||||
| @@ -828,6 +883,105 @@ export interface GetAssetCountByTimeBucketDto { | |||||||
|      */ |      */ | ||||||
|     'timeGroup': TimeGroupEnum; |     'timeGroup': TimeGroupEnum; | ||||||
| } | } | ||||||
|  | /** | ||||||
|  |  *  | ||||||
|  |  * @export | ||||||
|  |  * @enum {string} | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | export const JobCommand = { | ||||||
|  |     Start: 'start', | ||||||
|  |     Stop: 'stop' | ||||||
|  | } as const; | ||||||
|  |  | ||||||
|  | export type JobCommand = typeof JobCommand[keyof typeof JobCommand]; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  *  | ||||||
|  |  * @export | ||||||
|  |  * @interface JobCommandDto | ||||||
|  |  */ | ||||||
|  | export interface JobCommandDto { | ||||||
|  |     /** | ||||||
|  |      *  | ||||||
|  |      * @type {JobCommand} | ||||||
|  |      * @memberof JobCommandDto | ||||||
|  |      */ | ||||||
|  |     'command': JobCommand; | ||||||
|  | } | ||||||
|  | /** | ||||||
|  |  *  | ||||||
|  |  * @export | ||||||
|  |  * @interface JobCounts | ||||||
|  |  */ | ||||||
|  | export interface JobCounts { | ||||||
|  |     /** | ||||||
|  |      *  | ||||||
|  |      * @type {number} | ||||||
|  |      * @memberof JobCounts | ||||||
|  |      */ | ||||||
|  |     'active': number; | ||||||
|  |     /** | ||||||
|  |      *  | ||||||
|  |      * @type {number} | ||||||
|  |      * @memberof JobCounts | ||||||
|  |      */ | ||||||
|  |     'completed': number; | ||||||
|  |     /** | ||||||
|  |      *  | ||||||
|  |      * @type {number} | ||||||
|  |      * @memberof JobCounts | ||||||
|  |      */ | ||||||
|  |     'failed': number; | ||||||
|  |     /** | ||||||
|  |      *  | ||||||
|  |      * @type {number} | ||||||
|  |      * @memberof JobCounts | ||||||
|  |      */ | ||||||
|  |     'delayed': number; | ||||||
|  |     /** | ||||||
|  |      *  | ||||||
|  |      * @type {number} | ||||||
|  |      * @memberof JobCounts | ||||||
|  |      */ | ||||||
|  |     'waiting': number; | ||||||
|  | } | ||||||
|  | /** | ||||||
|  |  *  | ||||||
|  |  * @export | ||||||
|  |  * @enum {string} | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | export const JobId = { | ||||||
|  |     ThumbnailGeneration: 'thumbnail-generation', | ||||||
|  |     MetadataExtraction: 'metadata-extraction', | ||||||
|  |     VideoConversion: 'video-conversion', | ||||||
|  |     MachineLearning: 'machine-learning' | ||||||
|  | } as const; | ||||||
|  |  | ||||||
|  | export type JobId = typeof JobId[keyof typeof JobId]; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  *  | ||||||
|  |  * @export | ||||||
|  |  * @interface JobStatusResponseDto | ||||||
|  |  */ | ||||||
|  | export interface JobStatusResponseDto { | ||||||
|  |     /** | ||||||
|  |      *  | ||||||
|  |      * @type {boolean} | ||||||
|  |      * @memberof JobStatusResponseDto | ||||||
|  |      */ | ||||||
|  |     'isActive': boolean; | ||||||
|  |     /** | ||||||
|  |      *  | ||||||
|  |      * @type {object} | ||||||
|  |      * @memberof JobStatusResponseDto | ||||||
|  |      */ | ||||||
|  |     'queueCount': object; | ||||||
|  | } | ||||||
| /** | /** | ||||||
|  *  |  *  | ||||||
|  * @export |  * @export | ||||||
| @@ -3682,6 +3836,247 @@ export class DeviceInfoApi extends BaseAPI { | |||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * JobApi - axios parameter creator | ||||||
|  |  * @export | ||||||
|  |  */ | ||||||
|  | export const JobApiAxiosParamCreator = function (configuration?: Configuration) { | ||||||
|  |     return { | ||||||
|  |         /** | ||||||
|  |          *  | ||||||
|  |          * @param {*} [options] Override http request option. | ||||||
|  |          * @throws {RequiredError} | ||||||
|  |          */ | ||||||
|  |         getAllJobsStatus: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => { | ||||||
|  |             const localVarPath = `/jobs`; | ||||||
|  |             // use dummy base URL string because the URL constructor only accepts absolute URLs. | ||||||
|  |             const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); | ||||||
|  |             let baseOptions; | ||||||
|  |             if (configuration) { | ||||||
|  |                 baseOptions = configuration.baseOptions; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; | ||||||
|  |             const localVarHeaderParameter = {} as any; | ||||||
|  |             const localVarQueryParameter = {} as any; | ||||||
|  |  | ||||||
|  |             // authentication bearer required | ||||||
|  |             // http bearer authentication required | ||||||
|  |             await setBearerAuthToObject(localVarHeaderParameter, configuration) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |      | ||||||
|  |             setSearchParams(localVarUrlObj, localVarQueryParameter); | ||||||
|  |             let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; | ||||||
|  |             localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; | ||||||
|  |  | ||||||
|  |             return { | ||||||
|  |                 url: toPathString(localVarUrlObj), | ||||||
|  |                 options: localVarRequestOptions, | ||||||
|  |             }; | ||||||
|  |         }, | ||||||
|  |         /** | ||||||
|  |          *  | ||||||
|  |          * @param {JobId} jobId  | ||||||
|  |          * @param {*} [options] Override http request option. | ||||||
|  |          * @throws {RequiredError} | ||||||
|  |          */ | ||||||
|  |         getJobStatus: async (jobId: JobId, options: AxiosRequestConfig = {}): Promise<RequestArgs> => { | ||||||
|  |             // verify required parameter 'jobId' is not null or undefined | ||||||
|  |             assertParamExists('getJobStatus', 'jobId', jobId) | ||||||
|  |             const localVarPath = `/jobs/{jobId}` | ||||||
|  |                 .replace(`{${"jobId"}}`, encodeURIComponent(String(jobId))); | ||||||
|  |             // use dummy base URL string because the URL constructor only accepts absolute URLs. | ||||||
|  |             const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); | ||||||
|  |             let baseOptions; | ||||||
|  |             if (configuration) { | ||||||
|  |                 baseOptions = configuration.baseOptions; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; | ||||||
|  |             const localVarHeaderParameter = {} as any; | ||||||
|  |             const localVarQueryParameter = {} as any; | ||||||
|  |  | ||||||
|  |             // authentication bearer required | ||||||
|  |             // http bearer authentication required | ||||||
|  |             await setBearerAuthToObject(localVarHeaderParameter, configuration) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |      | ||||||
|  |             setSearchParams(localVarUrlObj, localVarQueryParameter); | ||||||
|  |             let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; | ||||||
|  |             localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; | ||||||
|  |  | ||||||
|  |             return { | ||||||
|  |                 url: toPathString(localVarUrlObj), | ||||||
|  |                 options: localVarRequestOptions, | ||||||
|  |             }; | ||||||
|  |         }, | ||||||
|  |         /** | ||||||
|  |          *  | ||||||
|  |          * @param {JobId} jobId  | ||||||
|  |          * @param {JobCommandDto} jobCommandDto  | ||||||
|  |          * @param {*} [options] Override http request option. | ||||||
|  |          * @throws {RequiredError} | ||||||
|  |          */ | ||||||
|  |         sendJobCommand: async (jobId: JobId, jobCommandDto: JobCommandDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => { | ||||||
|  |             // verify required parameter 'jobId' is not null or undefined | ||||||
|  |             assertParamExists('sendJobCommand', 'jobId', jobId) | ||||||
|  |             // verify required parameter 'jobCommandDto' is not null or undefined | ||||||
|  |             assertParamExists('sendJobCommand', 'jobCommandDto', jobCommandDto) | ||||||
|  |             const localVarPath = `/jobs/{jobId}` | ||||||
|  |                 .replace(`{${"jobId"}}`, encodeURIComponent(String(jobId))); | ||||||
|  |             // use dummy base URL string because the URL constructor only accepts absolute URLs. | ||||||
|  |             const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); | ||||||
|  |             let baseOptions; | ||||||
|  |             if (configuration) { | ||||||
|  |                 baseOptions = configuration.baseOptions; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             const localVarRequestOptions = { method: 'PUT', ...baseOptions, ...options}; | ||||||
|  |             const localVarHeaderParameter = {} as any; | ||||||
|  |             const localVarQueryParameter = {} as any; | ||||||
|  |  | ||||||
|  |             // authentication bearer required | ||||||
|  |             // http bearer authentication required | ||||||
|  |             await setBearerAuthToObject(localVarHeaderParameter, configuration) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |      | ||||||
|  |             localVarHeaderParameter['Content-Type'] = 'application/json'; | ||||||
|  |  | ||||||
|  |             setSearchParams(localVarUrlObj, localVarQueryParameter); | ||||||
|  |             let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; | ||||||
|  |             localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; | ||||||
|  |             localVarRequestOptions.data = serializeDataIfNeeded(jobCommandDto, localVarRequestOptions, configuration) | ||||||
|  |  | ||||||
|  |             return { | ||||||
|  |                 url: toPathString(localVarUrlObj), | ||||||
|  |                 options: localVarRequestOptions, | ||||||
|  |             }; | ||||||
|  |         }, | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * JobApi - functional programming interface | ||||||
|  |  * @export | ||||||
|  |  */ | ||||||
|  | export const JobApiFp = function(configuration?: Configuration) { | ||||||
|  |     const localVarAxiosParamCreator = JobApiAxiosParamCreator(configuration) | ||||||
|  |     return { | ||||||
|  |         /** | ||||||
|  |          *  | ||||||
|  |          * @param {*} [options] Override http request option. | ||||||
|  |          * @throws {RequiredError} | ||||||
|  |          */ | ||||||
|  |         async getAllJobsStatus(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<AllJobStatusResponseDto>> { | ||||||
|  |             const localVarAxiosArgs = await localVarAxiosParamCreator.getAllJobsStatus(options); | ||||||
|  |             return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); | ||||||
|  |         }, | ||||||
|  |         /** | ||||||
|  |          *  | ||||||
|  |          * @param {JobId} jobId  | ||||||
|  |          * @param {*} [options] Override http request option. | ||||||
|  |          * @throws {RequiredError} | ||||||
|  |          */ | ||||||
|  |         async getJobStatus(jobId: JobId, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<JobStatusResponseDto>> { | ||||||
|  |             const localVarAxiosArgs = await localVarAxiosParamCreator.getJobStatus(jobId, options); | ||||||
|  |             return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); | ||||||
|  |         }, | ||||||
|  |         /** | ||||||
|  |          *  | ||||||
|  |          * @param {JobId} jobId  | ||||||
|  |          * @param {JobCommandDto} jobCommandDto  | ||||||
|  |          * @param {*} [options] Override http request option. | ||||||
|  |          * @throws {RequiredError} | ||||||
|  |          */ | ||||||
|  |         async sendJobCommand(jobId: JobId, jobCommandDto: JobCommandDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<number>> { | ||||||
|  |             const localVarAxiosArgs = await localVarAxiosParamCreator.sendJobCommand(jobId, jobCommandDto, options); | ||||||
|  |             return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); | ||||||
|  |         }, | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * JobApi - factory interface | ||||||
|  |  * @export | ||||||
|  |  */ | ||||||
|  | export const JobApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { | ||||||
|  |     const localVarFp = JobApiFp(configuration) | ||||||
|  |     return { | ||||||
|  |         /** | ||||||
|  |          *  | ||||||
|  |          * @param {*} [options] Override http request option. | ||||||
|  |          * @throws {RequiredError} | ||||||
|  |          */ | ||||||
|  |         getAllJobsStatus(options?: any): AxiosPromise<AllJobStatusResponseDto> { | ||||||
|  |             return localVarFp.getAllJobsStatus(options).then((request) => request(axios, basePath)); | ||||||
|  |         }, | ||||||
|  |         /** | ||||||
|  |          *  | ||||||
|  |          * @param {JobId} jobId  | ||||||
|  |          * @param {*} [options] Override http request option. | ||||||
|  |          * @throws {RequiredError} | ||||||
|  |          */ | ||||||
|  |         getJobStatus(jobId: JobId, options?: any): AxiosPromise<JobStatusResponseDto> { | ||||||
|  |             return localVarFp.getJobStatus(jobId, options).then((request) => request(axios, basePath)); | ||||||
|  |         }, | ||||||
|  |         /** | ||||||
|  |          *  | ||||||
|  |          * @param {JobId} jobId  | ||||||
|  |          * @param {JobCommandDto} jobCommandDto  | ||||||
|  |          * @param {*} [options] Override http request option. | ||||||
|  |          * @throws {RequiredError} | ||||||
|  |          */ | ||||||
|  |         sendJobCommand(jobId: JobId, jobCommandDto: JobCommandDto, options?: any): AxiosPromise<number> { | ||||||
|  |             return localVarFp.sendJobCommand(jobId, jobCommandDto, options).then((request) => request(axios, basePath)); | ||||||
|  |         }, | ||||||
|  |     }; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * JobApi - object-oriented interface | ||||||
|  |  * @export | ||||||
|  |  * @class JobApi | ||||||
|  |  * @extends {BaseAPI} | ||||||
|  |  */ | ||||||
|  | export class JobApi extends BaseAPI { | ||||||
|  |     /** | ||||||
|  |      *  | ||||||
|  |      * @param {*} [options] Override http request option. | ||||||
|  |      * @throws {RequiredError} | ||||||
|  |      * @memberof JobApi | ||||||
|  |      */ | ||||||
|  |     public getAllJobsStatus(options?: AxiosRequestConfig) { | ||||||
|  |         return JobApiFp(this.configuration).getAllJobsStatus(options).then((request) => request(this.axios, this.basePath)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      *  | ||||||
|  |      * @param {JobId} jobId  | ||||||
|  |      * @param {*} [options] Override http request option. | ||||||
|  |      * @throws {RequiredError} | ||||||
|  |      * @memberof JobApi | ||||||
|  |      */ | ||||||
|  |     public getJobStatus(jobId: JobId, options?: AxiosRequestConfig) { | ||||||
|  |         return JobApiFp(this.configuration).getJobStatus(jobId, options).then((request) => request(this.axios, this.basePath)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      *  | ||||||
|  |      * @param {JobId} jobId  | ||||||
|  |      * @param {JobCommandDto} jobCommandDto  | ||||||
|  |      * @param {*} [options] Override http request option. | ||||||
|  |      * @throws {RequiredError} | ||||||
|  |      * @memberof JobApi | ||||||
|  |      */ | ||||||
|  |     public sendJobCommand(jobId: JobId, jobCommandDto: JobCommandDto, options?: AxiosRequestConfig) { | ||||||
|  |         return JobApiFp(this.configuration).sendJobCommand(jobId, jobCommandDto, options).then((request) => request(this.axios, this.basePath)); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * ServerInfoApi - axios parameter creator |  * ServerInfoApi - axios parameter creator | ||||||
|  * @export |  * @export | ||||||
|   | |||||||
							
								
								
									
										52
									
								
								web/src/lib/components/admin-page/jobs/job-tile.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								web/src/lib/components/admin-page/jobs/job-tile.svelte
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | |||||||
|  | <script lang="ts"> | ||||||
|  | 	import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte'; | ||||||
|  | 	import { createEventDispatcher } from 'svelte'; | ||||||
|  |  | ||||||
|  | 	export let title: string; | ||||||
|  | 	export let subtitle: string; | ||||||
|  | 	export let buttonTitle = 'Run'; | ||||||
|  | 	export let jobStatus: boolean; | ||||||
|  | 	export let waitingJobCount: number; | ||||||
|  | 	export let activeJobCount: number; | ||||||
|  | 	const dispatch = createEventDispatcher(); | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <div class="flex border p-6 rounded-2xl bg-white"> | ||||||
|  | 	<div class="w-[70%]"> | ||||||
|  | 		<h1 class="font-medium text-immich-primary">{title}</h1> | ||||||
|  | 		<p class="text-sm mt-1 font-medium">{subtitle}</p> | ||||||
|  | 		<p class="text-sm"> | ||||||
|  | 			<slot /> | ||||||
|  | 		</p> | ||||||
|  | 		<table class="text-left w-full mt-4"> | ||||||
|  | 			<!-- table header --> | ||||||
|  | 			<thead class="border rounded-md mb-2 bg-gray-50 flex text-immich-primary w-full h-12"> | ||||||
|  | 				<tr class="flex w-full place-items-center"> | ||||||
|  | 					<th class="text-center w-1/3 font-medium text-sm">Status</th> | ||||||
|  | 					<th class="text-center w-1/3 font-medium text-sm">Active</th> | ||||||
|  | 					<th class="text-center w-1/3 font-medium text-sm">Waiting</th> | ||||||
|  | 				</tr> | ||||||
|  | 			</thead> | ||||||
|  | 			<tbody class="overflow-y-auto rounded-md w-full max-h-[320px] block border"> | ||||||
|  | 				<tr class="text-center flex place-items-center w-full h-[40px]"> | ||||||
|  | 					<td class="text-sm px-2 w-1/3 text-ellipsis">{jobStatus ? 'Active' : 'Idle'}</td> | ||||||
|  | 					<td class="text-sm px-2 w-1/3 text-ellipsis">{activeJobCount}</td> | ||||||
|  | 					<td class="text-sm px-2 w-1/3 text-ellipsis">{waitingJobCount}</td> | ||||||
|  | 				</tr> | ||||||
|  | 			</tbody> | ||||||
|  | 		</table> | ||||||
|  | 	</div> | ||||||
|  | 	<div class="w-[30%] flex place-items-center place-content-end"> | ||||||
|  | 		<button | ||||||
|  | 			on:click={() => dispatch('click')} | ||||||
|  | 			class="border px-6 py-3 text-sm bg-gray-50 font-medium rounded-2xl hover:bg-immich-primary/10 transition-all hover:cursor-pointer disabled:cursor-not-allowed" | ||||||
|  | 			disabled={jobStatus} | ||||||
|  | 		> | ||||||
|  | 			{#if jobStatus} | ||||||
|  | 				<LoadingSpinner /> | ||||||
|  | 			{:else} | ||||||
|  | 				{buttonTitle} | ||||||
|  | 			{/if} | ||||||
|  | 		</button> | ||||||
|  | 	</div> | ||||||
|  | </div> | ||||||
							
								
								
									
										138
									
								
								web/src/lib/components/admin-page/jobs/jobs-panel.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								web/src/lib/components/admin-page/jobs/jobs-panel.svelte
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,138 @@ | |||||||
|  | <script lang="ts"> | ||||||
|  | 	import { | ||||||
|  | 		notificationController, | ||||||
|  | 		NotificationType | ||||||
|  | 	} from '$lib/components/shared-components/notification/notification'; | ||||||
|  | 	import { AllJobStatusResponseDto, api, JobCommand, JobId } from '@api'; | ||||||
|  | 	import { onDestroy, onMount } from 'svelte'; | ||||||
|  | 	import JobTile from './job-tile.svelte'; | ||||||
|  |  | ||||||
|  | 	let allJobsStatus: AllJobStatusResponseDto; | ||||||
|  | 	let setIntervalHandler: NodeJS.Timer; | ||||||
|  | 	onMount(async () => { | ||||||
|  | 		const { data } = await api.jobApi.getAllJobsStatus(); | ||||||
|  | 		allJobsStatus = data; | ||||||
|  |  | ||||||
|  | 		setIntervalHandler = setInterval(async () => { | ||||||
|  | 			const { data } = await api.jobApi.getAllJobsStatus(); | ||||||
|  | 			allJobsStatus = data; | ||||||
|  | 		}, 1000); | ||||||
|  | 	}); | ||||||
|  | 	1; | ||||||
|  |  | ||||||
|  | 	onDestroy(() => { | ||||||
|  | 		clearInterval(setIntervalHandler); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	const runThumbnailGeneration = async () => { | ||||||
|  | 		try { | ||||||
|  | 			const { data } = await api.jobApi.sendJobCommand(JobId.ThumbnailGeneration, { | ||||||
|  | 				command: JobCommand.Start | ||||||
|  | 			}); | ||||||
|  |  | ||||||
|  | 			if (data) { | ||||||
|  | 				notificationController.show({ | ||||||
|  | 					message: `Thumbnail generation job started for ${data} asset`, | ||||||
|  | 					type: NotificationType.Info | ||||||
|  | 				}); | ||||||
|  | 			} else { | ||||||
|  | 				notificationController.show({ | ||||||
|  | 					message: `No missing thumbnails found`, | ||||||
|  | 					type: NotificationType.Info | ||||||
|  | 				}); | ||||||
|  | 			} | ||||||
|  | 		} catch (e) { | ||||||
|  | 			console.log('[ERROR] runThumbnailGeneration', e); | ||||||
|  |  | ||||||
|  | 			notificationController.show({ | ||||||
|  | 				message: `Error running thumbnail generation job, check console for more detail`, | ||||||
|  | 				type: NotificationType.Error | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	const runExtractEXIF = async () => { | ||||||
|  | 		try { | ||||||
|  | 			const { data } = await api.jobApi.sendJobCommand(JobId.MetadataExtraction, { | ||||||
|  | 				command: JobCommand.Start | ||||||
|  | 			}); | ||||||
|  |  | ||||||
|  | 			if (data) { | ||||||
|  | 				notificationController.show({ | ||||||
|  | 					message: `Extract EXIF job started for ${data} asset`, | ||||||
|  | 					type: NotificationType.Info | ||||||
|  | 				}); | ||||||
|  | 			} else { | ||||||
|  | 				notificationController.show({ | ||||||
|  | 					message: `No missing EXIF found`, | ||||||
|  | 					type: NotificationType.Info | ||||||
|  | 				}); | ||||||
|  | 			} | ||||||
|  | 		} catch (e) { | ||||||
|  | 			console.log('[ERROR] runExtractEXIF', e); | ||||||
|  |  | ||||||
|  | 			notificationController.show({ | ||||||
|  | 				message: `Error running extract EXIF job, check console for more detail`, | ||||||
|  | 				type: NotificationType.Error | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	const runMachineLearning = async () => { | ||||||
|  | 		try { | ||||||
|  | 			const { data } = await api.jobApi.sendJobCommand(JobId.MachineLearning, { | ||||||
|  | 				command: JobCommand.Start | ||||||
|  | 			}); | ||||||
|  |  | ||||||
|  | 			if (data) { | ||||||
|  | 				notificationController.show({ | ||||||
|  | 					message: `Object detection job started for ${data} asset`, | ||||||
|  | 					type: NotificationType.Info | ||||||
|  | 				}); | ||||||
|  | 			} else { | ||||||
|  | 				notificationController.show({ | ||||||
|  | 					message: `No missing object detection found`, | ||||||
|  | 					type: NotificationType.Info | ||||||
|  | 				}); | ||||||
|  | 			} | ||||||
|  | 		} catch (e) { | ||||||
|  | 			console.log('[ERROR] runMachineLearning', e); | ||||||
|  |  | ||||||
|  | 			notificationController.show({ | ||||||
|  | 				message: `Error running machine learning job, check console for more detail`, | ||||||
|  | 				type: NotificationType.Error | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <div class="flex flex-col gap-6"> | ||||||
|  | 	<JobTile | ||||||
|  | 		title={'Generate thumbnails'} | ||||||
|  | 		subtitle={'Regenerate missing thumbnail (JPEG, WEBP)'} | ||||||
|  | 		on:click={runThumbnailGeneration} | ||||||
|  | 		jobStatus={allJobsStatus?.isThumbnailGenerationActive} | ||||||
|  | 		waitingJobCount={allJobsStatus?.thumbnailGenerationQueueCount.waiting} | ||||||
|  | 		activeJobCount={allJobsStatus?.thumbnailGenerationQueueCount.active} | ||||||
|  | 	/> | ||||||
|  |  | ||||||
|  | 	<JobTile | ||||||
|  | 		title={'Extract EXIF'} | ||||||
|  | 		subtitle={'Extract missing EXIF information'} | ||||||
|  | 		on:click={runExtractEXIF} | ||||||
|  | 		jobStatus={allJobsStatus?.isMetadataExtractionActive} | ||||||
|  | 		waitingJobCount={allJobsStatus?.metadataExtractionQueueCount.waiting} | ||||||
|  | 		activeJobCount={allJobsStatus?.metadataExtractionQueueCount.active} | ||||||
|  | 	/> | ||||||
|  |  | ||||||
|  | 	<JobTile | ||||||
|  | 		title={'Detect objects'} | ||||||
|  | 		subtitle={'Run machine learning process to detect and classify objects'} | ||||||
|  | 		on:click={runMachineLearning} | ||||||
|  | 		jobStatus={allJobsStatus?.isMachineLearningActive} | ||||||
|  | 		waitingJobCount={allJobsStatus?.machineLearningQueueCount.waiting} | ||||||
|  | 		activeJobCount={allJobsStatus?.machineLearningQueueCount.active} | ||||||
|  | 	> | ||||||
|  | 		Note that some asset does not have any object detected, this is normal. | ||||||
|  | 	</JobTile> | ||||||
|  | </div> | ||||||
| @@ -94,7 +94,7 @@ | |||||||
|  |  | ||||||
| <div | <div | ||||||
| 	id="immich-scrubbable-scrollbar" | 	id="immich-scrubbable-scrollbar" | ||||||
| 	class="fixed right-0 bg-immich-bg z-10 hover:cursor-row-resize select-none" | 	class="fixed right-0 bg-immich-bg z-[999] hover:cursor-row-resize select-none " | ||||||
| 	style:width={isDragging ? '100vw' : '60px'} | 	style:width={isDragging ? '100vw' : '60px'} | ||||||
| 	style:background-color={isDragging ? 'transparent' : 'transparent'} | 	style:background-color={isDragging ? 'transparent' : 'transparent'} | ||||||
| 	on:mouseenter={() => (isHover = true)} | 	on:mouseenter={() => (isHover = true)} | ||||||
|   | |||||||
| @@ -1,5 +1,7 @@ | |||||||
| export enum AdminSideBarSelection { | export enum AdminSideBarSelection { | ||||||
| 	USER_MANAGEMENT = 'User management' | 	USER_MANAGEMENT = 'User management', | ||||||
|  | 	JOBS = 'Jobs', | ||||||
|  | 	SETTINGS = 'Settings' | ||||||
| } | } | ||||||
|  |  | ||||||
| export enum AppSideBarSelection { | export enum AppSideBarSelection { | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								web/src/routes/admin/+layout.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								web/src/routes/admin/+layout.svelte
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | <main> | ||||||
|  | 	<slot /> | ||||||
|  | </main> | ||||||
| @@ -4,6 +4,7 @@ | |||||||
| 	import { AdminSideBarSelection } from '$lib/models/admin-sidebar-selection'; | 	import { AdminSideBarSelection } from '$lib/models/admin-sidebar-selection'; | ||||||
| 	import SideBarButton from '$lib/components/shared-components/side-bar/side-bar-button.svelte'; | 	import SideBarButton from '$lib/components/shared-components/side-bar/side-bar-button.svelte'; | ||||||
| 	import AccountMultipleOutline from 'svelte-material-icons/AccountMultipleOutline.svelte'; | 	import AccountMultipleOutline from 'svelte-material-icons/AccountMultipleOutline.svelte'; | ||||||
|  | 	import Cog from 'svelte-material-icons/Cog.svelte'; | ||||||
| 	import NavigationBar from '$lib/components/shared-components/navigation-bar.svelte'; | 	import NavigationBar from '$lib/components/shared-components/navigation-bar.svelte'; | ||||||
| 	import UserManagement from '$lib/components/admin-page/user-management.svelte'; | 	import UserManagement from '$lib/components/admin-page/user-management.svelte'; | ||||||
| 	import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte'; | 	import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte'; | ||||||
| @@ -12,6 +13,7 @@ | |||||||
| 	import StatusBox from '$lib/components/shared-components/status-box.svelte'; | 	import StatusBox from '$lib/components/shared-components/status-box.svelte'; | ||||||
| 	import type { PageData } from './$types'; | 	import type { PageData } from './$types'; | ||||||
| 	import { api, UserResponseDto } from '@api'; | 	import { api, UserResponseDto } from '@api'; | ||||||
|  | 	import JobsPanel from '$lib/components/admin-page/jobs/jobs-panel.svelte'; | ||||||
|  |  | ||||||
| 	let selectedAction: AdminSideBarSelection = AdminSideBarSelection.USER_MANAGEMENT; | 	let selectedAction: AdminSideBarSelection = AdminSideBarSelection.USER_MANAGEMENT; | ||||||
|  |  | ||||||
| @@ -104,14 +106,21 @@ | |||||||
| {/if} | {/if} | ||||||
|  |  | ||||||
| <section class="grid grid-cols-[250px_auto] relative pt-[72px] h-screen"> | <section class="grid grid-cols-[250px_auto] relative pt-[72px] h-screen"> | ||||||
| 	<section id="admin-sidebar" class="pt-8 pr-6 flex flex-col"> | 	<section id="admin-sidebar" class="pt-8 pr-6 flex flex-col gap-1"> | ||||||
| 		<SideBarButton | 		<SideBarButton | ||||||
| 			title="User" | 			title="Users" | ||||||
| 			logo={AccountMultipleOutline} | 			logo={AccountMultipleOutline} | ||||||
| 			actionType={AdminSideBarSelection.USER_MANAGEMENT} | 			actionType={AdminSideBarSelection.USER_MANAGEMENT} | ||||||
| 			isSelected={selectedAction === AdminSideBarSelection.USER_MANAGEMENT} | 			isSelected={selectedAction === AdminSideBarSelection.USER_MANAGEMENT} | ||||||
| 			on:selected={onButtonClicked} | 			on:selected={onButtonClicked} | ||||||
| 		/> | 		/> | ||||||
|  | 		<SideBarButton | ||||||
|  | 			title="Jobs" | ||||||
|  | 			logo={Cog} | ||||||
|  | 			actionType={AdminSideBarSelection.JOBS} | ||||||
|  | 			isSelected={selectedAction === AdminSideBarSelection.JOBS} | ||||||
|  | 			on:selected={onButtonClicked} | ||||||
|  | 		/> | ||||||
|  |  | ||||||
| 		<div class="mb-6 mt-auto"> | 		<div class="mb-6 mt-auto"> | ||||||
| 			<StatusBox /> | 			<StatusBox /> | ||||||
| @@ -132,6 +141,9 @@ | |||||||
| 						on:edit-user={editUserHandler} | 						on:edit-user={editUserHandler} | ||||||
| 					/> | 					/> | ||||||
| 				{/if} | 				{/if} | ||||||
|  | 				{#if selectedAction === AdminSideBarSelection.JOBS} | ||||||
|  | 					<JobsPanel /> | ||||||
|  | 				{/if} | ||||||
| 			</section> | 			</section> | ||||||
| 		</section> | 		</section> | ||||||
| 	</section> | 	</section> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user