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/AlbumCountResponseDto.md | ||||
| doc/AlbumResponseDto.md | ||||
| doc/AllJobStatusResponseDto.md | ||||
| doc/AssetApi.md | ||||
| doc/AssetCountByTimeBucket.md | ||||
| doc/AssetCountByTimeBucketResponseDto.md | ||||
| @@ -33,6 +34,12 @@ doc/DeviceTypeEnum.md | ||||
| doc/ExifResponseDto.md | ||||
| doc/GetAssetByTimeBucketDto.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/LoginResponseDto.md | ||||
| doc/LogoutResponseDto.md | ||||
| @@ -59,6 +66,7 @@ lib/api/album_api.dart | ||||
| lib/api/asset_api.dart | ||||
| lib/api/authentication_api.dart | ||||
| lib/api/device_info_api.dart | ||||
| lib/api/job_api.dart | ||||
| lib/api/server_info_api.dart | ||||
| lib/api/user_api.dart | ||||
| lib/api_client.dart | ||||
| @@ -74,6 +82,7 @@ lib/model/add_users_dto.dart | ||||
| lib/model/admin_signup_response_dto.dart | ||||
| lib/model/album_count_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_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/get_asset_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_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 |  | ||||
| *DeviceInfoApi* | [**createDeviceInfo**](doc//DeviceInfoApi.md#createdeviceinfo) | **POST** /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* | [**getServerVersion**](doc//ServerInfoApi.md#getserverversion) | **GET** /server-info/version |  | ||||
| *ServerInfoApi* | [**pingServer**](doc//ServerInfoApi.md#pingserver) | **GET** /server-info/ping |  | ||||
| @@ -117,6 +120,7 @@ Class | Method | HTTP request | Description | ||||
|  - [AdminSignupResponseDto](doc//AdminSignupResponseDto.md) | ||||
|  - [AlbumCountResponseDto](doc//AlbumCountResponseDto.md) | ||||
|  - [AlbumResponseDto](doc//AlbumResponseDto.md) | ||||
|  - [AllJobStatusResponseDto](doc//AllJobStatusResponseDto.md) | ||||
|  - [AssetCountByTimeBucket](doc//AssetCountByTimeBucket.md) | ||||
|  - [AssetCountByTimeBucketResponseDto](doc//AssetCountByTimeBucketResponseDto.md) | ||||
|  - [AssetCountByUserIdResponseDto](doc//AssetCountByUserIdResponseDto.md) | ||||
| @@ -139,6 +143,11 @@ Class | Method | HTTP request | Description | ||||
|  - [ExifResponseDto](doc//ExifResponseDto.md) | ||||
|  - [GetAssetByTimeBucketDto](doc//GetAssetByTimeBucketDto.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) | ||||
|  - [LoginResponseDto](doc//LoginResponseDto.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 | ||||
| Name | Type | Description | Notes | ||||
| ------------ | ------------- | ------------- | ------------- | ||||
| **id** | **String** |  | [optional]  | ||||
| **id** | **int** |  | [optional]  | ||||
| **fileSizeInByte** | **int** |  | [optional]  | ||||
| **make** | **String** |  | [optional]  | ||||
| **model** | **String** |  | [optional]  | ||||
| **imageName** | **String** |  | [optional]  | ||||
| **exifImageWidth** | **num** |  | [optional]  | ||||
| **exifImageHeight** | **num** |  | [optional]  | ||||
| **fileSizeInByte** | **num** |  | [optional]  | ||||
| **orientation** | **String** |  | [optional]  | ||||
| **dateTimeOriginal** | [**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/authentication_api.dart'; | ||||
| part 'api/device_info_api.dart'; | ||||
| part 'api/job_api.dart'; | ||||
| part 'api/server_info_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/album_count_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_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/get_asset_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_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); | ||||
|         case 'AlbumResponseDto': | ||||
|           return AlbumResponseDto.fromJson(value); | ||||
|         case 'AllJobStatusResponseDto': | ||||
|           return AllJobStatusResponseDto.fromJson(value); | ||||
|         case 'AssetCountByTimeBucket': | ||||
|           return AssetCountByTimeBucket.fromJson(value); | ||||
|         case 'AssetCountByTimeBucketResponseDto': | ||||
| @@ -246,6 +248,16 @@ class ApiClient { | ||||
|           return GetAssetByTimeBucketDto.fromJson(value); | ||||
|         case 'GetAssetCountByTimeBucketDto': | ||||
|           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': | ||||
|           return LoginCredentialDto.fromJson(value); | ||||
|         case 'LoginResponseDto': | ||||
|   | ||||
| @@ -64,6 +64,12 @@ String parameterToString(dynamic value) { | ||||
|   if (value is DeviceTypeEnum) { | ||||
|     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) { | ||||
|     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,9 +76,7 @@ class AssetResponseDto { | ||||
|   SmartInfoResponseDto? smartInfo; | ||||
|  | ||||
|   @override | ||||
|   bool operator ==(Object other) => | ||||
|       identical(this, other) || | ||||
|       other is AssetResponseDto && | ||||
|   bool operator ==(Object other) => identical(this, other) || other is AssetResponseDto && | ||||
|      other.type == type && | ||||
|      other.id == id && | ||||
|      other.deviceAssetId == deviceAssetId && | ||||
| @@ -117,8 +115,7 @@ class AssetResponseDto { | ||||
|     (smartInfo == null ? 0 : smartInfo!.hashCode); | ||||
|  | ||||
|   @override | ||||
|   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]'; | ||||
|   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]'; | ||||
|  | ||||
|   Map<String, dynamic> toJson() { | ||||
|     final _json = <String, dynamic>{}; | ||||
| @@ -175,13 +172,13 @@ class AssetResponseDto { | ||||
|       // 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 "AssetResponseDto[$key]" is missing from JSON.'); | ||||
|       //     assert(json[key] != null, 'Required key "AssetResponseDto[$key]" has a null value in JSON.'); | ||||
|       //   }); | ||||
|       //   return true; | ||||
|       // }()); | ||||
|       assert(() { | ||||
|         requiredKeys.forEach((key) { | ||||
|           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.'); | ||||
|         }); | ||||
|         return true; | ||||
|       }()); | ||||
|  | ||||
|       return AssetResponseDto( | ||||
|         type: AssetTypeEnum.fromJson(json[r'type'])!, | ||||
| @@ -205,10 +202,7 @@ class AssetResponseDto { | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   static List<AssetResponseDto>? listFromJson( | ||||
|     dynamic json, { | ||||
|     bool growable = false, | ||||
|   }) { | ||||
|   static List<AssetResponseDto>? listFromJson(dynamic json, {bool growable = false,}) { | ||||
|     final result = <AssetResponseDto>[]; | ||||
|     if (json is List && json.isNotEmpty) { | ||||
|       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 | ||||
|   static Map<String, List<AssetResponseDto>> mapListFromJson( | ||||
|     dynamic json, { | ||||
|     bool growable = false, | ||||
|   }) { | ||||
|   static Map<String, List<AssetResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) { | ||||
|     final map = <String, List<AssetResponseDto>>{}; | ||||
|     if (json is Map && json.isNotEmpty) { | ||||
|       json = json.cast<String, dynamic>(); // ignore: parameter_assignments | ||||
|       for (final entry in json.entries) { | ||||
|         final value = AssetResponseDto.listFromJson( | ||||
|           entry.value, | ||||
|           growable: growable, | ||||
|         ); | ||||
|         final value = AssetResponseDto.listFromJson(entry.value, growable: growable,); | ||||
|         if (value != null) { | ||||
|           map[entry.key] = value; | ||||
|         } | ||||
| @@ -274,3 +262,4 @@ class AssetResponseDto { | ||||
|     '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. | ||||
|   ExifResponseDto({ | ||||
|     this.id, | ||||
|     this.fileSizeInByte, | ||||
|     this.make, | ||||
|     this.model, | ||||
|     this.imageName, | ||||
|     this.exifImageWidth, | ||||
|     this.exifImageHeight, | ||||
|     this.fileSizeInByte, | ||||
|     this.orientation, | ||||
|     this.dateTimeOriginal, | ||||
|     this.modifyDate, | ||||
| @@ -35,7 +35,9 @@ class ExifResponseDto { | ||||
|     this.country, | ||||
|   }); | ||||
|  | ||||
|   String? id; | ||||
|   int? id; | ||||
|  | ||||
|   int? fileSizeInByte; | ||||
|  | ||||
|   String? make; | ||||
|  | ||||
| @@ -47,8 +49,6 @@ class ExifResponseDto { | ||||
|  | ||||
|   num? exifImageHeight; | ||||
|  | ||||
|   num? fileSizeInByte; | ||||
|  | ||||
|   String? orientation; | ||||
|  | ||||
|   DateTime? dateTimeOriginal; | ||||
| @@ -78,12 +78,12 @@ class ExifResponseDto { | ||||
|   @override | ||||
|   bool operator ==(Object other) => identical(this, other) || other is ExifResponseDto && | ||||
|      other.id == id && | ||||
|      other.fileSizeInByte == fileSizeInByte && | ||||
|      other.make == make && | ||||
|      other.model == model && | ||||
|      other.imageName == imageName && | ||||
|      other.exifImageWidth == exifImageWidth && | ||||
|      other.exifImageHeight == exifImageHeight && | ||||
|      other.fileSizeInByte == fileSizeInByte && | ||||
|      other.orientation == orientation && | ||||
|      other.dateTimeOriginal == dateTimeOriginal && | ||||
|      other.modifyDate == modifyDate && | ||||
| @@ -102,12 +102,12 @@ class ExifResponseDto { | ||||
|   int get hashCode => | ||||
|     // ignore: unnecessary_parenthesis | ||||
|     (id == null ? 0 : id!.hashCode) + | ||||
|     (fileSizeInByte == null ? 0 : fileSizeInByte!.hashCode) + | ||||
|     (make == null ? 0 : make!.hashCode) + | ||||
|     (model == null ? 0 : model!.hashCode) + | ||||
|     (imageName == null ? 0 : imageName!.hashCode) + | ||||
|     (exifImageWidth == null ? 0 : exifImageWidth!.hashCode) + | ||||
|     (exifImageHeight == null ? 0 : exifImageHeight!.hashCode) + | ||||
|     (fileSizeInByte == null ? 0 : fileSizeInByte!.hashCode) + | ||||
|     (orientation == null ? 0 : orientation!.hashCode) + | ||||
|     (dateTimeOriginal == null ? 0 : dateTimeOriginal!.hashCode) + | ||||
|     (modifyDate == null ? 0 : modifyDate!.hashCode) + | ||||
| @@ -123,7 +123,7 @@ class ExifResponseDto { | ||||
|     (country == null ? 0 : country!.hashCode); | ||||
|  | ||||
|   @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() { | ||||
|     final _json = <String, dynamic>{}; | ||||
| @@ -132,6 +132,11 @@ class ExifResponseDto { | ||||
|     } else { | ||||
|       _json[r'id'] = null; | ||||
|     } | ||||
|     if (fileSizeInByte != null) { | ||||
|       _json[r'fileSizeInByte'] = fileSizeInByte; | ||||
|     } else { | ||||
|       _json[r'fileSizeInByte'] = null; | ||||
|     } | ||||
|     if (make != null) { | ||||
|       _json[r'make'] = make; | ||||
|     } else { | ||||
| @@ -157,11 +162,6 @@ class ExifResponseDto { | ||||
|     } else { | ||||
|       _json[r'exifImageHeight'] = null; | ||||
|     } | ||||
|     if (fileSizeInByte != null) { | ||||
|       _json[r'fileSizeInByte'] = fileSizeInByte; | ||||
|     } else { | ||||
|       _json[r'fileSizeInByte'] = null; | ||||
|     } | ||||
|     if (orientation != null) { | ||||
|       _json[r'orientation'] = orientation; | ||||
|     } else { | ||||
| @@ -249,7 +249,8 @@ class 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'), | ||||
|         model: mapValueOfType<String>(json, r'model'), | ||||
|         imageName: mapValueOfType<String>(json, r'imageName'), | ||||
| @@ -259,9 +260,6 @@ class ExifResponseDto { | ||||
|         exifImageHeight: json[r'exifImageHeight'] == null | ||||
|             ? null | ||||
|             : num.parse(json[r'exifImageHeight'].toString()), | ||||
|         fileSizeInByte: json[r'fileSizeInByte'] == null | ||||
|             ? null | ||||
|             : num.parse(json[r'fileSizeInByte'].toString()), | ||||
|         orientation: mapValueOfType<String>(json, r'orientation'), | ||||
|         dateTimeOriginal: mapDateTime(json, r'dateTimeOriginal', ''), | ||||
|         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/ | ||||
| upload/ | ||||
| dist/ | ||||
|  | ||||
| .reverse-geocoding-dump | ||||
|   | ||||
| @@ -134,6 +134,9 @@ describe('Album service', () => { | ||||
|       getAssetByTimeBucket: jest.fn(), | ||||
|       getAssetByChecksum: jest.fn(), | ||||
|       getAssetCountByUserId: jest.fn(), | ||||
|       getAssetWithNoEXIF: jest.fn(), | ||||
|       getAssetWithNoThumbnail: jest.fn(), | ||||
|       getAssetWithNoSmartInfo: jest.fn(), | ||||
|     }; | ||||
|  | ||||
|     sut = new AlbumService(albumRepositoryMock, assetRepositoryMock); | ||||
|   | ||||
| @@ -29,6 +29,9 @@ export interface IAssetRepository { | ||||
|   getAssetCountByUserId(userId: string): Promise<AssetCountByUserIdResponseDto>; | ||||
|   getAssetByTimeBucket(userId: string, getAssetByTimeBucketDto: GetAssetByTimeBucketDto): Promise<AssetEntity[]>; | ||||
|   getAssetByChecksum(userId: string, checksum: Buffer): Promise<AssetEntity>; | ||||
|   getAssetWithNoThumbnail(): Promise<AssetEntity[]>; | ||||
|   getAssetWithNoEXIF(): Promise<AssetEntity[]>; | ||||
|   getAssetWithNoSmartInfo(): Promise<AssetEntity[]>; | ||||
| } | ||||
|  | ||||
| export const ASSET_REPOSITORY = 'ASSET_REPOSITORY'; | ||||
| @@ -40,6 +43,33 @@ export class AssetRepository implements IAssetRepository { | ||||
|     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> { | ||||
|     // Get asset count by AssetType | ||||
|     const res = await this.assetRepository | ||||
|   | ||||
| @@ -30,7 +30,7 @@ import { CommunicationGateway } from '../communication/communication.gateway'; | ||||
| import { InjectQueue } from '@nestjs/bull'; | ||||
| import { Queue } from 'bull'; | ||||
| 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 { CheckDuplicateAssetDto } from './dto/check-duplicate-asset.dto'; | ||||
| import { ApiBearerAuth, ApiBody, ApiConsumes, ApiTags } from '@nestjs/swagger'; | ||||
| @@ -59,7 +59,7 @@ export class AssetController { | ||||
|     private assetService: AssetService, | ||||
|     private backgroundTaskService: BackgroundTaskService, | ||||
|  | ||||
|     @InjectQueue(assetUploadedQueueName) | ||||
|     @InjectQueue(QueueNameEnum.ASSET_UPLOADED) | ||||
|     private assetUploadedQueue: Queue<IAssetUploadedJob>, | ||||
|   ) {} | ||||
|  | ||||
|   | ||||
| @@ -7,7 +7,7 @@ import { BullModule } from '@nestjs/bull'; | ||||
| import { BackgroundTaskModule } from '../../modules/background-task/background-task.module'; | ||||
| import { BackgroundTaskService } from '../../modules/background-task/background-task.service'; | ||||
| 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'; | ||||
|  | ||||
| @Module({ | ||||
| @@ -16,7 +16,7 @@ import { AssetRepository, ASSET_REPOSITORY } from './asset-repository'; | ||||
|     BackgroundTaskModule, | ||||
|     TypeOrmModule.forFeature([AssetEntity]), | ||||
|     BullModule.registerQueue({ | ||||
|       name: assetUploadedQueueName, | ||||
|       name: QueueNameEnum.ASSET_UPLOADED, | ||||
|       defaultJobOptions: { | ||||
|         attempts: 3, | ||||
|         removeOnComplete: true, | ||||
|   | ||||
| @@ -107,6 +107,9 @@ describe('AssetService', () => { | ||||
|       getAssetByTimeBucket: jest.fn(), | ||||
|       getAssetByChecksum: jest.fn(), | ||||
|       getAssetCountByUserId: jest.fn(), | ||||
|       getAssetWithNoEXIF: jest.fn(), | ||||
|       getAssetWithNoThumbnail: jest.fn(), | ||||
|       getAssetWithNoSmartInfo: jest.fn(), | ||||
|     }; | ||||
|  | ||||
|     sui = new AssetService(assetRepositoryMock, a); | ||||
|   | ||||
| @@ -1,12 +1,16 @@ | ||||
| import { ExifEntity } from '@app/database/entities/exif.entity'; | ||||
| import { ApiProperty } from '@nestjs/swagger'; | ||||
|  | ||||
| export class ExifResponseDto { | ||||
|   id?: string | null = null; | ||||
|   @ApiProperty({ type: 'integer', format: 'int64' }) | ||||
|   id?: number | null = null; | ||||
|   make?: string | null = null; | ||||
|   model?: string | null = null; | ||||
|   imageName?: string | null = null; | ||||
|   exifImageWidth?: number | null = null; | ||||
|   exifImageHeight?: number | null = null; | ||||
|  | ||||
|   @ApiProperty({ type: 'integer', format: 'int64' }) | ||||
|   fileSizeInByte?: number | null = null; | ||||
|   orientation?: string | null = null; | ||||
|   dateTimeOriginal?: Date | null = null; | ||||
| @@ -25,13 +29,13 @@ export class ExifResponseDto { | ||||
|  | ||||
| export function mapExif(entity: ExifEntity): ExifResponseDto { | ||||
|   return { | ||||
|     id: entity.id, | ||||
|     id: parseInt(entity.id), | ||||
|     make: entity.make, | ||||
|     model: entity.model, | ||||
|     imageName: entity.imageName, | ||||
|     exifImageWidth: entity.exifImageWidth, | ||||
|     exifImageHeight: entity.exifImageHeight, | ||||
|     fileSizeInByte: entity.fileSizeInByte, | ||||
|     fileSizeInByte: entity.fileSizeInByte ? parseInt(entity.fileSizeInByte.toString()) : null, | ||||
|     orientation: entity.orientation, | ||||
|     dateTimeOriginal: entity.dateTimeOriginal, | ||||
|     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; | ||||
|   diskAvailable!: string; | ||||
|  | ||||
|   @ApiProperty({ type: 'integer' }) | ||||
|   @ApiProperty({ type: 'integer', format: 'int64' }) | ||||
|   diskSizeRaw!: number; | ||||
|  | ||||
|   @ApiProperty({ type: 'integer' }) | ||||
|   @ApiProperty({ type: 'integer', format: 'int64' }) | ||||
|   diskUseRaw!: number; | ||||
|  | ||||
|   @ApiProperty({ type: 'integer' }) | ||||
|   @ApiProperty({ type: 'integer', format: 'int64' }) | ||||
|   diskAvailableRaw!: number; | ||||
|  | ||||
|   @ApiProperty({ type: 'number', format: 'float' }) | ||||
|   | ||||
| @@ -15,6 +15,7 @@ import { AppController } from './app.controller'; | ||||
| import { ScheduleModule } from '@nestjs/schedule'; | ||||
| import { ScheduleTasksModule } from './modules/schedule-tasks/schedule-tasks.module'; | ||||
| import { DatabaseModule } from '@app/database'; | ||||
| import { JobModule } from './api-v1/job/job.module'; | ||||
|  | ||||
| @Module({ | ||||
|   imports: [ | ||||
| @@ -55,6 +56,8 @@ import { DatabaseModule } from '@app/database'; | ||||
|     ScheduleModule.forRoot(), | ||||
|  | ||||
|     ScheduleTasksModule, | ||||
|  | ||||
|     JobModule, | ||||
|   ], | ||||
|   controllers: [AppController], | ||||
|   providers: [], | ||||
|   | ||||
| @@ -3,18 +3,14 @@ import { Module } from '@nestjs/common'; | ||||
| import { TypeOrmModule } from '@nestjs/typeorm'; | ||||
| import { AssetEntity } from '@app/database/entities/asset.entity'; | ||||
| import { ScheduleTasksService } from './schedule-tasks.service'; | ||||
| import { | ||||
|   metadataExtractionQueueName, | ||||
|   thumbnailGeneratorQueueName, | ||||
|   videoConversionQueueName, | ||||
| } from '@app/job/constants/queue-name.constant'; | ||||
| import { QueueNameEnum } from '@app/job/constants/queue-name.constant'; | ||||
| import { ExifEntity } from '@app/database/entities/exif.entity'; | ||||
|  | ||||
| @Module({ | ||||
|   imports: [ | ||||
|     TypeOrmModule.forFeature([AssetEntity, ExifEntity]), | ||||
|     BullModule.registerQueue({ | ||||
|       name: videoConversionQueueName, | ||||
|       name: QueueNameEnum.VIDEO_CONVERSION, | ||||
|       defaultJobOptions: { | ||||
|         attempts: 3, | ||||
|         removeOnComplete: true, | ||||
| @@ -22,7 +18,7 @@ import { ExifEntity } from '@app/database/entities/exif.entity'; | ||||
|       }, | ||||
|     }), | ||||
|     BullModule.registerQueue({ | ||||
|       name: thumbnailGeneratorQueueName, | ||||
|       name: QueueNameEnum.THUMBNAIL_GENERATION, | ||||
|       defaultJobOptions: { | ||||
|         attempts: 3, | ||||
|         removeOnComplete: true, | ||||
| @@ -31,7 +27,7 @@ import { ExifEntity } from '@app/database/entities/exif.entity'; | ||||
|     }), | ||||
|  | ||||
|     BullModule.registerQueue({ | ||||
|       name: metadataExtractionQueueName, | ||||
|       name: QueueNameEnum.METADATA_EXTRACTION, | ||||
|       defaultJobOptions: { | ||||
|         attempts: 3, | ||||
|         removeOnComplete: true, | ||||
|   | ||||
| @@ -12,11 +12,9 @@ import { | ||||
|   generateWEBPThumbnailProcessorName, | ||||
|   IMetadataExtractionJob, | ||||
|   IVideoTranscodeJob, | ||||
|   metadataExtractionQueueName, | ||||
|   mp4ConversionProcessorName, | ||||
|   QueueNameEnum, | ||||
|   reverseGeocodingProcessorName, | ||||
|   thumbnailGeneratorQueueName, | ||||
|   videoConversionQueueName, | ||||
|   videoMetadataExtractionProcessorName, | ||||
| } from '@app/job'; | ||||
| import { ConfigService } from '@nestjs/config'; | ||||
| @@ -30,13 +28,13 @@ export class ScheduleTasksService { | ||||
|     @InjectRepository(ExifEntity) | ||||
|     private exifRepository: Repository<ExifEntity>, | ||||
|  | ||||
|     @InjectQueue(thumbnailGeneratorQueueName) | ||||
|     @InjectQueue(QueueNameEnum.THUMBNAIL_GENERATION) | ||||
|     private thumbnailGeneratorQueue: Queue, | ||||
|  | ||||
|     @InjectQueue(videoConversionQueueName) | ||||
|     @InjectQueue(QueueNameEnum.VIDEO_CONVERSION) | ||||
|     private videoConversionQueue: Queue<IVideoTranscodeJob>, | ||||
|  | ||||
|     @InjectQueue(metadataExtractionQueueName) | ||||
|     @InjectQueue(QueueNameEnum.METADATA_EXTRACTION) | ||||
|     private metadataExtractionQueue: Queue<IMetadataExtractionJob>, | ||||
|  | ||||
|     private configService: ConfigService, | ||||
| @@ -108,11 +106,11 @@ export class ScheduleTasksService { | ||||
|  | ||||
|   @Cron(CronExpression.EVERY_DAY_AT_3AM) | ||||
|   async extractExif() { | ||||
|     const exifAssets = await this.assetRepository.find({ | ||||
|       where: { | ||||
|         exifInfo: IsNull(), | ||||
|       }, | ||||
|     }); | ||||
|     const exifAssets = await this.assetRepository | ||||
|       .createQueryBuilder('asset') | ||||
|       .leftJoinAndSelect('asset.exifInfo', 'ei') | ||||
|       .where('ei."assetId" IS NULL') | ||||
|       .getMany(); | ||||
|  | ||||
|     for (const asset of exifAssets) { | ||||
|       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 { SmartInfoEntity } from '@app/database/entities/smart-info.entity'; | ||||
| import { UserEntity } from '@app/database/entities/user.entity'; | ||||
| import { | ||||
|   assetUploadedQueueName, | ||||
|   generateChecksumQueueName, | ||||
|   metadataExtractionQueueName, | ||||
|   thumbnailGeneratorQueueName, | ||||
|   videoConversionQueueName, | ||||
| } from '@app/job/constants/queue-name.constant'; | ||||
| import { QueueNameEnum } from '@app/job/constants/queue-name.constant'; | ||||
| import { BullModule } from '@nestjs/bull'; | ||||
| import { Module } from '@nestjs/common'; | ||||
| 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 { AssetUploadedProcessor } from './processors/asset-uploaded.processor'; | ||||
| import { GenerateChecksumProcessor } from './processors/generate-checksum.processor'; | ||||
| import { MachineLearningProcessor } from './processors/machine-learning.processor'; | ||||
| import { MetadataExtractionProcessor } from './processors/metadata-extraction.processor'; | ||||
| import { ThumbnailGeneratorProcessor } from './processors/thumbnail.processor'; | ||||
| import { VideoTranscodeProcessor } from './processors/video-transcode.processor'; | ||||
| @@ -42,7 +37,7 @@ import { VideoTranscodeProcessor } from './processors/video-transcode.processor' | ||||
|     }), | ||||
|     BullModule.registerQueue( | ||||
|       { | ||||
|         name: thumbnailGeneratorQueueName, | ||||
|         name: QueueNameEnum.THUMBNAIL_GENERATION, | ||||
|         defaultJobOptions: { | ||||
|           attempts: 3, | ||||
|           removeOnComplete: true, | ||||
| @@ -50,7 +45,7 @@ import { VideoTranscodeProcessor } from './processors/video-transcode.processor' | ||||
|         }, | ||||
|       }, | ||||
|       { | ||||
|         name: assetUploadedQueueName, | ||||
|         name: QueueNameEnum.ASSET_UPLOADED, | ||||
|         defaultJobOptions: { | ||||
|           attempts: 3, | ||||
|           removeOnComplete: true, | ||||
| @@ -58,7 +53,7 @@ import { VideoTranscodeProcessor } from './processors/video-transcode.processor' | ||||
|         }, | ||||
|       }, | ||||
|       { | ||||
|         name: metadataExtractionQueueName, | ||||
|         name: QueueNameEnum.METADATA_EXTRACTION, | ||||
|         defaultJobOptions: { | ||||
|           attempts: 3, | ||||
|           removeOnComplete: true, | ||||
| @@ -66,7 +61,7 @@ import { VideoTranscodeProcessor } from './processors/video-transcode.processor' | ||||
|         }, | ||||
|       }, | ||||
|       { | ||||
|         name: videoConversionQueueName, | ||||
|         name: QueueNameEnum.VIDEO_CONVERSION, | ||||
|         defaultJobOptions: { | ||||
|           attempts: 3, | ||||
|           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: { | ||||
|           attempts: 3, | ||||
|           removeOnComplete: true, | ||||
| @@ -92,6 +95,7 @@ import { VideoTranscodeProcessor } from './processors/video-transcode.processor' | ||||
|     MetadataExtractionProcessor, | ||||
|     VideoTranscodeProcessor, | ||||
|     GenerateChecksumProcessor, | ||||
|     MachineLearningProcessor, | ||||
|     ConfigService, | ||||
|   ], | ||||
|   exports: [], | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import { generateChecksumQueueName } from '@app/job'; | ||||
| import { QueueNameEnum } from '@app/job'; | ||||
| import { InjectQueue } from '@nestjs/bull'; | ||||
| import { Injectable, OnModuleInit } from '@nestjs/common'; | ||||
| import { Queue } from 'bull'; | ||||
| @@ -7,13 +7,17 @@ import { randomUUID } from 'node:crypto'; | ||||
| @Injectable() | ||||
| export class MicroservicesService implements OnModuleInit { | ||||
|   constructor( | ||||
|     @InjectQueue(generateChecksumQueueName) | ||||
|     @InjectQueue(QueueNameEnum.CHECKSUM_GENERATION) | ||||
|     private generateChecksumQueue: Queue, | ||||
|   ) {} | ||||
|  | ||||
|   async onModuleInit() { | ||||
|     await this.generateChecksumQueue.add({}, { | ||||
|       jobId: randomUUID(), delay: 10000 // wait for migration | ||||
|     }); | ||||
|     await this.generateChecksumQueue.add( | ||||
|       {}, | ||||
|       { | ||||
|         jobId: randomUUID(), | ||||
|         delay: 10000, // wait for migration | ||||
|       }, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -4,30 +4,27 @@ import { | ||||
|   IMetadataExtractionJob, | ||||
|   IThumbnailGenerationJob, | ||||
|   IVideoTranscodeJob, | ||||
|   assetUploadedQueueName, | ||||
|   metadataExtractionQueueName, | ||||
|   thumbnailGeneratorQueueName, | ||||
|   videoConversionQueueName, | ||||
|   assetUploadedProcessorName, | ||||
|   exifExtractionProcessorName, | ||||
|   generateJPEGThumbnailProcessorName, | ||||
|   mp4ConversionProcessorName, | ||||
|   videoMetadataExtractionProcessorName, | ||||
|   QueueNameEnum, | ||||
| } from '@app/job'; | ||||
| import { InjectQueue, Process, Processor } from '@nestjs/bull'; | ||||
| import { Job, Queue } from 'bull'; | ||||
| import { randomUUID } from 'crypto'; | ||||
|  | ||||
| @Processor(assetUploadedQueueName) | ||||
| @Processor(QueueNameEnum.ASSET_UPLOADED) | ||||
| export class AssetUploadedProcessor { | ||||
|   constructor( | ||||
|     @InjectQueue(thumbnailGeneratorQueueName) | ||||
|     @InjectQueue(QueueNameEnum.THUMBNAIL_GENERATION) | ||||
|     private thumbnailGeneratorQueue: Queue<IThumbnailGenerationJob>, | ||||
|  | ||||
|     @InjectQueue(metadataExtractionQueueName) | ||||
|     @InjectQueue(QueueNameEnum.METADATA_EXTRACTION) | ||||
|     private metadataExtractionQueue: Queue<IMetadataExtractionJob>, | ||||
|  | ||||
|     @InjectQueue(videoConversionQueueName) | ||||
|     @InjectQueue(QueueNameEnum.VIDEO_CONVERSION) | ||||
|     private videoConversionQueue: Queue<IVideoTranscodeJob>, | ||||
|   ) {} | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| 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 { Logger } from '@nestjs/common'; | ||||
| import { InjectRepository } from '@nestjs/typeorm'; | ||||
| @@ -8,7 +8,7 @@ import fs from 'node:fs'; | ||||
| import { FindOptionsWhere, IsNull, MoreThan, QueryFailedError, Repository } from 'typeorm'; | ||||
|  | ||||
| // TODO: just temporary task to generate previous uploaded assets. | ||||
| @Processor(generateChecksumQueueName) | ||||
| @Processor(QueueNameEnum.CHECKSUM_GENERATION) | ||||
| export class GenerateChecksumProcessor { | ||||
|   constructor( | ||||
|     @InjectRepository(AssetEntity) | ||||
| @@ -33,7 +33,7 @@ export class GenerateChecksumProcessor { | ||||
|       const assets = await this.assetRepository.find({ | ||||
|         where: whereStat, | ||||
|         take: pageSize, | ||||
|         order: { id: 'ASC' } | ||||
|         order: { id: 'ASC' }, | ||||
|       }); | ||||
|  | ||||
|       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 { AssetEntity } from '@app/database/entities/asset.entity'; | ||||
| import { ExifEntity } from '@app/database/entities/exif.entity'; | ||||
| import { SmartInfoEntity } from '@app/database/entities/smart-info.entity'; | ||||
| import { | ||||
|   IExifExtractionProcessor, | ||||
|   IVideoLengthExtractionProcessor, | ||||
|   exifExtractionProcessorName, | ||||
|   imageTaggingProcessorName, | ||||
|   objectDetectionProcessorName, | ||||
|   videoMetadataExtractionProcessorName, | ||||
|   metadataExtractionQueueName, | ||||
|   reverseGeocodingProcessorName, | ||||
|   IReverseGeocodingProcessor, | ||||
|   QueueNameEnum, | ||||
| } from '@app/job'; | ||||
| import { Process, Processor } from '@nestjs/bull'; | ||||
| import { Logger } from '@nestjs/common'; | ||||
| import { ConfigService } from '@nestjs/config'; | ||||
| import { InjectRepository } from '@nestjs/typeorm'; | ||||
| import axios from 'axios'; | ||||
| import { Job } from 'bull'; | ||||
| import exifr from 'exifr'; | ||||
| import ffmpeg from 'fluent-ffmpeg'; | ||||
| @@ -79,7 +75,7 @@ export interface GeoData { | ||||
|   distance: number; | ||||
| } | ||||
|  | ||||
| @Processor(metadataExtractionQueueName) | ||||
| @Processor(QueueNameEnum.METADATA_EXTRACTION) | ||||
| export class MetadataExtractionProcessor { | ||||
|   private isGeocodeInitialized = false; | ||||
|   private logLevel: ImmichLogLevel; | ||||
| @@ -91,9 +87,6 @@ export class MetadataExtractionProcessor { | ||||
|     @InjectRepository(ExifEntity) | ||||
|     private exifRepository: Repository<ExifEntity>, | ||||
|  | ||||
|     @InjectRepository(SmartInfoEntity) | ||||
|     private smartInfoRepository: Repository<SmartInfoEntity>, | ||||
|  | ||||
|     private configService: ConfigService, | ||||
|   ) { | ||||
|     if (!configService.get('DISABLE_REVERSE_GEOCODING')) { | ||||
| @@ -109,7 +102,8 @@ export class MetadataExtractionProcessor { | ||||
|           alternateNames: false, | ||||
|         }, | ||||
|         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(() => { | ||||
|         this.isGeocodeInitialized = true; | ||||
|         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 }) | ||||
|   async extractVideoMetadata(job: Job<IVideoLengthExtractionProcessor>) { | ||||
|     const { asset, fileName } = job.data; | ||||
|   | ||||
| @@ -5,11 +5,9 @@ import { | ||||
|   WebpGeneratorProcessor, | ||||
|   generateJPEGThumbnailProcessorName, | ||||
|   generateWEBPThumbnailProcessorName, | ||||
|   imageTaggingProcessorName, | ||||
|   objectDetectionProcessorName, | ||||
|   metadataExtractionQueueName, | ||||
|   thumbnailGeneratorQueueName, | ||||
|   JpegGeneratorProcessor, | ||||
|   QueueNameEnum, | ||||
|   MachineLearningJobNameEnum, | ||||
| } from '@app/job'; | ||||
| import { InjectQueue, Process, Processor } from '@nestjs/bull'; | ||||
| import { Logger } from '@nestjs/common'; | ||||
| @@ -25,8 +23,9 @@ import sharp from 'sharp'; | ||||
| import { Repository } from 'typeorm/repository/Repository'; | ||||
| import { join } from 'path'; | ||||
| 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 { | ||||
|   private logLevel: ImmichLogLevel; | ||||
|  | ||||
| @@ -34,13 +33,13 @@ export class ThumbnailGeneratorProcessor { | ||||
|     @InjectRepository(AssetEntity) | ||||
|     private assetRepository: Repository<AssetEntity>, | ||||
|  | ||||
|     @InjectQueue(thumbnailGeneratorQueueName) | ||||
|     @InjectQueue(QueueNameEnum.THUMBNAIL_GENERATION) | ||||
|     private thumbnailGeneratorQueue: Queue, | ||||
|  | ||||
|     private wsCommunicationGateway: CommunicationGateway, | ||||
|  | ||||
|     @InjectQueue(metadataExtractionQueueName) | ||||
|     private metadataExtractionQueue: Queue, | ||||
|     @InjectQueue(QueueNameEnum.MACHINE_LEARNING) | ||||
|     private machineLearningQueue: Queue<IMachineLearningJob>, | ||||
|  | ||||
|     private configService: ConfigService, | ||||
|   ) { | ||||
| @@ -80,8 +79,12 @@ export class ThumbnailGeneratorProcessor { | ||||
|       asset.resizePath = jpegThumbnailPath; | ||||
|  | ||||
|       await this.thumbnailGeneratorQueue.add(generateWEBPThumbnailProcessorName, { asset }, { jobId: randomUUID() }); | ||||
|       await this.metadataExtractionQueue.add(imageTaggingProcessorName, { asset }, { jobId: randomUUID() }); | ||||
|       await this.metadataExtractionQueue.add(objectDetectionProcessorName, { asset }, { jobId: randomUUID() }); | ||||
|       await this.machineLearningQueue.add(MachineLearningJobNameEnum.IMAGE_TAGGING, { 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))); | ||||
|     } | ||||
|  | ||||
| @@ -110,8 +113,12 @@ export class ThumbnailGeneratorProcessor { | ||||
|       asset.resizePath = jpegThumbnailPath; | ||||
|  | ||||
|       await this.thumbnailGeneratorQueue.add(generateWEBPThumbnailProcessorName, { asset }, { jobId: randomUUID() }); | ||||
|       await this.metadataExtractionQueue.add(imageTaggingProcessorName, { asset }, { jobId: randomUUID() }); | ||||
|       await this.metadataExtractionQueue.add(objectDetectionProcessorName, { asset }, { jobId: randomUUID() }); | ||||
|       await this.machineLearningQueue.add(MachineLearningJobNameEnum.IMAGE_TAGGING, { 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))); | ||||
|     } | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import { APP_UPLOAD_LOCATION } from '@app/common/constants'; | ||||
| import { AssetEntity } from '@app/database/entities/asset.entity'; | ||||
| import { QueueNameEnum } from '@app/job'; | ||||
| 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 { Process, Processor } from '@nestjs/bull'; | ||||
| import { Logger } from '@nestjs/common'; | ||||
| @@ -11,7 +11,7 @@ import ffmpeg from 'fluent-ffmpeg'; | ||||
| import { existsSync, mkdirSync } from 'fs'; | ||||
| import { Repository } from 'typeorm'; | ||||
|  | ||||
| @Processor(videoConversionQueueName) | ||||
| @Processor(QueueNameEnum.VIDEO_CONVERSION) | ||||
| export class VideoTranscodeProcessor { | ||||
|   constructor( | ||||
|     @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 videoMetadataExtractionProcessorName = 'extract-video-metadata'; | ||||
| 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 const assetUploadedQueueName = 'asset-uploaded-queue'; | ||||
| export const metadataExtractionQueueName = 'metadata-extraction-queue'; | ||||
| export const videoConversionQueueName = 'video-conversion-queue'; | ||||
| export const generateChecksumQueueName = 'generate-checksum-queue'; | ||||
| export enum QueueNameEnum { | ||||
|   THUMBNAIL_GENERATION = 'thumbnail-generation-queue', | ||||
|   METADATA_EXTRACTION = 'metadata-extraction-queue', | ||||
|   VIDEO_CONVERSION = 'video-conversion-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", | ||||
|         "@openapitools/openapi-generator-cli": "2.5.1", | ||||
|         "@types/bcrypt": "^5.0.0", | ||||
|         "@types/bull": "^3.15.7", | ||||
|         "@types/bull": "^3.15.9", | ||||
|         "@types/cookie-parser": "^1.4.3", | ||||
|         "@types/cron": "^2.0.0", | ||||
|         "@types/express": "^4.17.13", | ||||
| @@ -2339,9 +2339,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@types/bull": { | ||||
|       "version": "3.15.7", | ||||
|       "resolved": "https://registry.npmjs.org/@types/bull/-/bull-3.15.7.tgz", | ||||
|       "integrity": "sha512-7NC7XN5NoS0A+leJ/dR69ZfKaegOlCZaii/xGgKnCyh1UYisRncibImb7VMwrc3OdJcbDJt6+4om70TeNl3J7g==", | ||||
|       "version": "3.15.9", | ||||
|       "resolved": "https://registry.npmjs.org/@types/bull/-/bull-3.15.9.tgz", | ||||
|       "integrity": "sha512-MPUcyPPQauAmynoO3ezHAmCOhbB0pWmYyijr/5ctaCqhbKWsjW0YCod38ZcLzUBprosfZ9dPqfYIcfdKjk7RNQ==", | ||||
|       "dev": true, | ||||
|       "dependencies": { | ||||
|         "@types/ioredis": "*", | ||||
| @@ -3764,6 +3764,27 @@ | ||||
|         "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": { | ||||
|       "version": "1.0.2", | ||||
|       "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", | ||||
|       "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": { | ||||
|       "version": "4.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", | ||||
| @@ -12900,9 +12928,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "@types/bull": { | ||||
|       "version": "3.15.7", | ||||
|       "resolved": "https://registry.npmjs.org/@types/bull/-/bull-3.15.7.tgz", | ||||
|       "integrity": "sha512-7NC7XN5NoS0A+leJ/dR69ZfKaegOlCZaii/xGgKnCyh1UYisRncibImb7VMwrc3OdJcbDJt6+4om70TeNl3J7g==", | ||||
|       "version": "3.15.9", | ||||
|       "resolved": "https://registry.npmjs.org/@types/bull/-/bull-3.15.9.tgz", | ||||
|       "integrity": "sha512-MPUcyPPQauAmynoO3ezHAmCOhbB0pWmYyijr/5ctaCqhbKWsjW0YCod38ZcLzUBprosfZ9dPqfYIcfdKjk7RNQ==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "@types/ioredis": "*", | ||||
| @@ -14073,6 +14101,26 @@ | ||||
|       "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", | ||||
|       "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": { | ||||
|       "version": "1.0.2", | ||||
|       "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", | ||||
|       "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": { | ||||
|       "version": "4.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", | ||||
|   | ||||
| @@ -78,7 +78,7 @@ | ||||
|     "@nestjs/testing": "^8.4.7", | ||||
|     "@openapitools/openapi-generator-cli": "2.5.1", | ||||
|     "@types/bcrypt": "^5.0.0", | ||||
|     "@types/bull": "^3.15.7", | ||||
|     "@types/bull": "^3.15.9", | ||||
|     "@types/cookie-parser": "^1.4.3", | ||||
|     "@types/cron": "^2.0.0", | ||||
|     "@types/express": "^4.17.13", | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import { | ||||
| 	AuthenticationApi, | ||||
| 	Configuration, | ||||
| 	DeviceInfoApi, | ||||
| 	JobApi, | ||||
| 	ServerInfoApi, | ||||
| 	UserApi | ||||
| } from './open-api'; | ||||
| @@ -15,6 +16,8 @@ class ImmichApi { | ||||
| 	public authenticationApi: AuthenticationApi; | ||||
| 	public deviceInfoApi: DeviceInfoApi; | ||||
| 	public serverInfoApi: ServerInfoApi; | ||||
| 	public jobApi: JobApi; | ||||
|  | ||||
| 	private config = new Configuration({ basePath: '/api' }); | ||||
|  | ||||
| 	constructor() { | ||||
| @@ -24,6 +27,7 @@ class ImmichApi { | ||||
| 		this.authenticationApi = new AuthenticationApi(this.config); | ||||
| 		this.deviceInfoApi = new DeviceInfoApi(this.config); | ||||
| 		this.serverInfoApi = new ServerInfoApi(this.config); | ||||
| 		this.jobApi = new JobApi(this.config); | ||||
| 	} | ||||
|  | ||||
| 	public setAccessToken(accessToken: string) { | ||||
|   | ||||
| @@ -170,6 +170,61 @@ export interface AlbumResponseDto { | ||||
|      */ | ||||
|     '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 | ||||
| @@ -683,10 +738,16 @@ export type DeviceTypeEnum = typeof DeviceTypeEnum[keyof typeof DeviceTypeEnum]; | ||||
| export interface ExifResponseDto { | ||||
|     /** | ||||
|      *  | ||||
|      * @type {string} | ||||
|      * @type {number} | ||||
|      * @memberof ExifResponseDto | ||||
|      */ | ||||
|     'id'?: string | null; | ||||
|     'id'?: number | null; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {number} | ||||
|      * @memberof ExifResponseDto | ||||
|      */ | ||||
|     'fileSizeInByte'?: number | null; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {string} | ||||
| @@ -717,12 +778,6 @@ export interface ExifResponseDto { | ||||
|      * @memberof ExifResponseDto | ||||
|      */ | ||||
|     'exifImageHeight'?: number | null; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {number} | ||||
|      * @memberof ExifResponseDto | ||||
|      */ | ||||
|     'fileSizeInByte'?: number | null; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {string} | ||||
| @@ -828,6 +883,105 @@ export interface GetAssetCountByTimeBucketDto { | ||||
|      */ | ||||
|     '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 | ||||
| @@ -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 | ||||
|  * @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 | ||||
| 	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:background-color={isDragging ? 'transparent' : 'transparent'} | ||||
| 	on:mouseenter={() => (isHover = true)} | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| export enum AdminSideBarSelection { | ||||
| 	USER_MANAGEMENT = 'User management' | ||||
| 	USER_MANAGEMENT = 'User management', | ||||
| 	JOBS = 'Jobs', | ||||
| 	SETTINGS = 'Settings' | ||||
| } | ||||
|  | ||||
| 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 SideBarButton from '$lib/components/shared-components/side-bar/side-bar-button.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 UserManagement from '$lib/components/admin-page/user-management.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 type { PageData } from './$types'; | ||||
| 	import { api, UserResponseDto } from '@api'; | ||||
| 	import JobsPanel from '$lib/components/admin-page/jobs/jobs-panel.svelte'; | ||||
|  | ||||
| 	let selectedAction: AdminSideBarSelection = AdminSideBarSelection.USER_MANAGEMENT; | ||||
|  | ||||
| @@ -104,14 +106,21 @@ | ||||
| {/if} | ||||
|  | ||||
| <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 | ||||
| 			title="User" | ||||
| 			title="Users" | ||||
| 			logo={AccountMultipleOutline} | ||||
| 			actionType={AdminSideBarSelection.USER_MANAGEMENT} | ||||
| 			isSelected={selectedAction === AdminSideBarSelection.USER_MANAGEMENT} | ||||
| 			on:selected={onButtonClicked} | ||||
| 		/> | ||||
| 		<SideBarButton | ||||
| 			title="Jobs" | ||||
| 			logo={Cog} | ||||
| 			actionType={AdminSideBarSelection.JOBS} | ||||
| 			isSelected={selectedAction === AdminSideBarSelection.JOBS} | ||||
| 			on:selected={onButtonClicked} | ||||
| 		/> | ||||
|  | ||||
| 		<div class="mb-6 mt-auto"> | ||||
| 			<StatusBox /> | ||||
| @@ -132,6 +141,9 @@ | ||||
| 						on:edit-user={editUserHandler} | ||||
| 					/> | ||||
| 				{/if} | ||||
| 				{#if selectedAction === AdminSideBarSelection.JOBS} | ||||
| 					<JobsPanel /> | ||||
| 				{/if} | ||||
| 			</section> | ||||
| 		</section> | ||||
| 	</section> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user