diff --git a/.github/workflows/build-mobile.yml b/.github/workflows/build-mobile.yml index 31d87a285df0fce5bbb7021ea46273c3ec709e58..54da83c52aafa2822445a104c2680a61f04d0e04 100644 --- a/.github/workflows/build-mobile.yml +++ b/.github/workflows/build-mobile.yml @@ -45,7 +45,7 @@ jobs: uses: subosito/flutter-action@v2 with: channel: "stable" - flutter-version: "3.10.5" + flutter-version: "3.13.0" cache: true - name: Create the Keystore diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 826873993c05d731fd0be8a6c2575fe74b3843c6..e7cc9cfd2f81bc871022c390f6eb742c4eec6655 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -42,7 +42,7 @@ jobs: uses: docker/setup-qemu-action@v2.2.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2.9.1 + uses: docker/setup-buildx-action@v2.10.0 # Workaround to fix error: # failed to push: failed to copy: io: read/write on closed pipe # See https://github.com/docker/build-push-action/issues/761 @@ -126,7 +126,7 @@ jobs: uses: docker/setup-qemu-action@v2.2.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2.9.1 + uses: docker/setup-buildx-action@v2.10.0 # Workaround to fix error: # failed to push: failed to copy: io: read/write on closed pipe # See https://github.com/docker/build-push-action/issues/761 diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index 62a6069394d6e038a671ed7240f350bb6eef2a5f..85a538464f2b7591b2fa42ab0181297c2672229a 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -23,7 +23,7 @@ jobs: uses: subosito/flutter-action@v2 with: channel: "stable" - flutter-version: "3.10.5" + flutter-version: "3.13.0" - name: Install dependencies run: dart pub get diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8a3c116a3b222d4c52f81b7c79e3728305233fbd..df3e03d5a60d03f7ad1327e73916b0850adfd0d9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -149,7 +149,7 @@ jobs: uses: subosito/flutter-action@v2 with: channel: "stable" - flutter-version: "3.10.5" + flutter-version: "3.13.0" - name: Run tests working-directory: ./mobile run: flutter test -j 1 @@ -171,6 +171,7 @@ jobs: - name: Install dependencies run: | poetry install --with dev + poetry run pip install --no-deps -r requirements.txt - name: Lint with ruff run: | poetry run ruff check --format=github app diff --git a/cli/src/api/open-api/api.ts b/cli/src/api/open-api/api.ts index 8ade1d5fde43cf95617cb02eafed9d9218bdcdcf..36189d39f4840047673377a2c7c9991a6d44761d 100644 --- a/cli/src/api/open-api/api.ts +++ b/cli/src/api/open-api/api.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.73.0 + * The version of the OpenAPI document: 1.75.2 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). @@ -344,6 +344,31 @@ export interface AllJobStatusResponseDto { */ 'videoConversion': JobStatusDto; } +/** + * + * @export + * @interface AssetBulkUpdateDto + */ +export interface AssetBulkUpdateDto { + /** + * + * @type {Array} + * @memberof AssetBulkUpdateDto + */ + 'ids': Array; + /** + * + * @type {boolean} + * @memberof AssetBulkUpdateDto + */ + 'isArchived'?: boolean; + /** + * + * @type {boolean} + * @memberof AssetBulkUpdateDto + */ + 'isFavorite'?: boolean; +} /** * * @export @@ -500,6 +525,42 @@ export const AssetIdsResponseDtoErrorEnum = { export type AssetIdsResponseDtoErrorEnum = typeof AssetIdsResponseDtoErrorEnum[keyof typeof AssetIdsResponseDtoErrorEnum]; +/** + * + * @export + * @enum {string} + */ + +export const AssetJobName = { + RegenerateThumbnail: 'regenerate-thumbnail', + RefreshMetadata: 'refresh-metadata', + TranscodeVideo: 'transcode-video' +} as const; + +export type AssetJobName = typeof AssetJobName[keyof typeof AssetJobName]; + + +/** + * + * @export + * @interface AssetJobsDto + */ +export interface AssetJobsDto { + /** + * + * @type {Array} + * @memberof AssetJobsDto + */ + 'assetIds': Array; + /** + * + * @type {AssetJobName} + * @memberof AssetJobsDto + */ + 'name': AssetJobName; +} + + /** * * @export @@ -691,6 +752,25 @@ export const AudioCodec = { export type AudioCodec = typeof AudioCodec[keyof typeof AudioCodec]; +/** + * + * @export + * @interface AuditDeletesResponseDto + */ +export interface AuditDeletesResponseDto { + /** + * + * @type {Array} + * @memberof AuditDeletesResponseDto + */ + 'ids': Array; + /** + * + * @type {boolean} + * @memberof AuditDeletesResponseDto + */ + 'needsFullSync': boolean; +} /** * * @export @@ -1182,6 +1262,20 @@ export interface DownloadResponseDto { */ 'totalSize': number; } +/** + * + * @export + * @enum {string} + */ + +export const EntityType = { + Asset: 'ASSET', + Album: 'ALBUM' +} as const; + +export type EntityType = typeof EntityType[keyof typeof EntityType]; + + /** * * @export @@ -1779,6 +1873,12 @@ export interface PeopleUpdateDto { * @interface PeopleUpdateItem */ export interface PeopleUpdateItem { + /** + * Person date of birth. + * @type {string} + * @memberof PeopleUpdateItem + */ + 'birthDate'?: string | null; /** * Asset is used to get the feature face thumbnail. * @type {string} @@ -1810,6 +1910,12 @@ export interface PeopleUpdateItem { * @interface PersonResponseDto */ export interface PersonResponseDto { + /** + * + * @type {string} + * @memberof PersonResponseDto + */ + 'birthDate': string | null; /** * * @type {string} @@ -1841,6 +1947,12 @@ export interface PersonResponseDto { * @interface PersonUpdateDto */ export interface PersonUpdateDto { + /** + * Person date of birth. + * @type {string} + * @memberof PersonUpdateDto + */ + 'birthDate'?: string | null; /** * Asset is used to get the feature face thumbnail. * @type {string} @@ -1954,19 +2066,6 @@ export interface SearchAssetResponseDto { */ 'total': number; } -/** - * - * @export - * @interface SearchConfigResponseDto - */ -export interface SearchConfigResponseDto { - /** - * - * @type {boolean} - * @memberof SearchConfigResponseDto - */ - 'enabled': boolean; -} /** * * @export @@ -2062,6 +2161,67 @@ export interface SearchResponseDto { */ 'assets': SearchAssetResponseDto; } +/** + * + * @export + * @interface ServerFeaturesDto + */ +export interface ServerFeaturesDto { + /** + * + * @type {boolean} + * @memberof ServerFeaturesDto + */ + 'clipEncode': boolean; + /** + * + * @type {boolean} + * @memberof ServerFeaturesDto + */ + 'configFile': boolean; + /** + * + * @type {boolean} + * @memberof ServerFeaturesDto + */ + 'facialRecognition': boolean; + /** + * + * @type {boolean} + * @memberof ServerFeaturesDto + */ + 'oauth': boolean; + /** + * + * @type {boolean} + * @memberof ServerFeaturesDto + */ + 'oauthAutoLaunch': boolean; + /** + * + * @type {boolean} + * @memberof ServerFeaturesDto + */ + 'passwordLogin': boolean; + /** + * + * @type {boolean} + * @memberof ServerFeaturesDto + */ + 'search': boolean; + /** + * + * @type {boolean} + * @memberof ServerFeaturesDto + */ + 'sidecar': boolean; + /** + * + * @type {boolean} + * @memberof ServerFeaturesDto + */ + 'tagImage': boolean; +} /** * * @export @@ -2183,25 +2343,25 @@ export interface ServerStatsResponseDto { /** * * @export - * @interface ServerVersionReponseDto + * @interface ServerVersionResponseDto */ -export interface ServerVersionReponseDto { +export interface ServerVersionResponseDto { /** * * @type {number} - * @memberof ServerVersionReponseDto + * @memberof ServerVersionResponseDto */ 'major': number; /** * * @type {number} - * @memberof ServerVersionReponseDto + * @memberof ServerVersionResponseDto */ 'minor': number; /** * * @type {number} - * @memberof ServerVersionReponseDto + * @memberof ServerVersionResponseDto */ 'patch': number; } @@ -2462,6 +2622,12 @@ export interface SystemConfigDto { * @memberof SystemConfigDto */ 'job': SystemConfigJobDto; + /** + * + * @type {SystemConfigMachineLearningDto} + * @memberof SystemConfigDto + */ + 'machineLearning': SystemConfigMachineLearningDto; /** * * @type {SystemConfigOAuthDto} @@ -2629,6 +2795,43 @@ export interface SystemConfigJobDto { */ 'videoConversion': JobSettingsDto; } +/** + * + * @export + * @interface SystemConfigMachineLearningDto + */ +export interface SystemConfigMachineLearningDto { + /** + * + * @type {boolean} + * @memberof SystemConfigMachineLearningDto + */ + 'clipEncodeEnabled': boolean; + /** + * + * @type {boolean} + * @memberof SystemConfigMachineLearningDto + */ + 'enabled': boolean; + /** + * + * @type {boolean} + * @memberof SystemConfigMachineLearningDto + */ + 'facialRecognitionEnabled': boolean; + /** + * + * @type {boolean} + * @memberof SystemConfigMachineLearningDto + */ + 'tagImageEnabled': boolean; + /** + * + * @type {string} + * @memberof SystemConfigMachineLearningDto + */ + 'url': string; +} /** * * @export @@ -5002,13 +5205,13 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration * @param {string} [userId] * @param {boolean} [isFavorite] * @param {boolean} [isArchived] - * @param {boolean} [withoutThumbs] Include assets without thumbnails * @param {number} [skip] + * @param {string} [updatedAfter] * @param {string} [ifNoneMatch] ETag of data already cached on the client * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getAllAssets: async (userId?: string, isFavorite?: boolean, isArchived?: boolean, withoutThumbs?: boolean, skip?: number, ifNoneMatch?: string, options: AxiosRequestConfig = {}): Promise => { + getAllAssets: async (userId?: string, isFavorite?: boolean, isArchived?: boolean, skip?: number, updatedAfter?: string, ifNoneMatch?: string, options: AxiosRequestConfig = {}): Promise => { const localVarPath = `/asset`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -5042,14 +5245,16 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration localVarQueryParameter['isArchived'] = isArchived; } - if (withoutThumbs !== undefined) { - localVarQueryParameter['withoutThumbs'] = withoutThumbs; - } - if (skip !== undefined) { localVarQueryParameter['skip'] = skip; } + if (updatedAfter !== undefined) { + localVarQueryParameter['updatedAfter'] = (updatedAfter as any instanceof Date) ? + (updatedAfter as any).toISOString() : + updatedAfter; + } + if (ifNoneMatch != null) { localVarHeaderParameter['if-none-match'] = String(ifNoneMatch); } @@ -5722,6 +5927,50 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration options: localVarRequestOptions, }; }, + /** + * + * @param {AssetJobsDto} assetJobsDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + runAssetJobs: async (assetJobsDto: AssetJobsDto, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'assetJobsDto' is not null or undefined + assertParamExists('runAssetJobs', 'assetJobsDto', assetJobsDto) + const localVarPath = `/asset/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: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication cookie required + + // authentication api_key required + await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration) + + // 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(assetJobsDto, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * * @param {SearchAssetDto} searchAssetDto @@ -5871,6 +6120,50 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration options: localVarRequestOptions, }; }, + /** + * + * @param {AssetBulkUpdateDto} assetBulkUpdateDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + updateAssets: async (assetBulkUpdateDto: AssetBulkUpdateDto, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'assetBulkUpdateDto' is not null or undefined + assertParamExists('updateAssets', 'assetBulkUpdateDto', assetBulkUpdateDto) + const localVarPath = `/asset`; + // 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 cookie required + + // authentication api_key required + await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration) + + // 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(assetBulkUpdateDto, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * * @param {File} assetData @@ -6068,14 +6361,14 @@ export const AssetApiFp = function(configuration?: Configuration) { * @param {string} [userId] * @param {boolean} [isFavorite] * @param {boolean} [isArchived] - * @param {boolean} [withoutThumbs] Include assets without thumbnails * @param {number} [skip] + * @param {string} [updatedAfter] * @param {string} [ifNoneMatch] ETag of data already cached on the client * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getAllAssets(userId?: string, isFavorite?: boolean, isArchived?: boolean, withoutThumbs?: boolean, skip?: number, ifNoneMatch?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getAllAssets(userId, isFavorite, isArchived, withoutThumbs, skip, ifNoneMatch, options); + async getAllAssets(userId?: string, isFavorite?: boolean, isArchived?: boolean, skip?: number, updatedAfter?: string, ifNoneMatch?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getAllAssets(userId, isFavorite, isArchived, skip, updatedAfter, ifNoneMatch, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -6225,6 +6518,16 @@ export const AssetApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.importFile(importAssetDto, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @param {AssetJobsDto} assetJobsDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async runAssetJobs(assetJobsDto: AssetJobsDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.runAssetJobs(assetJobsDto, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @param {SearchAssetDto} searchAssetDto @@ -6261,10 +6564,20 @@ export const AssetApiFp = function(configuration?: Configuration) { }, /** * - * @param {File} assetData - * @param {string} deviceAssetId - * @param {string} deviceId - * @param {string} fileCreatedAt + * @param {AssetBulkUpdateDto} assetBulkUpdateDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async updateAssets(assetBulkUpdateDto: AssetBulkUpdateDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.updateAssets(assetBulkUpdateDto, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @param {File} assetData + * @param {string} deviceAssetId + * @param {string} deviceId + * @param {string} fileCreatedAt * @param {string} fileModifiedAt * @param {boolean} isFavorite * @param {string} [key] @@ -6352,7 +6665,7 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath * @throws {RequiredError} */ getAllAssets(requestParameters: AssetApiGetAllAssetsRequest = {}, options?: AxiosRequestConfig): AxiosPromise> { - return localVarFp.getAllAssets(requestParameters.userId, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.withoutThumbs, requestParameters.skip, requestParameters.ifNoneMatch, options).then((request) => request(axios, basePath)); + return localVarFp.getAllAssets(requestParameters.userId, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.skip, requestParameters.updatedAfter, requestParameters.ifNoneMatch, options).then((request) => request(axios, basePath)); }, /** * Get a single asset\'s information @@ -6468,6 +6781,15 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath importFile(requestParameters: AssetApiImportFileRequest, options?: AxiosRequestConfig): AxiosPromise { return localVarFp.importFile(requestParameters.importAssetDto, options).then((request) => request(axios, basePath)); }, + /** + * + * @param {AssetApiRunAssetJobsRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + runAssetJobs(requestParameters: AssetApiRunAssetJobsRequest, options?: AxiosRequestConfig): AxiosPromise { + return localVarFp.runAssetJobs(requestParameters.assetJobsDto, options).then((request) => request(axios, basePath)); + }, /** * * @param {AssetApiSearchAssetRequest} requestParameters Request parameters. @@ -6495,6 +6817,15 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath updateAsset(requestParameters: AssetApiUpdateAssetRequest, options?: AxiosRequestConfig): AxiosPromise { return localVarFp.updateAsset(requestParameters.id, requestParameters.updateAssetDto, options).then((request) => request(axios, basePath)); }, + /** + * + * @param {AssetApiUpdateAssetsRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + updateAssets(requestParameters: AssetApiUpdateAssetsRequest, options?: AxiosRequestConfig): AxiosPromise { + return localVarFp.updateAssets(requestParameters.assetBulkUpdateDto, options).then((request) => request(axios, basePath)); + }, /** * * @param {AssetApiUploadFileRequest} requestParameters Request parameters. @@ -6640,18 +6971,18 @@ export interface AssetApiGetAllAssetsRequest { readonly isArchived?: boolean /** - * Include assets without thumbnails - * @type {boolean} + * + * @type {number} * @memberof AssetApiGetAllAssets */ - readonly withoutThumbs?: boolean + readonly skip?: number /** * - * @type {number} + * @type {string} * @memberof AssetApiGetAllAssets */ - readonly skip?: number + readonly updatedAfter?: string /** * ETag of data already cached on the client @@ -6941,6 +7272,20 @@ export interface AssetApiImportFileRequest { readonly importAssetDto: ImportAssetDto } +/** + * Request parameters for runAssetJobs operation in AssetApi. + * @export + * @interface AssetApiRunAssetJobsRequest + */ +export interface AssetApiRunAssetJobsRequest { + /** + * + * @type {AssetJobsDto} + * @memberof AssetApiRunAssetJobs + */ + readonly assetJobsDto: AssetJobsDto +} + /** * Request parameters for searchAsset operation in AssetApi. * @export @@ -7011,6 +7356,20 @@ export interface AssetApiUpdateAssetRequest { readonly updateAssetDto: UpdateAssetDto } +/** + * Request parameters for updateAssets operation in AssetApi. + * @export + * @interface AssetApiUpdateAssetsRequest + */ +export interface AssetApiUpdateAssetsRequest { + /** + * + * @type {AssetBulkUpdateDto} + * @memberof AssetApiUpdateAssets + */ + readonly assetBulkUpdateDto: AssetBulkUpdateDto +} + /** * Request parameters for uploadFile operation in AssetApi. * @export @@ -7190,7 +7549,7 @@ export class AssetApi extends BaseAPI { * @memberof AssetApi */ public getAllAssets(requestParameters: AssetApiGetAllAssetsRequest = {}, options?: AxiosRequestConfig) { - return AssetApiFp(this.configuration).getAllAssets(requestParameters.userId, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.withoutThumbs, requestParameters.skip, requestParameters.ifNoneMatch, options).then((request) => request(this.axios, this.basePath)); + return AssetApiFp(this.configuration).getAllAssets(requestParameters.userId, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.skip, requestParameters.updatedAfter, requestParameters.ifNoneMatch, options).then((request) => request(this.axios, this.basePath)); } /** @@ -7333,6 +7692,17 @@ export class AssetApi extends BaseAPI { return AssetApiFp(this.configuration).importFile(requestParameters.importAssetDto, options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @param {AssetApiRunAssetJobsRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof AssetApi + */ + public runAssetJobs(requestParameters: AssetApiRunAssetJobsRequest, options?: AxiosRequestConfig) { + return AssetApiFp(this.configuration).runAssetJobs(requestParameters.assetJobsDto, options).then((request) => request(this.axios, this.basePath)); + } + /** * * @param {AssetApiSearchAssetRequest} requestParameters Request parameters. @@ -7366,6 +7736,17 @@ export class AssetApi extends BaseAPI { return AssetApiFp(this.configuration).updateAsset(requestParameters.id, requestParameters.updateAssetDto, options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @param {AssetApiUpdateAssetsRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof AssetApi + */ + public updateAssets(requestParameters: AssetApiUpdateAssetsRequest, options?: AxiosRequestConfig) { + return AssetApiFp(this.configuration).updateAssets(requestParameters.assetBulkUpdateDto, options).then((request) => request(this.axios, this.basePath)); + } + /** * * @param {AssetApiUploadFileRequest} requestParameters Request parameters. @@ -7379,6 +7760,163 @@ export class AssetApi extends BaseAPI { } +/** + * AuditApi - axios parameter creator + * @export + */ +export const AuditApiAxiosParamCreator = function (configuration?: Configuration) { + return { + /** + * + * @param {EntityType} entityType + * @param {string} after + * @param {string} [userId] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getAuditDeletes: async (entityType: EntityType, after: string, userId?: string, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'entityType' is not null or undefined + assertParamExists('getAuditDeletes', 'entityType', entityType) + // verify required parameter 'after' is not null or undefined + assertParamExists('getAuditDeletes', 'after', after) + const localVarPath = `/audit/deletes`; + // 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 cookie required + + // authentication api_key required + await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration) + + // authentication bearer required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + if (entityType !== undefined) { + localVarQueryParameter['entityType'] = entityType; + } + + if (userId !== undefined) { + localVarQueryParameter['userId'] = userId; + } + + if (after !== undefined) { + localVarQueryParameter['after'] = (after as any instanceof Date) ? + (after as any).toISOString() : + after; + } + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + } +}; + +/** + * AuditApi - functional programming interface + * @export + */ +export const AuditApiFp = function(configuration?: Configuration) { + const localVarAxiosParamCreator = AuditApiAxiosParamCreator(configuration) + return { + /** + * + * @param {EntityType} entityType + * @param {string} after + * @param {string} [userId] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getAuditDeletes(entityType: EntityType, after: string, userId?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getAuditDeletes(entityType, after, userId, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + } +}; + +/** + * AuditApi - factory interface + * @export + */ +export const AuditApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { + const localVarFp = AuditApiFp(configuration) + return { + /** + * + * @param {AuditApiGetAuditDeletesRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getAuditDeletes(requestParameters: AuditApiGetAuditDeletesRequest, options?: AxiosRequestConfig): AxiosPromise { + return localVarFp.getAuditDeletes(requestParameters.entityType, requestParameters.after, requestParameters.userId, options).then((request) => request(axios, basePath)); + }, + }; +}; + +/** + * Request parameters for getAuditDeletes operation in AuditApi. + * @export + * @interface AuditApiGetAuditDeletesRequest + */ +export interface AuditApiGetAuditDeletesRequest { + /** + * + * @type {EntityType} + * @memberof AuditApiGetAuditDeletes + */ + readonly entityType: EntityType + + /** + * + * @type {string} + * @memberof AuditApiGetAuditDeletes + */ + readonly after: string + + /** + * + * @type {string} + * @memberof AuditApiGetAuditDeletes + */ + readonly userId?: string +} + +/** + * AuditApi - object-oriented interface + * @export + * @class AuditApi + * @extends {BaseAPI} + */ +export class AuditApi extends BaseAPI { + /** + * + * @param {AuditApiGetAuditDeletesRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof AuditApi + */ + public getAuditDeletes(requestParameters: AuditApiGetAuditDeletesRequest, options?: AxiosRequestConfig) { + return AuditApiFp(this.configuration).getAuditDeletes(requestParameters.entityType, requestParameters.after, requestParameters.userId, options).then((request) => request(this.axios, this.basePath)); + } +} + + /** * AuthenticationApi - axios parameter creator * @export @@ -9622,44 +10160,6 @@ export const SearchApiAxiosParamCreator = function (configuration?: Configuratio - setSearchParams(localVarUrlObj, localVarQueryParameter); - let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; - localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; - - return { - url: toPathString(localVarUrlObj), - options: localVarRequestOptions, - }; - }, - /** - * - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - getSearchConfig: async (options: AxiosRequestConfig = {}): Promise => { - const localVarPath = `/search/config`; - // 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 cookie required - - // authentication api_key required - await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration) - - // 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}; @@ -9806,15 +10306,6 @@ export const SearchApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.getExploreData(options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, - /** - * - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - async getSearchConfig(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getSearchConfig(options); - return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); - }, /** * * @param {string} [q] @@ -9858,14 +10349,6 @@ export const SearchApiFactory = function (configuration?: Configuration, basePat getExploreData(options?: AxiosRequestConfig): AxiosPromise> { return localVarFp.getExploreData(options).then((request) => request(axios, basePath)); }, - /** - * - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - getSearchConfig(options?: AxiosRequestConfig): AxiosPromise { - return localVarFp.getSearchConfig(options).then((request) => request(axios, basePath)); - }, /** * * @param {SearchApiSearchRequest} requestParameters Request parameters. @@ -10014,16 +10497,6 @@ export class SearchApi extends BaseAPI { return SearchApiFp(this.configuration).getExploreData(options).then((request) => request(this.axios, this.basePath)); } - /** - * - * @param {*} [options] Override http request option. - * @throws {RequiredError} - * @memberof SearchApi - */ - public getSearchConfig(options?: AxiosRequestConfig) { - return SearchApiFp(this.configuration).getSearchConfig(options).then((request) => request(this.axios, this.basePath)); - } - /** * * @param {SearchApiSearchRequest} requestParameters Request parameters. @@ -10043,6 +10516,35 @@ export class SearchApi extends BaseAPI { */ export const ServerInfoApiAxiosParamCreator = function (configuration?: Configuration) { return { + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getServerFeatures: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/server-info/features`; + // 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; + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * * @param {*} [options] Override http request option. @@ -10216,6 +10718,15 @@ export const ServerInfoApiAxiosParamCreator = function (configuration?: Configur export const ServerInfoApiFp = function(configuration?: Configuration) { const localVarAxiosParamCreator = ServerInfoApiAxiosParamCreator(configuration) return { + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getServerFeatures(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getServerFeatures(options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @param {*} [options] Override http request option. @@ -10230,7 +10741,7 @@ export const ServerInfoApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getServerVersion(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + async getServerVersion(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { const localVarAxiosArgs = await localVarAxiosParamCreator.getServerVersion(options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, @@ -10271,6 +10782,14 @@ export const ServerInfoApiFp = function(configuration?: Configuration) { export const ServerInfoApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { const localVarFp = ServerInfoApiFp(configuration) return { + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getServerFeatures(options?: AxiosRequestConfig): AxiosPromise { + return localVarFp.getServerFeatures(options).then((request) => request(axios, basePath)); + }, /** * * @param {*} [options] Override http request option. @@ -10284,7 +10803,7 @@ export const ServerInfoApiFactory = function (configuration?: Configuration, bas * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getServerVersion(options?: AxiosRequestConfig): AxiosPromise { + getServerVersion(options?: AxiosRequestConfig): AxiosPromise { return localVarFp.getServerVersion(options).then((request) => request(axios, basePath)); }, /** @@ -10321,6 +10840,16 @@ export const ServerInfoApiFactory = function (configuration?: Configuration, bas * @extends {BaseAPI} */ export class ServerInfoApi extends BaseAPI { + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof ServerInfoApi + */ + public getServerFeatures(options?: AxiosRequestConfig) { + return ServerInfoApiFp(this.configuration).getServerFeatures(options).then((request) => request(this.axios, this.basePath)); + } + /** * * @param {*} [options] Override http request option. diff --git a/cli/src/api/open-api/base.ts b/cli/src/api/open-api/base.ts index 7109cba71e3ada8951c688443ec7b9e7ddcf0a65..d1eaca8a975828eb55ebcbeecccf74027d1dfd10 100644 --- a/cli/src/api/open-api/base.ts +++ b/cli/src/api/open-api/base.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.73.0 + * The version of the OpenAPI document: 1.75.2 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/cli/src/api/open-api/common.ts b/cli/src/api/open-api/common.ts index 66890914ad3d9123755c40e6fe0b0d89342b8548..91139577adc8e686591f367625b6e4314617e84f 100644 --- a/cli/src/api/open-api/common.ts +++ b/cli/src/api/open-api/common.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.73.0 + * The version of the OpenAPI document: 1.75.2 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/cli/src/api/open-api/configuration.ts b/cli/src/api/open-api/configuration.ts index 21fd71538b1036460f6114bbb8b219c6c272110e..da61d422161fc257078b3312792f3f32d24df05a 100644 --- a/cli/src/api/open-api/configuration.ts +++ b/cli/src/api/open-api/configuration.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.73.0 + * The version of the OpenAPI document: 1.75.2 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/cli/src/api/open-api/index.ts b/cli/src/api/open-api/index.ts index 937e9a24aded17608c54b3196e3443b2fb0712a9..75b153fbcf186dbcf4728581769bee372f9b261a 100644 --- a/cli/src/api/open-api/index.ts +++ b/cli/src/api/open-api/index.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.73.0 + * The version of the OpenAPI document: 1.75.2 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/cli/src/cli/base-command.ts b/cli/src/cli/base-command.ts index 06247e29c89356fc42d910eb2405e330b5a3ac52..c2fb8fee92c4f9cde7bdb216e09d9c83ac1d1bb7 100644 --- a/cli/src/cli/base-command.ts +++ b/cli/src/cli/base-command.ts @@ -4,14 +4,14 @@ import { SessionService } from '../services/session.service'; import { LoginError } from '../cores/errors/login-error'; import { exit } from 'node:process'; import os from 'os'; -import { ServerVersionReponseDto, UserResponseDto } from 'src/api/open-api'; +import { ServerVersionResponseDto, UserResponseDto } from 'src/api/open-api'; export abstract class BaseCommand { protected sessionService!: SessionService; protected immichApi!: ImmichApi; protected deviceId!: string; protected user!: UserResponseDto; - protected serverVersion!: ServerVersionReponseDto; + protected serverVersion!: ServerVersionResponseDto; protected configDir; protected authPath; diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index c8b69da0e4cdebba1872a13150b070aaa1520374..14c5238d0dc016f920c540f588ec2a556b7c8671 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -100,8 +100,8 @@ services: environment: - TYPESENSE_API_KEY=${TYPESENSE_API_KEY} - TYPESENSE_DATA_DIR=/data - logging: - driver: none + # remove this to get debug messages + - GLOG_minloglevel=1 volumes: - tsdata:/data diff --git a/docker/docker-compose.prod.yml b/docker/docker-compose.prod.yml index 8e4a6ca613268d2b8f6ee923ef5dd7e189c55acc..0374def08f8cdf7f26b1451e112466acad8acf06 100644 --- a/docker/docker-compose.prod.yml +++ b/docker/docker-compose.prod.yml @@ -68,8 +68,8 @@ services: environment: - TYPESENSE_API_KEY=${TYPESENSE_API_KEY} - TYPESENSE_DATA_DIR=/data - logging: - driver: none + # remove this to get debug messages + - GLOG_minloglevel=1 volumes: - tsdata:/data restart: always diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index c7e3be9934067c40a9c63fcabfb4fea6face2171..d96763972319c4fa7b6fdb6133b84e1e2796d742 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -54,6 +54,8 @@ services: environment: - TYPESENSE_API_KEY=${TYPESENSE_API_KEY} - TYPESENSE_DATA_DIR=/data + # remove this to get debug messages + - GLOG_minloglevel=1 volumes: - tsdata:/data restart: always diff --git a/docs/docs/FAQ.md b/docs/docs/FAQ.md index c0aff99ab6103be0bbeef35f7afaa7a0deef42ba..af43f919246eed576dbbcb59eabb4c69341dfdeb 100644 --- a/docs/docs/FAQ.md +++ b/docs/docs/FAQ.md @@ -39,7 +39,7 @@ This often happens when using a reverse proxy or cloudflare tunnel in front of I ### Why is Immich slow on low-memory systems like the Raspberry Pi? -Immich uses optional machine-learning features to enhance search results. This feature, however, can be too heavy to run on a Raspberry Pi. To disable machine learning, comment out the `immich-machine-learning` section of your docker-compose.yml and set `IMMICH_MACHINE_LEARNING_URL=false` in your .env file. +Immich uses optional machine-learning features to enhance search results. This feature, however, can be too heavy to run on a Raspberry Pi. To disable machine learning, comment out the `immich-machine-learning` section of your docker-compose.yml and set `IMMICH_MACHINE_LEARNING_ENABLED=false` in your .env file. ### How to disable machine-learning and TypeSense? @@ -47,7 +47,7 @@ Immich uses optional machine-learning features to enhance search results. This f Disabling both will result in poor search experience and typesense utilizes CLIP embeddings which are generated by machine-learning. ::: -These features can be disabled by commenting out `immich-typesense` and `immich-machine-learning` sections of the docker-compose.yml and setting `IMMICH_MACHINE_LEARNING_URL=false` & `TYPESENSE_ENABLED=false` in your .env file. +These features can be disabled by commenting out `immich-typesense` and `immich-machine-learning` sections of the docker-compose.yml and setting `IMMICH_MACHINE_LEARNING_ENABLED=false` & `TYPESENSE_ENABLED=false` in your .env file. ### What happens to existing files after I choose a new [Storage Template](/docs/administration/storage-template.mdx)? diff --git a/docs/docs/install/config-file.md b/docs/docs/install/config-file.md new file mode 100644 index 0000000000000000000000000000000000000000..0cf131a02fe3e751c40be199cc99051cdfda153f --- /dev/null +++ b/docs/docs/install/config-file.md @@ -0,0 +1,91 @@ +# Config File + +A config file can be provided as an alternative to the UI configuration. + +### Step 1 - Create a new config file + +In JSON format, create a new config file (e.g. `immich.config`) and put it in a location that can be accessed by Immich. +The default configuration looks like this: + +```json +{ + "ffmpeg": { + "crf": 23, + "threads": 0, + "preset": "ultrafast", + "targetVideoCodec": "h264", + "targetAudioCodec": "aac", + "targetResolution": "720", + "maxBitrate": "0", + "twoPass": false, + "transcode": "required", + "tonemap": "hable", + "accel": "disabled" + }, + "job": { + "backgroundTask": { + "concurrency": 5 + }, + "clipEncoding": { + "concurrency": 2 + }, + "metadataExtraction": { + "concurrency": 5 + }, + "objectTagging": { + "concurrency": 2 + }, + "recognizeFaces": { + "concurrency": 2 + }, + "search": { + "concurrency": 5 + }, + "sidecar": { + "concurrency": 5 + }, + "storageTemplateMigration": { + "concurrency": 5 + }, + "thumbnailGeneration": { + "concurrency": 5 + }, + "videoConversion": { + "concurrency": 1 + } + }, + "oauth": { + "enabled": false, + "issuerUrl": "", + "clientId": "", + "clientSecret": "", + "mobileOverrideEnabled": false, + "mobileRedirectUri": "", + "scope": "openid email profile", + "storageLabelClaim": "preferred_username", + "buttonText": "Login with OAuth", + "autoRegister": true, + "autoLaunch": false + }, + "passwordLogin": { + "enabled": true + }, + "storageTemplate": { + "template": "{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}" + }, + "thumbnail": { + "webpSize": 250, + "jpegSize": 1440 + } +} +``` + +:::tip +In Administration > Settings is a button to copy the current configuration to your clipboard. +So you can just grab it from there, paste it into a file and you're pretty much good to go. +::: + +### Step 2 - Specify the file location + +In your `.env` file, set the variable `IMMICH_CONFIG_FILE` to the path of your config. +For more information, refer to the [Environment Variables](https://docs.immich.app/docs/install/environment-variables) section. diff --git a/docs/docs/install/docker-compose.md b/docs/docs/install/docker-compose.md index dca8c0211c7c8a484d888b374fde6d42c7fa0252..d09ba531fc1f5cd5031204a9007111decd86c784 100644 --- a/docs/docs/install/docker-compose.md +++ b/docs/docs/install/docker-compose.md @@ -132,7 +132,6 @@ PUBLIC_LOGIN_PAGE_MESSAGE="My Family Photos and Videos Backup Server" IMMICH_WEB_URL=http://immich-web:3000 IMMICH_SERVER_URL=http://immich-server:3001 -IMMICH_MACHINE_LEARNING_URL=http://immich-machine-learning:3003 #################################################################################### # Alternative API's External Address - Optional diff --git a/docs/docs/install/environment-variables.md b/docs/docs/install/environment-variables.md index 09a4305167d87fa4cb0a94d267d29017ca75a0ba..a344b6fd935d6ce1ce66b9b6247a81d71bb22259 100644 --- a/docs/docs/install/environment-variables.md +++ b/docs/docs/install/environment-variables.md @@ -1,3 +1,7 @@ +--- +sidebar_position: 90 +--- + # Environment Variables ## Docker Compose @@ -22,6 +26,7 @@ These environment variables are used by the `docker-compose.yml` file and do **N | `LOG_LEVEL` | Log Level (verbose, debug, log, warn, error) | `log` | server, microservices | | `IMMICH_MEDIA_LOCATION` | Media Location | `./upload` | server, microservices | | `PUBLIC_LOGIN_PAGE_MESSAGE` | Public Login Page Message | | web | +| `IMMICH_CONFIG_FILE` | Path to config file | | server | :::tip @@ -50,13 +55,14 @@ These environment variables are used by the `docker-compose.yml` file and do **N ## URLs -| Variable | Description | Default | Services | -| :---------------------------- | :------------------------------------------------------- | :-----------------------------------: | :-------------------- | -| `IMMICH_WEB_URL` | Immich Web URL | `http://immich-web:3000` | proxy | -| `IMMICH_SERVER_URL` | Immich Server URL | `http://immich-server:3001` | web, proxy | -| `IMMICH_MACHINE_LEARNING_URL` | Immich Machine Learning URL, set `"false"` to disable ML | `http://immich-machine-learning:3003` | server, microservices | -| `PUBLIC_IMMICH_SERVER_URL` | Public Immich URL | `http://immich-server:3001` | web | -| `IMMICH_API_URL_EXTERNAL` | Immich API URL External | `/api` | web | +| Variable | Description | Default | Services | +| :-------------------------------- | :--------------------------- | :-----------------------------------: | :-------------------- | +| `IMMICH_WEB_URL` | Immich Web URL | `http://immich-web:3000` | proxy | +| `IMMICH_SERVER_URL` | Immich Server URL | `http://immich-server:3001` | web, proxy | +| `IMMICH_MACHINE_LEARNING_ENABLED` | Enabled machine learning | `true` | server, microservices | +| `IMMICH_MACHINE_LEARNING_URL` | Immich Machine Learning URL, | `http://immich-machine-learning:3003` | server, microservices | +| `PUBLIC_IMMICH_SERVER_URL` | Public Immich URL | `http://immich-server:3001` | web | +| `IMMICH_API_URL_EXTERNAL` | Immich API URL External | `/api` | web | :::info diff --git a/docs/docs/install/post-install.mdx b/docs/docs/install/post-install.mdx index 82fecc52f34a52eb54d7ae84f4216ceff1bb2764..131c83a4ff42640362a359cf80ae35f9e4b7c7eb 100644 --- a/docs/docs/install/post-install.mdx +++ b/docs/docs/install/post-install.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 100 +sidebar_position: 80 --- import RegisterAdminUser from '../partials/_register-admin.md'; diff --git a/machine-learning/Dockerfile b/machine-learning/Dockerfile index 4413f28f25ff6e699104f3fee42185128ac74171..103c8ba659b11df50b4af311b743478121b009cd 100644 --- a/machine-learning/Dockerfile +++ b/machine-learning/Dockerfile @@ -10,8 +10,9 @@ RUN poetry config installer.max-workers 10 && \ RUN python -m venv /opt/venv ENV VIRTUAL_ENV="/opt/venv" PATH="/opt/venv/bin:${PATH}" -COPY poetry.lock pyproject.toml ./ +COPY poetry.lock pyproject.toml requirements.txt ./ RUN poetry install --sync --no-interaction --no-ansi --no-root --only main +RUN pip install --no-deps -r requirements.txt FROM python:3.11.4-slim-bullseye@sha256:91d194f58f50594cda71dcd2e8fdefd90e7ecc57d07823813b67c8521e565dcd diff --git a/machine-learning/app/config.py b/machine-learning/app/config.py index 5714c0f4f72b3698d7238af0a17402749c75ae5a..8a64d911982ed55b4c9724ce9df6f4fa47f1a65c 100644 --- a/machine-learning/app/config.py +++ b/machine-learning/app/config.py @@ -1,3 +1,4 @@ +import os from pathlib import Path from pydantic import BaseSettings @@ -8,25 +9,31 @@ from .schemas import ModelType class Settings(BaseSettings): cache_folder: str = "/cache" classification_model: str = "microsoft/resnet-50" - clip_image_model: str = "clip-ViT-B-32" - clip_text_model: str = "clip-ViT-B-32" + clip_image_model: str = "ViT-B-32::openai" + clip_text_model: str = "ViT-B-32::openai" facial_recognition_model: str = "buffalo_l" min_tag_score: float = 0.9 - eager_startup: bool = True + eager_startup: bool = False model_ttl: int = 0 host: str = "0.0.0.0" port: int = 3003 workers: int = 1 min_face_score: float = 0.7 test_full: bool = False + request_threads: int = os.cpu_count() or 4 + model_inter_op_threads: int = 1 + model_intra_op_threads: int = 2 class Config: env_prefix = "MACHINE_LEARNING_" case_sensitive = False +_clean_name = str.maketrans(":\\/", "___", ".") + + def get_cache_dir(model_name: str, model_type: ModelType) -> Path: - return Path(settings.cache_folder, model_type.value, model_name) + return Path(settings.cache_folder) / model_type.value / model_name.translate(_clean_name) settings = Settings() diff --git a/machine-learning/app/main.py b/machine-learning/app/main.py index 59327d575cee5e2f54c6d92c17fdac957d6624a6..ddbce0f6597fbb40b60a9885ea5badb87ae976e1 100644 --- a/machine-learning/app/main.py +++ b/machine-learning/app/main.py @@ -1,4 +1,6 @@ +import asyncio import os +from concurrent.futures import ThreadPoolExecutor from io import BytesIO from typing import Any @@ -8,6 +10,8 @@ import uvicorn from fastapi import Body, Depends, FastAPI from PIL import Image +from app.models.base import InferenceModel + from .config import settings from .models.cache import ModelCache from .schemas import ( @@ -25,19 +29,21 @@ app = FastAPI() def init_state() -> None: app.state.model_cache = ModelCache(ttl=settings.model_ttl, revalidate=settings.model_ttl > 0) + # asyncio is a huge bottleneck for performance, so we use a thread pool to run blocking code + app.state.thread_pool = ThreadPoolExecutor(settings.request_threads) async def load_models() -> None: - models = [ - (settings.classification_model, ModelType.IMAGE_CLASSIFICATION), - (settings.clip_image_model, ModelType.CLIP), - (settings.clip_text_model, ModelType.CLIP), - (settings.facial_recognition_model, ModelType.FACIAL_RECOGNITION), + models: list[tuple[str, ModelType, dict[str, Any]]] = [ + (settings.classification_model, ModelType.IMAGE_CLASSIFICATION, {}), + (settings.clip_image_model, ModelType.CLIP, {"mode": "vision"}), + (settings.clip_text_model, ModelType.CLIP, {"mode": "text"}), + (settings.facial_recognition_model, ModelType.FACIAL_RECOGNITION, {}), ] # Get all models - for model_name, model_type in models: - await app.state.model_cache.get(model_name, model_type, eager=settings.eager_startup) + for model_name, model_type, model_kwargs in models: + await app.state.model_cache.get(model_name, model_type, eager=settings.eager_startup, **model_kwargs) @app.on_event("startup") @@ -46,11 +52,16 @@ async def startup_event() -> None: await load_models() +@app.on_event("shutdown") +async def shutdown_event() -> None: + app.state.thread_pool.shutdown() + + def dep_pil_image(byte_image: bytes = Body(...)) -> Image.Image: return Image.open(BytesIO(byte_image)) -def dep_cv_image(byte_image: bytes = Body(...)) -> cv2.Mat: +def dep_cv_image(byte_image: bytes = Body(...)) -> np.ndarray[int, np.dtype[Any]]: byte_image_np = np.frombuffer(byte_image, np.uint8) return cv2.imdecode(byte_image_np, cv2.IMREAD_COLOR) @@ -74,7 +85,7 @@ async def image_classification( image: Image.Image = Depends(dep_pil_image), ) -> list[str]: model = await app.state.model_cache.get(settings.classification_model, ModelType.IMAGE_CLASSIFICATION) - labels = model.predict(image) + labels = await predict(model, image) return labels @@ -86,8 +97,8 @@ async def image_classification( async def clip_encode_image( image: Image.Image = Depends(dep_pil_image), ) -> list[float]: - model = await app.state.model_cache.get(settings.clip_image_model, ModelType.CLIP) - embedding = model.predict(image) + model = await app.state.model_cache.get(settings.clip_image_model, ModelType.CLIP, mode="vision") + embedding = await predict(model, image) return embedding @@ -97,8 +108,8 @@ async def clip_encode_image( status_code=200, ) async def clip_encode_text(payload: TextModelRequest) -> list[float]: - model = await app.state.model_cache.get(settings.clip_text_model, ModelType.CLIP) - embedding = model.predict(payload.text) + model = await app.state.model_cache.get(settings.clip_text_model, ModelType.CLIP, mode="text") + embedding = await predict(model, payload.text) return embedding @@ -111,10 +122,14 @@ async def facial_recognition( image: cv2.Mat = Depends(dep_cv_image), ) -> list[dict[str, Any]]: model = await app.state.model_cache.get(settings.facial_recognition_model, ModelType.FACIAL_RECOGNITION) - faces = model.predict(image) + faces = await predict(model, image) return faces +async def predict(model: InferenceModel, inputs: Any) -> Any: + return await asyncio.get_running_loop().run_in_executor(app.state.thread_pool, model.predict, inputs) + + if __name__ == "__main__": is_dev = os.getenv("NODE_ENV") == "development" uvicorn.run( diff --git a/machine-learning/app/models/__init__.py b/machine-learning/app/models/__init__.py index e5b5aa7599f8877fca5c78318c056a43f8ff7286..7d1dd0c718f131d3cbb5c6fbbef01d7c4d1e7525 100644 --- a/machine-learning/app/models/__init__.py +++ b/machine-learning/app/models/__init__.py @@ -1,3 +1,3 @@ -from .clip import CLIPSTEncoder +from .clip import CLIPEncoder from .facial_recognition import FaceRecognizer from .image_classification import ImageClassifier diff --git a/machine-learning/app/models/base.py b/machine-learning/app/models/base.py index 9bdbb08b3182dce2a0ea45a97bb37ffc4147e350..07bc16dcdcb9bd71c98da0692981ff194bac671f 100644 --- a/machine-learning/app/models/base.py +++ b/machine-learning/app/models/base.py @@ -1,14 +1,17 @@ from __future__ import annotations +import os +import pickle from abc import ABC, abstractmethod from pathlib import Path from shutil import rmtree from typing import Any from zipfile import BadZipFile +import onnxruntime as ort from onnxruntime.capi.onnxruntime_pybind11_state import InvalidProtobuf # type: ignore -from ..config import get_cache_dir +from ..config import get_cache_dir, settings from ..schemas import ModelType @@ -16,12 +19,31 @@ class InferenceModel(ABC): _model_type: ModelType def __init__( - self, model_name: str, cache_dir: Path | str | None = None, eager: bool = True, **model_kwargs: Any + self, + model_name: str, + cache_dir: Path | str | None = None, + eager: bool = True, + inter_op_num_threads: int = settings.model_inter_op_threads, + intra_op_num_threads: int = settings.model_intra_op_threads, + **model_kwargs: Any, ) -> None: self.model_name = model_name self._loaded = False self._cache_dir = Path(cache_dir) if cache_dir is not None else get_cache_dir(model_name, self.model_type) loader = self.load if eager else self.download + + self.providers = model_kwargs.pop("providers", ["CPUExecutionProvider"]) + # don't pre-allocate more memory than needed + self.provider_options = model_kwargs.pop( + "provider_options", [{"arena_extend_strategy": "kSameAsRequested"}] * len(self.providers) + ) + self.sess_options = PicklableSessionOptions() + # avoid thread contention between models + if inter_op_num_threads > 1: + self.sess_options.execution_mode = ort.ExecutionMode.ORT_PARALLEL + self.sess_options.inter_op_num_threads = inter_op_num_threads + self.sess_options.intra_op_num_threads = intra_op_num_threads + try: loader(**model_kwargs) except (OSError, InvalidProtobuf, BadZipFile): @@ -30,6 +52,7 @@ class InferenceModel(ABC): def download(self, **model_kwargs: Any) -> None: if not self.cached: + print(f"Downloading {self.model_type.value.replace('_', ' ')} model. This may take a while...") self._download(**model_kwargs) def load(self, **model_kwargs: Any) -> None: @@ -39,6 +62,7 @@ class InferenceModel(ABC): def predict(self, inputs: Any) -> Any: if not self._loaded: + print(f"Loading {self.model_type.value.replace('_', ' ')} model...") self.load() return self._predict(inputs) @@ -89,3 +113,14 @@ class InferenceModel(ABC): else: self.cache_dir.unlink() self.cache_dir.mkdir(parents=True, exist_ok=True) + + +# HF deep copies configs, so we need to make session options picklable +class PicklableSessionOptions(ort.SessionOptions): + def __getstate__(self) -> bytes: + return pickle.dumps([(attr, getattr(self, attr)) for attr in dir(self) if not callable(getattr(self, attr))]) + + def __setstate__(self, state: Any) -> None: + self.__init__() # type: ignore + for attr, val in pickle.loads(state): + setattr(self, attr, val) diff --git a/machine-learning/app/models/cache.py b/machine-learning/app/models/cache.py index b9d5f75a0db39d5feb6d344ceaca0ce215e73723..f9094dc798cc9dac377e4163e76247f929d558bc 100644 --- a/machine-learning/app/models/cache.py +++ b/machine-learning/app/models/cache.py @@ -46,7 +46,7 @@ class ModelCache: model: The requested model. """ - key = self.cache.build_key(model_name, model_type.value) + key = f"{model_name}{model_type.value}{model_kwargs.get('mode', '')}" async with OptimisticLock(self.cache, key) as lock: model = await self.cache.get(key) if model is None: diff --git a/machine-learning/app/models/clip.py b/machine-learning/app/models/clip.py index 875671d391e63453a53f334f3c395db7bf25289d..c59abfbb9fca81ba5f9dd3c35fe36cad9510c781 100644 --- a/machine-learning/app/models/clip.py +++ b/machine-learning/app/models/clip.py @@ -1,31 +1,141 @@ -from typing import Any +import os +import zipfile +from typing import Any, Literal +import onnxruntime as ort +import torch +from clip_server.model.clip import BICUBIC, _convert_image_to_rgb +from clip_server.model.clip_onnx import _MODELS, _S3_BUCKET_V2, CLIPOnnxModel, download_model +from clip_server.model.pretrained_models import _VISUAL_MODEL_IMAGE_SIZE +from clip_server.model.tokenization import Tokenizer from PIL.Image import Image -from sentence_transformers import SentenceTransformer -from sentence_transformers.util import snapshot_download +from torchvision.transforms import CenterCrop, Compose, Normalize, Resize, ToTensor from ..schemas import ModelType from .base import InferenceModel +_ST_TO_JINA_MODEL_NAME = { + "clip-ViT-B-16": "ViT-B-16::openai", + "clip-ViT-B-32": "ViT-B-32::openai", + "clip-ViT-B-32-multilingual-v1": "M-CLIP/XLM-Roberta-Large-Vit-B-32", + "clip-ViT-L-14": "ViT-L-14::openai", +} -class CLIPSTEncoder(InferenceModel): + +class CLIPEncoder(InferenceModel): _model_type = ModelType.CLIP + def __init__( + self, + model_name: str, + cache_dir: str | None = None, + mode: Literal["text", "vision"] | None = None, + **model_kwargs: Any, + ) -> None: + if mode is not None and mode not in ("text", "vision"): + raise ValueError(f"Mode must be 'text', 'vision', or omitted; got '{mode}'") + if "vit-b" not in model_name.lower(): + raise ValueError(f"Only ViT-B models are currently supported; got '{model_name}'") + self.mode = mode + jina_model_name = self._get_jina_model_name(model_name) + super().__init__(jina_model_name, cache_dir, **model_kwargs) + def _download(self, **model_kwargs: Any) -> None: - repo_id = self.model_name if "/" in self.model_name else f"sentence-transformers/{self.model_name}" - snapshot_download( - cache_dir=self.cache_dir, - repo_id=repo_id, - library_name="sentence-transformers", - ignore_files=["flax_model.msgpack", "rust_model.ot", "tf_model.h5"], - ) + models: tuple[tuple[str, str], tuple[str, str]] = _MODELS[self.model_name] + text_onnx_path = self.cache_dir / "textual.onnx" + vision_onnx_path = self.cache_dir / "visual.onnx" + + if not text_onnx_path.is_file(): + self._download_model(*models[0]) + + if not vision_onnx_path.is_file(): + self._download_model(*models[1]) def _load(self, **model_kwargs: Any) -> None: - self.model = SentenceTransformer( - self.model_name, - cache_folder=self.cache_dir.as_posix(), - **model_kwargs, - ) + if self.mode == "text" or self.mode is None: + self.text_model = ort.InferenceSession( + self.cache_dir / "textual.onnx", + sess_options=self.sess_options, + providers=self.providers, + provider_options=self.provider_options, + ) + self.text_outputs = [output.name for output in self.text_model.get_outputs()] + self.tokenizer = Tokenizer(self.model_name) + + if self.mode == "vision" or self.mode is None: + self.vision_model = ort.InferenceSession( + self.cache_dir / "visual.onnx", + sess_options=self.sess_options, + providers=self.providers, + provider_options=self.provider_options, + ) + self.vision_outputs = [output.name for output in self.vision_model.get_outputs()] + + image_size = _VISUAL_MODEL_IMAGE_SIZE[CLIPOnnxModel.get_model_name(self.model_name)] + self.transform = _transform_pil_image(image_size) def _predict(self, image_or_text: Image | str) -> list[float]: - return self.model.encode(image_or_text).tolist() + match image_or_text: + case Image(): + if self.mode == "text": + raise TypeError("Cannot encode image as text-only model") + pixel_values = self.transform(image_or_text) + assert isinstance(pixel_values, torch.Tensor) + pixel_values = torch.unsqueeze(pixel_values, 0).numpy() + outputs = self.vision_model.run(self.vision_outputs, {"pixel_values": pixel_values}) + case str(): + if self.mode == "vision": + raise TypeError("Cannot encode text as vision-only model") + text_inputs: dict[str, torch.Tensor] = self.tokenizer(image_or_text) + inputs = { + "input_ids": text_inputs["input_ids"].int().numpy(), + "attention_mask": text_inputs["attention_mask"].int().numpy(), + } + outputs = self.text_model.run(self.text_outputs, inputs) + case _: + raise TypeError(f"Expected Image or str, but got: {type(image_or_text)}") + + return outputs[0][0].tolist() + + def _get_jina_model_name(self, model_name: str) -> str: + if model_name in _MODELS: + return model_name + elif model_name in _ST_TO_JINA_MODEL_NAME: + print( + (f"Warning: Sentence-Transformer model names such as '{model_name}' are no longer supported."), + (f"Using '{_ST_TO_JINA_MODEL_NAME[model_name]}' instead as it is the best match for '{model_name}'."), + ) + return _ST_TO_JINA_MODEL_NAME[model_name] + else: + raise ValueError(f"Unknown model name {model_name}.") + + def _download_model(self, model_name: str, model_md5: str) -> bool: + # downloading logic is adapted from clip-server's CLIPOnnxModel class + download_model( + url=_S3_BUCKET_V2 + model_name, + target_folder=self.cache_dir.as_posix(), + md5sum=model_md5, + with_resume=True, + ) + file = self.cache_dir / model_name.split("/")[1] + if file.suffix == ".zip": + with zipfile.ZipFile(file, "r") as zip_ref: + zip_ref.extractall(self.cache_dir) + os.remove(file) + return True + + +# same as `_transform_blob` without `_blob2image` +def _transform_pil_image(n_px: int) -> Compose: + return Compose( + [ + Resize(n_px, interpolation=BICUBIC), + CenterCrop(n_px), + _convert_image_to_rgb, + ToTensor(), + Normalize( + (0.48145466, 0.4578275, 0.40821073), + (0.26862954, 0.26130258, 0.27577711), + ), + ] + ) diff --git a/machine-learning/app/models/facial_recognition.py b/machine-learning/app/models/facial_recognition.py index 32ea629dfcafef0b81d1bc55e67a2957e88d8191..b0ff1d04143d59614606c084fa751cd529d5bcf8 100644 --- a/machine-learning/app/models/facial_recognition.py +++ b/machine-learning/app/models/facial_recognition.py @@ -4,6 +4,7 @@ from typing import Any import cv2 import numpy as np +import onnxruntime as ort from insightface.model_zoo import ArcFaceONNX, RetinaFace from insightface.utils.face_align import norm_crop from insightface.utils.storage import BASE_REPO_URL, download_file @@ -42,15 +43,31 @@ class FaceRecognizer(InferenceModel): rec_file = next(self.cache_dir.glob("w600k_*.onnx")) except StopIteration: raise FileNotFoundError("Facial recognition models not found in cache directory") - self.det_model = RetinaFace(det_file.as_posix()) - self.rec_model = ArcFaceONNX(rec_file.as_posix()) + + self.det_model = RetinaFace( + session=ort.InferenceSession( + det_file.as_posix(), + sess_options=self.sess_options, + providers=self.providers, + provider_options=self.provider_options, + ), + ) + self.rec_model = ArcFaceONNX( + rec_file.as_posix(), + session=ort.InferenceSession( + rec_file.as_posix(), + sess_options=self.sess_options, + providers=self.providers, + provider_options=self.provider_options, + ), + ) self.det_model.prepare( - ctx_id=-1, + ctx_id=0, det_thresh=self.min_score, input_size=(640, 640), ) - self.rec_model.prepare(ctx_id=-1) + self.rec_model.prepare(ctx_id=0) def _predict(self, image: cv2.Mat) -> list[dict[str, Any]]: bboxes, kpss = self.det_model.detect(image) diff --git a/machine-learning/app/models/image_classification.py b/machine-learning/app/models/image_classification.py index 9a9ba4219426828c060b3e8d5a12d0417b31e0bd..6c12dd8d76db68eaeed545902605b7871d057c32 100644 --- a/machine-learning/app/models/image_classification.py +++ b/machine-learning/app/models/image_classification.py @@ -2,8 +2,10 @@ from pathlib import Path from typing import Any from huggingface_hub import snapshot_download +from optimum.onnxruntime import ORTModelForImageClassification +from optimum.pipelines import pipeline from PIL.Image import Image -from transformers.pipelines import pipeline +from transformers import AutoImageProcessor from ..config import settings from ..schemas import ModelType @@ -25,15 +27,34 @@ class ImageClassifier(InferenceModel): def _download(self, **model_kwargs: Any) -> None: snapshot_download( - cache_dir=self.cache_dir, repo_id=self.model_name, allow_patterns=["*.bin", "*.json", "*.txt"] + cache_dir=self.cache_dir, + repo_id=self.model_name, + allow_patterns=["*.bin", "*.json", "*.txt"], + local_dir=self.cache_dir, + local_dir_use_symlinks=True, ) def _load(self, **model_kwargs: Any) -> None: - self.model = pipeline( - self.model_type.value, - self.model_name, - model_kwargs={"cache_dir": self.cache_dir, **model_kwargs}, - ) + processor = AutoImageProcessor.from_pretrained(self.cache_dir) + model_kwargs |= { + "cache_dir": self.cache_dir, + "provider": self.providers[0], + "provider_options": self.provider_options[0], + "session_options": self.sess_options, + } + model_path = self.cache_dir / "model.onnx" + + if model_path.exists(): + model = ORTModelForImageClassification.from_pretrained(self.cache_dir, **model_kwargs) + self.model = pipeline(self.model_type.value, model, feature_extractor=processor) + else: + self.sess_options.optimized_model_filepath = model_path.as_posix() + self.model = pipeline( + self.model_type.value, + self.model_name, + model_kwargs=model_kwargs, + feature_extractor=processor, + ) def _predict(self, image: Image) -> list[str]: predictions: list[dict[str, Any]] = self.model(image) # type: ignore diff --git a/machine-learning/app/test_main.py b/machine-learning/app/test_main.py index 465624004f2fe46c1ccfc31ec4e8b5f7fd7c013b..d8bc8a252cf5af9cc8a92d1552e04d91a684bbb0 100644 --- a/machine-learning/app/test_main.py +++ b/machine-learning/app/test_main.py @@ -1,17 +1,20 @@ +import pickle from io import BytesIO from typing import TypeAlias from unittest import mock import cv2 import numpy as np +import onnxruntime as ort import pytest from fastapi.testclient import TestClient from PIL import Image from pytest_mock import MockerFixture from .config import settings +from .models.base import PicklableSessionOptions from .models.cache import ModelCache -from .models.clip import CLIPSTEncoder +from .models.clip import CLIPEncoder from .models.facial_recognition import FaceRecognizer from .models.image_classification import ImageClassifier from .schemas import ModelType @@ -72,45 +75,47 @@ class TestCLIP: embedding = np.random.rand(512).astype(np.float32) def test_eager_init(self, mocker: MockerFixture) -> None: - mocker.patch.object(CLIPSTEncoder, "download") - mock_load = mocker.patch.object(CLIPSTEncoder, "load") - clip_model = CLIPSTEncoder("test_model_name", cache_dir="test_cache", eager=True, test_arg="test_arg") + mocker.patch.object(CLIPEncoder, "download") + mock_load = mocker.patch.object(CLIPEncoder, "load") + clip_model = CLIPEncoder("ViT-B-32::openai", cache_dir="test_cache", eager=True, test_arg="test_arg") - assert clip_model.model_name == "test_model_name" + assert clip_model.model_name == "ViT-B-32::openai" mock_load.assert_called_once_with(test_arg="test_arg") def test_lazy_init(self, mocker: MockerFixture) -> None: - mock_download = mocker.patch.object(CLIPSTEncoder, "download") - mock_load = mocker.patch.object(CLIPSTEncoder, "load") - clip_model = CLIPSTEncoder("test_model_name", cache_dir="test_cache", eager=False, test_arg="test_arg") + mock_download = mocker.patch.object(CLIPEncoder, "download") + mock_load = mocker.patch.object(CLIPEncoder, "load") + clip_model = CLIPEncoder("ViT-B-32::openai", cache_dir="test_cache", eager=False, test_arg="test_arg") - assert clip_model.model_name == "test_model_name" + assert clip_model.model_name == "ViT-B-32::openai" mock_download.assert_called_once_with(test_arg="test_arg") mock_load.assert_not_called() def test_basic_image(self, pil_image: Image.Image, mocker: MockerFixture) -> None: - mocker.patch.object(CLIPSTEncoder, "load") - clip_encoder = CLIPSTEncoder("test_model_name", cache_dir="test_cache") - clip_encoder.model = mock.Mock() - clip_encoder.model.encode.return_value = self.embedding + mocker.patch.object(CLIPEncoder, "download") + mocked = mocker.patch("app.models.clip.ort.InferenceSession", autospec=True) + mocked.return_value.run.return_value = [[self.embedding]] + clip_encoder = CLIPEncoder("ViT-B-32::openai", cache_dir="test_cache", mode="vision") + assert clip_encoder.mode == "vision" embedding = clip_encoder.predict(pil_image) assert isinstance(embedding, list) assert len(embedding) == 512 assert all([isinstance(num, float) for num in embedding]) - clip_encoder.model.encode.assert_called_once() + clip_encoder.vision_model.run.assert_called_once() def test_basic_text(self, mocker: MockerFixture) -> None: - mocker.patch.object(CLIPSTEncoder, "load") - clip_encoder = CLIPSTEncoder("test_model_name", cache_dir="test_cache") - clip_encoder.model = mock.Mock() - clip_encoder.model.encode.return_value = self.embedding + mocker.patch.object(CLIPEncoder, "download") + mocked = mocker.patch("app.models.clip.ort.InferenceSession", autospec=True) + mocked.return_value.run.return_value = [[self.embedding]] + clip_encoder = CLIPEncoder("ViT-B-32::openai", cache_dir="test_cache", mode="text") + assert clip_encoder.mode == "text" embedding = clip_encoder.predict("test search query") assert isinstance(embedding, list) assert len(embedding) == 512 assert all([isinstance(num, float) for num in embedding]) - clip_encoder.model.encode.assert_called_once() + clip_encoder.text_model.run.assert_called_once() class TestFaceRecognition: @@ -254,3 +259,13 @@ class TestEndpoints: headers=headers, ) assert response.status_code == 200 + + +def test_sess_options() -> None: + sess_options = PicklableSessionOptions() + sess_options.intra_op_num_threads = 1 + sess_options.inter_op_num_threads = 1 + pickled = pickle.dumps(sess_options) + unpickled = pickle.loads(pickled) + assert unpickled.intra_op_num_threads == 1 + assert unpickled.inter_op_num_threads == 1 diff --git a/machine-learning/poetry.lock b/machine-learning/poetry.lock index bb59ee5c416c9a3312cdc2ec0c3f3b9a41452f8a..cb668f9a1763b0230ebe87f80f72906e172465de 100644 --- a/machine-learning/poetry.lock +++ b/machine-learning/poetry.lock @@ -2,13 +2,13 @@ [[package]] name = "aiocache" -version = "0.12.1" +version = "0.12.2" description = "multi backend asyncio cache" optional = false python-versions = "*" files = [ - {file = "aiocache-0.12.1-py2.py3-none-any.whl", hash = "sha256:15eb17188e12d7226678c22a890824ff9c406cf7e5b418f86944395db63a3c1b"}, - {file = "aiocache-0.12.1.tar.gz", hash = "sha256:7d9ffbd4ca2b1c51e5b213fd7d42dc43b80af01a436b14be5ebcbde199709fb3"}, + {file = "aiocache-0.12.2-py2.py3-none-any.whl", hash = "sha256:9b6fa30634ab0bfc3ecc44928a91ff07c6ea16d27d55469636b296ebc6eb5918"}, + {file = "aiocache-0.12.2.tar.gz", hash = "sha256:b41c9a145b050a5dcbae1599f847db6dd445193b1f3bd172d8e0fe0cb9e96684"}, ] [package.extras] @@ -16,6 +16,128 @@ memcached = ["aiomcache (>=0.5.2)"] msgpack = ["msgpack (>=0.5.5)"] redis = ["redis (>=4.2.0)"] +[[package]] +name = "aiohttp" +version = "3.8.5" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.6" +files = [ + {file = "aiohttp-3.8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a94159871304770da4dd371f4291b20cac04e8c94f11bdea1c3478e557fbe0d8"}, + {file = "aiohttp-3.8.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:13bf85afc99ce6f9ee3567b04501f18f9f8dbbb2ea11ed1a2e079670403a7c84"}, + {file = "aiohttp-3.8.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ce2ac5708501afc4847221a521f7e4b245abf5178cf5ddae9d5b3856ddb2f3a"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96943e5dcc37a6529d18766597c491798b7eb7a61d48878611298afc1fca946c"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ad5c3c4590bb3cc28b4382f031f3783f25ec223557124c68754a2231d989e2b"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c413c633d0512df4dc7fd2373ec06cc6a815b7b6d6c2f208ada7e9e93a5061d"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df72ac063b97837a80d80dec8d54c241af059cc9bb42c4de68bd5b61ceb37caa"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c48c5c0271149cfe467c0ff8eb941279fd6e3f65c9a388c984e0e6cf57538e14"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:368a42363c4d70ab52c2c6420a57f190ed3dfaca6a1b19afda8165ee16416a82"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7607ec3ce4993464368505888af5beb446845a014bc676d349efec0e05085905"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0d21c684808288a98914e5aaf2a7c6a3179d4df11d249799c32d1808e79503b5"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:312fcfbacc7880a8da0ae8b6abc6cc7d752e9caa0051a53d217a650b25e9a691"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ad093e823df03bb3fd37e7dec9d4670c34f9e24aeace76808fc20a507cace825"}, + {file = "aiohttp-3.8.5-cp310-cp310-win32.whl", hash = "sha256:33279701c04351a2914e1100b62b2a7fdb9a25995c4a104259f9a5ead7ed4802"}, + {file = "aiohttp-3.8.5-cp310-cp310-win_amd64.whl", hash = "sha256:6e4a280e4b975a2e7745573e3fc9c9ba0d1194a3738ce1cbaa80626cc9b4f4df"}, + {file = "aiohttp-3.8.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae871a964e1987a943d83d6709d20ec6103ca1eaf52f7e0d36ee1b5bebb8b9b9"}, + {file = "aiohttp-3.8.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:461908b2578955045efde733719d62f2b649c404189a09a632d245b445c9c975"}, + {file = "aiohttp-3.8.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:72a860c215e26192379f57cae5ab12b168b75db8271f111019509a1196dfc780"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc14be025665dba6202b6a71cfcdb53210cc498e50068bc088076624471f8bb9"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8af740fc2711ad85f1a5c034a435782fbd5b5f8314c9a3ef071424a8158d7f6b"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:841cd8233cbd2111a0ef0a522ce016357c5e3aff8a8ce92bcfa14cef890d698f"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ed1c46fb119f1b59304b5ec89f834f07124cd23ae5b74288e364477641060ff"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84f8ae3e09a34f35c18fa57f015cc394bd1389bce02503fb30c394d04ee6b938"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62360cb771707cb70a6fd114b9871d20d7dd2163a0feafe43fd115cfe4fe845e"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:23fb25a9f0a1ca1f24c0a371523546366bb642397c94ab45ad3aedf2941cec6a"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0ba0d15164eae3d878260d4c4df859bbdc6466e9e6689c344a13334f988bb53"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5d20003b635fc6ae3f96d7260281dfaf1894fc3aa24d1888a9b2628e97c241e5"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0175d745d9e85c40dcc51c8f88c74bfbaef9e7afeeeb9d03c37977270303064c"}, + {file = "aiohttp-3.8.5-cp311-cp311-win32.whl", hash = "sha256:2e1b1e51b0774408f091d268648e3d57f7260c1682e7d3a63cb00d22d71bb945"}, + {file = "aiohttp-3.8.5-cp311-cp311-win_amd64.whl", hash = "sha256:043d2299f6dfdc92f0ac5e995dfc56668e1587cea7f9aa9d8a78a1b6554e5755"}, + {file = "aiohttp-3.8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cae533195e8122584ec87531d6df000ad07737eaa3c81209e85c928854d2195c"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f21e83f355643c345177a5d1d8079f9f28b5133bcd154193b799d380331d5d3"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7a75ef35f2df54ad55dbf4b73fe1da96f370e51b10c91f08b19603c64004acc"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e2e9839e14dd5308ee773c97115f1e0a1cb1d75cbeeee9f33824fa5144c7634"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44e65da1de4403d0576473e2344828ef9c4c6244d65cf4b75549bb46d40b8dd"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78d847e4cde6ecc19125ccbc9bfac4a7ab37c234dd88fbb3c5c524e8e14da543"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:c7a815258e5895d8900aec4454f38dca9aed71085f227537208057853f9d13f2"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:8b929b9bd7cd7c3939f8bcfffa92fae7480bd1aa425279d51a89327d600c704d"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:5db3a5b833764280ed7618393832e0853e40f3d3e9aa128ac0ba0f8278d08649"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:a0215ce6041d501f3155dc219712bc41252d0ab76474615b9700d63d4d9292af"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:fd1ed388ea7fbed22c4968dd64bab0198de60750a25fe8c0c9d4bef5abe13824"}, + {file = "aiohttp-3.8.5-cp36-cp36m-win32.whl", hash = "sha256:6e6783bcc45f397fdebc118d772103d751b54cddf5b60fbcc958382d7dd64f3e"}, + {file = "aiohttp-3.8.5-cp36-cp36m-win_amd64.whl", hash = "sha256:b5411d82cddd212644cf9360879eb5080f0d5f7d809d03262c50dad02f01421a"}, + {file = "aiohttp-3.8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:01d4c0c874aa4ddfb8098e85d10b5e875a70adc63db91f1ae65a4b04d3344cda"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5980a746d547a6ba173fd5ee85ce9077e72d118758db05d229044b469d9029a"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a482e6da906d5e6e653be079b29bc173a48e381600161c9932d89dfae5942ef"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80bd372b8d0715c66c974cf57fe363621a02f359f1ec81cba97366948c7fc873"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1161b345c0a444ebcf46bf0a740ba5dcf50612fd3d0528883fdc0eff578006a"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd56db019015b6acfaaf92e1ac40eb8434847d9bf88b4be4efe5bfd260aee692"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:153c2549f6c004d2754cc60603d4668899c9895b8a89397444a9c4efa282aaf4"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4a01951fabc4ce26ab791da5f3f24dca6d9a6f24121746eb19756416ff2d881b"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bfb9162dcf01f615462b995a516ba03e769de0789de1cadc0f916265c257e5d8"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:7dde0009408969a43b04c16cbbe252c4f5ef4574ac226bc8815cd7342d2028b6"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4149d34c32f9638f38f544b3977a4c24052042affa895352d3636fa8bffd030a"}, + {file = "aiohttp-3.8.5-cp37-cp37m-win32.whl", hash = "sha256:68c5a82c8779bdfc6367c967a4a1b2aa52cd3595388bf5961a62158ee8a59e22"}, + {file = "aiohttp-3.8.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2cf57fb50be5f52bda004b8893e63b48530ed9f0d6c96c84620dc92fe3cd9b9d"}, + {file = "aiohttp-3.8.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:eca4bf3734c541dc4f374ad6010a68ff6c6748f00451707f39857f429ca36ced"}, + {file = "aiohttp-3.8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1274477e4c71ce8cfe6c1ec2f806d57c015ebf84d83373676036e256bc55d690"}, + {file = "aiohttp-3.8.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:28c543e54710d6158fc6f439296c7865b29e0b616629767e685a7185fab4a6b9"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:910bec0c49637d213f5d9877105d26e0c4a4de2f8b1b29405ff37e9fc0ad52b8"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5443910d662db951b2e58eb70b0fbe6b6e2ae613477129a5805d0b66c54b6cb7"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e460be6978fc24e3df83193dc0cc4de46c9909ed92dd47d349a452ef49325b7"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb1558def481d84f03b45888473fc5a1f35747b5f334ef4e7a571bc0dfcb11f8"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34dd0c107799dcbbf7d48b53be761a013c0adf5571bf50c4ecad5643fe9cfcd0"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aa1990247f02a54185dc0dff92a6904521172a22664c863a03ff64c42f9b5410"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0e584a10f204a617d71d359fe383406305a4b595b333721fa50b867b4a0a1548"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:a3cf433f127efa43fee6b90ea4c6edf6c4a17109d1d037d1a52abec84d8f2e42"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:c11f5b099adafb18e65c2c997d57108b5bbeaa9eeee64a84302c0978b1ec948b"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:84de26ddf621d7ac4c975dbea4c945860e08cccde492269db4e1538a6a6f3c35"}, + {file = "aiohttp-3.8.5-cp38-cp38-win32.whl", hash = "sha256:ab88bafedc57dd0aab55fa728ea10c1911f7e4d8b43e1d838a1739f33712921c"}, + {file = "aiohttp-3.8.5-cp38-cp38-win_amd64.whl", hash = "sha256:5798a9aad1879f626589f3df0f8b79b3608a92e9beab10e5fda02c8a2c60db2e"}, + {file = "aiohttp-3.8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a6ce61195c6a19c785df04e71a4537e29eaa2c50fe745b732aa937c0c77169f3"}, + {file = "aiohttp-3.8.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:773dd01706d4db536335fcfae6ea2440a70ceb03dd3e7378f3e815b03c97ab51"}, + {file = "aiohttp-3.8.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f83a552443a526ea38d064588613aca983d0ee0038801bc93c0c916428310c28"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f7372f7341fcc16f57b2caded43e81ddd18df53320b6f9f042acad41f8e049a"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea353162f249c8097ea63c2169dd1aa55de1e8fecbe63412a9bc50816e87b761"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d47ae48db0b2dcf70bc8a3bc72b3de86e2a590fc299fdbbb15af320d2659de"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d827176898a2b0b09694fbd1088c7a31836d1a505c243811c87ae53a3f6273c1"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3562b06567c06439d8b447037bb655ef69786c590b1de86c7ab81efe1c9c15d8"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4e874cbf8caf8959d2adf572a78bba17cb0e9d7e51bb83d86a3697b686a0ab4d"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6809a00deaf3810e38c628e9a33271892f815b853605a936e2e9e5129762356c"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:33776e945d89b29251b33a7e7d006ce86447b2cfd66db5e5ded4e5cd0340585c"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:eaeed7abfb5d64c539e2db173f63631455f1196c37d9d8d873fc316470dfbacd"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e91d635961bec2d8f19dfeb41a539eb94bd073f075ca6dae6c8dc0ee89ad6f91"}, + {file = "aiohttp-3.8.5-cp39-cp39-win32.whl", hash = "sha256:00ad4b6f185ec67f3e6562e8a1d2b69660be43070bd0ef6fcec5211154c7df67"}, + {file = "aiohttp-3.8.5-cp39-cp39-win_amd64.whl", hash = "sha256:c0a9034379a37ae42dea7ac1e048352d96286626251862e448933c0f59cbd79c"}, + {file = "aiohttp-3.8.5.tar.gz", hash = "sha256:b9552ec52cc147dbf1944ac7ac98af7602e51ea2dcd076ed194ca3c0d1c7d0bc"}, +] + +[package.dependencies] +aiosignal = ">=1.1.2" +async-timeout = ">=4.0.0a3,<5.0" +attrs = ">=17.3.0" +charset-normalizer = ">=2.0,<4.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["Brotli", "aiodns", "cchardet"] + +[[package]] +name = "aiosignal" +version = "1.3.1" +description = "aiosignal: a list of registered asynchronous callbacks" +optional = false +python-versions = ">=3.7" +files = [ + {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, + {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, +] + +[package.dependencies] +frozenlist = ">=1.1.0" + [[package]] name = "albumentations" version = "1.3.1" @@ -60,6 +182,35 @@ doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd- test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] trio = ["trio (<0.22)"] +[[package]] +name = "async-timeout" +version = "4.0.3" +description = "Timeout context manager for asyncio programs" +optional = false +python-versions = ">=3.7" +files = [ + {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, + {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, +] + +[[package]] +name = "attrs" +version = "23.1.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, + {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[docs,tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] + [[package]] name = "black" version = "23.7.0" @@ -379,13 +530,13 @@ files = [ [[package]] name = "click" -version = "8.1.6" +version = "8.1.7" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" files = [ - {file = "click-8.1.6-py3-none-any.whl", hash = "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5"}, - {file = "click-8.1.6.tar.gz", hash = "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd"}, + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, ] [package.dependencies] @@ -494,71 +645,63 @@ test-no-images = ["pytest", "pytest-cov", "wurlitzer"] [[package]] name = "coverage" -version = "7.2.7" +version = "7.3.0" description = "Code coverage measurement for Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"}, - {file = "coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495"}, - {file = "coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818"}, - {file = "coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850"}, - {file = "coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f"}, - {file = "coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a"}, - {file = "coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a"}, - {file = "coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562"}, - {file = "coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"}, - {file = "coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"}, - {file = "coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"}, - {file = "coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02"}, - {file = "coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f"}, - {file = "coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0"}, - {file = "coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"}, - {file = "coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"}, - {file = "coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"}, - {file = "coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"}, - {file = "coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"}, - {file = "coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2"}, - {file = "coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb"}, - {file = "coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27"}, - {file = "coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d"}, - {file = "coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"}, + {file = "coverage-7.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:db76a1bcb51f02b2007adacbed4c88b6dee75342c37b05d1822815eed19edee5"}, + {file = "coverage-7.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c02cfa6c36144ab334d556989406837336c1d05215a9bdf44c0bc1d1ac1cb637"}, + {file = "coverage-7.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:477c9430ad5d1b80b07f3c12f7120eef40bfbf849e9e7859e53b9c93b922d2af"}, + {file = "coverage-7.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce2ee86ca75f9f96072295c5ebb4ef2a43cecf2870b0ca5e7a1cbdd929cf67e1"}, + {file = "coverage-7.3.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68d8a0426b49c053013e631c0cdc09b952d857efa8f68121746b339912d27a12"}, + {file = "coverage-7.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b3eb0c93e2ea6445b2173da48cb548364f8f65bf68f3d090404080d338e3a689"}, + {file = "coverage-7.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:90b6e2f0f66750c5a1178ffa9370dec6c508a8ca5265c42fbad3ccac210a7977"}, + {file = "coverage-7.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:96d7d761aea65b291a98c84e1250cd57b5b51726821a6f2f8df65db89363be51"}, + {file = "coverage-7.3.0-cp310-cp310-win32.whl", hash = "sha256:63c5b8ecbc3b3d5eb3a9d873dec60afc0cd5ff9d9f1c75981d8c31cfe4df8527"}, + {file = "coverage-7.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:97c44f4ee13bce914272589b6b41165bbb650e48fdb7bd5493a38bde8de730a1"}, + {file = "coverage-7.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:74c160285f2dfe0acf0f72d425f3e970b21b6de04157fc65adc9fd07ee44177f"}, + {file = "coverage-7.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b543302a3707245d454fc49b8ecd2c2d5982b50eb63f3535244fd79a4be0c99d"}, + {file = "coverage-7.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad0f87826c4ebd3ef484502e79b39614e9c03a5d1510cfb623f4a4a051edc6fd"}, + {file = "coverage-7.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13c6cbbd5f31211d8fdb477f0f7b03438591bdd077054076eec362cf2207b4a7"}, + {file = "coverage-7.3.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fac440c43e9b479d1241fe9d768645e7ccec3fb65dc3a5f6e90675e75c3f3e3a"}, + {file = "coverage-7.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3c9834d5e3df9d2aba0275c9f67989c590e05732439b3318fa37a725dff51e74"}, + {file = "coverage-7.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4c8e31cf29b60859876474034a83f59a14381af50cbe8a9dbaadbf70adc4b214"}, + {file = "coverage-7.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7a9baf8e230f9621f8e1d00c580394a0aa328fdac0df2b3f8384387c44083c0f"}, + {file = "coverage-7.3.0-cp311-cp311-win32.whl", hash = "sha256:ccc51713b5581e12f93ccb9c5e39e8b5d4b16776d584c0f5e9e4e63381356482"}, + {file = "coverage-7.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:887665f00ea4e488501ba755a0e3c2cfd6278e846ada3185f42d391ef95e7e70"}, + {file = "coverage-7.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d000a739f9feed900381605a12a61f7aaced6beae832719ae0d15058a1e81c1b"}, + {file = "coverage-7.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:59777652e245bb1e300e620ce2bef0d341945842e4eb888c23a7f1d9e143c446"}, + {file = "coverage-7.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9737bc49a9255d78da085fa04f628a310c2332b187cd49b958b0e494c125071"}, + {file = "coverage-7.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5247bab12f84a1d608213b96b8af0cbb30d090d705b6663ad794c2f2a5e5b9fe"}, + {file = "coverage-7.3.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2ac9a1de294773b9fa77447ab7e529cf4fe3910f6a0832816e5f3d538cfea9a"}, + {file = "coverage-7.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:85b7335c22455ec12444cec0d600533a238d6439d8d709d545158c1208483873"}, + {file = "coverage-7.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:36ce5d43a072a036f287029a55b5c6a0e9bd73db58961a273b6dc11a2c6eb9c2"}, + {file = "coverage-7.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:211a4576e984f96d9fce61766ffaed0115d5dab1419e4f63d6992b480c2bd60b"}, + {file = "coverage-7.3.0-cp312-cp312-win32.whl", hash = "sha256:56afbf41fa4a7b27f6635bc4289050ac3ab7951b8a821bca46f5b024500e6321"}, + {file = "coverage-7.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f297e0c1ae55300ff688568b04ff26b01c13dfbf4c9d2b7d0cb688ac60df479"}, + {file = "coverage-7.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac0dec90e7de0087d3d95fa0533e1d2d722dcc008bc7b60e1143402a04c117c1"}, + {file = "coverage-7.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:438856d3f8f1e27f8e79b5410ae56650732a0dcfa94e756df88c7e2d24851fcd"}, + {file = "coverage-7.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1084393c6bda8875c05e04fce5cfe1301a425f758eb012f010eab586f1f3905e"}, + {file = "coverage-7.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49ab200acf891e3dde19e5aa4b0f35d12d8b4bd805dc0be8792270c71bd56c54"}, + {file = "coverage-7.3.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67e6bbe756ed458646e1ef2b0778591ed4d1fcd4b146fc3ba2feb1a7afd4254"}, + {file = "coverage-7.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8f39c49faf5344af36042b293ce05c0d9004270d811c7080610b3e713251c9b0"}, + {file = "coverage-7.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7df91fb24c2edaabec4e0eee512ff3bc6ec20eb8dccac2e77001c1fe516c0c84"}, + {file = "coverage-7.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:34f9f0763d5fa3035a315b69b428fe9c34d4fc2f615262d6be3d3bf3882fb985"}, + {file = "coverage-7.3.0-cp38-cp38-win32.whl", hash = "sha256:bac329371d4c0d456e8d5f38a9b0816b446581b5f278474e416ea0c68c47dcd9"}, + {file = "coverage-7.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b859128a093f135b556b4765658d5d2e758e1fae3e7cc2f8c10f26fe7005e543"}, + {file = "coverage-7.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fc0ed8d310afe013db1eedd37176d0839dc66c96bcfcce8f6607a73ffea2d6ba"}, + {file = "coverage-7.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61260ec93f99f2c2d93d264b564ba912bec502f679793c56f678ba5251f0393"}, + {file = "coverage-7.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97af9554a799bd7c58c0179cc8dbf14aa7ab50e1fd5fa73f90b9b7215874ba28"}, + {file = "coverage-7.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3558e5b574d62f9c46b76120a5c7c16c4612dc2644c3d48a9f4064a705eaee95"}, + {file = "coverage-7.3.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37d5576d35fcb765fca05654f66aa71e2808d4237d026e64ac8b397ffa66a56a"}, + {file = "coverage-7.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:07ea61bcb179f8f05ffd804d2732b09d23a1238642bf7e51dad62082b5019b34"}, + {file = "coverage-7.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:80501d1b2270d7e8daf1b64b895745c3e234289e00d5f0e30923e706f110334e"}, + {file = "coverage-7.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4eddd3153d02204f22aef0825409091a91bf2a20bce06fe0f638f5c19a85de54"}, + {file = "coverage-7.3.0-cp39-cp39-win32.whl", hash = "sha256:2d22172f938455c156e9af2612650f26cceea47dc86ca048fa4e0b2d21646ad3"}, + {file = "coverage-7.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:60f64e2007c9144375dd0f480a54d6070f00bb1a28f65c408370544091c9bc9e"}, + {file = "coverage-7.3.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:5492a6ce3bdb15c6ad66cb68a0244854d9917478877a25671d70378bdc8562d0"}, + {file = "coverage-7.3.0.tar.gz", hash = "sha256:49dbb19cdcafc130f597d9e04a29d0a032ceedf729e41b181f51cd170e6ee865"}, ] [package.extras] @@ -642,6 +785,62 @@ files = [ {file = "Cython-3.0.0.tar.gz", hash = "sha256:350b18f9673e63101dbbfcf774ee2f57c20ac4636d255741d76ca79016b1bd82"}, ] +[[package]] +name = "datasets" +version = "2.14.4" +description = "HuggingFace community-driven open-source library of datasets" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "datasets-2.14.4-py3-none-any.whl", hash = "sha256:29336bd316a7d827ccd4da2236596279b20ca2ac78f64c04c9483da7cbc2459b"}, + {file = "datasets-2.14.4.tar.gz", hash = "sha256:ef29c2b5841de488cd343cfc26ab979bff77efa4d2285af51f1ad7db5c46a83b"}, +] + +[package.dependencies] +aiohttp = "*" +dill = ">=0.3.0,<0.3.8" +fsspec = {version = ">=2021.11.1", extras = ["http"]} +huggingface-hub = ">=0.14.0,<1.0.0" +multiprocess = "*" +numpy = ">=1.17" +packaging = "*" +pandas = "*" +pyarrow = ">=8.0.0" +pyyaml = ">=5.1" +requests = ">=2.19.0" +tqdm = ">=4.62.1" +xxhash = "*" + +[package.extras] +apache-beam = ["apache-beam (>=2.26.0,<2.44.0)"] +audio = ["librosa", "soundfile (>=0.12.1)"] +benchmarks = ["tensorflow (==2.12.0)", "torch (==2.0.1)", "transformers (==4.30.1)"] +dev = ["Pillow (>=6.2.1)", "absl-py", "apache-beam (>=2.26.0,<2.44.0)", "black (>=23.1,<24.0)", "elasticsearch (<8.0.0)", "faiss-cpu (>=1.6.4)", "joblib (<1.3.0)", "joblibspark", "librosa", "lz4", "py7zr", "pyspark (>=3.4)", "pytest", "pytest-datadir", "pytest-xdist", "pyyaml (>=5.3.1)", "rarfile (>=4.0)", "ruff (>=0.0.241)", "s3fs", "s3fs (>=2021.11.1)", "soundfile (>=0.12.1)", "sqlalchemy (<2.0.0)", "tensorflow (>=2.2.0,!=2.6.0,!=2.6.1)", "tensorflow (>=2.3,!=2.6.0,!=2.6.1)", "tensorflow-macos", "tiktoken", "torch", "transformers", "zstandard"] +docs = ["s3fs", "tensorflow (>=2.2.0,!=2.6.0,!=2.6.1)", "tensorflow-macos", "torch", "transformers"] +jax = ["jax (>=0.2.8,!=0.3.2,<=0.3.25)", "jaxlib (>=0.1.65,<=0.3.25)"] +metrics-tests = ["Werkzeug (>=1.0.1)", "accelerate", "bert-score (>=0.3.6)", "jiwer", "langdetect", "mauve-text", "nltk", "requests-file (>=1.5.1)", "rouge-score", "sacrebleu", "sacremoses", "scikit-learn", "scipy", "sentencepiece", "seqeval", "six (>=1.15.0,<1.16.0)", "spacy (>=3.0.0)", "texttable (>=1.6.3)", "tldextract", "tldextract (>=3.1.0)", "toml (>=0.10.1)", "typer (<0.5.0)"] +quality = ["black (>=23.1,<24.0)", "pyyaml (>=5.3.1)", "ruff (>=0.0.241)"] +s3 = ["s3fs"] +tensorflow = ["tensorflow (>=2.2.0,!=2.6.0,!=2.6.1)", "tensorflow-macos"] +tensorflow-gpu = ["tensorflow-gpu (>=2.2.0,!=2.6.0,!=2.6.1)"] +tests = ["Pillow (>=6.2.1)", "absl-py", "apache-beam (>=2.26.0,<2.44.0)", "elasticsearch (<8.0.0)", "faiss-cpu (>=1.6.4)", "joblib (<1.3.0)", "joblibspark", "librosa", "lz4", "py7zr", "pyspark (>=3.4)", "pytest", "pytest-datadir", "pytest-xdist", "rarfile (>=4.0)", "s3fs (>=2021.11.1)", "soundfile (>=0.12.1)", "sqlalchemy (<2.0.0)", "tensorflow (>=2.3,!=2.6.0,!=2.6.1)", "tensorflow-macos", "tiktoken", "torch", "transformers", "zstandard"] +torch = ["torch"] +vision = ["Pillow (>=6.2.1)"] + +[[package]] +name = "dill" +version = "0.3.7" +description = "serialize all of Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "dill-0.3.7-py3-none-any.whl", hash = "sha256:76b122c08ef4ce2eedcd4d1abd8e641114bfc6c2867f49f3c41facf65bf19f5e"}, + {file = "dill-0.3.7.tar.gz", hash = "sha256:cc1c8b182eb3013e24bd475ff2e9295af86c1a38eb1aff128dac8962a9ce3c03"}, +] + +[package.extras] +graph = ["objgraph (>=1.7.2)"] + [[package]] name = "easydict" version = "1.10" @@ -750,45 +949,45 @@ files = [ [[package]] name = "fonttools" -version = "4.42.0" +version = "4.42.1" description = "Tools to manipulate font files" optional = false python-versions = ">=3.8" files = [ - {file = "fonttools-4.42.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9c456d1f23deff64ffc8b5b098718e149279abdea4d8692dba69172fb6a0d597"}, - {file = "fonttools-4.42.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:150122ed93127a26bc3670ebab7e2add1e0983d30927733aec327ebf4255b072"}, - {file = "fonttools-4.42.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48e82d776d2e93f88ca56567509d102266e7ab2fb707a0326f032fe657335238"}, - {file = "fonttools-4.42.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58c1165f9b2662645de9b19a8c8bdd636b36294ccc07e1b0163856b74f10bafc"}, - {file = "fonttools-4.42.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2d6dc3fa91414ff4daa195c05f946e6a575bd214821e26d17ca50f74b35b0fe4"}, - {file = "fonttools-4.42.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fae4e801b774cc62cecf4a57b1eae4097903fced00c608d9e2bc8f84cd87b54a"}, - {file = "fonttools-4.42.0-cp310-cp310-win32.whl", hash = "sha256:b8600ae7dce6ec3ddfb201abb98c9d53abbf8064d7ac0c8a0d8925e722ccf2a0"}, - {file = "fonttools-4.42.0-cp310-cp310-win_amd64.whl", hash = "sha256:57b68eab183fafac7cd7d464a7bfa0fcd4edf6c67837d14fb09c1c20516cf20b"}, - {file = "fonttools-4.42.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0a1466713e54bdbf5521f2f73eebfe727a528905ff5ec63cda40961b4b1eea95"}, - {file = "fonttools-4.42.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3fb2a69870bfe143ec20b039a1c8009e149dd7780dd89554cc8a11f79e5de86b"}, - {file = "fonttools-4.42.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae881e484702efdb6cf756462622de81d4414c454edfd950b137e9a7352b3cb9"}, - {file = "fonttools-4.42.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27ec3246a088555629f9f0902f7412220c67340553ca91eb540cf247aacb1983"}, - {file = "fonttools-4.42.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8ece1886d12bb36c48c00b2031518877f41abae317e3a55620d38e307d799b7e"}, - {file = "fonttools-4.42.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:10dac980f2b975ef74532e2a94bb00e97a95b4595fb7f98db493c474d5f54d0e"}, - {file = "fonttools-4.42.0-cp311-cp311-win32.whl", hash = "sha256:83b98be5d291e08501bd4fc0c4e0f8e6e05b99f3924068b17c5c9972af6fff84"}, - {file = "fonttools-4.42.0-cp311-cp311-win_amd64.whl", hash = "sha256:e35bed436726194c5e6e094fdfb423fb7afaa0211199f9d245e59e11118c576c"}, - {file = "fonttools-4.42.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c36c904ce0322df01e590ba814d5d69e084e985d7e4c2869378671d79662a7d4"}, - {file = "fonttools-4.42.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d54e600a2bcfa5cdaa860237765c01804a03b08404d6affcd92942fa7315ffba"}, - {file = "fonttools-4.42.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01cfe02416b6d416c5c8d15e30315cbcd3e97d1b50d3b34b0ce59f742ef55258"}, - {file = "fonttools-4.42.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f81ed9065b4bd3f4f3ce8e4873cd6a6b3f4e92b1eddefde35d332c6f414acc3"}, - {file = "fonttools-4.42.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:685a4dd6cf31593b50d6d441feb7781a4a7ef61e19551463e14ed7c527b86f9f"}, - {file = "fonttools-4.42.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:329341ba3d86a36e482610db56b30705384cb23bd595eac8cbb045f627778e9d"}, - {file = "fonttools-4.42.0-cp38-cp38-win32.whl", hash = "sha256:4655c480a1a4d706152ff54f20e20cf7609084016f1df3851cce67cef768f40a"}, - {file = "fonttools-4.42.0-cp38-cp38-win_amd64.whl", hash = "sha256:6bd7e4777bff1dcb7c4eff4786998422770f3bfbef8be401c5332895517ba3fa"}, - {file = "fonttools-4.42.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9b55d2a3b360e0c7fc5bd8badf1503ca1c11dd3a1cd20f2c26787ffa145a9c7"}, - {file = "fonttools-4.42.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0df8ef75ba5791e873c9eac2262196497525e3f07699a2576d3ab9ddf41cb619"}, - {file = "fonttools-4.42.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cd2363ea7728496827658682d049ffb2e98525e2247ca64554864a8cc945568"}, - {file = "fonttools-4.42.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d40673b2e927f7cd0819c6f04489dfbeb337b4a7b10fc633c89bf4f34ecb9620"}, - {file = "fonttools-4.42.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c8bf88f9e3ce347c716921804ef3a8330cb128284eb6c0b6c4b3574f3c580023"}, - {file = "fonttools-4.42.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:703101eb0490fae32baf385385d47787b73d9ea55253df43b487c89ec767e0d7"}, - {file = "fonttools-4.42.0-cp39-cp39-win32.whl", hash = "sha256:f0290ea7f9945174bd4dfd66e96149037441eb2008f3649094f056201d99e293"}, - {file = "fonttools-4.42.0-cp39-cp39-win_amd64.whl", hash = "sha256:ae7df0ae9ee2f3f7676b0ff6f4ebe48ad0acaeeeaa0b6839d15dbf0709f2c5ef"}, - {file = "fonttools-4.42.0-py3-none-any.whl", hash = "sha256:dfe7fa7e607f7e8b58d0c32501a3a7cac148538300626d1b930082c90ae7f6bd"}, - {file = "fonttools-4.42.0.tar.gz", hash = "sha256:614b1283dca88effd20ee48160518e6de275ce9b5456a3134d5f235523fc5065"}, + {file = "fonttools-4.42.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ed1a13a27f59d1fc1920394a7f596792e9d546c9ca5a044419dca70c37815d7c"}, + {file = "fonttools-4.42.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c9b1ce7a45978b821a06d375b83763b27a3a5e8a2e4570b3065abad240a18760"}, + {file = "fonttools-4.42.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f720fa82a11c0f9042376fd509b5ed88dab7e3cd602eee63a1af08883b37342b"}, + {file = "fonttools-4.42.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db55cbaea02a20b49fefbd8e9d62bd481aaabe1f2301dabc575acc6b358874fa"}, + {file = "fonttools-4.42.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a35981d90feebeaef05e46e33e6b9e5b5e618504672ca9cd0ff96b171e4bfff"}, + {file = "fonttools-4.42.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:68a02bbe020dc22ee0540e040117535f06df9358106d3775e8817d826047f3fd"}, + {file = "fonttools-4.42.1-cp310-cp310-win32.whl", hash = "sha256:12a7c247d1b946829bfa2f331107a629ea77dc5391dfd34fdcd78efa61f354ca"}, + {file = "fonttools-4.42.1-cp310-cp310-win_amd64.whl", hash = "sha256:a398bdadb055f8de69f62b0fc70625f7cbdab436bbb31eef5816e28cab083ee8"}, + {file = "fonttools-4.42.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:689508b918332fb40ce117131633647731d098b1b10d092234aa959b4251add5"}, + {file = "fonttools-4.42.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e36344e48af3e3bde867a1ca54f97c308735dd8697005c2d24a86054a114a71"}, + {file = "fonttools-4.42.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19b7db825c8adee96fac0692e6e1ecd858cae9affb3b4812cdb9d934a898b29e"}, + {file = "fonttools-4.42.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:113337c2d29665839b7d90b39f99b3cac731f72a0eda9306165a305c7c31d341"}, + {file = "fonttools-4.42.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:37983b6bdab42c501202500a2be3a572f50d4efe3237e0686ee9d5f794d76b35"}, + {file = "fonttools-4.42.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6ed2662a3d9c832afa36405f8748c250be94ae5dfc5283d668308391f2102861"}, + {file = "fonttools-4.42.1-cp311-cp311-win32.whl", hash = "sha256:179737095eb98332a2744e8f12037b2977f22948cf23ff96656928923ddf560a"}, + {file = "fonttools-4.42.1-cp311-cp311-win_amd64.whl", hash = "sha256:f2b82f46917d8722e6b5eafeefb4fb585d23babd15d8246c664cd88a5bddd19c"}, + {file = "fonttools-4.42.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:62f481ac772fd68901573956231aea3e4b1ad87b9b1089a61613a91e2b50bb9b"}, + {file = "fonttools-4.42.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f2f806990160d1ce42d287aa419df3ffc42dfefe60d473695fb048355fe0c6a0"}, + {file = "fonttools-4.42.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db372213d39fa33af667c2aa586a0c1235e88e9c850f5dd5c8e1f17515861868"}, + {file = "fonttools-4.42.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d18fc642fd0ac29236ff88ecfccff229ec0386090a839dd3f1162e9a7944a40"}, + {file = "fonttools-4.42.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8708b98c278012ad267ee8a7433baeb809948855e81922878118464b274c909d"}, + {file = "fonttools-4.42.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c95b0724a6deea2c8c5d3222191783ced0a2f09bd6d33f93e563f6f1a4b3b3a4"}, + {file = "fonttools-4.42.1-cp38-cp38-win32.whl", hash = "sha256:4aa79366e442dbca6e2c8595645a3a605d9eeabdb7a094d745ed6106816bef5d"}, + {file = "fonttools-4.42.1-cp38-cp38-win_amd64.whl", hash = "sha256:acb47f6f8680de24c1ab65ebde39dd035768e2a9b571a07c7b8da95f6c8815fd"}, + {file = "fonttools-4.42.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5fb289b7a815638a7613d46bcf324c9106804725b2bb8ad913c12b6958ffc4ec"}, + {file = "fonttools-4.42.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:53eb5091ddc8b1199330bb7b4a8a2e7995ad5d43376cadce84523d8223ef3136"}, + {file = "fonttools-4.42.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46a0ec8adbc6ff13494eb0c9c2e643b6f009ce7320cf640de106fb614e4d4360"}, + {file = "fonttools-4.42.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cc7d685b8eeca7ae69dc6416833fbfea61660684b7089bca666067cb2937dcf"}, + {file = "fonttools-4.42.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:be24fcb80493b2c94eae21df70017351851652a37de514de553435b256b2f249"}, + {file = "fonttools-4.42.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:515607ec756d7865f23070682622c49d922901943697871fc292277cf1e71967"}, + {file = "fonttools-4.42.1-cp39-cp39-win32.whl", hash = "sha256:0eb79a2da5eb6457a6f8ab904838454accc7d4cccdaff1fd2bd3a0679ea33d64"}, + {file = "fonttools-4.42.1-cp39-cp39-win_amd64.whl", hash = "sha256:7286aed4ea271df9eab8d7a9b29e507094b51397812f7ce051ecd77915a6e26b"}, + {file = "fonttools-4.42.1-py3-none-any.whl", hash = "sha256:9398f244e28e0596e2ee6024f808b06060109e33ed38dcc9bded452fd9bbb853"}, + {file = "fonttools-4.42.1.tar.gz", hash = "sha256:c391cd5af88aacaf41dd7cfb96eeedfad297b5899a39e12f4c2c3706d0a3329d"}, ] [package.extras] @@ -805,6 +1004,76 @@ ufo = ["fs (>=2.2.0,<3)"] unicode = ["unicodedata2 (>=15.0.0)"] woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] +[[package]] +name = "frozenlist" +version = "1.4.0" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = false +python-versions = ">=3.8" +files = [ + {file = "frozenlist-1.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:764226ceef3125e53ea2cb275000e309c0aa5464d43bd72abd661e27fffc26ab"}, + {file = "frozenlist-1.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d6484756b12f40003c6128bfcc3fa9f0d49a687e171186c2d85ec82e3758c559"}, + {file = "frozenlist-1.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9ac08e601308e41eb533f232dbf6b7e4cea762f9f84f6357136eed926c15d12c"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d081f13b095d74b67d550de04df1c756831f3b83dc9881c38985834387487f1b"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71932b597f9895f011f47f17d6428252fc728ba2ae6024e13c3398a087c2cdea"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:981b9ab5a0a3178ff413bca62526bb784249421c24ad7381e39d67981be2c326"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e41f3de4df3e80de75845d3e743b3f1c4c8613c3997a912dbf0229fc61a8b963"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6918d49b1f90821e93069682c06ffde41829c346c66b721e65a5c62b4bab0300"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e5c8764c7829343d919cc2dfc587a8db01c4f70a4ebbc49abde5d4b158b007b"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8d0edd6b1c7fb94922bf569c9b092ee187a83f03fb1a63076e7774b60f9481a8"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e29cda763f752553fa14c68fb2195150bfab22b352572cb36c43c47bedba70eb"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:0c7c1b47859ee2cac3846fde1c1dc0f15da6cec5a0e5c72d101e0f83dcb67ff9"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:901289d524fdd571be1c7be054f48b1f88ce8dddcbdf1ec698b27d4b8b9e5d62"}, + {file = "frozenlist-1.4.0-cp310-cp310-win32.whl", hash = "sha256:1a0848b52815006ea6596c395f87449f693dc419061cc21e970f139d466dc0a0"}, + {file = "frozenlist-1.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:b206646d176a007466358aa21d85cd8600a415c67c9bd15403336c331a10d956"}, + {file = "frozenlist-1.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:de343e75f40e972bae1ef6090267f8260c1446a1695e77096db6cfa25e759a95"}, + {file = "frozenlist-1.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad2a9eb6d9839ae241701d0918f54c51365a51407fd80f6b8289e2dfca977cc3"}, + {file = "frozenlist-1.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bd7bd3b3830247580de99c99ea2a01416dfc3c34471ca1298bccabf86d0ff4dc"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bdf1847068c362f16b353163391210269e4f0569a3c166bc6a9f74ccbfc7e839"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38461d02d66de17455072c9ba981d35f1d2a73024bee7790ac2f9e361ef1cd0c"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5a32087d720c608f42caed0ef36d2b3ea61a9d09ee59a5142d6070da9041b8f"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd65632acaf0d47608190a71bfe46b209719bf2beb59507db08ccdbe712f969b"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:261b9f5d17cac914531331ff1b1d452125bf5daa05faf73b71d935485b0c510b"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b89ac9768b82205936771f8d2eb3ce88503b1556324c9f903e7156669f521472"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:008eb8b31b3ea6896da16c38c1b136cb9fec9e249e77f6211d479db79a4eaf01"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e74b0506fa5aa5598ac6a975a12aa8928cbb58e1f5ac8360792ef15de1aa848f"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:490132667476f6781b4c9458298b0c1cddf237488abd228b0b3650e5ecba7467"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:76d4711f6f6d08551a7e9ef28c722f4a50dd0fc204c56b4bcd95c6cc05ce6fbb"}, + {file = "frozenlist-1.4.0-cp311-cp311-win32.whl", hash = "sha256:a02eb8ab2b8f200179b5f62b59757685ae9987996ae549ccf30f983f40602431"}, + {file = "frozenlist-1.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:515e1abc578dd3b275d6a5114030b1330ba044ffba03f94091842852f806f1c1"}, + {file = "frozenlist-1.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f0ed05f5079c708fe74bf9027e95125334b6978bf07fd5ab923e9e55e5fbb9d3"}, + {file = "frozenlist-1.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ca265542ca427bf97aed183c1676e2a9c66942e822b14dc6e5f42e038f92a503"}, + {file = "frozenlist-1.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:491e014f5c43656da08958808588cc6c016847b4360e327a62cb308c791bd2d9"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17ae5cd0f333f94f2e03aaf140bb762c64783935cc764ff9c82dff626089bebf"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e78fb68cf9c1a6aa4a9a12e960a5c9dfbdb89b3695197aa7064705662515de2"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5655a942f5f5d2c9ed93d72148226d75369b4f6952680211972a33e59b1dfdc"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c11b0746f5d946fecf750428a95f3e9ebe792c1ee3b1e96eeba145dc631a9672"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e66d2a64d44d50d2543405fb183a21f76b3b5fd16f130f5c99187c3fb4e64919"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:88f7bc0fcca81f985f78dd0fa68d2c75abf8272b1f5c323ea4a01a4d7a614efc"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5833593c25ac59ede40ed4de6d67eb42928cca97f26feea219f21d0ed0959b79"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:fec520865f42e5c7f050c2a79038897b1c7d1595e907a9e08e3353293ffc948e"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:b826d97e4276750beca7c8f0f1a4938892697a6bcd8ec8217b3312dad6982781"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ceb6ec0a10c65540421e20ebd29083c50e6d1143278746a4ef6bcf6153171eb8"}, + {file = "frozenlist-1.4.0-cp38-cp38-win32.whl", hash = "sha256:2b8bcf994563466db019fab287ff390fffbfdb4f905fc77bc1c1d604b1c689cc"}, + {file = "frozenlist-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:a6c8097e01886188e5be3e6b14e94ab365f384736aa1fca6a0b9e35bd4a30bc7"}, + {file = "frozenlist-1.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6c38721585f285203e4b4132a352eb3daa19121a035f3182e08e437cface44bf"}, + {file = "frozenlist-1.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a0c6da9aee33ff0b1a451e867da0c1f47408112b3391dd43133838339e410963"}, + {file = "frozenlist-1.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:93ea75c050c5bb3d98016b4ba2497851eadf0ac154d88a67d7a6816206f6fa7f"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f61e2dc5ad442c52b4887f1fdc112f97caeff4d9e6ebe78879364ac59f1663e1"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa384489fefeb62321b238e64c07ef48398fe80f9e1e6afeff22e140e0850eef"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10ff5faaa22786315ef57097a279b833ecab1a0bfb07d604c9cbb1c4cdc2ed87"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:007df07a6e3eb3e33e9a1fe6a9db7af152bbd8a185f9aaa6ece10a3529e3e1c6"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f4f399d28478d1f604c2ff9119907af9726aed73680e5ed1ca634d377abb087"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c5374b80521d3d3f2ec5572e05adc94601985cc526fb276d0c8574a6d749f1b3"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ce31ae3e19f3c902de379cf1323d90c649425b86de7bbdf82871b8a2a0615f3d"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7211ef110a9194b6042449431e08c4d80c0481e5891e58d429df5899690511c2"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:556de4430ce324c836789fa4560ca62d1591d2538b8ceb0b4f68fb7b2384a27a"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7645a8e814a3ee34a89c4a372011dcd817964ce8cb273c8ed6119d706e9613e3"}, + {file = "frozenlist-1.4.0-cp39-cp39-win32.whl", hash = "sha256:19488c57c12d4e8095a922f328df3f179c820c212940a498623ed39160bc3c2f"}, + {file = "frozenlist-1.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:6221d84d463fb110bdd7619b69cb43878a11d51cbb9394ae3105d082d5199167"}, + {file = "frozenlist-1.4.0.tar.gz", hash = "sha256:09163bdf0b2907454042edb19f887c6d33806adc71fbd54afc14908bfdc22251"}, +] + [[package]] name = "fsspec" version = "2023.6.0" @@ -816,6 +1085,10 @@ files = [ {file = "fsspec-2023.6.0.tar.gz", hash = "sha256:d0b2f935446169753e7a5c5c55681c54ea91996cc67be93c39a154fb3a2742af"}, ] +[package.dependencies] +aiohttp = {version = "<4.0.0a0 || >4.0.0a0,<4.0.0a1 || >4.0.0a1", optional = true, markers = "extra == \"http\""} +requests = {version = "*", optional = true, markers = "extra == \"http\""} + [package.extras] abfs = ["adlfs"] adl = ["adlfs"] @@ -840,6 +1113,20 @@ smb = ["smbprotocol"] ssh = ["paramiko"] tqdm = ["tqdm"] +[[package]] +name = "ftfy" +version = "6.1.1" +description = "Fixes mojibake and other problems with Unicode, after the fact" +optional = false +python-versions = ">=3.7,<4" +files = [ + {file = "ftfy-6.1.1-py3-none-any.whl", hash = "sha256:0ffd33fce16b54cccaec78d6ec73d95ad370e5df5a25255c8966a6147bd667ca"}, + {file = "ftfy-6.1.1.tar.gz", hash = "sha256:bfc2019f84fcd851419152320a6375604a0f1459c281b5b199b2cd0d2e727f8f"}, +] + +[package.dependencies] +wcwidth = ">=0.2.5" + [[package]] name = "gevent" version = "23.7.0" @@ -1422,13 +1709,13 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "joblib" -version = "1.3.1" +version = "1.3.2" description = "Lightweight pipelining with Python functions" optional = false python-versions = ">=3.7" files = [ - {file = "joblib-1.3.1-py3-none-any.whl", hash = "sha256:89cf0529520e01b3de7ac7b74a8102c90d16d54c64b5dd98cafcd14307fdf915"}, - {file = "joblib-1.3.1.tar.gz", hash = "sha256:1f937906df65329ba98013dc9692fe22a4c5e4a648112de500508b18a21b41e3"}, + {file = "joblib-1.3.2-py3-none-any.whl", hash = "sha256:ef4331c65f239985f3f2220ecc87db222f08fd22097a3dd5698f693875f8cbb9"}, + {file = "joblib-1.3.2.tar.gz", hash = "sha256:92f865e621e17784e7955080b6d042489e3b8e294949cc44c6eac304f59772b1"}, ] [[package]] @@ -1550,6 +1837,30 @@ roundrobin = ">=0.0.2" typing-extensions = ">=3.7.4.3" Werkzeug = ">=2.0.0" +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + [[package]] name = "markupsafe" version = "2.1.3" @@ -1670,6 +1981,17 @@ pillow = ">=6.2.0" pyparsing = ">=2.3.1,<3.1" python-dateutil = ">=2.7" +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + [[package]] name = "mpmath" version = "1.3.0" @@ -1759,39 +2081,151 @@ files = [ {file = "msgpack-1.0.5.tar.gz", hash = "sha256:c075544284eadc5cddc70f4757331d99dcbc16b2bbd4849d15f8aae4cf36d31c"}, ] +[[package]] +name = "multidict" +version = "6.0.4" +description = "multidict implementation" +optional = false +python-versions = ">=3.7" +files = [ + {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"}, + {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"}, + {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"}, + {file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"}, + {file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"}, + {file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"}, + {file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"}, + {file = "multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"}, + {file = "multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"}, + {file = "multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"}, + {file = "multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"}, + {file = "multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"}, + {file = "multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"}, + {file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"}, + {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, +] + +[[package]] +name = "multiprocess" +version = "0.70.15" +description = "better multiprocessing and multithreading in Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "multiprocess-0.70.15-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:aa36c7ed16f508091438687fe9baa393a7a8e206731d321e443745e743a0d4e5"}, + {file = "multiprocess-0.70.15-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:20e024018c46d0d1602024c613007ac948f9754659e3853b0aa705e83f6931d8"}, + {file = "multiprocess-0.70.15-pp37-pypy37_pp73-manylinux_2_24_i686.whl", hash = "sha256:e576062981c91f0fe8a463c3d52506e598dfc51320a8dd8d78b987dfca91c5db"}, + {file = "multiprocess-0.70.15-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:e73f497e6696a0f5433ada2b3d599ae733b87a6e8b008e387c62ac9127add177"}, + {file = "multiprocess-0.70.15-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:73db2e7b32dcc7f9b0f075c2ffa45c90b6729d3f1805f27e88534c8d321a1be5"}, + {file = "multiprocess-0.70.15-pp38-pypy38_pp73-manylinux_2_24_i686.whl", hash = "sha256:4271647bd8a49c28ecd6eb56a7fdbd3c212c45529ad5303b40b3c65fc6928e5f"}, + {file = "multiprocess-0.70.15-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:cf981fb998d6ec3208cb14f0cf2e9e80216e834f5d51fd09ebc937c32b960902"}, + {file = "multiprocess-0.70.15-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:18f9f2c7063346d1617bd1684fdcae8d33380ae96b99427260f562e1a1228b67"}, + {file = "multiprocess-0.70.15-pp39-pypy39_pp73-manylinux_2_24_i686.whl", hash = "sha256:0eac53214d664c49a34695e5824872db4006b1a465edd7459a251809c3773370"}, + {file = "multiprocess-0.70.15-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:1a51dd34096db47fb21fa2b839e615b051d51b97af9a67afbcdaa67186b44883"}, + {file = "multiprocess-0.70.15-py310-none-any.whl", hash = "sha256:7dd58e33235e83cf09d625e55cffd7b0f0eede7ee9223cdd666a87624f60c21a"}, + {file = "multiprocess-0.70.15-py311-none-any.whl", hash = "sha256:134f89053d82c9ed3b73edd3a2531eb791e602d4f4156fc92a79259590bd9670"}, + {file = "multiprocess-0.70.15-py37-none-any.whl", hash = "sha256:f7d4a1629bccb433114c3b4885f69eccc200994323c80f6feee73b0edc9199c5"}, + {file = "multiprocess-0.70.15-py38-none-any.whl", hash = "sha256:bee9afba476c91f9ebee7beeee0601face9eff67d822e893f9a893725fbd6316"}, + {file = "multiprocess-0.70.15-py39-none-any.whl", hash = "sha256:3e0953f5d52b4c76f1c973eaf8214554d146f2be5decb48e928e55c7a2d19338"}, + {file = "multiprocess-0.70.15.tar.gz", hash = "sha256:f20eed3036c0ef477b07a4177cf7c1ba520d9a2677870a4f47fe026f0cd6787e"}, +] + +[package.dependencies] +dill = ">=0.3.7" + [[package]] name = "mypy" -version = "1.4.1" +version = "1.5.1" description = "Optional static typing for Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "mypy-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8"}, - {file = "mypy-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878"}, - {file = "mypy-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dde1d180cd84f0624c5dcaaa89c89775550a675aff96b5848de78fb11adabcd"}, - {file = "mypy-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c4d8e89aa7de683e2056a581ce63c46a0c41e31bd2b6d34144e2c80f5ea53dc"}, - {file = "mypy-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:bfdca17c36ae01a21274a3c387a63aa1aafe72bff976522886869ef131b937f1"}, - {file = "mypy-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7549fbf655e5825d787bbc9ecf6028731973f78088fbca3a1f4145c39ef09462"}, - {file = "mypy-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98324ec3ecf12296e6422939e54763faedbfcc502ea4a4c38502082711867258"}, - {file = "mypy-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141dedfdbfe8a04142881ff30ce6e6653c9685b354876b12e4fe6c78598b45e2"}, - {file = "mypy-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8207b7105829eca6f3d774f64a904190bb2231de91b8b186d21ffd98005f14a7"}, - {file = "mypy-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:16f0db5b641ba159eff72cff08edc3875f2b62b2fa2bc24f68c1e7a4e8232d01"}, - {file = "mypy-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:470c969bb3f9a9efcedbadcd19a74ffb34a25f8e6b0e02dae7c0e71f8372f97b"}, - {file = "mypy-1.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5952d2d18b79f7dc25e62e014fe5a23eb1a3d2bc66318df8988a01b1a037c5b"}, - {file = "mypy-1.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:190b6bab0302cec4e9e6767d3eb66085aef2a1cc98fe04936d8a42ed2ba77bb7"}, - {file = "mypy-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9d40652cc4fe33871ad3338581dca3297ff5f2213d0df345bcfbde5162abf0c9"}, - {file = "mypy-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01fd2e9f85622d981fd9063bfaef1aed6e336eaacca00892cd2d82801ab7c042"}, - {file = "mypy-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2460a58faeea905aeb1b9b36f5065f2dc9a9c6e4c992a6499a2360c6c74ceca3"}, - {file = "mypy-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2746d69a8196698146a3dbe29104f9eb6a2a4d8a27878d92169a6c0b74435b6"}, - {file = "mypy-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae704dcfaa180ff7c4cfbad23e74321a2b774f92ca77fd94ce1049175a21c97f"}, - {file = "mypy-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:43d24f6437925ce50139a310a64b2ab048cb2d3694c84c71c3f2a1626d8101dc"}, - {file = "mypy-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c482e1246726616088532b5e964e39765b6d1520791348e6c9dc3af25b233828"}, - {file = "mypy-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43b592511672017f5b1a483527fd2684347fdffc041c9ef53428c8dc530f79a3"}, - {file = "mypy-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34a9239d5b3502c17f07fd7c0b2ae6b7dd7d7f6af35fbb5072c6208e76295816"}, - {file = "mypy-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5703097c4936bbb9e9bce41478c8d08edd2865e177dc4c52be759f81ee4dd26c"}, - {file = "mypy-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e02d700ec8d9b1859790c0475df4e4092c7bf3272a4fd2c9f33d87fac4427b8f"}, - {file = "mypy-1.4.1-py3-none-any.whl", hash = "sha256:45d32cec14e7b97af848bddd97d85ea4f0db4d5a149ed9676caa4eb2f7402bb4"}, - {file = "mypy-1.4.1.tar.gz", hash = "sha256:9bbcd9ab8ea1f2e1c8031c21445b511442cc45c89951e49bbf852cbb70755b1b"}, + {file = "mypy-1.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f33592ddf9655a4894aef22d134de7393e95fcbdc2d15c1ab65828eee5c66c70"}, + {file = "mypy-1.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:258b22210a4a258ccd077426c7a181d789d1121aca6db73a83f79372f5569ae0"}, + {file = "mypy-1.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9ec1f695f0c25986e6f7f8778e5ce61659063268836a38c951200c57479cc12"}, + {file = "mypy-1.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:abed92d9c8f08643c7d831300b739562b0a6c9fcb028d211134fc9ab20ccad5d"}, + {file = "mypy-1.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:a156e6390944c265eb56afa67c74c0636f10283429171018446b732f1a05af25"}, + {file = "mypy-1.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ac9c21bfe7bc9f7f1b6fae441746e6a106e48fc9de530dea29e8cd37a2c0cc4"}, + {file = "mypy-1.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51cb1323064b1099e177098cb939eab2da42fea5d818d40113957ec954fc85f4"}, + {file = "mypy-1.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:596fae69f2bfcb7305808c75c00f81fe2829b6236eadda536f00610ac5ec2243"}, + {file = "mypy-1.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:32cb59609b0534f0bd67faebb6e022fe534bdb0e2ecab4290d683d248be1b275"}, + {file = "mypy-1.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:159aa9acb16086b79bbb0016145034a1a05360626046a929f84579ce1666b315"}, + {file = "mypy-1.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f6b0e77db9ff4fda74de7df13f30016a0a663928d669c9f2c057048ba44f09bb"}, + {file = "mypy-1.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26f71b535dfc158a71264e6dc805a9f8d2e60b67215ca0bfa26e2e1aa4d4d373"}, + {file = "mypy-1.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fc3a600f749b1008cc75e02b6fb3d4db8dbcca2d733030fe7a3b3502902f161"}, + {file = "mypy-1.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:26fb32e4d4afa205b24bf645eddfbb36a1e17e995c5c99d6d00edb24b693406a"}, + {file = "mypy-1.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:82cb6193de9bbb3844bab4c7cf80e6227d5225cc7625b068a06d005d861ad5f1"}, + {file = "mypy-1.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4a465ea2ca12804d5b34bb056be3a29dc47aea5973b892d0417c6a10a40b2d65"}, + {file = "mypy-1.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9fece120dbb041771a63eb95e4896791386fe287fefb2837258925b8326d6160"}, + {file = "mypy-1.5.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d28ddc3e3dfeab553e743e532fb95b4e6afad51d4706dd22f28e1e5e664828d2"}, + {file = "mypy-1.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:57b10c56016adce71fba6bc6e9fd45d8083f74361f629390c556738565af8eeb"}, + {file = "mypy-1.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:ff0cedc84184115202475bbb46dd99f8dcb87fe24d5d0ddfc0fe6b8575c88d2f"}, + {file = "mypy-1.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8f772942d372c8cbac575be99f9cc9d9fb3bd95c8bc2de6c01411e2c84ebca8a"}, + {file = "mypy-1.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5d627124700b92b6bbaa99f27cbe615c8ea7b3402960f6372ea7d65faf376c14"}, + {file = "mypy-1.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:361da43c4f5a96173220eb53340ace68cda81845cd88218f8862dfb0adc8cddb"}, + {file = "mypy-1.5.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:330857f9507c24de5c5724235e66858f8364a0693894342485e543f5b07c8693"}, + {file = "mypy-1.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:c543214ffdd422623e9fedd0869166c2f16affe4ba37463975043ef7d2ea8770"}, + {file = "mypy-1.5.1-py3-none-any.whl", hash = "sha256:f757063a83970d67c444f6e01d9550a7402322af3557ce7630d3c957386fa8f5"}, + {file = "mypy-1.5.1.tar.gz", hash = "sha256:b031b9601f1060bf1281feab89697324726ba0c0bae9d7cd7ab4b690940f0b92"}, ] [package.dependencies] @@ -1801,7 +2235,6 @@ typing-extensions = ">=4.1.0" [package.extras] dmypy = ["psutil (>=4.0)"] install-types = ["pip"] -python2 = ["typed-ast (>=1.4.0,<2)"] reports = ["lxml"] [[package]] @@ -1833,31 +2266,6 @@ doc = ["nb2plots (>=0.6)", "numpydoc (>=1.5)", "pillow (>=9.4)", "pydata-sphinx- extra = ["lxml (>=4.6)", "pydot (>=1.4.2)", "pygraphviz (>=1.10)", "sympy (>=1.10)"] test = ["codecov (>=2.1)", "pytest (>=7.2)", "pytest-cov (>=4.0)"] -[[package]] -name = "nltk" -version = "3.8.1" -description = "Natural Language Toolkit" -optional = false -python-versions = ">=3.7" -files = [ - {file = "nltk-3.8.1-py3-none-any.whl", hash = "sha256:fd5c9109f976fa86bcadba8f91e47f5e9293bd034474752e92a520f81c93dda5"}, - {file = "nltk-3.8.1.zip", hash = "sha256:1834da3d0682cba4f2cede2f9aad6b0fafb6461ba451db0efb6f9c39798d64d3"}, -] - -[package.dependencies] -click = "*" -joblib = "*" -regex = ">=2021.8.3" -tqdm = "*" - -[package.extras] -all = ["matplotlib", "numpy", "pyparsing", "python-crfsuite", "requests", "scikit-learn", "scipy", "twython"] -corenlp = ["requests"] -machine-learning = ["numpy", "python-crfsuite", "scikit-learn", "scipy"] -plot = ["matplotlib"] -tgrep = ["pyparsing"] -twitter = ["twython"] - [[package]] name = "numpy" version = "1.25.2" @@ -1981,20 +2389,45 @@ packaging = "*" protobuf = "*" sympy = "*" +[[package]] +name = "open-clip-torch" +version = "2.20.0" +description = "OpenCLIP" +optional = false +python-versions = ">=3.7" +files = [ + {file = "open_clip_torch-2.20.0-py3-none-any.whl", hash = "sha256:6288207de5b0e1cdd7824ab58186dfc9757a05a3bfd7674d10b1005e3653b25d"}, + {file = "open_clip_torch-2.20.0.tar.gz", hash = "sha256:735d599d24dbecc29ae6277bca5587152bf58b3fb0c961b734f448fc24040e85"}, +] + +[package.dependencies] +ftfy = "*" +huggingface-hub = "*" +protobuf = "<4" +regex = "*" +sentencepiece = "*" +timm = "*" +torch = ">=1.9.0" +torchvision = "*" +tqdm = "*" + +[package.extras] +training = ["braceexpand", "fsspec", "ftfy", "huggingface-hub", "pandas", "regex", "timm", "torch (>=1.9.0)", "torchvision", "tqdm", "transformers", "webdataset (>=0.2.5)"] + [[package]] name = "opencv-python-headless" -version = "4.8.0.74" +version = "4.8.0.76" description = "Wrapper package for OpenCV python bindings." optional = false python-versions = ">=3.6" files = [ - {file = "opencv-python-headless-4.8.0.74.tar.gz", hash = "sha256:a72770b8f2e08358b1faa41c8372b17d040aa9bb3e446ab7090e358f6f4e91ba"}, - {file = "opencv_python_headless-4.8.0.74-cp37-abi3-macosx_10_16_x86_64.whl", hash = "sha256:f645b17667fc9bffec8c3b3a72a217430c8d7090ed3ebb80fd8efc657f372d6c"}, - {file = "opencv_python_headless-4.8.0.74-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:7c88744ce922b81747c86f2b7f1b3ec84ff2f53477ee9219a5e75ae2012b443f"}, - {file = "opencv_python_headless-4.8.0.74-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34e92fef90e8f768d92dca875aefaff8b11556427f82484378a3848eb690d4c0"}, - {file = "opencv_python_headless-4.8.0.74-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8a88620c43a5d17f7df8317767da93b935656ab9236df92e1e2e0dd23195756"}, - {file = "opencv_python_headless-4.8.0.74-cp37-abi3-win32.whl", hash = "sha256:b52a6eb2d92fff0f660e6aade958cb4373fbfcbafa0488f7ae88c4e6ce3f47f6"}, - {file = "opencv_python_headless-4.8.0.74-cp37-abi3-win_amd64.whl", hash = "sha256:19d30269ae710f67e0ae549361a68b755e28599a572d5f72d0bbb177ab438a28"}, + {file = "opencv-python-headless-4.8.0.76.tar.gz", hash = "sha256:bc15726187dae26d8a08777faf6bc71d38f20c785c102677f58ba0e935003afb"}, + {file = "opencv_python_headless-4.8.0.76-cp37-abi3-macosx_10_16_x86_64.whl", hash = "sha256:f85d2e3b9d952db35d31f9db8882d073c903921b72b8db1cfed8bbc75e8d3e63"}, + {file = "opencv_python_headless-4.8.0.76-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:8ee3bf1c9086493c340c6a87899f1c7778d729de92bce8560b8c31ab8a9cdf79"}, + {file = "opencv_python_headless-4.8.0.76-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c675b8dec6298ba6a1eec2ce24077a393b4236a043f68dfacb06bf594354ce06"}, + {file = "opencv_python_headless-4.8.0.76-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:220d2e292fa45ef0582aab730460bbc15cfe61f2089208167a372ccf76f01e21"}, + {file = "opencv_python_headless-4.8.0.76-cp37-abi3-win32.whl", hash = "sha256:df0608de207ae9b094ad9eaf1a475cf6e9a069fb12cd289d4a18cefdab2f8aa8"}, + {file = "opencv_python_headless-4.8.0.76-cp37-abi3-win_amd64.whl", hash = "sha256:9c094faf6ec7bd360244647b26ebdf8f54edec1d9292cb9179fff9badcca7be8"}, ] [package.dependencies] @@ -2007,6 +2440,49 @@ numpy = [ {version = ">=1.17.3", markers = "python_version >= \"3.8\""}, ] +[[package]] +name = "optimum" +version = "1.11.2" +description = "Optimum Library is an extension of the Hugging Face Transformers library, providing a framework to integrate third-party libraries from Hardware Partners and interface with their specific functionality." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "optimum-1.11.2-py3-none-any.whl", hash = "sha256:1382d923f95e053db677b1bc84803828921b41dd15d094cc61deab33ab5d4fb2"}, + {file = "optimum-1.11.2.tar.gz", hash = "sha256:664fa01aa4734d13ba291d41e7b901d333cea45b6c55d66098f43160bf7533ab"}, +] + +[package.dependencies] +coloredlogs = "*" +datasets = "*" +huggingface-hub = ">=0.8.0" +numpy = "*" +packaging = "*" +sympy = "*" +torch = ">=1.9" +transformers = {version = ">=4.26.0", extras = ["sentencepiece"]} + +[package.extras] +benchmark = ["evaluate (>=0.2.0)", "optuna", "scikit-learn", "seqeval", "torchvision", "tqdm"] +dev = ["Pillow", "black (>=23.1,<24.0)", "diffusers (>=0.17.0)", "einops", "invisible-watermark", "parameterized", "pytest", "pytest-xdist", "requests", "ruff (>=0.0.241,<=0.0.259)", "sacremoses", "torchaudio", "torchvision"] +diffusers = ["diffusers"] +doc-build = ["accelerate"] +exporters = ["onnx", "onnxruntime", "timm"] +exporters-gpu = ["onnx", "onnxruntime-gpu", "timm"] +exporters-tf = ["h5py", "numpy (<1.24.0)", "onnx", "onnxruntime", "tensorflow (>=2.4)", "tf2onnx", "timm"] +furiosa = ["optimum-furiosa"] +graphcore = ["optimum-graphcore"] +habana = ["optimum-habana"] +intel = ["optimum-intel (>=1.10.1)"] +neural-compressor = ["optimum-intel[neural-compressor] (>=1.9.2)"] +neuron = ["optimum-neuron[neuron]"] +neuronx = ["optimum-neuron[neuronx]"] +nncf = ["optimum-intel[nncf] (>=1.10.1)"] +onnxruntime = ["datasets (>=1.2.1)", "evaluate", "onnx", "onnxruntime (>=1.11.0)", "protobuf (>=3.20.1)"] +onnxruntime-gpu = ["accelerate", "datasets (>=1.2.1)", "evaluate", "onnx", "onnxruntime-gpu (>=1.11.0)", "protobuf (>=3.20.1)"] +openvino = ["optimum-intel[openvino] (>=1.10.1)"] +quality = ["black (>=23.1,<24.0)", "ruff (>=0.0.241,<=0.0.259)"] +tests = ["Pillow", "diffusers (>=0.17.0)", "einops", "invisible-watermark", "parameterized", "pytest", "pytest-xdist", "requests", "sacremoses", "torchaudio", "torchvision"] + [[package]] name = "packaging" version = "23.1" @@ -2018,6 +2494,72 @@ files = [ {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, ] +[[package]] +name = "pandas" +version = "2.0.3" +description = "Powerful data structures for data analysis, time series, and statistics" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pandas-2.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c7c9f27a4185304c7caf96dc7d91bc60bc162221152de697c98eb0b2648dd8"}, + {file = "pandas-2.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f167beed68918d62bffb6ec64f2e1d8a7d297a038f86d4aed056b9493fca407f"}, + {file = "pandas-2.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce0c6f76a0f1ba361551f3e6dceaff06bde7514a374aa43e33b588ec10420183"}, + {file = "pandas-2.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba619e410a21d8c387a1ea6e8a0e49bb42216474436245718d7f2e88a2f8d7c0"}, + {file = "pandas-2.0.3-cp310-cp310-win32.whl", hash = "sha256:3ef285093b4fe5058eefd756100a367f27029913760773c8bf1d2d8bebe5d210"}, + {file = "pandas-2.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:9ee1a69328d5c36c98d8e74db06f4ad518a1840e8ccb94a4ba86920986bb617e"}, + {file = "pandas-2.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b084b91d8d66ab19f5bb3256cbd5ea661848338301940e17f4492b2ce0801fe8"}, + {file = "pandas-2.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37673e3bdf1551b95bf5d4ce372b37770f9529743d2498032439371fc7b7eb26"}, + {file = "pandas-2.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9cb1e14fdb546396b7e1b923ffaeeac24e4cedd14266c3497216dd4448e4f2d"}, + {file = "pandas-2.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9cd88488cceb7635aebb84809d087468eb33551097d600c6dad13602029c2df"}, + {file = "pandas-2.0.3-cp311-cp311-win32.whl", hash = "sha256:694888a81198786f0e164ee3a581df7d505024fbb1f15202fc7db88a71d84ebd"}, + {file = "pandas-2.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:6a21ab5c89dcbd57f78d0ae16630b090eec626360085a4148693def5452d8a6b"}, + {file = "pandas-2.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4da0d45e7f34c069fe4d522359df7d23badf83abc1d1cef398895822d11061"}, + {file = "pandas-2.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:32fca2ee1b0d93dd71d979726b12b61faa06aeb93cf77468776287f41ff8fdc5"}, + {file = "pandas-2.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:258d3624b3ae734490e4d63c430256e716f488c4fcb7c8e9bde2d3aa46c29089"}, + {file = "pandas-2.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eae3dc34fa1aa7772dd3fc60270d13ced7346fcbcfee017d3132ec625e23bb0"}, + {file = "pandas-2.0.3-cp38-cp38-win32.whl", hash = "sha256:f3421a7afb1a43f7e38e82e844e2bca9a6d793d66c1a7f9f0ff39a795bbc5e02"}, + {file = "pandas-2.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:69d7f3884c95da3a31ef82b7618af5710dba95bb885ffab339aad925c3e8ce78"}, + {file = "pandas-2.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5247fb1ba347c1261cbbf0fcfba4a3121fbb4029d95d9ef4dc45406620b25c8b"}, + {file = "pandas-2.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:81af086f4543c9d8bb128328b5d32e9986e0c84d3ee673a2ac6fb57fd14f755e"}, + {file = "pandas-2.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1994c789bf12a7c5098277fb43836ce090f1073858c10f9220998ac74f37c69b"}, + {file = "pandas-2.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ec591c48e29226bcbb316e0c1e9423622bc7a4eaf1ef7c3c9fa1a3981f89641"}, + {file = "pandas-2.0.3-cp39-cp39-win32.whl", hash = "sha256:04dbdbaf2e4d46ca8da896e1805bc04eb85caa9a82e259e8eed00254d5e0c682"}, + {file = "pandas-2.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:1168574b036cd8b93abc746171c9b4f1b83467438a5e45909fed645cf8692dbc"}, + {file = "pandas-2.0.3.tar.gz", hash = "sha256:c02f372a88e0d17f36d3093a644c73cfc1788e876a7c4bcb4020a77512e2043c"}, +] + +[package.dependencies] +numpy = [ + {version = ">=1.21.0", markers = "python_version >= \"3.10\""}, + {version = ">=1.23.2", markers = "python_version >= \"3.11\""}, +] +python-dateutil = ">=2.8.2" +pytz = ">=2020.1" +tzdata = ">=2022.1" + +[package.extras] +all = ["PyQt5 (>=5.15.1)", "SQLAlchemy (>=1.4.16)", "beautifulsoup4 (>=4.9.3)", "bottleneck (>=1.3.2)", "brotlipy (>=0.7.0)", "fastparquet (>=0.6.3)", "fsspec (>=2021.07.0)", "gcsfs (>=2021.07.0)", "html5lib (>=1.1)", "hypothesis (>=6.34.2)", "jinja2 (>=3.0.0)", "lxml (>=4.6.3)", "matplotlib (>=3.6.1)", "numba (>=0.53.1)", "numexpr (>=2.7.3)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.7)", "pandas-gbq (>=0.15.0)", "psycopg2 (>=2.8.6)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.2)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)", "python-snappy (>=0.6.0)", "pyxlsb (>=1.0.8)", "qtpy (>=2.2.0)", "s3fs (>=2021.08.0)", "scipy (>=1.7.1)", "tables (>=3.6.1)", "tabulate (>=0.8.9)", "xarray (>=0.21.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=1.4.3)", "zstandard (>=0.15.2)"] +aws = ["s3fs (>=2021.08.0)"] +clipboard = ["PyQt5 (>=5.15.1)", "qtpy (>=2.2.0)"] +compression = ["brotlipy (>=0.7.0)", "python-snappy (>=0.6.0)", "zstandard (>=0.15.2)"] +computation = ["scipy (>=1.7.1)", "xarray (>=0.21.0)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.0.7)", "pyxlsb (>=1.0.8)", "xlrd (>=2.0.1)", "xlsxwriter (>=1.4.3)"] +feather = ["pyarrow (>=7.0.0)"] +fss = ["fsspec (>=2021.07.0)"] +gcp = ["gcsfs (>=2021.07.0)", "pandas-gbq (>=0.15.0)"] +hdf5 = ["tables (>=3.6.1)"] +html = ["beautifulsoup4 (>=4.9.3)", "html5lib (>=1.1)", "lxml (>=4.6.3)"] +mysql = ["SQLAlchemy (>=1.4.16)", "pymysql (>=1.0.2)"] +output-formatting = ["jinja2 (>=3.0.0)", "tabulate (>=0.8.9)"] +parquet = ["pyarrow (>=7.0.0)"] +performance = ["bottleneck (>=1.3.2)", "numba (>=0.53.1)", "numexpr (>=2.7.1)"] +plot = ["matplotlib (>=3.6.1)"] +postgresql = ["SQLAlchemy (>=1.4.16)", "psycopg2 (>=2.8.6)"] +spss = ["pyreadstat (>=1.1.2)"] +sql-other = ["SQLAlchemy (>=1.4.16)"] +test = ["hypothesis (>=6.34.2)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)"] +xml = ["lxml (>=4.6.3)"] + [[package]] name = "pathspec" version = "0.11.2" @@ -2156,24 +2698,33 @@ tests = ["pytest", "pytest-cov", "pytest-lazy-fixture"] [[package]] name = "protobuf" -version = "4.23.4" -description = "" +version = "3.20.3" +description = "Protocol Buffers" optional = false python-versions = ">=3.7" files = [ - {file = "protobuf-4.23.4-cp310-abi3-win32.whl", hash = "sha256:5fea3c64d41ea5ecf5697b83e41d09b9589e6f20b677ab3c48e5f242d9b7897b"}, - {file = "protobuf-4.23.4-cp310-abi3-win_amd64.whl", hash = "sha256:7b19b6266d92ca6a2a87effa88ecc4af73ebc5cfde194dc737cf8ef23a9a3b12"}, - {file = "protobuf-4.23.4-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8547bf44fe8cec3c69e3042f5c4fb3e36eb2a7a013bb0a44c018fc1e427aafbd"}, - {file = "protobuf-4.23.4-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:fee88269a090ada09ca63551bf2f573eb2424035bcf2cb1b121895b01a46594a"}, - {file = "protobuf-4.23.4-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:effeac51ab79332d44fba74660d40ae79985901ac21bca408f8dc335a81aa597"}, - {file = "protobuf-4.23.4-cp37-cp37m-win32.whl", hash = "sha256:c3e0939433c40796ca4cfc0fac08af50b00eb66a40bbbc5dee711998fb0bbc1e"}, - {file = "protobuf-4.23.4-cp37-cp37m-win_amd64.whl", hash = "sha256:9053df6df8e5a76c84339ee4a9f5a2661ceee4a0dab019e8663c50ba324208b0"}, - {file = "protobuf-4.23.4-cp38-cp38-win32.whl", hash = "sha256:e1c915778d8ced71e26fcf43c0866d7499891bca14c4368448a82edc61fdbc70"}, - {file = "protobuf-4.23.4-cp38-cp38-win_amd64.whl", hash = "sha256:351cc90f7d10839c480aeb9b870a211e322bf05f6ab3f55fcb2f51331f80a7d2"}, - {file = "protobuf-4.23.4-cp39-cp39-win32.whl", hash = "sha256:6dd9b9940e3f17077e820b75851126615ee38643c2c5332aa7a359988820c720"}, - {file = "protobuf-4.23.4-cp39-cp39-win_amd64.whl", hash = "sha256:0a5759f5696895de8cc913f084e27fd4125e8fb0914bb729a17816a33819f474"}, - {file = "protobuf-4.23.4-py3-none-any.whl", hash = "sha256:e9d0be5bf34b275b9f87ba7407796556abeeba635455d036c7351f7c183ef8ff"}, - {file = "protobuf-4.23.4.tar.gz", hash = "sha256:ccd9430c0719dce806b93f89c91de7977304729e55377f872a92465d548329a9"}, + {file = "protobuf-3.20.3-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:f4bd856d702e5b0d96a00ec6b307b0f51c1982c2bf9c0052cf9019e9a544ba99"}, + {file = "protobuf-3.20.3-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9aae4406ea63d825636cc11ffb34ad3379335803216ee3a856787bcf5ccc751e"}, + {file = "protobuf-3.20.3-cp310-cp310-win32.whl", hash = "sha256:28545383d61f55b57cf4df63eebd9827754fd2dc25f80c5253f9184235db242c"}, + {file = "protobuf-3.20.3-cp310-cp310-win_amd64.whl", hash = "sha256:67a3598f0a2dcbc58d02dd1928544e7d88f764b47d4a286202913f0b2801c2e7"}, + {file = "protobuf-3.20.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:899dc660cd599d7352d6f10d83c95df430a38b410c1b66b407a6b29265d66469"}, + {file = "protobuf-3.20.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e64857f395505ebf3d2569935506ae0dfc4a15cb80dc25261176c784662cdcc4"}, + {file = "protobuf-3.20.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:d9e4432ff660d67d775c66ac42a67cf2453c27cb4d738fc22cb53b5d84c135d4"}, + {file = "protobuf-3.20.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:74480f79a023f90dc6e18febbf7b8bac7508420f2006fabd512013c0c238f454"}, + {file = "protobuf-3.20.3-cp37-cp37m-win32.whl", hash = "sha256:b6cc7ba72a8850621bfec987cb72623e703b7fe2b9127a161ce61e61558ad905"}, + {file = "protobuf-3.20.3-cp37-cp37m-win_amd64.whl", hash = "sha256:8c0c984a1b8fef4086329ff8dd19ac77576b384079247c770f29cc8ce3afa06c"}, + {file = "protobuf-3.20.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:de78575669dddf6099a8a0f46a27e82a1783c557ccc38ee620ed8cc96d3be7d7"}, + {file = "protobuf-3.20.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:f4c42102bc82a51108e449cbb32b19b180022941c727bac0cfd50170341f16ee"}, + {file = "protobuf-3.20.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:44246bab5dd4b7fbd3c0c80b6f16686808fab0e4aca819ade6e8d294a29c7050"}, + {file = "protobuf-3.20.3-cp38-cp38-win32.whl", hash = "sha256:c02ce36ec760252242a33967d51c289fd0e1c0e6e5cc9397e2279177716add86"}, + {file = "protobuf-3.20.3-cp38-cp38-win_amd64.whl", hash = "sha256:447d43819997825d4e71bf5769d869b968ce96848b6479397e29fc24c4a5dfe9"}, + {file = "protobuf-3.20.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:398a9e0c3eaceb34ec1aee71894ca3299605fa8e761544934378bbc6c97de23b"}, + {file = "protobuf-3.20.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:bf01b5720be110540be4286e791db73f84a2b721072a3711efff6c324cdf074b"}, + {file = "protobuf-3.20.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:daa564862dd0d39c00f8086f88700fdbe8bc717e993a21e90711acfed02f2402"}, + {file = "protobuf-3.20.3-cp39-cp39-win32.whl", hash = "sha256:819559cafa1a373b7096a482b504ae8a857c89593cf3a25af743ac9ecbd23480"}, + {file = "protobuf-3.20.3-cp39-cp39-win_amd64.whl", hash = "sha256:03038ac1cfbc41aa21f6afcbcd357281d7521b4157926f30ebecc8d4ea59dcb7"}, + {file = "protobuf-3.20.3-py2.py3-none-any.whl", hash = "sha256:a7ca6d488aa8ff7f329d4c545b2dbad8ac31464f1d8b1c87ad1346717731e4db"}, + {file = "protobuf-3.20.3.tar.gz", hash = "sha256:2e3427429c9cffebf259491be0af70189607f365c2f41c7c3764af6f337105f2"}, ] [[package]] @@ -2202,6 +2753,43 @@ files = [ [package.extras] test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] +[[package]] +name = "pyarrow" +version = "12.0.1" +description = "Python library for Apache Arrow" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyarrow-12.0.1-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:6d288029a94a9bb5407ceebdd7110ba398a00412c5b0155ee9813a40d246c5df"}, + {file = "pyarrow-12.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:345e1828efdbd9aa4d4de7d5676778aba384a2c3add896d995b23d368e60e5af"}, + {file = "pyarrow-12.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d6009fdf8986332b2169314da482baed47ac053311c8934ac6651e614deacd6"}, + {file = "pyarrow-12.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d3c4cbbf81e6dd23fe921bc91dc4619ea3b79bc58ef10bce0f49bdafb103daf"}, + {file = "pyarrow-12.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:cdacf515ec276709ac8042c7d9bd5be83b4f5f39c6c037a17a60d7ebfd92c890"}, + {file = "pyarrow-12.0.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:749be7fd2ff260683f9cc739cb862fb11be376de965a2a8ccbf2693b098db6c7"}, + {file = "pyarrow-12.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6895b5fb74289d055c43db3af0de6e16b07586c45763cb5e558d38b86a91e3a7"}, + {file = "pyarrow-12.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1887bdae17ec3b4c046fcf19951e71b6a619f39fa674f9881216173566c8f718"}, + {file = "pyarrow-12.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2c9cb8eeabbadf5fcfc3d1ddea616c7ce893db2ce4dcef0ac13b099ad7ca082"}, + {file = "pyarrow-12.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:ce4aebdf412bd0eeb800d8e47db854f9f9f7e2f5a0220440acf219ddfddd4f63"}, + {file = "pyarrow-12.0.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:e0d8730c7f6e893f6db5d5b86eda42c0a130842d101992b581e2138e4d5663d3"}, + {file = "pyarrow-12.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43364daec02f69fec89d2315f7fbfbeec956e0d991cbbef471681bd77875c40f"}, + {file = "pyarrow-12.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:051f9f5ccf585f12d7de836e50965b3c235542cc896959320d9776ab93f3b33d"}, + {file = "pyarrow-12.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:be2757e9275875d2a9c6e6052ac7957fbbfc7bc7370e4a036a9b893e96fedaba"}, + {file = "pyarrow-12.0.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:cf812306d66f40f69e684300f7af5111c11f6e0d89d6b733e05a3de44961529d"}, + {file = "pyarrow-12.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:459a1c0ed2d68671188b2118c63bac91eaef6fc150c77ddd8a583e3c795737bf"}, + {file = "pyarrow-12.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85e705e33eaf666bbe508a16fd5ba27ca061e177916b7a317ba5a51bee43384c"}, + {file = "pyarrow-12.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9120c3eb2b1f6f516a3b7a9714ed860882d9ef98c4b17edcdc91d95b7528db60"}, + {file = "pyarrow-12.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:c780f4dc40460015d80fcd6a6140de80b615349ed68ef9adb653fe351778c9b3"}, + {file = "pyarrow-12.0.1-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:a3c63124fc26bf5f95f508f5d04e1ece8cc23a8b0af2a1e6ab2b1ec3fdc91b24"}, + {file = "pyarrow-12.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b13329f79fa4472324f8d32dc1b1216616d09bd1e77cfb13104dec5463632c36"}, + {file = "pyarrow-12.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb656150d3d12ec1396f6dde542db1675a95c0cc8366d507347b0beed96e87ca"}, + {file = "pyarrow-12.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6251e38470da97a5b2e00de5c6a049149f7b2bd62f12fa5dbb9ac674119ba71a"}, + {file = "pyarrow-12.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:3de26da901216149ce086920547dfff5cd22818c9eab67ebc41e863a5883bac7"}, + {file = "pyarrow-12.0.1.tar.gz", hash = "sha256:cce317fc96e5b71107bf1f9f184d5e54e2bd14bbf3f9a3d62819961f0af86fec"}, +] + +[package.dependencies] +numpy = ">=1.16.6" + [[package]] name = "pycparser" version = "2.21" @@ -2265,6 +2853,20 @@ typing-extensions = ">=4.2.0" dotenv = ["python-dotenv (>=0.10.4)"] email = ["email-validator (>=1.0.3)"] +[[package]] +name = "pygments" +version = "2.16.1" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, + {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, +] + +[package.extras] +plugins = ["importlib-metadata"] + [[package]] name = "pyparsing" version = "3.0.9" @@ -2391,6 +2993,17 @@ files = [ [package.extras] cli = ["click (>=5.0)"] +[[package]] +name = "pytz" +version = "2023.3" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"}, + {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, +] + [[package]] name = "pywavelets" version = "1.4.1" @@ -2502,88 +3115,104 @@ files = [ [[package]] name = "pyzmq" -version = "25.1.0" +version = "25.1.1" description = "Python bindings for 0MQ" optional = false python-versions = ">=3.6" files = [ - {file = "pyzmq-25.1.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:1a6169e69034eaa06823da6a93a7739ff38716142b3596c180363dee729d713d"}, - {file = "pyzmq-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:19d0383b1f18411d137d891cab567de9afa609b214de68b86e20173dc624c101"}, - {file = "pyzmq-25.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1e931d9a92f628858a50f5bdffdfcf839aebe388b82f9d2ccd5d22a38a789dc"}, - {file = "pyzmq-25.1.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:97d984b1b2f574bc1bb58296d3c0b64b10e95e7026f8716ed6c0b86d4679843f"}, - {file = "pyzmq-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:154bddda2a351161474b36dba03bf1463377ec226a13458725183e508840df89"}, - {file = "pyzmq-25.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:cb6d161ae94fb35bb518b74bb06b7293299c15ba3bc099dccd6a5b7ae589aee3"}, - {file = "pyzmq-25.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:90146ab578931e0e2826ee39d0c948d0ea72734378f1898939d18bc9c823fcf9"}, - {file = "pyzmq-25.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:831ba20b660b39e39e5ac8603e8193f8fce1ee03a42c84ade89c36a251449d80"}, - {file = "pyzmq-25.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3a522510e3434e12aff80187144c6df556bb06fe6b9d01b2ecfbd2b5bfa5c60c"}, - {file = "pyzmq-25.1.0-cp310-cp310-win32.whl", hash = "sha256:be24a5867b8e3b9dd5c241de359a9a5217698ff616ac2daa47713ba2ebe30ad1"}, - {file = "pyzmq-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:5693dcc4f163481cf79e98cf2d7995c60e43809e325b77a7748d8024b1b7bcba"}, - {file = "pyzmq-25.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:13bbe36da3f8aaf2b7ec12696253c0bf6ffe05f4507985a8844a1081db6ec22d"}, - {file = "pyzmq-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:69511d604368f3dc58d4be1b0bad99b61ee92b44afe1cd9b7bd8c5e34ea8248a"}, - {file = "pyzmq-25.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a983c8694667fd76d793ada77fd36c8317e76aa66eec75be2653cef2ea72883"}, - {file = "pyzmq-25.1.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:332616f95eb400492103ab9d542b69d5f0ff628b23129a4bc0a2fd48da6e4e0b"}, - {file = "pyzmq-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58416db767787aedbfd57116714aad6c9ce57215ffa1c3758a52403f7c68cff5"}, - {file = "pyzmq-25.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:cad9545f5801a125f162d09ec9b724b7ad9b6440151b89645241d0120e119dcc"}, - {file = "pyzmq-25.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d6128d431b8dfa888bf51c22a04d48bcb3d64431caf02b3cb943269f17fd2994"}, - {file = "pyzmq-25.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:2b15247c49d8cbea695b321ae5478d47cffd496a2ec5ef47131a9e79ddd7e46c"}, - {file = "pyzmq-25.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:442d3efc77ca4d35bee3547a8e08e8d4bb88dadb54a8377014938ba98d2e074a"}, - {file = "pyzmq-25.1.0-cp311-cp311-win32.whl", hash = "sha256:65346f507a815a731092421d0d7d60ed551a80d9b75e8b684307d435a5597425"}, - {file = "pyzmq-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:8b45d722046fea5a5694cba5d86f21f78f0052b40a4bbbbf60128ac55bfcc7b6"}, - {file = "pyzmq-25.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f45808eda8b1d71308c5416ef3abe958f033fdbb356984fabbfc7887bed76b3f"}, - {file = "pyzmq-25.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b697774ea8273e3c0460cf0bba16cd85ca6c46dfe8b303211816d68c492e132"}, - {file = "pyzmq-25.1.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b324fa769577fc2c8f5efcd429cef5acbc17d63fe15ed16d6dcbac2c5eb00849"}, - {file = "pyzmq-25.1.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:5873d6a60b778848ce23b6c0ac26c39e48969823882f607516b91fb323ce80e5"}, - {file = "pyzmq-25.1.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:f0d9e7ba6a815a12c8575ba7887da4b72483e4cfc57179af10c9b937f3f9308f"}, - {file = "pyzmq-25.1.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:414b8beec76521358b49170db7b9967d6974bdfc3297f47f7d23edec37329b00"}, - {file = "pyzmq-25.1.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:01f06f33e12497dca86353c354461f75275a5ad9eaea181ac0dc1662da8074fa"}, - {file = "pyzmq-25.1.0-cp36-cp36m-win32.whl", hash = "sha256:b5a07c4f29bf7cb0164664ef87e4aa25435dcc1f818d29842118b0ac1eb8e2b5"}, - {file = "pyzmq-25.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:968b0c737797c1809ec602e082cb63e9824ff2329275336bb88bd71591e94a90"}, - {file = "pyzmq-25.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:47b915ba666c51391836d7ed9a745926b22c434efa76c119f77bcffa64d2c50c"}, - {file = "pyzmq-25.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5af31493663cf76dd36b00dafbc839e83bbca8a0662931e11816d75f36155897"}, - {file = "pyzmq-25.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5489738a692bc7ee9a0a7765979c8a572520d616d12d949eaffc6e061b82b4d1"}, - {file = "pyzmq-25.1.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1fc56a0221bdf67cfa94ef2d6ce5513a3d209c3dfd21fed4d4e87eca1822e3a3"}, - {file = "pyzmq-25.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:75217e83faea9edbc29516fc90c817bc40c6b21a5771ecb53e868e45594826b0"}, - {file = "pyzmq-25.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3830be8826639d801de9053cf86350ed6742c4321ba4236e4b5568528d7bfed7"}, - {file = "pyzmq-25.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3575699d7fd7c9b2108bc1c6128641a9a825a58577775ada26c02eb29e09c517"}, - {file = "pyzmq-25.1.0-cp37-cp37m-win32.whl", hash = "sha256:95bd3a998d8c68b76679f6b18f520904af5204f089beebb7b0301d97704634dd"}, - {file = "pyzmq-25.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:dbc466744a2db4b7ca05589f21ae1a35066afada2f803f92369f5877c100ef62"}, - {file = "pyzmq-25.1.0-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:3bed53f7218490c68f0e82a29c92335daa9606216e51c64f37b48eb78f1281f4"}, - {file = "pyzmq-25.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:eb52e826d16c09ef87132c6e360e1879c984f19a4f62d8a935345deac43f3c12"}, - {file = "pyzmq-25.1.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ddbef8b53cd16467fdbfa92a712eae46dd066aa19780681a2ce266e88fbc7165"}, - {file = "pyzmq-25.1.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9301cf1d7fc1ddf668d0abbe3e227fc9ab15bc036a31c247276012abb921b5ff"}, - {file = "pyzmq-25.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7e23a8c3b6c06de40bdb9e06288180d630b562db8ac199e8cc535af81f90e64b"}, - {file = "pyzmq-25.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4a82faae00d1eed4809c2f18b37f15ce39a10a1c58fe48b60ad02875d6e13d80"}, - {file = "pyzmq-25.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c8398a1b1951aaa330269c35335ae69744be166e67e0ebd9869bdc09426f3871"}, - {file = "pyzmq-25.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d40682ac60b2a613d36d8d3a0cd14fbdf8e7e0618fbb40aa9fa7b796c9081584"}, - {file = "pyzmq-25.1.0-cp38-cp38-win32.whl", hash = "sha256:33d5c8391a34d56224bccf74f458d82fc6e24b3213fc68165c98b708c7a69325"}, - {file = "pyzmq-25.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:c66b7ff2527e18554030319b1376d81560ca0742c6e0b17ff1ee96624a5f1afd"}, - {file = "pyzmq-25.1.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:af56229ea6527a849ac9fb154a059d7e32e77a8cba27e3e62a1e38d8808cb1a5"}, - {file = "pyzmq-25.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bdca18b94c404af6ae5533cd1bc310c4931f7ac97c148bbfd2cd4bdd62b96253"}, - {file = "pyzmq-25.1.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0b6b42f7055bbc562f63f3df3b63e3dd1ebe9727ff0f124c3aa7bcea7b3a00f9"}, - {file = "pyzmq-25.1.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4c2fc7aad520a97d64ffc98190fce6b64152bde57a10c704b337082679e74f67"}, - {file = "pyzmq-25.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be86a26415a8b6af02cd8d782e3a9ae3872140a057f1cadf0133de685185c02b"}, - {file = "pyzmq-25.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:851fb2fe14036cfc1960d806628b80276af5424db09fe5c91c726890c8e6d943"}, - {file = "pyzmq-25.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2a21fec5c3cea45421a19ccbe6250c82f97af4175bc09de4d6dd78fb0cb4c200"}, - {file = "pyzmq-25.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bad172aba822444b32eae54c2d5ab18cd7dee9814fd5c7ed026603b8cae2d05f"}, - {file = "pyzmq-25.1.0-cp39-cp39-win32.whl", hash = "sha256:4d67609b37204acad3d566bb7391e0ecc25ef8bae22ff72ebe2ad7ffb7847158"}, - {file = "pyzmq-25.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:71c7b5896e40720d30cd77a81e62b433b981005bbff0cb2f739e0f8d059b5d99"}, - {file = "pyzmq-25.1.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4cb27ef9d3bdc0c195b2dc54fcb8720e18b741624686a81942e14c8b67cc61a6"}, - {file = "pyzmq-25.1.0-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0c4fc2741e0513b5d5a12fe200d6785bbcc621f6f2278893a9ca7bed7f2efb7d"}, - {file = "pyzmq-25.1.0-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fc34fdd458ff77a2a00e3c86f899911f6f269d393ca5675842a6e92eea565bae"}, - {file = "pyzmq-25.1.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8751f9c1442624da391bbd92bd4b072def6d7702a9390e4479f45c182392ff78"}, - {file = "pyzmq-25.1.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:6581e886aec3135964a302a0f5eb68f964869b9efd1dbafdebceaaf2934f8a68"}, - {file = "pyzmq-25.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5482f08d2c3c42b920e8771ae8932fbaa0a67dff925fc476996ddd8155a170f3"}, - {file = "pyzmq-25.1.0-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5e7fbcafa3ea16d1de1f213c226005fea21ee16ed56134b75b2dede5a2129e62"}, - {file = "pyzmq-25.1.0-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:adecf6d02b1beab8d7c04bc36f22bb0e4c65a35eb0b4750b91693631d4081c70"}, - {file = "pyzmq-25.1.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6d39e42a0aa888122d1beb8ec0d4ddfb6c6b45aecb5ba4013c27e2f28657765"}, - {file = "pyzmq-25.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:7018289b402ebf2b2c06992813523de61d4ce17bd514c4339d8f27a6f6809492"}, - {file = "pyzmq-25.1.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9e68ae9864d260b18f311b68d29134d8776d82e7f5d75ce898b40a88df9db30f"}, - {file = "pyzmq-25.1.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e21cc00e4debe8f54c3ed7b9fcca540f46eee12762a9fa56feb8512fd9057161"}, - {file = "pyzmq-25.1.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f666ae327a6899ff560d741681fdcdf4506f990595201ed39b44278c471ad98"}, - {file = "pyzmq-25.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f5efcc29056dfe95e9c9db0dfbb12b62db9c4ad302f812931b6d21dd04a9119"}, - {file = "pyzmq-25.1.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:48e5e59e77c1a83162ab3c163fc01cd2eebc5b34560341a67421b09be0891287"}, - {file = "pyzmq-25.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:108c96ebbd573d929740d66e4c3d1bdf31d5cde003b8dc7811a3c8c5b0fc173b"}, - {file = "pyzmq-25.1.0.tar.gz", hash = "sha256:80c41023465d36280e801564a69cbfce8ae85ff79b080e1913f6e90481fb8957"}, + {file = "pyzmq-25.1.1-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:381469297409c5adf9a0e884c5eb5186ed33137badcbbb0560b86e910a2f1e76"}, + {file = "pyzmq-25.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:955215ed0604dac5b01907424dfa28b40f2b2292d6493445dd34d0dfa72586a8"}, + {file = "pyzmq-25.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:985bbb1316192b98f32e25e7b9958088431d853ac63aca1d2c236f40afb17c83"}, + {file = "pyzmq-25.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:afea96f64efa98df4da6958bae37f1cbea7932c35878b185e5982821bc883369"}, + {file = "pyzmq-25.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76705c9325d72a81155bb6ab48d4312e0032bf045fb0754889133200f7a0d849"}, + {file = "pyzmq-25.1.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:77a41c26205d2353a4c94d02be51d6cbdf63c06fbc1295ea57dad7e2d3381b71"}, + {file = "pyzmq-25.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:12720a53e61c3b99d87262294e2b375c915fea93c31fc2336898c26d7aed34cd"}, + {file = "pyzmq-25.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:57459b68e5cd85b0be8184382cefd91959cafe79ae019e6b1ae6e2ba8a12cda7"}, + {file = "pyzmq-25.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:292fe3fc5ad4a75bc8df0dfaee7d0babe8b1f4ceb596437213821f761b4589f9"}, + {file = "pyzmq-25.1.1-cp310-cp310-win32.whl", hash = "sha256:35b5ab8c28978fbbb86ea54958cd89f5176ce747c1fb3d87356cf698048a7790"}, + {file = "pyzmq-25.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:11baebdd5fc5b475d484195e49bae2dc64b94a5208f7c89954e9e354fc609d8f"}, + {file = "pyzmq-25.1.1-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:d20a0ddb3e989e8807d83225a27e5c2eb2260eaa851532086e9e0fa0d5287d83"}, + {file = "pyzmq-25.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e1c1be77bc5fb77d923850f82e55a928f8638f64a61f00ff18a67c7404faf008"}, + {file = "pyzmq-25.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d89528b4943d27029a2818f847c10c2cecc79fa9590f3cb1860459a5be7933eb"}, + {file = "pyzmq-25.1.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:90f26dc6d5f241ba358bef79be9ce06de58d477ca8485e3291675436d3827cf8"}, + {file = "pyzmq-25.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2b92812bd214018e50b6380ea3ac0c8bb01ac07fcc14c5f86a5bb25e74026e9"}, + {file = "pyzmq-25.1.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:2f957ce63d13c28730f7fd6b72333814221c84ca2421298f66e5143f81c9f91f"}, + {file = "pyzmq-25.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:047a640f5c9c6ade7b1cc6680a0e28c9dd5a0825135acbd3569cc96ea00b2505"}, + {file = "pyzmq-25.1.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7f7e58effd14b641c5e4dec8c7dab02fb67a13df90329e61c869b9cc607ef752"}, + {file = "pyzmq-25.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c2910967e6ab16bf6fbeb1f771c89a7050947221ae12a5b0b60f3bca2ee19bca"}, + {file = "pyzmq-25.1.1-cp311-cp311-win32.whl", hash = "sha256:76c1c8efb3ca3a1818b837aea423ff8a07bbf7aafe9f2f6582b61a0458b1a329"}, + {file = "pyzmq-25.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:44e58a0554b21fc662f2712814a746635ed668d0fbc98b7cb9d74cb798d202e6"}, + {file = "pyzmq-25.1.1-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:e1ffa1c924e8c72778b9ccd386a7067cddf626884fd8277f503c48bb5f51c762"}, + {file = "pyzmq-25.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1af379b33ef33757224da93e9da62e6471cf4a66d10078cf32bae8127d3d0d4a"}, + {file = "pyzmq-25.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cff084c6933680d1f8b2f3b4ff5bbb88538a4aac00d199ac13f49d0698727ecb"}, + {file = "pyzmq-25.1.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2400a94f7dd9cb20cd012951a0cbf8249e3d554c63a9c0cdfd5cbb6c01d2dec"}, + {file = "pyzmq-25.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d81f1ddae3858b8299d1da72dd7d19dd36aab654c19671aa8a7e7fb02f6638a"}, + {file = "pyzmq-25.1.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:255ca2b219f9e5a3a9ef3081512e1358bd4760ce77828e1028b818ff5610b87b"}, + {file = "pyzmq-25.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a882ac0a351288dd18ecae3326b8a49d10c61a68b01419f3a0b9a306190baf69"}, + {file = "pyzmq-25.1.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:724c292bb26365659fc434e9567b3f1adbdb5e8d640c936ed901f49e03e5d32e"}, + {file = "pyzmq-25.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ca1ed0bb2d850aa8471387882247c68f1e62a4af0ce9c8a1dbe0d2bf69e41fb"}, + {file = "pyzmq-25.1.1-cp312-cp312-win32.whl", hash = "sha256:b3451108ab861040754fa5208bca4a5496c65875710f76789a9ad27c801a0075"}, + {file = "pyzmq-25.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:eadbefd5e92ef8a345f0525b5cfd01cf4e4cc651a2cffb8f23c0dd184975d787"}, + {file = "pyzmq-25.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:db0b2af416ba735c6304c47f75d348f498b92952f5e3e8bff449336d2728795d"}, + {file = "pyzmq-25.1.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7c133e93b405eb0d36fa430c94185bdd13c36204a8635470cccc200723c13bb"}, + {file = "pyzmq-25.1.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:273bc3959bcbff3f48606b28229b4721716598d76b5aaea2b4a9d0ab454ec062"}, + {file = "pyzmq-25.1.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cbc8df5c6a88ba5ae385d8930da02201165408dde8d8322072e3e5ddd4f68e22"}, + {file = "pyzmq-25.1.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:18d43df3f2302d836f2a56f17e5663e398416e9dd74b205b179065e61f1a6edf"}, + {file = "pyzmq-25.1.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:73461eed88a88c866656e08f89299720a38cb4e9d34ae6bf5df6f71102570f2e"}, + {file = "pyzmq-25.1.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:34c850ce7976d19ebe7b9d4b9bb8c9dfc7aac336c0958e2651b88cbd46682123"}, + {file = "pyzmq-25.1.1-cp36-cp36m-win32.whl", hash = "sha256:d2045d6d9439a0078f2a34b57c7b18c4a6aef0bee37f22e4ec9f32456c852c71"}, + {file = "pyzmq-25.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:458dea649f2f02a0b244ae6aef8dc29325a2810aa26b07af8374dc2a9faf57e3"}, + {file = "pyzmq-25.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7cff25c5b315e63b07a36f0c2bab32c58eafbe57d0dce61b614ef4c76058c115"}, + {file = "pyzmq-25.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1579413ae492b05de5a6174574f8c44c2b9b122a42015c5292afa4be2507f28"}, + {file = "pyzmq-25.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3d0a409d3b28607cc427aa5c30a6f1e4452cc44e311f843e05edb28ab5e36da0"}, + {file = "pyzmq-25.1.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:21eb4e609a154a57c520e3d5bfa0d97e49b6872ea057b7c85257b11e78068222"}, + {file = "pyzmq-25.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:034239843541ef7a1aee0c7b2cb7f6aafffb005ede965ae9cbd49d5ff4ff73cf"}, + {file = "pyzmq-25.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f8115e303280ba09f3898194791a153862cbf9eef722ad8f7f741987ee2a97c7"}, + {file = "pyzmq-25.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1a5d26fe8f32f137e784f768143728438877d69a586ddeaad898558dc971a5ae"}, + {file = "pyzmq-25.1.1-cp37-cp37m-win32.whl", hash = "sha256:f32260e556a983bc5c7ed588d04c942c9a8f9c2e99213fec11a031e316874c7e"}, + {file = "pyzmq-25.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:abf34e43c531bbb510ae7e8f5b2b1f2a8ab93219510e2b287a944432fad135f3"}, + {file = "pyzmq-25.1.1-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:87e34f31ca8f168c56d6fbf99692cc8d3b445abb5bfd08c229ae992d7547a92a"}, + {file = "pyzmq-25.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c9c6c9b2c2f80747a98f34ef491c4d7b1a8d4853937bb1492774992a120f475d"}, + {file = "pyzmq-25.1.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5619f3f5a4db5dbb572b095ea3cb5cc035335159d9da950830c9c4db2fbb6995"}, + {file = "pyzmq-25.1.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5a34d2395073ef862b4032343cf0c32a712f3ab49d7ec4f42c9661e0294d106f"}, + {file = "pyzmq-25.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25f0e6b78220aba09815cd1f3a32b9c7cb3e02cb846d1cfc526b6595f6046618"}, + {file = "pyzmq-25.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3669cf8ee3520c2f13b2e0351c41fea919852b220988d2049249db10046a7afb"}, + {file = "pyzmq-25.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2d163a18819277e49911f7461567bda923461c50b19d169a062536fffe7cd9d2"}, + {file = "pyzmq-25.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:df27ffddff4190667d40de7beba4a950b5ce78fe28a7dcc41d6f8a700a80a3c0"}, + {file = "pyzmq-25.1.1-cp38-cp38-win32.whl", hash = "sha256:a382372898a07479bd34bda781008e4a954ed8750f17891e794521c3e21c2e1c"}, + {file = "pyzmq-25.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:52533489f28d62eb1258a965f2aba28a82aa747202c8fa5a1c7a43b5db0e85c1"}, + {file = "pyzmq-25.1.1-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:03b3f49b57264909aacd0741892f2aecf2f51fb053e7d8ac6767f6c700832f45"}, + {file = "pyzmq-25.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:330f9e188d0d89080cde66dc7470f57d1926ff2fb5576227f14d5be7ab30b9fa"}, + {file = "pyzmq-25.1.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2ca57a5be0389f2a65e6d3bb2962a971688cbdd30b4c0bd188c99e39c234f414"}, + {file = "pyzmq-25.1.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d457aed310f2670f59cc5b57dcfced452aeeed77f9da2b9763616bd57e4dbaae"}, + {file = "pyzmq-25.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c56d748ea50215abef7030c72b60dd723ed5b5c7e65e7bc2504e77843631c1a6"}, + {file = "pyzmq-25.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8f03d3f0d01cb5a018debeb412441996a517b11c5c17ab2001aa0597c6d6882c"}, + {file = "pyzmq-25.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:820c4a08195a681252f46926de10e29b6bbf3e17b30037bd4250d72dd3ddaab8"}, + {file = "pyzmq-25.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:17ef5f01d25b67ca8f98120d5fa1d21efe9611604e8eb03a5147360f517dd1e2"}, + {file = "pyzmq-25.1.1-cp39-cp39-win32.whl", hash = "sha256:04ccbed567171579ec2cebb9c8a3e30801723c575601f9a990ab25bcac6b51e2"}, + {file = "pyzmq-25.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:e61f091c3ba0c3578411ef505992d356a812fb200643eab27f4f70eed34a29ef"}, + {file = "pyzmq-25.1.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ade6d25bb29c4555d718ac6d1443a7386595528c33d6b133b258f65f963bb0f6"}, + {file = "pyzmq-25.1.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0c95ddd4f6e9fca4e9e3afaa4f9df8552f0ba5d1004e89ef0a68e1f1f9807c7"}, + {file = "pyzmq-25.1.1-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48e466162a24daf86f6b5ca72444d2bf39a5e58da5f96370078be67c67adc978"}, + {file = "pyzmq-25.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abc719161780932c4e11aaebb203be3d6acc6b38d2f26c0f523b5b59d2fc1996"}, + {file = "pyzmq-25.1.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1ccf825981640b8c34ae54231b7ed00271822ea1c6d8ba1090ebd4943759abf5"}, + {file = "pyzmq-25.1.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c2f20ce161ebdb0091a10c9ca0372e023ce24980d0e1f810f519da6f79c60800"}, + {file = "pyzmq-25.1.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:deee9ca4727f53464daf089536e68b13e6104e84a37820a88b0a057b97bba2d2"}, + {file = "pyzmq-25.1.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:aa8d6cdc8b8aa19ceb319aaa2b660cdaccc533ec477eeb1309e2a291eaacc43a"}, + {file = "pyzmq-25.1.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:019e59ef5c5256a2c7378f2fb8560fc2a9ff1d315755204295b2eab96b254d0a"}, + {file = "pyzmq-25.1.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:b9af3757495c1ee3b5c4e945c1df7be95562277c6e5bccc20a39aec50f826cd0"}, + {file = "pyzmq-25.1.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:548d6482dc8aadbe7e79d1b5806585c8120bafa1ef841167bc9090522b610fa6"}, + {file = "pyzmq-25.1.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:057e824b2aae50accc0f9a0570998adc021b372478a921506fddd6c02e60308e"}, + {file = "pyzmq-25.1.1-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2243700cc5548cff20963f0ca92d3e5e436394375ab8a354bbea2b12911b20b0"}, + {file = "pyzmq-25.1.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79986f3b4af059777111409ee517da24a529bdbd46da578b33f25580adcff728"}, + {file = "pyzmq-25.1.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:11d58723d44d6ed4dd677c5615b2ffb19d5c426636345567d6af82be4dff8a55"}, + {file = "pyzmq-25.1.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:49d238cf4b69652257db66d0c623cd3e09b5d2e9576b56bc067a396133a00d4a"}, + {file = "pyzmq-25.1.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fedbdc753827cf014c01dbbee9c3be17e5a208dcd1bf8641ce2cd29580d1f0d4"}, + {file = "pyzmq-25.1.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc16ac425cc927d0a57d242589f87ee093884ea4804c05a13834d07c20db203c"}, + {file = "pyzmq-25.1.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11c1d2aed9079c6b0c9550a7257a836b4a637feb334904610f06d70eb44c56d2"}, + {file = "pyzmq-25.1.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e8a701123029cc240cea61dd2d16ad57cab4691804143ce80ecd9286b464d180"}, + {file = "pyzmq-25.1.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:61706a6b6c24bdece85ff177fec393545a3191eeda35b07aaa1458a027ad1304"}, + {file = "pyzmq-25.1.1.tar.gz", hash = "sha256:259c22485b71abacdfa8bf79720cd7bcf4b9d128b30ea554f01ae71fdbfdaa23"}, ] [package.dependencies] @@ -2608,99 +3237,99 @@ typing-extensions = "*" [[package]] name = "regex" -version = "2023.6.3" +version = "2023.8.8" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.6" files = [ - {file = "regex-2023.6.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:824bf3ac11001849aec3fa1d69abcb67aac3e150a933963fb12bda5151fe1bfd"}, - {file = "regex-2023.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:05ed27acdf4465c95826962528f9e8d41dbf9b1aa8531a387dee6ed215a3e9ef"}, - {file = "regex-2023.6.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b49c764f88a79160fa64f9a7b425620e87c9f46095ef9c9920542ab2495c8bc"}, - {file = "regex-2023.6.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8e3f1316c2293e5469f8f09dc2d76efb6c3982d3da91ba95061a7e69489a14ef"}, - {file = "regex-2023.6.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:43e1dd9d12df9004246bacb79a0e5886b3b6071b32e41f83b0acbf293f820ee8"}, - {file = "regex-2023.6.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4959e8bcbfda5146477d21c3a8ad81b185cd252f3d0d6e4724a5ef11c012fb06"}, - {file = "regex-2023.6.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af4dd387354dc83a3bff67127a124c21116feb0d2ef536805c454721c5d7993d"}, - {file = "regex-2023.6.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2239d95d8e243658b8dbb36b12bd10c33ad6e6933a54d36ff053713f129aa536"}, - {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:890e5a11c97cf0d0c550eb661b937a1e45431ffa79803b942a057c4fb12a2da2"}, - {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a8105e9af3b029f243ab11ad47c19b566482c150c754e4c717900a798806b222"}, - {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:25be746a8ec7bc7b082783216de8e9473803706723b3f6bef34b3d0ed03d57e2"}, - {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:3676f1dd082be28b1266c93f618ee07741b704ab7b68501a173ce7d8d0d0ca18"}, - {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:10cb847aeb1728412c666ab2e2000ba6f174f25b2bdc7292e7dd71b16db07568"}, - {file = "regex-2023.6.3-cp310-cp310-win32.whl", hash = "sha256:dbbbfce33cd98f97f6bffb17801b0576e653f4fdb1d399b2ea89638bc8d08ae1"}, - {file = "regex-2023.6.3-cp310-cp310-win_amd64.whl", hash = "sha256:c5f8037000eb21e4823aa485149f2299eb589f8d1fe4b448036d230c3f4e68e0"}, - {file = "regex-2023.6.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c123f662be8ec5ab4ea72ea300359023a5d1df095b7ead76fedcd8babbedf969"}, - {file = "regex-2023.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9edcbad1f8a407e450fbac88d89e04e0b99a08473f666a3f3de0fd292badb6aa"}, - {file = "regex-2023.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcba6dae7de533c876255317c11f3abe4907ba7d9aa15d13e3d9710d4315ec0e"}, - {file = "regex-2023.6.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29cdd471ebf9e0f2fb3cac165efedc3c58db841d83a518b082077e612d3ee5df"}, - {file = "regex-2023.6.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12b74fbbf6cbbf9dbce20eb9b5879469e97aeeaa874145517563cca4029db65c"}, - {file = "regex-2023.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c29ca1bd61b16b67be247be87390ef1d1ef702800f91fbd1991f5c4421ebae8"}, - {file = "regex-2023.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77f09bc4b55d4bf7cc5eba785d87001d6757b7c9eec237fe2af57aba1a071d9"}, - {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ea353ecb6ab5f7e7d2f4372b1e779796ebd7b37352d290096978fea83c4dba0c"}, - {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:10590510780b7541969287512d1b43f19f965c2ece6c9b1c00fc367b29d8dce7"}, - {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e2fbd6236aae3b7f9d514312cdb58e6494ee1c76a9948adde6eba33eb1c4264f"}, - {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:6b2675068c8b56f6bfd5a2bda55b8accbb96c02fd563704732fd1c95e2083461"}, - {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74419d2b50ecb98360cfaa2974da8689cb3b45b9deff0dcf489c0d333bcc1477"}, - {file = "regex-2023.6.3-cp311-cp311-win32.whl", hash = "sha256:fb5ec16523dc573a4b277663a2b5a364e2099902d3944c9419a40ebd56a118f9"}, - {file = "regex-2023.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:09e4a1a6acc39294a36b7338819b10baceb227f7f7dbbea0506d419b5a1dd8af"}, - {file = "regex-2023.6.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0654bca0cdf28a5956c83839162692725159f4cda8d63e0911a2c0dc76166525"}, - {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:463b6a3ceb5ca952e66550a4532cef94c9a0c80dc156c4cc343041951aec1697"}, - {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87b2a5bb5e78ee0ad1de71c664d6eb536dc3947a46a69182a90f4410f5e3f7dd"}, - {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6343c6928282c1f6a9db41f5fd551662310e8774c0e5ebccb767002fcf663ca9"}, - {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6192d5af2ccd2a38877bfef086d35e6659566a335b1492786ff254c168b1693"}, - {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74390d18c75054947e4194019077e243c06fbb62e541d8817a0fa822ea310c14"}, - {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:742e19a90d9bb2f4a6cf2862b8b06dea5e09b96c9f2df1779e53432d7275331f"}, - {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8abbc5d54ea0ee80e37fef009e3cec5dafd722ed3c829126253d3e22f3846f1e"}, - {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:c2b867c17a7a7ae44c43ebbeb1b5ff406b3e8d5b3e14662683e5e66e6cc868d3"}, - {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:d831c2f8ff278179705ca59f7e8524069c1a989e716a1874d6d1aab6119d91d1"}, - {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ee2d1a9a253b1729bb2de27d41f696ae893507c7db224436abe83ee25356f5c1"}, - {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:61474f0b41fe1a80e8dfa70f70ea1e047387b7cd01c85ec88fa44f5d7561d787"}, - {file = "regex-2023.6.3-cp36-cp36m-win32.whl", hash = "sha256:0b71e63226e393b534105fcbdd8740410dc6b0854c2bfa39bbda6b0d40e59a54"}, - {file = "regex-2023.6.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bbb02fd4462f37060122e5acacec78e49c0fbb303c30dd49c7f493cf21fc5b27"}, - {file = "regex-2023.6.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b862c2b9d5ae38a68b92e215b93f98d4c5e9454fa36aae4450f61dd33ff48487"}, - {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:976d7a304b59ede34ca2921305b57356694f9e6879db323fd90a80f865d355a3"}, - {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:83320a09188e0e6c39088355d423aa9d056ad57a0b6c6381b300ec1a04ec3d16"}, - {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9427a399501818a7564f8c90eced1e9e20709ece36be701f394ada99890ea4b3"}, - {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7178bbc1b2ec40eaca599d13c092079bf529679bf0371c602edaa555e10b41c3"}, - {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:837328d14cde912af625d5f303ec29f7e28cdab588674897baafaf505341f2fc"}, - {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2d44dc13229905ae96dd2ae2dd7cebf824ee92bc52e8cf03dcead37d926da019"}, - {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d54af539295392611e7efbe94e827311eb8b29668e2b3f4cadcfe6f46df9c777"}, - {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7117d10690c38a622e54c432dfbbd3cbd92f09401d622902c32f6d377e2300ee"}, - {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bb60b503ec8a6e4e3e03a681072fa3a5adcbfa5479fa2d898ae2b4a8e24c4591"}, - {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:65ba8603753cec91c71de423a943ba506363b0e5c3fdb913ef8f9caa14b2c7e0"}, - {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:271f0bdba3c70b58e6f500b205d10a36fb4b58bd06ac61381b68de66442efddb"}, - {file = "regex-2023.6.3-cp37-cp37m-win32.whl", hash = "sha256:9beb322958aaca059f34975b0df135181f2e5d7a13b84d3e0e45434749cb20f7"}, - {file = "regex-2023.6.3-cp37-cp37m-win_amd64.whl", hash = "sha256:fea75c3710d4f31389eed3c02f62d0b66a9da282521075061ce875eb5300cf23"}, - {file = "regex-2023.6.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8f56fcb7ff7bf7404becdfc60b1e81a6d0561807051fd2f1860b0d0348156a07"}, - {file = "regex-2023.6.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d2da3abc88711bce7557412310dfa50327d5769a31d1c894b58eb256459dc289"}, - {file = "regex-2023.6.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a99b50300df5add73d307cf66abea093304a07eb017bce94f01e795090dea87c"}, - {file = "regex-2023.6.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5708089ed5b40a7b2dc561e0c8baa9535b77771b64a8330b684823cfd5116036"}, - {file = "regex-2023.6.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:687ea9d78a4b1cf82f8479cab23678aff723108df3edeac098e5b2498879f4a7"}, - {file = "regex-2023.6.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d3850beab9f527f06ccc94b446c864059c57651b3f911fddb8d9d3ec1d1b25d"}, - {file = "regex-2023.6.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8915cc96abeb8983cea1df3c939e3c6e1ac778340c17732eb63bb96247b91d2"}, - {file = "regex-2023.6.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:841d6e0e5663d4c7b4c8099c9997be748677d46cbf43f9f471150e560791f7ff"}, - {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9edce5281f965cf135e19840f4d93d55b3835122aa76ccacfd389e880ba4cf82"}, - {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b956231ebdc45f5b7a2e1f90f66a12be9610ce775fe1b1d50414aac1e9206c06"}, - {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:36efeba71c6539d23c4643be88295ce8c82c88bbd7c65e8a24081d2ca123da3f"}, - {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:cf67ca618b4fd34aee78740bea954d7c69fdda419eb208c2c0c7060bb822d747"}, - {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b4598b1897837067a57b08147a68ac026c1e73b31ef6e36deeeb1fa60b2933c9"}, - {file = "regex-2023.6.3-cp38-cp38-win32.whl", hash = "sha256:f415f802fbcafed5dcc694c13b1292f07fe0befdb94aa8a52905bd115ff41e88"}, - {file = "regex-2023.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:d4f03bb71d482f979bda92e1427f3ec9b220e62a7dd337af0aa6b47bf4498f72"}, - {file = "regex-2023.6.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ccf91346b7bd20c790310c4147eee6ed495a54ddb6737162a36ce9dbef3e4751"}, - {file = "regex-2023.6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b28f5024a3a041009eb4c333863d7894d191215b39576535c6734cd88b0fcb68"}, - {file = "regex-2023.6.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0bb18053dfcfed432cc3ac632b5e5e5c5b7e55fb3f8090e867bfd9b054dbcbf"}, - {file = "regex-2023.6.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a5bfb3004f2144a084a16ce19ca56b8ac46e6fd0651f54269fc9e230edb5e4a"}, - {file = "regex-2023.6.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c6b48d0fa50d8f4df3daf451be7f9689c2bde1a52b1225c5926e3f54b6a9ed1"}, - {file = "regex-2023.6.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:051da80e6eeb6e239e394ae60704d2b566aa6a7aed6f2890a7967307267a5dc6"}, - {file = "regex-2023.6.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4c3b7fa4cdaa69268748665a1a6ff70c014d39bb69c50fda64b396c9116cf77"}, - {file = "regex-2023.6.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:457b6cce21bee41ac292d6753d5e94dcbc5c9e3e3a834da285b0bde7aa4a11e9"}, - {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:aad51907d74fc183033ad796dd4c2e080d1adcc4fd3c0fd4fd499f30c03011cd"}, - {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0385e73da22363778ef2324950e08b689abdf0b108a7d8decb403ad7f5191938"}, - {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c6a57b742133830eec44d9b2290daf5cbe0a2f1d6acee1b3c7b1c7b2f3606df7"}, - {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3e5219bf9e75993d73ab3d25985c857c77e614525fac9ae02b1bebd92f7cecac"}, - {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e5087a3c59eef624a4591ef9eaa6e9a8d8a94c779dade95d27c0bc24650261cd"}, - {file = "regex-2023.6.3-cp39-cp39-win32.whl", hash = "sha256:20326216cc2afe69b6e98528160b225d72f85ab080cbdf0b11528cbbaba2248f"}, - {file = "regex-2023.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:bdff5eab10e59cf26bc479f565e25ed71a7d041d1ded04ccf9aee1d9f208487a"}, - {file = "regex-2023.6.3.tar.gz", hash = "sha256:72d1a25bf36d2050ceb35b517afe13864865268dfb45910e2e17a84be6cbfeb0"}, + {file = "regex-2023.8.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:88900f521c645f784260a8d346e12a1590f79e96403971241e64c3a265c8ecdb"}, + {file = "regex-2023.8.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3611576aff55918af2697410ff0293d6071b7e00f4b09e005d614686ac4cd57c"}, + {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8a0ccc8f2698f120e9e5742f4b38dc944c38744d4bdfc427616f3a163dd9de5"}, + {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c662a4cbdd6280ee56f841f14620787215a171c4e2d1744c9528bed8f5816c96"}, + {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cf0633e4a1b667bfe0bb10b5e53fe0d5f34a6243ea2530eb342491f1adf4f739"}, + {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:551ad543fa19e94943c5b2cebc54c73353ffff08228ee5f3376bd27b3d5b9800"}, + {file = "regex-2023.8.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54de2619f5ea58474f2ac211ceea6b615af2d7e4306220d4f3fe690c91988a61"}, + {file = "regex-2023.8.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5ec4b3f0aebbbe2fc0134ee30a791af522a92ad9f164858805a77442d7d18570"}, + {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3ae646c35cb9f820491760ac62c25b6d6b496757fda2d51be429e0e7b67ae0ab"}, + {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ca339088839582d01654e6f83a637a4b8194d0960477b9769d2ff2cfa0fa36d2"}, + {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:d9b6627408021452dcd0d2cdf8da0534e19d93d070bfa8b6b4176f99711e7f90"}, + {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:bd3366aceedf274f765a3a4bc95d6cd97b130d1dda524d8f25225d14123c01db"}, + {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7aed90a72fc3654fba9bc4b7f851571dcc368120432ad68b226bd593f3f6c0b7"}, + {file = "regex-2023.8.8-cp310-cp310-win32.whl", hash = "sha256:80b80b889cb767cc47f31d2b2f3dec2db8126fbcd0cff31b3925b4dc6609dcdb"}, + {file = "regex-2023.8.8-cp310-cp310-win_amd64.whl", hash = "sha256:b82edc98d107cbc7357da7a5a695901b47d6eb0420e587256ba3ad24b80b7d0b"}, + {file = "regex-2023.8.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1e7d84d64c84ad97bf06f3c8cb5e48941f135ace28f450d86af6b6512f1c9a71"}, + {file = "regex-2023.8.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ce0f9fbe7d295f9922c0424a3637b88c6c472b75eafeaff6f910494a1fa719ef"}, + {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06c57e14ac723b04458df5956cfb7e2d9caa6e9d353c0b4c7d5d54fcb1325c46"}, + {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e7a9aaa5a1267125eef22cef3b63484c3241aaec6f48949b366d26c7250e0357"}, + {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b7408511fca48a82a119d78a77c2f5eb1b22fe88b0d2450ed0756d194fe7a9a"}, + {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14dc6f2d88192a67d708341f3085df6a4f5a0c7b03dec08d763ca2cd86e9f559"}, + {file = "regex-2023.8.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48c640b99213643d141550326f34f0502fedb1798adb3c9eb79650b1ecb2f177"}, + {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0085da0f6c6393428bf0d9c08d8b1874d805bb55e17cb1dfa5ddb7cfb11140bf"}, + {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:964b16dcc10c79a4a2be9f1273fcc2684a9eedb3906439720598029a797b46e6"}, + {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7ce606c14bb195b0e5108544b540e2c5faed6843367e4ab3deb5c6aa5e681208"}, + {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:40f029d73b10fac448c73d6eb33d57b34607f40116e9f6e9f0d32e9229b147d7"}, + {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3b8e6ea6be6d64104d8e9afc34c151926f8182f84e7ac290a93925c0db004bfd"}, + {file = "regex-2023.8.8-cp311-cp311-win32.whl", hash = "sha256:942f8b1f3b223638b02df7df79140646c03938d488fbfb771824f3d05fc083a8"}, + {file = "regex-2023.8.8-cp311-cp311-win_amd64.whl", hash = "sha256:51d8ea2a3a1a8fe4f67de21b8b93757005213e8ac3917567872f2865185fa7fb"}, + {file = "regex-2023.8.8-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e951d1a8e9963ea51efd7f150450803e3b95db5939f994ad3d5edac2b6f6e2b4"}, + {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:704f63b774218207b8ccc6c47fcef5340741e5d839d11d606f70af93ee78e4d4"}, + {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22283c769a7b01c8ac355d5be0715bf6929b6267619505e289f792b01304d898"}, + {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91129ff1bb0619bc1f4ad19485718cc623a2dc433dff95baadbf89405c7f6b57"}, + {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de35342190deb7b866ad6ba5cbcccb2d22c0487ee0cbb251efef0843d705f0d4"}, + {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b993b6f524d1e274a5062488a43e3f9f8764ee9745ccd8e8193df743dbe5ee61"}, + {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3026cbcf11d79095a32d9a13bbc572a458727bd5b1ca332df4a79faecd45281c"}, + {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:293352710172239bf579c90a9864d0df57340b6fd21272345222fb6371bf82b3"}, + {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d909b5a3fff619dc7e48b6b1bedc2f30ec43033ba7af32f936c10839e81b9217"}, + {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:3d370ff652323c5307d9c8e4c62efd1956fb08051b0e9210212bc51168b4ff56"}, + {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:b076da1ed19dc37788f6a934c60adf97bd02c7eea461b73730513921a85d4235"}, + {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e9941a4ada58f6218694f382e43fdd256e97615db9da135e77359da257a7168b"}, + {file = "regex-2023.8.8-cp36-cp36m-win32.whl", hash = "sha256:a8c65c17aed7e15a0c824cdc63a6b104dfc530f6fa8cb6ac51c437af52b481c7"}, + {file = "regex-2023.8.8-cp36-cp36m-win_amd64.whl", hash = "sha256:aadf28046e77a72f30dcc1ab185639e8de7f4104b8cb5c6dfa5d8ed860e57236"}, + {file = "regex-2023.8.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:423adfa872b4908843ac3e7a30f957f5d5282944b81ca0a3b8a7ccbbfaa06103"}, + {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ae594c66f4a7e1ea67232a0846649a7c94c188d6c071ac0210c3e86a5f92109"}, + {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e51c80c168074faa793685656c38eb7a06cbad7774c8cbc3ea05552d615393d8"}, + {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:09b7f4c66aa9d1522b06e31a54f15581c37286237208df1345108fcf4e050c18"}, + {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e73e5243af12d9cd6a9d6a45a43570dbe2e5b1cdfc862f5ae2b031e44dd95a8"}, + {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:941460db8fe3bd613db52f05259c9336f5a47ccae7d7def44cc277184030a116"}, + {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f0ccf3e01afeb412a1a9993049cb160d0352dba635bbca7762b2dc722aa5742a"}, + {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:2e9216e0d2cdce7dbc9be48cb3eacb962740a09b011a116fd7af8c832ab116ca"}, + {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:5cd9cd7170459b9223c5e592ac036e0704bee765706445c353d96f2890e816c8"}, + {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4873ef92e03a4309b3ccd8281454801b291b689f6ad45ef8c3658b6fa761d7ac"}, + {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:239c3c2a339d3b3ddd51c2daef10874410917cd2b998f043c13e2084cb191684"}, + {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1005c60ed7037be0d9dea1f9c53cc42f836188227366370867222bda4c3c6bd7"}, + {file = "regex-2023.8.8-cp37-cp37m-win32.whl", hash = "sha256:e6bd1e9b95bc5614a7a9c9c44fde9539cba1c823b43a9f7bc11266446dd568e3"}, + {file = "regex-2023.8.8-cp37-cp37m-win_amd64.whl", hash = "sha256:9a96edd79661e93327cfeac4edec72a4046e14550a1d22aa0dd2e3ca52aec921"}, + {file = "regex-2023.8.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f2181c20ef18747d5f4a7ea513e09ea03bdd50884a11ce46066bb90fe4213675"}, + {file = "regex-2023.8.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a2ad5add903eb7cdde2b7c64aaca405f3957ab34f16594d2b78d53b8b1a6a7d6"}, + {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9233ac249b354c54146e392e8a451e465dd2d967fc773690811d3a8c240ac601"}, + {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:920974009fb37b20d32afcdf0227a2e707eb83fe418713f7a8b7de038b870d0b"}, + {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2b6c5dfe0929b6c23dde9624483380b170b6e34ed79054ad131b20203a1a63"}, + {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96979d753b1dc3b2169003e1854dc67bfc86edf93c01e84757927f810b8c3c93"}, + {file = "regex-2023.8.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ae54a338191e1356253e7883d9d19f8679b6143703086245fb14d1f20196be9"}, + {file = "regex-2023.8.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2162ae2eb8b079622176a81b65d486ba50b888271302190870b8cc488587d280"}, + {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c884d1a59e69e03b93cf0dfee8794c63d7de0ee8f7ffb76e5f75be8131b6400a"}, + {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cf9273e96f3ee2ac89ffcb17627a78f78e7516b08f94dc435844ae72576a276e"}, + {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:83215147121e15d5f3a45d99abeed9cf1fe16869d5c233b08c56cdf75f43a504"}, + {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3f7454aa427b8ab9101f3787eb178057c5250478e39b99540cfc2b889c7d0586"}, + {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0640913d2c1044d97e30d7c41728195fc37e54d190c5385eacb52115127b882"}, + {file = "regex-2023.8.8-cp38-cp38-win32.whl", hash = "sha256:0c59122ceccb905a941fb23b087b8eafc5290bf983ebcb14d2301febcbe199c7"}, + {file = "regex-2023.8.8-cp38-cp38-win_amd64.whl", hash = "sha256:c12f6f67495ea05c3d542d119d270007090bad5b843f642d418eb601ec0fa7be"}, + {file = "regex-2023.8.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:82cd0a69cd28f6cc3789cc6adeb1027f79526b1ab50b1f6062bbc3a0ccb2dbc3"}, + {file = "regex-2023.8.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bb34d1605f96a245fc39790a117ac1bac8de84ab7691637b26ab2c5efb8f228c"}, + {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:987b9ac04d0b38ef4f89fbc035e84a7efad9cdd5f1e29024f9289182c8d99e09"}, + {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9dd6082f4e2aec9b6a0927202c85bc1b09dcab113f97265127c1dc20e2e32495"}, + {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7eb95fe8222932c10d4436e7a6f7c99991e3fdd9f36c949eff16a69246dee2dc"}, + {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7098c524ba9f20717a56a8d551d2ed491ea89cbf37e540759ed3b776a4f8d6eb"}, + {file = "regex-2023.8.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b694430b3f00eb02c594ff5a16db30e054c1b9589a043fe9174584c6efa8033"}, + {file = "regex-2023.8.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b2aeab3895d778155054abea5238d0eb9a72e9242bd4b43f42fd911ef9a13470"}, + {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:988631b9d78b546e284478c2ec15c8a85960e262e247b35ca5eaf7ee22f6050a"}, + {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:67ecd894e56a0c6108ec5ab1d8fa8418ec0cff45844a855966b875d1039a2e34"}, + {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:14898830f0a0eb67cae2bbbc787c1a7d6e34ecc06fbd39d3af5fe29a4468e2c9"}, + {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:f2200e00b62568cfd920127782c61bc1c546062a879cdc741cfcc6976668dfcf"}, + {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9691a549c19c22d26a4f3b948071e93517bdf86e41b81d8c6ac8a964bb71e5a6"}, + {file = "regex-2023.8.8-cp39-cp39-win32.whl", hash = "sha256:6ab2ed84bf0137927846b37e882745a827458689eb969028af8032b1b3dac78e"}, + {file = "regex-2023.8.8-cp39-cp39-win_amd64.whl", hash = "sha256:5543c055d8ec7801901e1193a51570643d6a6ab8751b1f7dd9af71af467538bb"}, + {file = "regex-2023.8.8.tar.gz", hash = "sha256:fcbdc5f2b0f1cd0f6a56cdb46fe41d2cce1e644e3b68832f3eeebc5fb0f7712e"}, ] [[package]] @@ -2724,6 +3353,24 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "rich" +version = "13.5.2" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "rich-13.5.2-py3-none-any.whl", hash = "sha256:146a90b3b6b47cac4a73c12866a499e9817426423f57c5a66949c086191a8808"}, + {file = "rich-13.5.2.tar.gz", hash = "sha256:fb9d6c0a0f643c99eed3875b5377a184132ba9be4d61516a55273d3554d75a39"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + [[package]] name = "roundrobin" version = "0.0.4" @@ -2762,59 +3409,62 @@ files = [ [[package]] name = "safetensors" -version = "0.3.1" +version = "0.3.2" description = "Fast and Safe Tensor serialization" optional = false python-versions = "*" files = [ - {file = "safetensors-0.3.1-cp310-cp310-macosx_10_11_x86_64.whl", hash = "sha256:2ae9b7dd268b4bae6624729dac86deb82104820e9786429b0583e5168db2f770"}, - {file = "safetensors-0.3.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:08c85c1934682f1e2cd904d38433b53cd2a98245a7cc31f5689f9322a2320bbf"}, - {file = "safetensors-0.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba625c7af9e1c5d0d91cb83d2fba97d29ea69d4db2015d9714d24c7f6d488e15"}, - {file = "safetensors-0.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b57d5890c619ec10d9f1b6426b8690d0c9c2868a90dc52f13fae6f6407ac141f"}, - {file = "safetensors-0.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c9f562ea696d50b95cadbeb1716dc476714a87792ffe374280c0835312cbfe2"}, - {file = "safetensors-0.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c115951b3a865ece8d98ee43882f2fd0a999c0200d6e6fec24134715ebe3b57"}, - {file = "safetensors-0.3.1-cp310-cp310-win32.whl", hash = "sha256:118f8f7503ea312fc7af27e934088a1b589fb1eff5a7dea2cd1de6c71ee33391"}, - {file = "safetensors-0.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:54846eaae25fded28a7bebbb66be563cad221b4c80daee39e2f55df5e5e0266f"}, - {file = "safetensors-0.3.1-cp311-cp311-macosx_10_11_universal2.whl", hash = "sha256:5af82e10946c4822506db0f29269f43147e889054704dde994d4e22f0c37377b"}, - {file = "safetensors-0.3.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:626c86dd1d930963c8ea7f953a3787ae85322551e3a5203ac731d6e6f3e18f44"}, - {file = "safetensors-0.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12e30677e6af1f4cc4f2832546e91dbb3b0aa7d575bfa473d2899d524e1ace08"}, - {file = "safetensors-0.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d534b80bc8d39945bb902f34b0454773971fe9e5e1f2142af451759d7e52b356"}, - {file = "safetensors-0.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ddd0ddd502cf219666e7d30f23f196cb87e829439b52b39f3e7da7918c3416df"}, - {file = "safetensors-0.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997a2cc14023713f423e6d16536d55cb16a3d72850f142e05f82f0d4c76d383b"}, - {file = "safetensors-0.3.1-cp311-cp311-win32.whl", hash = "sha256:6ae9ca63d9e22f71ec40550207bd284a60a6b4916ae6ca12c85a8d86bf49e0c3"}, - {file = "safetensors-0.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:62aa7421ca455418423e35029524489480adda53e3f702453580180ecfebe476"}, - {file = "safetensors-0.3.1-cp37-cp37m-macosx_10_11_x86_64.whl", hash = "sha256:6d54b3ed367b6898baab75dfd057c24f36ec64d3938ffff2af981d56bfba2f42"}, - {file = "safetensors-0.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:262423aeda91117010f8c607889066028f680fbb667f50cfe6eae96f22f9d150"}, - {file = "safetensors-0.3.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10efe2513a8327fd628cea13167089588acc23093ba132aecfc536eb9a4560fe"}, - {file = "safetensors-0.3.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:689b3d6a7ebce70ee9438267ee55ea89b575c19923876645e927d08757b552fe"}, - {file = "safetensors-0.3.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14cd9a87bc73ce06903e9f8ee8b05b056af6f3c9f37a6bd74997a16ed36ff5f4"}, - {file = "safetensors-0.3.1-cp37-cp37m-win32.whl", hash = "sha256:a77cb39624480d5f143c1cc272184f65a296f573d61629eff5d495d2e0541d3e"}, - {file = "safetensors-0.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9eff3190bfbbb52eef729911345c643f875ca4dbb374aa6c559675cfd0ab73db"}, - {file = "safetensors-0.3.1-cp38-cp38-macosx_10_11_x86_64.whl", hash = "sha256:05cbfef76e4daa14796db1bbb52072d4b72a44050c368b2b1f6fd3e610669a89"}, - {file = "safetensors-0.3.1-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:c49061461f4a81e5ec3415070a3f135530834c89cbd6a7db7cd49e3cb9d9864b"}, - {file = "safetensors-0.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22cf7e73ca42974f098ce0cf4dd8918983700b6b07a4c6827d50c8daefca776e"}, - {file = "safetensors-0.3.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04f909442d6223ff0016cd2e1b2a95ef8039b92a558014627363a2e267213f62"}, - {file = "safetensors-0.3.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c573c5a0d5d45791ae8c179e26d74aff86e719056591aa7edb3ca7be55bc961"}, - {file = "safetensors-0.3.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6994043b12e717cf2a6ba69077ac41f0d3675b2819734f07f61819e854c622c7"}, - {file = "safetensors-0.3.1-cp38-cp38-win32.whl", hash = "sha256:158ede81694180a0dbba59422bc304a78c054b305df993c0c6e39c6330fa9348"}, - {file = "safetensors-0.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:afdc725beff7121ea8d39a7339f5a6abcb01daa189ea56290b67fe262d56e20f"}, - {file = "safetensors-0.3.1-cp39-cp39-macosx_10_11_x86_64.whl", hash = "sha256:cba910fcc9e5e64d32d62b837388721165e9c7e45d23bc3a38ad57694b77f40d"}, - {file = "safetensors-0.3.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:a4f7dbfe7285573cdaddd85ef6fa84ebbed995d3703ab72d71257944e384612f"}, - {file = "safetensors-0.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54aed0802f9eaa83ca7b1cbb986bfb90b8e2c67b6a4bcfe245627e17dad565d4"}, - {file = "safetensors-0.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34b75a766f3cfc99fd4c33e329b76deae63f5f388e455d863a5d6e99472fca8e"}, - {file = "safetensors-0.3.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a0f31904f35dc14919a145b2d7a2d8842a43a18a629affe678233c4ea90b4af"}, - {file = "safetensors-0.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcf527ecc5f58907fd9031510378105487f318cc91ecdc5aee3c7cc8f46030a8"}, - {file = "safetensors-0.3.1-cp39-cp39-win32.whl", hash = "sha256:e2f083112cf97aa9611e2a05cc170a2795eccec5f6ff837f4565f950670a9d83"}, - {file = "safetensors-0.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:5f4f614b8e8161cd8a9ca19c765d176a82b122fa3d3387b77862145bfe9b4e93"}, - {file = "safetensors-0.3.1.tar.gz", hash = "sha256:571da56ff8d0bec8ae54923b621cda98d36dcef10feb36fd492c4d0c2cd0e869"}, -] - -[package.extras] -all = ["black (==22.3)", "click (==8.0.4)", "flake8 (>=3.8.3)", "flax (>=0.6.3)", "h5py (>=3.7.0)", "huggingface-hub (>=0.12.1)", "isort (>=5.5.4)", "jax (>=0.3.25)", "jaxlib (>=0.3.25)", "numpy (>=1.21.6)", "paddlepaddle (>=2.4.1)", "pytest (>=7.2.0)", "pytest-benchmark (>=4.0.0)", "setuptools-rust (>=1.5.2)", "tensorflow (>=2.11.0)", "torch (>=1.10)"] -dev = ["black (==22.3)", "click (==8.0.4)", "flake8 (>=3.8.3)", "flax (>=0.6.3)", "h5py (>=3.7.0)", "huggingface-hub (>=0.12.1)", "isort (>=5.5.4)", "jax (>=0.3.25)", "jaxlib (>=0.3.25)", "numpy (>=1.21.6)", "paddlepaddle (>=2.4.1)", "pytest (>=7.2.0)", "pytest-benchmark (>=4.0.0)", "setuptools-rust (>=1.5.2)", "tensorflow (>=2.11.0)", "torch (>=1.10)"] + {file = "safetensors-0.3.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:b6a66989075c2891d743153e8ba9ca84ee7232c8539704488f454199b8b8f84d"}, + {file = "safetensors-0.3.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:670d6bc3a3b377278ce2971fa7c36ebc0a35041c4ea23b9df750a39380800195"}, + {file = "safetensors-0.3.2-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:564f42838721925b5313ae864ba6caa6f4c80a9fbe63cf24310c3be98ab013cd"}, + {file = "safetensors-0.3.2-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:7f80af7e4ab3188daaff12d43d078da3017a90d732d38d7af4eb08b6ca2198a5"}, + {file = "safetensors-0.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec30d78f20f1235b252d59cbb9755beb35a1fde8c24c89b3c98e6a1804cfd432"}, + {file = "safetensors-0.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16063d94d8f600768d3c331b1e97964b1bf3772e19710105fe24ec5a6af63770"}, + {file = "safetensors-0.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbb44e140bf2aeda98d9dde669dbec15f7b77f96a9274469b91a6cf4bcc5ec3b"}, + {file = "safetensors-0.3.2-cp310-cp310-win32.whl", hash = "sha256:2961c1243fd0da46aa6a1c835305cc4595486f8ac64632a604d0eb5f2de76175"}, + {file = "safetensors-0.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c813920482c337d1424d306e1b05824a38e3ef94303748a0a287dea7a8c4f805"}, + {file = "safetensors-0.3.2-cp311-cp311-macosx_10_11_universal2.whl", hash = "sha256:707df34bd9b9047e97332136ad98e57028faeccdb9cfe1c3b52aba5964cc24bf"}, + {file = "safetensors-0.3.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:becc5bb85b2947eae20ed23b407ebfd5277d9a560f90381fe2c42e6c043677ba"}, + {file = "safetensors-0.3.2-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:30a75707be5cc9686490bde14b9a371cede4af53244ea72b340cfbabfffdf58a"}, + {file = "safetensors-0.3.2-cp311-cp311-macosx_13_0_universal2.whl", hash = "sha256:54ad6af663e15e2b99e2ea3280981b7514485df72ba6d014dc22dae7ba6a5e6c"}, + {file = "safetensors-0.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37764b3197656ef507a266c453e909a3477dabc795962b38e3ad28226f53153b"}, + {file = "safetensors-0.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4939067736783acd8391d83cd97d6c202f94181951ce697d519f9746381b6a39"}, + {file = "safetensors-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ada0fac127ff8fb04834da5c6d85a8077e6a1c9180a11251d96f8068db922a17"}, + {file = "safetensors-0.3.2-cp311-cp311-win32.whl", hash = "sha256:155b82dbe2b0ebff18cde3f76b42b6d9470296e92561ef1a282004d449fa2b4c"}, + {file = "safetensors-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:a86428d196959619ce90197731be9391b5098b35100a7228ef4643957648f7f5"}, + {file = "safetensors-0.3.2-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:c1f8ab41ed735c5b581f451fd15d9602ff51aa88044bfa933c5fa4b1d0c644d1"}, + {file = "safetensors-0.3.2-cp37-cp37m-macosx_13_0_x86_64.whl", hash = "sha256:bc9cfb3c9ea2aec89685b4d656f9f2296f0f0d67ecf2bebf950870e3be89b3db"}, + {file = "safetensors-0.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ace5d471e3d78e0d93f952707d808b5ab5eac77ddb034ceb702e602e9acf2be9"}, + {file = "safetensors-0.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de3e20a388b444381bcda1a3193cce51825ddca277e4cf3ed1fe8d9b2d5722cd"}, + {file = "safetensors-0.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d7d70d48585fe8df00725aa788f2e64fd24a4c9ae07cd6be34f6859d0f89a9c"}, + {file = "safetensors-0.3.2-cp37-cp37m-win32.whl", hash = "sha256:6ff59bc90cdc857f68b1023be9085fda6202bbe7f2fd67d06af8f976d6adcc10"}, + {file = "safetensors-0.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8b05c93da15fa911763a89281906ca333ed800ab0ef1c7ce53317aa1a2322f19"}, + {file = "safetensors-0.3.2-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:8969cfd9e8d904e8d3c67c989e1bd9a95e3cc8980d4f95e4dcd43c299bb94253"}, + {file = "safetensors-0.3.2-cp38-cp38-macosx_13_0_x86_64.whl", hash = "sha256:f54148ac027556eb02187e9bc1556c4d916c99ca3cb34ca36a7d304d675035c1"}, + {file = "safetensors-0.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:caec25fedbcf73f66c9261984f07885680f71417fc173f52279276c7f8a5edd3"}, + {file = "safetensors-0.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:50224a1d99927ccf3b75e27c3d412f7043280431ab100b4f08aad470c37cf99a"}, + {file = "safetensors-0.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa98f49e95f02eb750d32c4947e7d5aa43883149ebd0414920866446525b70f0"}, + {file = "safetensors-0.3.2-cp38-cp38-win32.whl", hash = "sha256:33409df5e28a83dc5cc5547a3ac17c0f1b13a1847b1eb3bc4b3be0df9915171e"}, + {file = "safetensors-0.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:e04a7cbbb3856159ab99e3adb14521544f65fcb8548cce773a1435a0f8d78d27"}, + {file = "safetensors-0.3.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:7c864cf5dcbfb608c5378f83319c60cc9c97263343b57c02756b7613cd5ab4dd"}, + {file = "safetensors-0.3.2-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:14e8c19d6dc51d4f70ee33c46aff04c8ba3f95812e74daf8036c24bc86e75cae"}, + {file = "safetensors-0.3.2-cp39-cp39-macosx_13_0_arm64.whl", hash = "sha256:042a60f633c3c7009fdf6a7c182b165cb7283649d2a1e9c7a4a1c23454bd9a5b"}, + {file = "safetensors-0.3.2-cp39-cp39-macosx_13_0_x86_64.whl", hash = "sha256:fafd95e5ef41e8f312e2a32b7031f7b9b2a621b255f867b221f94bb2e9f51ae8"}, + {file = "safetensors-0.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ed77cf358abce2307f03634694e0b2a29822e322a1623e0b1aa4b41e871bf8b"}, + {file = "safetensors-0.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d344e8b2681a33aafc197c90b0def3229b3317d749531c72fa6259d0caa5c8c"}, + {file = "safetensors-0.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87ff0024ef2e5722a79af24688ce4a430f70601d0cf712a744105ed4b8f67ba5"}, + {file = "safetensors-0.3.2-cp39-cp39-win32.whl", hash = "sha256:827af9478b78977248ba93e2fd97ea307fb63f463f80cef4824460f8c2542a52"}, + {file = "safetensors-0.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:9b09f27c456efa301f98681ea14b12f81f2637889f6336223ccab71e42c34541"}, + {file = "safetensors-0.3.2.tar.gz", hash = "sha256:2dbd34554ed3b99435a0e84df077108f5334c8336b5ed9cb8b6b98f7b10da2f6"}, +] + +[package.extras] +all = ["black (==22.3)", "click (==8.0.4)", "flake8 (>=3.8.3)", "flax (>=0.6.3)", "h5py (>=3.7.0)", "huggingface-hub (>=0.12.1)", "isort (>=5.5.4)", "jax (>=0.3.25)", "jaxlib (>=0.3.25)", "numpy (>=1.21.6)", "paddlepaddle (>=2.4.1)", "pytest (>=7.2.0)", "pytest-benchmark (>=4.0.0)", "setuptools-rust (>=1.5.2)", "tensorflow (==2.11.0)", "torch (>=1.10)"] +dev = ["black (==22.3)", "click (==8.0.4)", "flake8 (>=3.8.3)", "flax (>=0.6.3)", "h5py (>=3.7.0)", "huggingface-hub (>=0.12.1)", "isort (>=5.5.4)", "jax (>=0.3.25)", "jaxlib (>=0.3.25)", "numpy (>=1.21.6)", "paddlepaddle (>=2.4.1)", "pytest (>=7.2.0)", "pytest-benchmark (>=4.0.0)", "setuptools-rust (>=1.5.2)", "tensorflow (==2.11.0)", "torch (>=1.10)"] jax = ["flax (>=0.6.3)", "jax (>=0.3.25)", "jaxlib (>=0.3.25)"] numpy = ["numpy (>=1.21.6)"] paddlepaddle = ["paddlepaddle (>=2.4.1)"] +pinned-tf = ["tensorflow (==2.11.0)"] quality = ["black (==22.3)", "click (==8.0.4)", "flake8 (>=3.8.3)", "isort (>=5.5.4)"] tensorflow = ["tensorflow (>=2.11.0)"] testing = ["h5py (>=3.7.0)", "huggingface-hub (>=0.12.1)", "numpy (>=1.21.6)", "pytest (>=7.2.0)", "pytest-benchmark (>=4.0.0)", "setuptools-rust (>=1.5.2)"] @@ -2950,28 +3600,6 @@ dev = ["flake8", "mypy", "pycodestyle", "typing_extensions"] doc = ["matplotlib (>2)", "numpydoc", "pydata-sphinx-theme (==0.9.0)", "sphinx (!=4.1.0)", "sphinx-panels (>=0.5.2)", "sphinx-tabs"] test = ["asv", "gmpy2", "mpmath", "pytest", "pytest-cov", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] -[[package]] -name = "sentence-transformers" -version = "2.2.2" -description = "Multilingual text embeddings" -optional = false -python-versions = ">=3.6.0" -files = [ - {file = "sentence-transformers-2.2.2.tar.gz", hash = "sha256:dbc60163b27de21076c9a30d24b5b7b6fa05141d68cf2553fa9a77bf79a29136"}, -] - -[package.dependencies] -huggingface-hub = ">=0.4.0" -nltk = "*" -numpy = "*" -scikit-learn = "*" -scipy = "*" -sentencepiece = "*" -torch = ">=1.6.0" -torchvision = "*" -tqdm = "*" -transformers = ">=4.6.0,<5.0.0" - [[package]] name = "sentencepiece" version = "0.1.99" @@ -3028,18 +3656,18 @@ files = [ [[package]] name = "setuptools" -version = "68.0.0" +version = "68.1.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, - {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, + {file = "setuptools-68.1.2-py3-none-any.whl", hash = "sha256:3d8083eed2d13afc9426f227b24fd1659489ec107c0e86cec2ffdde5c92e790b"}, + {file = "setuptools-68.1.2.tar.gz", hash = "sha256:3d4dfa6d95f1b101d695a6160a7626e15583af71a5f52176efa5d39a054d475d"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5,<=7.1.2)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] @@ -3108,20 +3736,38 @@ files = [ [[package]] name = "tifffile" -version = "2023.7.18" +version = "2023.8.12" description = "Read and write TIFF files" optional = false python-versions = ">=3.9" files = [ - {file = "tifffile-2023.7.18-py3-none-any.whl", hash = "sha256:a9449ab688b82b69f3ddf80e4e0b4de7b5b02549974a56e112061b816b3c5585"}, - {file = "tifffile-2023.7.18.tar.gz", hash = "sha256:5a5a624b2f7ab7f37e9ec4174ae2df1805b9658f89013f9b4b5550672f65f2a1"}, + {file = "tifffile-2023.8.12-py3-none-any.whl", hash = "sha256:d1ef06461a947a6800ba6121b330b54a57fb9cbf7e5bc0adab8307081297d66b"}, + {file = "tifffile-2023.8.12.tar.gz", hash = "sha256:824956b6d974b9d346aae59932bea862a2ad18fcc2b1a820b6941b7f6ddb2bca"}, ] [package.dependencies] numpy = "*" [package.extras] -all = ["defusedxml", "fsspec", "imagecodecs (>=2023.1.23)", "lxml", "matplotlib", "zarr"] +all = ["defusedxml", "fsspec", "imagecodecs (>=2023.8.12)", "lxml", "matplotlib", "zarr"] + +[[package]] +name = "timm" +version = "0.9.5" +description = "PyTorch Image Models" +optional = false +python-versions = ">=3.7" +files = [ + {file = "timm-0.9.5-py3-none-any.whl", hash = "sha256:6e70af3a347bddb4167db46c3252a83c59165332ecf6b3df480d49c22866fa46"}, + {file = "timm-0.9.5.tar.gz", hash = "sha256:669835f0030cfb2412c464b7b563bb240d4d41a141226afbbf1b457e4f18cff1"}, +] + +[package.dependencies] +huggingface-hub = "*" +pyyaml = "*" +safetensors = "*" +torch = ">=1.7" +torchvision = "*" [[package]] name = "tokenizers" @@ -3286,22 +3932,53 @@ torch = "2.0.1" [package.extras] scipy = ["scipy"] +[[package]] +name = "torchvision" +version = "0.15.2+cpu" +description = "image and video datasets and models for torch deep learning" +optional = false +python-versions = ">=3.8" +files = [ + {file = "torchvision-0.15.2+cpu-cp310-cp310-linux_x86_64.whl", hash = "sha256:aae0be6883d2cd5a23cb544ee0928288a27df0455430ef9dd6e631c5464095f5"}, + {file = "torchvision-0.15.2+cpu-cp310-cp310-win_amd64.whl", hash = "sha256:44d4b8976b5aaf97811ec05cb526531ac08dc5c76c2f5bc1c919b9a653cbc206"}, + {file = "torchvision-0.15.2+cpu-cp311-cp311-linux_x86_64.whl", hash = "sha256:1633c0b7ead69d2cd3fbe8aeaa146c76a68691bd3599facfffd2da925973e044"}, + {file = "torchvision-0.15.2+cpu-cp311-cp311-win_amd64.whl", hash = "sha256:3ec9785df54906d1631c5574ba0df3cc3adcd43286774b93fbe82c97c4714977"}, + {file = "torchvision-0.15.2+cpu-cp38-cp38-linux_x86_64.whl", hash = "sha256:af6bc2c7b0b8b2a32745e56f057a5956caf0d6031a94eb80b283dcd475d8feb0"}, + {file = "torchvision-0.15.2+cpu-cp38-cp38-win_amd64.whl", hash = "sha256:f61cc175f75392c7565ad1144d2e511b14002d061a2fd31a73b045259eb9100e"}, + {file = "torchvision-0.15.2+cpu-cp39-cp39-linux_x86_64.whl", hash = "sha256:e1e3988373afa29c813b140dbb5b7a074214813fd0ef97d2538966b9bd8d92d9"}, + {file = "torchvision-0.15.2+cpu-cp39-cp39-win_amd64.whl", hash = "sha256:b9c2745a966d5ffcad7b17807ef3acf4149f497c80f5f4571f69a1817436e56f"}, +] + +[package.dependencies] +numpy = "*" +pillow = ">=5.3.0,<8.3.dev0 || >=8.4.dev0" +requests = "*" +torch = "2.0.1" + +[package.extras] +scipy = ["scipy"] + +[package.source] +type = "legacy" +url = "https://download.pytorch.org/whl/cpu" +reference = "pytorch-cpu" + [[package]] name = "tqdm" -version = "4.65.0" +version = "4.66.1" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" files = [ - {file = "tqdm-4.65.0-py3-none-any.whl", hash = "sha256:c4f53a17fe37e132815abceec022631be8ffe1b9381c2e6e30aa70edc99e9671"}, - {file = "tqdm-4.65.0.tar.gz", hash = "sha256:1871fb68a86b8fb3b59ca4cdd3dcccbc7e6d613eeed31f4c332531977b89beb5"}, + {file = "tqdm-4.66.1-py3-none-any.whl", hash = "sha256:d302b3c5b53d47bce91fea46679d9c3c6508cf6332229aa1e7d8653723793386"}, + {file = "tqdm-4.66.1.tar.gz", hash = "sha256:d88e651f9db8d8551a62556d3cff9e3034274ca5d66e93197cf2490e2dcb69c7"}, ] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} [package.extras] -dev = ["py-make (>=0.1.0)", "twine", "wheel"] +dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] notebook = ["ipywidgets (>=6)"] slack = ["slack-sdk"] telegram = ["requests"] @@ -3322,10 +3999,12 @@ filelock = "*" huggingface-hub = ">=0.14.1,<1.0" numpy = ">=1.17" packaging = ">=20.0" +protobuf = {version = "*", optional = true, markers = "extra == \"sentencepiece\""} pyyaml = ">=5.1" regex = "!=2019.12.17" requests = "*" safetensors = ">=0.3.1" +sentencepiece = {version = ">=0.1.91,<0.1.92 || >0.1.92", optional = true, markers = "extra == \"sentencepiece\""} tokenizers = ">=0.11.1,<0.11.3 || >0.11.3,<0.14" tqdm = ">=4.27" @@ -3386,6 +4065,17 @@ files = [ {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, ] +[[package]] +name = "tzdata" +version = "2023.3" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +files = [ + {file = "tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"}, + {file = "tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"}, +] + [[package]] name = "urllib3" version = "2.0.4" @@ -3598,13 +4288,13 @@ files = [ [[package]] name = "werkzeug" -version = "2.3.6" +version = "2.3.7" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.8" files = [ - {file = "Werkzeug-2.3.6-py3-none-any.whl", hash = "sha256:935539fa1413afbb9195b24880778422ed620c0fc09670945185cce4d91a8890"}, - {file = "Werkzeug-2.3.6.tar.gz", hash = "sha256:98c774df2f91b05550078891dee5f0eb0cb797a522c757a2452b9cee5b202330"}, + {file = "werkzeug-2.3.7-py3-none-any.whl", hash = "sha256:effc12dba7f3bd72e605ce49807bbe692bd729c3bb122a3b91747a6ae77df528"}, + {file = "werkzeug-2.3.7.tar.gz", hash = "sha256:2b8c0e447b4b9dbcc85dd97b6eeb4dcbaf6c8b6c3be0bd654e25553e0a2157d8"}, ] [package.dependencies] @@ -3613,6 +4303,187 @@ MarkupSafe = ">=2.1.1" [package.extras] watchdog = ["watchdog (>=2.3)"] +[[package]] +name = "xxhash" +version = "3.3.0" +description = "Python binding for xxHash" +optional = false +python-versions = ">=3.7" +files = [ + {file = "xxhash-3.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:70ef7288d1cb1ad16e02d101ea43bb0e392d985d60b9b0035aee80663530960d"}, + {file = "xxhash-3.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:44ff8c673cab50be46784e0aec62aa6f0ca9ea765e2b0690e8945d0cd950dcaf"}, + {file = "xxhash-3.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfebc90273ae2beb813d8118a2bfffb5a5a81ac054fbfd061ea18fd0a81db0ac"}, + {file = "xxhash-3.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9084e68bedbd665c7e9241a7b597c28f4775edeb3941bf608ecb38732a5f8fb5"}, + {file = "xxhash-3.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d72493a14a3e89564b1a6c7400b9b40621e8f4692410706ef27c66aeadc7b431"}, + {file = "xxhash-3.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98779cbe9068dd7734cc3210693894d5cc9b156920e9c336f10fb99f46bebbd8"}, + {file = "xxhash-3.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:499f8a12767dd28b98ab6b7c7da7d294564e4c9024a2aaa5d0b0b98a8bef2f92"}, + {file = "xxhash-3.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dabda7f42c548f98d8e07e390bda2953fc58302c0e07ded7b3fe0637e7ecd2f"}, + {file = "xxhash-3.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c416409646c793c46370f0f1859253302ee70aeda5278c2a0ca41462f8ec1244"}, + {file = "xxhash-3.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b8bd31aaad8a80a7302730676cec26bea3ef1fd9835875aa47fea073aca9fe05"}, + {file = "xxhash-3.3.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:3af8e3bcd630f905efbdfe7a51b51fc1ca3c9dca8b155f841925f3ad41685d41"}, + {file = "xxhash-3.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d86b79c707fc7025d967af71db652429a06a8179175e45bd2e9f17b8af6f5949"}, + {file = "xxhash-3.3.0-cp310-cp310-win32.whl", hash = "sha256:98fe771f36ee9d3a1f5741424a956a2ba9651d9508a9f64a024b57f2cf796414"}, + {file = "xxhash-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:0a65131f7f731ecf7e3dd27f09d877aff3000a79a446caaa2c0d8d0ec0bc7186"}, + {file = "xxhash-3.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a9761e425e79d23797fa0bec2d781dbadb9fe5dcc2bf69030855f5e393c3bec8"}, + {file = "xxhash-3.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d28c7ef1deb3c3ac5f5290176ca3d501daa97c2e1f7443bf5d8b61ac651794b2"}, + {file = "xxhash-3.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:701b7cefffc25de1b7ddfae6505da70a3b3a11e312c2e2b33b09e180bbceb43d"}, + {file = "xxhash-3.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b1644f8b8e19a242c3047a089541067248a651038cabb9fcab3c13eb1dfcd757"}, + {file = "xxhash-3.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:20e7d0e3488cc0f0dbe360731b7fe32e1f2df46bf2de2db3317d301efb93084c"}, + {file = "xxhash-3.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:156c52eca2b20f9839723bef0b929a290f6c2f1c98ccb24e82f58f96f3c16007"}, + {file = "xxhash-3.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d6ce4d3828d79044ed08994e196c20f69c18133ed8a4286afe3e98989adeeac"}, + {file = "xxhash-3.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b85b63757ade2439c8d7d71842c40d42c0ab3b69279ed02afbd3b1635f7d2b4b"}, + {file = "xxhash-3.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b2b9051e40b7b649a9a2a38fb223ca6a593d332012df885746b81968948f9435"}, + {file = "xxhash-3.3.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:81b7ce050f26fc1daaaa0d24e320815306736d14608e1ba31920e693a7ca9afb"}, + {file = "xxhash-3.3.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:7442500fcce71669953ca959682dcd47452bc3f9c95c8d88315874aeabec9f82"}, + {file = "xxhash-3.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:36a05bf59a515cfb07f3f83373c527fff2ecaa77eaf30c968c788aea582070a1"}, + {file = "xxhash-3.3.0-cp311-cp311-win32.whl", hash = "sha256:da16f9cd62c6fde74683be1b28c28ef865e706da13e3bee4ba836fcc520de0cc"}, + {file = "xxhash-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:40fd49ef6964b1c90c0bea63cd184f6d0b36e59144a080e8b3ac2c4c06bf6bf2"}, + {file = "xxhash-3.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:672c60cce1f8026ae32c651f877aa64f342876083a36a4b1ff91bc876aaf0e34"}, + {file = "xxhash-3.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bb6c83d7a65dd3065566c77425ba72df96982174e8ef613d809052d68ae77ab"}, + {file = "xxhash-3.3.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a4170f3016b621e3200ebfcc18de6f50eb8e8fc1303e16324b1f5625afd51b57"}, + {file = "xxhash-3.3.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bfb9c45d502ab38c0f4edf98a678694ae0f345613ef4900ade98c71f64db4d78"}, + {file = "xxhash-3.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48af026a2b1569666da42a478248a1f03f4e2350a34eb661afe3cb45429ca1d7"}, + {file = "xxhash-3.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe627de8fe8ddfa8b6477bda4ae5d5843ad1a0c83601dcff72247039465cc901"}, + {file = "xxhash-3.3.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:427fc60a188e345534f35b0aa76f7640c5ddf0354f1c9ad826a2bc086282982d"}, + {file = "xxhash-3.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d80acb20c7f268fe3150ac0be6a6b798062af56a1795eef855b26c9eae11a99c"}, + {file = "xxhash-3.3.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:e71100818943422d1fbbe460e7be7fc4f2d2ba9371b2a745eb09e29ef0493f4a"}, + {file = "xxhash-3.3.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:e3b9bb5fdbe284c7b61c5d82c76688e52bbaf48ab1e53de98c072cc696fa331f"}, + {file = "xxhash-3.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1e25f6c8c46cf1ed8237f610abb231093a748c97d6c2c092789a7cad7e7ef290"}, + {file = "xxhash-3.3.0-cp37-cp37m-win32.whl", hash = "sha256:928208dfecc563be59ae91868d1658e78809cb1e6a0bd74960a96c915db6390c"}, + {file = "xxhash-3.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:bd1b4531a66da6dde1974662c1fd6fb1a2f27e40542e3df5e5e5dbab8ea4aee7"}, + {file = "xxhash-3.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:deebb296df92e082b6d0171a7d6227b503e2897cea4f8bdd3d708094974d4cf6"}, + {file = "xxhash-3.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cd96e9cb0e2baa294e6d572207d9731c3bb8e2511f1ff70f2bf17266b4488bd9"}, + {file = "xxhash-3.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3756b44bf247e422a2e47a38f25d03cf4a5ed539fdc2be3c60043e872e6ff13d"}, + {file = "xxhash-3.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69550c3c053b8f135ceac97b85dc1b2bc54b7613a966f550f32b43bed81c788a"}, + {file = "xxhash-3.3.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fc8736fc3e0c5aad435520873b9d2e27ddcc5a830b07e00e9c4d3a61ded9675"}, + {file = "xxhash-3.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80ead7774392efbd95f9f701155048f9ca26cf55133db6f5bb5a0ec69376bda5"}, + {file = "xxhash-3.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8737c9b3fd944d856faafa92c95f6198649ad57987935b6d965d086938be917"}, + {file = "xxhash-3.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2c8e078d0b9f85212801c41bd9eec8122003929686b0ee33360ffbfdf1a189ab"}, + {file = "xxhash-3.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f399269d20ef1dd910331f9ad49e8510c3ba2aa657b623293b536038f266a5c5"}, + {file = "xxhash-3.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f3661decef5f9ff7ab50edbef463bf7dc717621b56755dbae5458a946a033b10"}, + {file = "xxhash-3.3.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5ec374d0f1e7d43ef48a4ff643600833d7a325ecc6933b4d6ad9282f55751cf7"}, + {file = "xxhash-3.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:39a947ff02d9a85673f5ce1f6f34059e24c714a797440485bd81b2c3cb69a7ff"}, + {file = "xxhash-3.3.0-cp38-cp38-win32.whl", hash = "sha256:4a4f0645a0ec03b229fb04f2e66bdbcb1ffd341a70d6c86c3ee015ffdcd70fad"}, + {file = "xxhash-3.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:8af5a687c0fb4357c230eec8a57ca07d3172faa3cb69beb0cbad40672ae6fa4b"}, + {file = "xxhash-3.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e5bfafda019ecc6202af6f3cf08220fa66af9612ba16ef831033ae3ac7bd1f89"}, + {file = "xxhash-3.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3d113b433bc817adf845689a051363777835577858263ec4325d1934fcb7e394"}, + {file = "xxhash-3.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56aacf4bf65f575c0392be958aceff719d850950bb6af7d804b32d4bc293159c"}, + {file = "xxhash-3.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f5d3e4e0937dad05585e9bd772bbdf0ca40cd8b2f54789d7a1f3091b608118c"}, + {file = "xxhash-3.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23605d7fc67bc7daa0d263b3a26de3375cfcc0b51ab7de5026625415c05b6fed"}, + {file = "xxhash-3.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe525be0392d493558a2b10d764bcaae9850cc262b417176a8b001f16e085fc6"}, + {file = "xxhash-3.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b234d08786884f5c8d55dfebb839cfbd846d812e3a052c39ca7e8ce7055fed68"}, + {file = "xxhash-3.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b031395b4b9c3085d9ea1ce89896ab01a65fc63172b2bfda5dd318fefe5e2f93"}, + {file = "xxhash-3.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:5afe44da46b48c75169e622a532dca3fe585343c0577cfd7c18ecd3f1200305d"}, + {file = "xxhash-3.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c59f233f38b6a49d5e4ddf16be910a5bbf36a2989b6b2c8591853fb9f5a5e691"}, + {file = "xxhash-3.3.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:ed016e278c5c4633270903c7cf3b9dfb0bd293b7335e43fe695cb95541da53c9"}, + {file = "xxhash-3.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7a8bd6612fb35487e9ab329bb37b3df44f58baf752010dde9282593edbfed7e7"}, + {file = "xxhash-3.3.0-cp39-cp39-win32.whl", hash = "sha256:015a0498bde85364abc53fcc713af962dd4555391929736d9c0ff2c555436a03"}, + {file = "xxhash-3.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:06a484097af32caf1cfffadd60c3ca140c9e52b40a551fb1f6f0fdfd6f7f8977"}, + {file = "xxhash-3.3.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6c3809740124bbc777d29e3ae53de24f4c13fd5e62878086a8feadf0dcb654a5"}, + {file = "xxhash-3.3.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae092f0daaeece2acdd6ec46e2ab307d8d6f22b01ecca14dc6078844dbd88339"}, + {file = "xxhash-3.3.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3498e72ff2610b049b97bb81d1ea6e7bfa5b7a45efb3f255d77ec2fa2bc91653"}, + {file = "xxhash-3.3.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0004dded9d86f129961326e980420187640fb7ba65a184009429861c1d09df7"}, + {file = "xxhash-3.3.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:41c8bfd27191928bae6fd2b66872965532267785094a03c0ee5f358d9dba51c2"}, + {file = "xxhash-3.3.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:71db8498e329cef3588b0617f762a3fe31d899872e76a68ce2840e35a1318a5b"}, + {file = "xxhash-3.3.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d1d24d71b6209bc0124286932c4f0660c1103cb996fe34cb374bc12ac251940"}, + {file = "xxhash-3.3.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61004587a09b5b385e43d95ffe3a76c9d934dfd79ea38272d5c20ddfba8eab8f"}, + {file = "xxhash-3.3.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f0c92e3fa826425c73acafb31e022a719c85423847a9433d3a9e61e4ac97543"}, + {file = "xxhash-3.3.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:367e03f1484ce471c94e731b98f5e4a05b43e7188b16692998e1cc89fd1159a5"}, + {file = "xxhash-3.3.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ed04c47dfaab98fcda0b748af9ee6fe8c888a0a0fbd13720e0f0221671e387e1"}, + {file = "xxhash-3.3.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cbfde62516435ca198220aff048a8793383cb7047c7b88714a061968bca786d"}, + {file = "xxhash-3.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73682225faa973ee56743f0fcd36bfcbfec503be258e0e420fb34313f52f1e7b"}, + {file = "xxhash-3.3.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d49efdce2086c2c506af20ed18a1115b40af7aad6d4ee27cb31d7c810585a3f2"}, + {file = "xxhash-3.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:546a0bb8e5a657cadf0da290b30ccd561cb89c256a5421ab8d5eb12eaf087349"}, + {file = "xxhash-3.3.0.tar.gz", hash = "sha256:c3f9e322b1ebeebd44e3d9d2d9b124e0c550c1ef41bd552afdcdd719516ee41a"}, +] + +[[package]] +name = "yarl" +version = "1.9.2" +description = "Yet another URL library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c2ad583743d16ddbdf6bb14b5cd76bf43b0d0006e918809d5d4ddf7bde8dd82"}, + {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82aa6264b36c50acfb2424ad5ca537a2060ab6de158a5bd2a72a032cc75b9eb8"}, + {file = "yarl-1.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0c77533b5ed4bcc38e943178ccae29b9bcf48ffd1063f5821192f23a1bd27b9"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee4afac41415d52d53a9833ebae7e32b344be72835bbb589018c9e938045a560"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bf345c3a4f5ba7f766430f97f9cc1320786f19584acc7086491f45524a551ac"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a96c19c52ff442a808c105901d0bdfd2e28575b3d5f82e2f5fd67e20dc5f4ea"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:891c0e3ec5ec881541f6c5113d8df0315ce5440e244a716b95f2525b7b9f3608"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3a53ba34a636a256d767c086ceb111358876e1fb6b50dfc4d3f4951d40133d5"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:566185e8ebc0898b11f8026447eacd02e46226716229cea8db37496c8cdd26e0"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2b0738fb871812722a0ac2154be1f049c6223b9f6f22eec352996b69775b36d4"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:32f1d071b3f362c80f1a7d322bfd7b2d11e33d2adf395cc1dd4df36c9c243095"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e9fdc7ac0d42bc3ea78818557fab03af6181e076a2944f43c38684b4b6bed8e3"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56ff08ab5df8429901ebdc5d15941b59f6253393cb5da07b4170beefcf1b2528"}, + {file = "yarl-1.9.2-cp310-cp310-win32.whl", hash = "sha256:8ea48e0a2f931064469bdabca50c2f578b565fc446f302a79ba6cc0ee7f384d3"}, + {file = "yarl-1.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:50f33040f3836e912ed16d212f6cc1efb3231a8a60526a407aeb66c1c1956dde"}, + {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:646d663eb2232d7909e6601f1a9107e66f9791f290a1b3dc7057818fe44fc2b6"}, + {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aff634b15beff8902d1f918012fc2a42e0dbae6f469fce134c8a0dc51ca423bb"}, + {file = "yarl-1.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a83503934c6273806aed765035716216cc9ab4e0364f7f066227e1aaea90b8d0"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b25322201585c69abc7b0e89e72790469f7dad90d26754717f3310bfe30331c2"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22a94666751778629f1ec4280b08eb11815783c63f52092a5953faf73be24191"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ec53a0ea2a80c5cd1ab397925f94bff59222aa3cf9c6da938ce05c9ec20428d"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:159d81f22d7a43e6eabc36d7194cb53f2f15f498dbbfa8edc8a3239350f59fe7"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:832b7e711027c114d79dffb92576acd1bd2decc467dec60e1cac96912602d0e6"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:95d2ecefbcf4e744ea952d073c6922e72ee650ffc79028eb1e320e732898d7e8"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d4e2c6d555e77b37288eaf45b8f60f0737c9efa3452c6c44626a5455aeb250b9"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:783185c75c12a017cc345015ea359cc801c3b29a2966c2655cd12b233bf5a2be"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:b8cc1863402472f16c600e3e93d542b7e7542a540f95c30afd472e8e549fc3f7"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:822b30a0f22e588b32d3120f6d41e4ed021806418b4c9f0bc3048b8c8cb3f92a"}, + {file = "yarl-1.9.2-cp311-cp311-win32.whl", hash = "sha256:a60347f234c2212a9f0361955007fcf4033a75bf600a33c88a0a8e91af77c0e8"}, + {file = "yarl-1.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:be6b3fdec5c62f2a67cb3f8c6dbf56bbf3f61c0f046f84645cd1ca73532ea051"}, + {file = "yarl-1.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38a3928ae37558bc1b559f67410df446d1fbfa87318b124bf5032c31e3447b74"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac9bb4c5ce3975aeac288cfcb5061ce60e0d14d92209e780c93954076c7c4367"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3da8a678ca8b96c8606bbb8bfacd99a12ad5dd288bc6f7979baddd62f71c63ef"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13414591ff516e04fcdee8dc051c13fd3db13b673c7a4cb1350e6b2ad9639ad3"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf74d08542c3a9ea97bb8f343d4fcbd4d8f91bba5ec9d5d7f792dbe727f88938"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7221580dc1db478464cfeef9b03b95c5852cc22894e418562997df0d074ccc"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:494053246b119b041960ddcd20fd76224149cfea8ed8777b687358727911dd33"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:52a25809fcbecfc63ac9ba0c0fb586f90837f5425edfd1ec9f3372b119585e45"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:e65610c5792870d45d7b68c677681376fcf9cc1c289f23e8e8b39c1485384185"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:1b1bba902cba32cdec51fca038fd53f8beee88b77efc373968d1ed021024cc04"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:662e6016409828ee910f5d9602a2729a8a57d74b163c89a837de3fea050c7582"}, + {file = "yarl-1.9.2-cp37-cp37m-win32.whl", hash = "sha256:f364d3480bffd3aa566e886587eaca7c8c04d74f6e8933f3f2c996b7f09bee1b"}, + {file = "yarl-1.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6a5883464143ab3ae9ba68daae8e7c5c95b969462bbe42e2464d60e7e2698368"}, + {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5610f80cf43b6202e2c33ba3ec2ee0a2884f8f423c8f4f62906731d876ef4fac"}, + {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b9a4e67ad7b646cd6f0938c7ebfd60e481b7410f574c560e455e938d2da8e0f4"}, + {file = "yarl-1.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:83fcc480d7549ccebe9415d96d9263e2d4226798c37ebd18c930fce43dfb9574"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fcd436ea16fee7d4207c045b1e340020e58a2597301cfbcfdbe5abd2356c2fb"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84e0b1599334b1e1478db01b756e55937d4614f8654311eb26012091be109d59"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3458a24e4ea3fd8930e934c129b676c27452e4ebda80fbe47b56d8c6c7a63a9e"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:838162460b3a08987546e881a2bfa573960bb559dfa739e7800ceeec92e64417"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4e2d08f07a3d7d3e12549052eb5ad3eab1c349c53ac51c209a0e5991bbada78"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:de119f56f3c5f0e2fb4dee508531a32b069a5f2c6e827b272d1e0ff5ac040333"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:149ddea5abf329752ea5051b61bd6c1d979e13fbf122d3a1f9f0c8be6cb6f63c"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:674ca19cbee4a82c9f54e0d1eee28116e63bc6fd1e96c43031d11cbab8b2afd5"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:9b3152f2f5677b997ae6c804b73da05a39daa6a9e85a512e0e6823d81cdad7cc"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5415d5a4b080dc9612b1b63cba008db84e908b95848369aa1da3686ae27b6d2b"}, + {file = "yarl-1.9.2-cp38-cp38-win32.whl", hash = "sha256:f7a3d8146575e08c29ed1cd287068e6d02f1c7bdff8970db96683b9591b86ee7"}, + {file = "yarl-1.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:63c48f6cef34e6319a74c727376e95626f84ea091f92c0250a98e53e62c77c72"}, + {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75df5ef94c3fdc393c6b19d80e6ef1ecc9ae2f4263c09cacb178d871c02a5ba9"}, + {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c027a6e96ef77d401d8d5a5c8d6bc478e8042f1e448272e8d9752cb0aff8b5c8"}, + {file = "yarl-1.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3b078dbe227f79be488ffcfc7a9edb3409d018e0952cf13f15fd6512847f3f7"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59723a029760079b7d991a401386390c4be5bfec1e7dd83e25a6a0881859e716"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b03917871bf859a81ccb180c9a2e6c1e04d2f6a51d953e6a5cdd70c93d4e5a2a"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1012fa63eb6c032f3ce5d2171c267992ae0c00b9e164efe4d73db818465fac3"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a74dcbfe780e62f4b5a062714576f16c2f3493a0394e555ab141bf0d746bb955"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c56986609b057b4839968ba901944af91b8e92f1725d1a2d77cbac6972b9ed1"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2c315df3293cd521033533d242d15eab26583360b58f7ee5d9565f15fee1bef4"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b7232f8dfbd225d57340e441d8caf8652a6acd06b389ea2d3222b8bc89cbfca6"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:53338749febd28935d55b41bf0bcc79d634881195a39f6b2f767870b72514caf"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:066c163aec9d3d073dc9ffe5dd3ad05069bcb03fcaab8d221290ba99f9f69ee3"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8288d7cd28f8119b07dd49b7230d6b4562f9b61ee9a4ab02221060d21136be80"}, + {file = "yarl-1.9.2-cp39-cp39-win32.whl", hash = "sha256:b124e2a6d223b65ba8768d5706d103280914d61f5cae3afbc50fc3dfcc016623"}, + {file = "yarl-1.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:61016e7d582bc46a5378ffdd02cd0314fb8ba52f40f9cf4d9a5e7dbef88dee18"}, + {file = "yarl-1.9.2.tar.gz", hash = "sha256:04ab9d4b9f587c06d801c2abfe9317b77cdf996c65a90d5e84ecc45010823571"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + [[package]] name = "zope-event" version = "5.0" @@ -3681,4 +4552,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "0a4f26164e0dd32ce9d63da9322739c0812e56a5bdfb4148c973e22434344032" +content-hash = "1a8981686295b3de58b41192c608a69a2c05983c47f93451c8a6ed50304b7cb3" diff --git a/machine-learning/pyproject.toml b/machine-learning/pyproject.toml index b2fa1b28e3d29d15cc1cd14052d54734841b3886..355fb23bddaedfb1d34ed212e9f1aeedc6fd2431 100644 --- a/machine-learning/pyproject.toml +++ b/machine-learning/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "machine-learning" -version = "1.73.0" +version = "1.75.2" description = "" authors = ["Hau Tran "] readme = "README.md" @@ -13,7 +13,6 @@ torch = [ {markers = "platform_machine == 'amd64' or platform_machine == 'x86_64'", version = "=2.0.1", source = "pytorch-cpu"} ] transformers = "^4.29.2" -sentence-transformers = "^2.2.2" onnxruntime = "^1.15.0" insightface = "^0.7.3" opencv-python-headless = "^4.7.0.72" @@ -22,6 +21,15 @@ fastapi = "^0.95.2" uvicorn = {extras = ["standard"], version = "^0.22.0"} pydantic = "^1.10.8" aiocache = "^0.12.1" +optimum = "^1.9.1" +torchvision = [ + {markers = "platform_machine == 'arm64' or platform_machine == 'aarch64'", version = "=0.15.2", source = "pypi"}, + {markers = "platform_machine == 'amd64' or platform_machine == 'x86_64'", version = "=0.15.2", source = "pytorch-cpu"} +] +rich = "^13.4.2" +ftfy = "^6.1.1" +setuptools = "^68.0.0" +open-clip-torch = "^2.20.0" [tool.poetry.group.dev.dependencies] mypy = "^1.3.0" @@ -62,13 +70,20 @@ warn_untyped_fields = true [[tool.mypy.overrides]] module = [ "huggingface_hub", - "transformers.pipelines", + "transformers", "cv2", "insightface.model_zoo", "insightface.utils.face_align", "insightface.utils.storage", - "sentence_transformers", - "sentence_transformers.util", + "onnxruntime", + "optimum", + "optimum.pipelines", + "optimum.onnxruntime", + "clip_server.model.clip", + "clip_server.model.clip_onnx", + "clip_server.model.pretrained_models", + "clip_server.model.tokenization", + "torchvision.transforms", "aiocache.backends.memory", "aiocache.lock", "aiocache.plugins" diff --git a/machine-learning/requirements.txt b/machine-learning/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..c3caa064473e707ca44b521c7c67a584c5d8caf9 --- /dev/null +++ b/machine-learning/requirements.txt @@ -0,0 +1,2 @@ +# requirements to be installed with `--no-deps` flag +clip-server==0.8.* \ No newline at end of file diff --git a/mobile/.fvm/fvm_config.json b/mobile/.fvm/fvm_config.json index 0870e7648ff391ace2d016c424d71ffa02ab1634..04c1b862c97bb67c468b739f30c1cbab6f61395a 100644 --- a/mobile/.fvm/fvm_config.json +++ b/mobile/.fvm/fvm_config.json @@ -1,4 +1,4 @@ { - "flutterSdkVersion": "3.10.5", + "flutterSdkVersion": "3.13.0", "flavors": {} } diff --git a/mobile/android/app/build.gradle b/mobile/android/app/build.gradle index ccf63214027ff1622fb47628b6944e867a972f4e..732a8c7530fddee02491d0b905172d42cb4b657e 100644 --- a/mobile/android/app/build.gradle +++ b/mobile/android/app/build.gradle @@ -52,7 +52,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "app.alextran.immich" - minSdkVersion 23 + minSdkVersion 26 targetSdkVersion 33 versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/mobile/android/app/src/main/AndroidManifest.xml b/mobile/android/app/src/main/AndroidManifest.xml index c3b7f1c9280909a23504a20a2b7cbf2da2403a27..71ff1230db3a2815fe7ebed2070153cba5ca88fc 100644 --- a/mobile/android/app/src/main/AndroidManifest.xml +++ b/mobile/android/app/src/main/AndroidManifest.xml @@ -56,7 +56,7 @@ - + @@ -64,6 +64,7 @@ + diff --git a/mobile/android/fastlane/Fastfile b/mobile/android/fastlane/Fastfile index e9be5f6548c6e6f158c401a7581b394f252774b3..09741ae07bd0e1802efe23b3b72ca4a4abf198a0 100644 --- a/mobile/android/fastlane/Fastfile +++ b/mobile/android/fastlane/Fastfile @@ -35,8 +35,8 @@ platform :android do task: 'bundle', build_type: 'Release', properties: { - "android.injected.version.code" => 96, - "android.injected.version.name" => "1.73.0", + "android.injected.version.code" => 98, + "android.injected.version.name" => "1.75.2", } ) upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab') diff --git a/mobile/assets/i18n/en-US.json b/mobile/assets/i18n/en-US.json index 8aeae7043a3821d732b76fbc4e1d7331b3a67f03..ddf5b88e72ffb28cec3076fa668c3f0c9f5b1f8d 100644 --- a/mobile/assets/i18n/en-US.json +++ b/mobile/assets/i18n/en-US.json @@ -300,5 +300,21 @@ "version_announcement_overlay_text_1": "Hi friend, there is a new release of", "version_announcement_overlay_text_2": "please take your time to visit the ", "version_announcement_overlay_text_3": " and ensure your docker-compose and .env setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your server application automatically.", - "version_announcement_overlay_title": "New Server Version Available \uD83C\uDF89" -} \ No newline at end of file + "version_announcement_overlay_title": "New Server Version Available \uD83C\uDF89", + "translated_text_options": "Options", + "map_no_assets_in_bounds": "No photos in this area", + "map_zoom_to_see_photos": "Zoom out to see photos", + "map_settings_dialog_title": "Map Settings", + "map_settings_dark_mode": "Dark mode", + "map_settings_only_show_favorites": "Show Favorite Only", + "map_settings_only_relative_range": "Date range", + "map_settings_dialog_cancel": "Cancel", + "map_settings_dialog_save": "Save", + "map_cannot_get_user_location": "Cannot get user's location", + "map_location_service_disabled_title": "Location Service disabled", + "map_location_service_disabled_content": "Location service needs to be enabled to display assets from your current location. Do you want to enable it now?", + "map_no_location_permission_title": "Location Permission denied", + "map_no_location_permission_content": "Location permission is needed to display assets from your current location. Do you want to allow it now?", + "map_location_dialog_cancel": "Cancel", + "map_location_dialog_yes": "Yes" +} diff --git a/mobile/assets/lighthouse.png b/mobile/assets/lighthouse.png new file mode 100644 index 0000000000000000000000000000000000000000..e2df34e106ccc3a397f06f3b1fec275b10c2edfb Binary files /dev/null and b/mobile/assets/lighthouse.png differ diff --git a/mobile/ios/Podfile.lock b/mobile/ios/Podfile.lock index 4902388c6d9d0fe582a817643cd35f7e5f0f5bab..75168ce1c910e77a9a9741b650ec78a9e632bb27 100644 --- a/mobile/ios/Podfile.lock +++ b/mobile/ios/Podfile.lock @@ -20,6 +20,8 @@ PODS: - FMDB (2.7.5): - FMDB/standard (= 2.7.5) - FMDB/standard (2.7.5) + - geolocator_apple (1.2.0): + - Flutter - image_picker_ios (0.0.1): - Flutter - integration_test (0.0.1): @@ -33,7 +35,7 @@ PODS: - FlutterMacOS - path_provider_ios (0.0.1): - Flutter - - permission_handler_apple (9.0.4): + - permission_handler_apple (9.1.1): - Flutter - photo_manager (2.0.0): - Flutter @@ -53,7 +55,7 @@ PODS: - Flutter - video_player_avfoundation (0.0.1): - Flutter - - wakelock (0.0.1): + - wakelock_plus (0.0.1): - Flutter DEPENDENCIES: @@ -65,6 +67,7 @@ DEPENDENCIES: - flutter_udid (from `.symlinks/plugins/flutter_udid/ios`) - flutter_web_auth (from `.symlinks/plugins/flutter_web_auth/ios`) - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) + - geolocator_apple (from `.symlinks/plugins/geolocator_apple/ios`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - integration_test (from `.symlinks/plugins/integration_test/ios`) - isar_flutter_libs (from `.symlinks/plugins/isar_flutter_libs/ios`) @@ -78,7 +81,7 @@ DEPENDENCIES: - sqflite (from `.symlinks/plugins/sqflite/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/ios`) - - wakelock (from `.symlinks/plugins/wakelock/ios`) + - wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`) SPEC REPOS: trunk: @@ -104,6 +107,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_web_auth/ios" fluttertoast: :path: ".symlinks/plugins/fluttertoast/ios" + geolocator_apple: + :path: ".symlinks/plugins/geolocator_apple/ios" image_picker_ios: :path: ".symlinks/plugins/image_picker_ios/ios" integration_test: @@ -130,8 +135,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/url_launcher_ios/ios" video_player_avfoundation: :path: ".symlinks/plugins/video_player_avfoundation/ios" - wakelock: - :path: ".symlinks/plugins/wakelock/ios" + wakelock_plus: + :path: ".symlinks/plugins/wakelock_plus/ios" SPEC CHECKSUMS: connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a @@ -141,26 +146,27 @@ SPEC CHECKSUMS: flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef flutter_udid: 0848809dbed4c055175747ae6a45a8b4f6771e1c flutter_web_auth: c25208760459cec375a3c39f6a8759165ca0fa4d - fluttertoast: eb263d302cc92e04176c053d2385237e9f43fad0 + fluttertoast: fafc4fa4d01a6a9e4f772ecd190ffa525e9e2d9c FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a + geolocator_apple: cc556e6844d508c95df1e87e3ea6fa4e58c50401 image_picker_ios: 4a8aadfbb6dc30ad5141a2ce3832af9214a705b5 integration_test: 13825b8a9334a850581300559b8839134b124670 isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073 - package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e - path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8 + package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7 + path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02 - permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce + permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6 photo_manager: 4f6810b7dfc4feb03b461ac1a70dacf91fba7604 ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c - share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68 - shared_preferences_foundation: e2dae3258e06f44cc55f49d42024fd8dd03c590c + share_plus: 599aa54e4ea31d4b4c0e9c911bcc26c55e791028 + shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a Toast: 91b396c56ee72a5790816f40d3a94dd357abc196 url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4 video_player_avfoundation: 81e49bb3d9fb63dccf9fa0f6d877dc3ddbeac126 - wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f + wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47 PODFILE CHECKSUM: 599d8aeb73728400c15364e734525722250a5382 -COCOAPODS: 1.12.1 +COCOAPODS: 1.11.3 diff --git a/mobile/ios/Runner.xcodeproj/project.pbxproj b/mobile/ios/Runner.xcodeproj/project.pbxproj index 8bb1e319277bb6b7798edc84458903d6aee44798..afba814a6f7ba5e27c18d926501ac42df15a4748 100644 --- a/mobile/ios/Runner.xcodeproj/project.pbxproj +++ b/mobile/ios/Runner.xcodeproj/project.pbxproj @@ -171,7 +171,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { diff --git a/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 83d495ca8accbe49acc6362780a0d48c12b8ff8e..43b89d762ecaafefd5cb1f328c7adbeda3a0c729 100644 --- a/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ NSCameraUsageDescription We need to access the camera to let you take beautiful video using this app - NSLocationAlwaysUsageDescription - Enable location setting to show position of assets on map NSLocationWhenInUseUsageDescription Enable location setting to show position of assets on map NSMicrophoneUsageDescription diff --git a/mobile/ios/ci_scripts/ci_post_clone.sh b/mobile/ios/ci_scripts/ci_post_clone.sh index 1a4f0cd169960c2f8683a8a40596a2884a788ed0..ea244484bbbe340296c4d0f71a89a8aff0f00689 100755 --- a/mobile/ios/ci_scripts/ci_post_clone.sh +++ b/mobile/ios/ci_scripts/ci_post_clone.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/usr/bin/env sh # The default execution directory of this script is the ci_scripts directory. cd $CI_WORKSPACE/mobile diff --git a/mobile/ios/fastlane/Fastfile b/mobile/ios/fastlane/Fastfile index 7707eadb9d51c16a95c3f455e65e52f0b7faee64..b0bac12e00dd8d0d03542018471913ee44c96e6c 100644 --- a/mobile/ios/fastlane/Fastfile +++ b/mobile/ios/fastlane/Fastfile @@ -19,7 +19,7 @@ platform :ios do desc "iOS Beta" lane :beta do increment_version_number( - version_number: "1.73.0" + version_number: "1.75.2" ) increment_build_number( build_number: latest_testflight_build_number + 1, diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index b447481c692fc4b52e2f44ff14d376d376ee532a..07dc9813931fb46f5d0a9c98df85fdf0931db40a 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -63,11 +63,15 @@ Future initApp() async { FlutterError.onError = (details) { FlutterError.presentError(details); - log.severe(details.toString(), details, details.stack); + log.severe( + 'Catch all error: ${details.toString()} - ${details.exception} - ${details.library} - ${details.context} - ${details.stack}', + details, + details.stack, + ); }; PlatformDispatcher.instance.onError = (error, stack) { - log.severe(error.toString(), error, stack); + log.severe('Catch all error: ${error.toString()} - $error', error, stack); return true; }; } @@ -139,6 +143,10 @@ class ImmichAppState extends ConsumerState debugPrint("[APP STATE] detached"); ref.read(appStateProvider.notifier).handleAppDetached(); break; + case AppLifecycleState.hidden: + debugPrint("[APP STATE] hidden"); + ref.read(appStateProvider.notifier).handleAppHidden(); + break; } } diff --git a/mobile/lib/modules/album/providers/shared_album.provider.dart b/mobile/lib/modules/album/providers/shared_album.provider.dart index 8b342dd45f08e032c66c4c89940602df8db5601f..4f36c4633d9c1242c0d695510641434cd0e47034 100644 --- a/mobile/lib/modules/album/providers/shared_album.provider.dart +++ b/mobile/lib/modules/album/providers/shared_album.provider.dart @@ -56,6 +56,16 @@ class SharedAlbumNotifier extends StateNotifier> { return _albumService.removeAssetFromAlbum(album, assets); } + Future removeUserFromAlbum(Album album, User user) async { + final result = await _albumService.removeUserFromAlbum(album, user); + + if (result && album.sharedUsers.isEmpty) { + state = state.where((element) => element.id != album.id).toList(); + } + + return result; + } + @override void dispose() { _streamSub.cancel(); diff --git a/mobile/lib/modules/album/services/album.service.dart b/mobile/lib/modules/album/services/album.service.dart index 0960978a47a58c2d8fbb38f927a8b87c4f8e999a..4488eca23e72a40b10019323e248256746f75952 100644 --- a/mobile/lib/modules/album/services/album.service.dart +++ b/mobile/lib/modules/album/services/album.service.dart @@ -348,6 +348,26 @@ class AlbumService { } } + Future removeUserFromAlbum( + Album album, + User user, + ) async { + try { + await _apiService.albumApi.removeUserFromAlbum( + album.remoteId!, + user.id, + ); + + album.sharedUsers.remove(user); + await _db.writeTxn(() => album.sharedUsers.update(unlink: [user])); + + return true; + } catch (e) { + debugPrint("Error removeUserFromAlbum ${e.toString()}"); + return false; + } + } + Future changeTitleAlbum( Album album, String newAlbumTitle, diff --git a/mobile/lib/modules/album/ui/album_thumbnail_listtile.dart b/mobile/lib/modules/album/ui/album_thumbnail_listtile.dart index 83dae248cbaa4c059bc4f92354dfca990f94854d..c9237ea27de9defcf9b92510dad6fdaea72d377c 100644 --- a/mobile/lib/modules/album/ui/album_thumbnail_listtile.dart +++ b/mobile/lib/modules/album/ui/album_thumbnail_listtile.dart @@ -49,7 +49,7 @@ class AlbumThumbnailListTile extends StatelessWidget { type: ThumbnailFormat.JPEG, ), httpHeaders: { - "Authorization": "Bearer ${Store.get(StoreKey.accessToken)}" + "Authorization": "Bearer ${Store.get(StoreKey.accessToken)}", }, cacheKey: getAlbumThumbNailCacheKey(album, type: ThumbnailFormat.JPEG), errorWidget: (context, url, error) => @@ -105,9 +105,9 @@ class AlbumThumbnailListTile extends StatelessWidget { style: TextStyle( fontSize: 12, ), - ).tr() + ).tr(), ], - ) + ), ], ), ), diff --git a/mobile/lib/modules/album/ui/album_title_text_field.dart b/mobile/lib/modules/album/ui/album_title_text_field.dart index 8e63506b8fa310eb077174afa2971340c597aebc..38c1f681a7ff1cd421892da945b160e7fc71a071 100644 --- a/mobile/lib/modules/album/ui/album_title_text_field.dart +++ b/mobile/lib/modules/album/ui/album_title_text_field.dart @@ -69,6 +69,11 @@ class AlbumTitleTextField extends ConsumerWidget { borderRadius: BorderRadius.circular(10), ), hintText: 'share_add_title'.tr(), + hintStyle: TextStyle( + fontSize: 28, + color: isDarkTheme ? Colors.grey[300] : Colors.grey[700], + fontWeight: FontWeight.bold, + ), focusColor: Colors.grey[300], fillColor: isDarkTheme ? const Color.fromARGB(255, 32, 33, 35) diff --git a/mobile/lib/modules/album/ui/album_viewer_appbar.dart b/mobile/lib/modules/album/ui/album_viewer_appbar.dart index 3392ed51833dd80e716b733ca35e15964ca22539..5ca9bb81fdc51d1bc0301877e4a61f574a55cddf 100644 --- a/mobile/lib/modules/album/ui/album_viewer_appbar.dart +++ b/mobile/lib/modules/album/ui/album_viewer_appbar.dart @@ -39,7 +39,7 @@ class AlbumViewerAppbar extends HookConsumerWidget final newAlbumTitle = ref.watch(albumViewerProvider).editTitleText; final isEditAlbum = ref.watch(albumViewerProvider).isEditAlbum; - void onDeleteAlbumPressed() async { + deleteAlbum() async { ImmichLoadingOverlayController.appLoader.show(); final bool success; @@ -65,6 +65,52 @@ class AlbumViewerAppbar extends HookConsumerWidget ImmichLoadingOverlayController.appLoader.hide(); } + Future showConfirmationDialog() async { + return showDialog( + context: context, + barrierDismissible: false, // user must tap button! + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Delete album'), + content: const Text( + 'Are you sure you want to delete this album from your account?', + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, 'Cancel'), + child: Text( + 'Cancel', + style: TextStyle( + color: Theme.of(context).primaryColor, + fontWeight: FontWeight.bold, + ), + ), + ), + TextButton( + onPressed: () { + Navigator.pop(context, 'Confirm'); + deleteAlbum(); + }, + child: Text( + 'Confirm', + style: TextStyle( + fontWeight: FontWeight.bold, + color: Theme.of(context).brightness == Brightness.light + ? Colors.red + : Colors.red[300], + ), + ), + ), + ], + ); + }, + ); + } + + void onDeleteAlbumPressed() async { + showConfirmationDialog(); + } + void onLeaveAlbumPressed() async { ImmichLoadingOverlayController.appLoader.show(); @@ -152,43 +198,61 @@ class AlbumViewerAppbar extends HookConsumerWidget } void buildBottomSheet() { + final ownerActions = [ + ListTile( + leading: const Icon(Icons.person_add_alt_rounded), + onTap: () { + Navigator.pop(context); + onAddUsers!(album); + }, + title: const Text( + "album_viewer_page_share_add_users", + style: TextStyle(fontWeight: FontWeight.bold), + ).tr(), + ), + ListTile( + leading: const Icon(Icons.settings_rounded), + onTap: () => + AutoRouter.of(context).navigate(AlbumOptionsRoute(album: album)), + title: const Text( + "translated_text_options", + style: TextStyle(fontWeight: FontWeight.bold), + ).tr(), + ), + ]; + + final commonActions = [ + ListTile( + leading: const Icon(Icons.add_photo_alternate_outlined), + onTap: () { + Navigator.pop(context); + onAddPhotos!(album); + }, + title: const Text( + "share_add_photos", + style: TextStyle(fontWeight: FontWeight.bold), + ).tr(), + ), + ]; showModalBottomSheet( backgroundColor: Theme.of(context).scaffoldBackgroundColor, isScrollControlled: false, context: context, builder: (context) { return SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - buildBottomSheetActionButton(), - if (selected.isEmpty && onAddPhotos != null) - ListTile( - leading: const Icon(Icons.add_photo_alternate_outlined), - onTap: () { - Navigator.pop(context); - onAddPhotos!(album); - }, - title: const Text( - "share_add_photos", - style: TextStyle(fontWeight: FontWeight.bold), - ).tr(), - ), - if (selected.isEmpty && - onAddPhotos != null && - userId == album.ownerId) - ListTile( - leading: const Icon(Icons.person_add_alt_rounded), - onTap: () { - Navigator.pop(context); - onAddUsers!(album); - }, - title: const Text( - "album_viewer_page_share_add_users", - style: TextStyle(fontWeight: FontWeight.bold), - ).tr(), - ), - ], + child: Padding( + padding: const EdgeInsets.only(top: 24.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + buildBottomSheetActionButton(), + if (selected.isEmpty && onAddPhotos != null) ...commonActions, + if (selected.isEmpty && + onAddPhotos != null && + userId == album.ownerId) + ...ownerActions, + ], + ), ), ); }, @@ -217,6 +281,8 @@ class AlbumViewerAppbar extends HookConsumerWidget toastType: ToastType.error, ); } + + titleFocusNode.unfocus(); }, icon: const Icon(Icons.check_rounded), splashRadius: 25, diff --git a/mobile/lib/modules/album/ui/album_viewer_editable_title.dart b/mobile/lib/modules/album/ui/album_viewer_editable_title.dart index b7a5d3544ce1e2ffe738da7795ef5a6f079b2e50..8a7e46f8ca5948b62fa24489ed238d9fc201d5f0 100644 --- a/mobile/lib/modules/album/ui/album_viewer_editable_title.dart +++ b/mobile/lib/modules/album/ui/album_viewer_editable_title.dart @@ -84,6 +84,11 @@ class AlbumViewerEditableTitle extends HookConsumerWidget { : Colors.grey[200], filled: titleFocusNode.hasFocus, hintText: 'share_add_title'.tr(), + hintStyle: TextStyle( + fontSize: 28, + color: isDarkTheme ? Colors.grey[300] : Colors.grey[700], + fontWeight: FontWeight.bold, + ), ), ); } diff --git a/mobile/lib/modules/album/views/album_options_part.dart b/mobile/lib/modules/album/views/album_options_part.dart new file mode 100644 index 0000000000000000000000000000000000000000..eb08b6bda2c9898c06b16d35adcec39cf1ca7971 --- /dev/null +++ b/mobile/lib/modules/album/views/album_options_part.dart @@ -0,0 +1,205 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart'; +import 'package:immich_mobile/modules/login/providers/authentication.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/shared/models/album.dart'; +import 'package:immich_mobile/shared/models/user.dart'; +import 'package:immich_mobile/shared/ui/immich_toast.dart'; +import 'package:immich_mobile/shared/ui/user_circle_avatar.dart'; +import 'package:immich_mobile/shared/views/immich_loading_overlay.dart'; + +class AlbumOptionsPage extends HookConsumerWidget { + final Album album; + + const AlbumOptionsPage({super.key, required this.album}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final sharedUsers = useState(album.sharedUsers.toList()); + final owner = album.owner.value; + final userId = ref.watch(authenticationProvider).userId; + final isOwner = owner?.id == userId; + + void showErrorMessage() { + Navigator.pop(context); + ImmichToast.show( + context: context, + msg: "Error leaving/removing from album", + toastType: ToastType.error, + gravity: ToastGravity.BOTTOM, + ); + } + + void leaveAlbum() async { + ImmichLoadingOverlayController.appLoader.show(); + + try { + final isSuccess = + await ref.read(sharedAlbumProvider.notifier).leaveAlbum(album); + + if (isSuccess) { + AutoRouter.of(context) + .navigate(const TabControllerRoute(children: [SharingRoute()])); + } else { + showErrorMessage(); + } + } catch (_) { + showErrorMessage(); + } + + ImmichLoadingOverlayController.appLoader.hide(); + } + + void removeUserFromAlbum(User user) async { + ImmichLoadingOverlayController.appLoader.show(); + + try { + await ref + .read(sharedAlbumProvider.notifier) + .removeUserFromAlbum(album, user); + album.sharedUsers.remove(user); + sharedUsers.value = album.sharedUsers.toList(); + } catch (error) { + showErrorMessage(); + } + + Navigator.pop(context); + ImmichLoadingOverlayController.appLoader.hide(); + } + + void handleUserClick(User user) { + var actions = []; + + if (user.id == userId) { + actions = [ + ListTile( + leading: const Icon(Icons.exit_to_app_rounded), + title: const Text("Leave album"), + onTap: leaveAlbum, + ), + ]; + } + + if (isOwner) { + actions = [ + ListTile( + leading: const Icon(Icons.person_remove_rounded), + title: const Text("Remove user from album"), + onTap: () => removeUserFromAlbum(user), + ), + ]; + } + + showModalBottomSheet( + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + isScrollControlled: false, + context: context, + builder: (context) { + return SafeArea( + child: Padding( + padding: const EdgeInsets.only(top: 24.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [...actions], + ), + ), + ); + }, + ); + } + + buildOwnerInfo() { + return ListTile( + leading: owner != null + ? UserCircleAvatar( + user: owner, + useRandomBackgroundColor: true, + ) + : const SizedBox(), + title: Text( + album.owner.value?.firstName ?? "", + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + subtitle: Text( + album.owner.value?.email ?? "", + style: TextStyle(color: Colors.grey[500]), + ), + trailing: const Text( + "Owner", + style: TextStyle( + fontWeight: FontWeight.bold, + ), + ), + ); + } + + buildSharedUsersList() { + return ListView.builder( + shrinkWrap: true, + itemCount: sharedUsers.value.length, + itemBuilder: (context, index) { + final user = sharedUsers.value[index]; + return ListTile( + leading: UserCircleAvatar( + user: user, + useRandomBackgroundColor: true, + radius: 22, + ), + title: Text( + user.firstName, + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + subtitle: Text( + user.email, + style: TextStyle(color: Colors.grey[500]), + ), + trailing: userId == user.id || isOwner + ? const Icon(Icons.more_horiz_rounded) + : const SizedBox(), + onTap: userId == user.id || isOwner + ? () => handleUserClick(user) + : null, + ); + }, + ); + } + + buildSectionTitle(String text) { + return Padding( + padding: const EdgeInsets.all(16.0), + child: Text(text, style: Theme.of(context).textTheme.bodySmall), + ); + } + + return Scaffold( + appBar: AppBar( + leading: IconButton( + icon: const Icon(Icons.arrow_back_ios_new_rounded), + onPressed: () { + AutoRouter.of(context).pop(null); + }, + ), + centerTitle: true, + title: Text("translated_text_options".tr()), + ), + body: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildSectionTitle("PEOPLE"), + buildOwnerInfo(), + buildSharedUsersList(), + ], + ), + ); + } +} diff --git a/mobile/lib/modules/album/views/album_viewer_page.dart b/mobile/lib/modules/album/views/album_viewer_page.dart index 31c48c4503012d0be41253a6e23a35a204220f7f..cb389059af4e12e2f9f4e2f04f1ff9b9f0563b7f 100644 --- a/mobile/lib/modules/album/views/album_viewer_page.dart +++ b/mobile/lib/modules/album/views/album_viewer_page.dart @@ -17,6 +17,7 @@ import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/shared/models/album.dart'; import 'package:immich_mobile/shared/models/asset.dart'; import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; +import 'package:immich_mobile/shared/ui/user_circle_avatar.dart'; import 'package:immich_mobile/shared/views/immich_loading_overlay.dart'; class AlbumViewerPage extends HookConsumerWidget { @@ -116,7 +117,7 @@ class AlbumViewerPage extends HookConsumerWidget { Widget buildControlButton(Album album) { return Padding( - padding: const EdgeInsets.only(left: 16.0, top: 8, bottom: 8), + padding: const EdgeInsets.only(left: 16.0, top: 8, bottom: 16), child: SizedBox( height: 40, child: ListView( @@ -141,7 +142,7 @@ class AlbumViewerPage extends HookConsumerWidget { Widget buildTitle(Album album) { return Padding( - padding: const EdgeInsets.only(left: 8, right: 8, top: 16), + padding: const EdgeInsets.only(left: 8, right: 8, top: 24), child: userId == album.ownerId && album.isRemote ? AlbumViewerEditableTitle( album: album, @@ -172,7 +173,6 @@ class AlbumViewerPage extends HookConsumerWidget { return Padding( padding: EdgeInsets.only( left: 16.0, - top: 8.0, bottom: album.shared ? 0.0 : 8.0, ), child: Text( @@ -180,7 +180,34 @@ class AlbumViewerPage extends HookConsumerWidget { style: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, - color: Colors.grey, + ), + ), + ); + } + + Widget buildSharedUserIconsRow(Album album) { + return GestureDetector( + onTap: () async { + await AutoRouter.of(context).push(AlbumOptionsRoute(album: album)); + ref.invalidate(albumDetailProvider(album.id)); + }, + child: SizedBox( + height: 50, + child: ListView.builder( + padding: const EdgeInsets.only(left: 16), + scrollDirection: Axis.horizontal, + itemBuilder: ((context, index) { + return Padding( + padding: const EdgeInsets.only(right: 8.0), + child: UserCircleAvatar( + user: album.sharedUsers.toList()[index], + radius: 18, + size: 36, + useRandomBackgroundColor: true, + ), + ); + }), + itemCount: album.sharedUsers.length, ), ), ); @@ -193,33 +220,7 @@ class AlbumViewerPage extends HookConsumerWidget { children: [ buildTitle(album), if (album.assets.isNotEmpty == true) buildAlbumDateRange(album), - if (album.shared) - SizedBox( - height: 50, - child: ListView.builder( - padding: const EdgeInsets.only(left: 16), - scrollDirection: Axis.horizontal, - itemBuilder: ((context, index) { - return Padding( - padding: const EdgeInsets.only(right: 8.0), - child: CircleAvatar( - backgroundColor: Colors.grey[300], - radius: 18, - child: Padding( - padding: const EdgeInsets.all(2.0), - child: ClipRRect( - borderRadius: BorderRadius.circular(50.0), - child: Image.asset( - 'assets/immich-logo-no-outline.png', - ), - ), - ), - ), - ); - }), - itemCount: album.sharedUsers.length, - ), - ), + if (album.shared) buildSharedUserIconsRow(album), ], ); } diff --git a/mobile/lib/modules/album/views/asset_selection_page.dart b/mobile/lib/modules/album/views/asset_selection_page.dart index 7ea60e2496559386a0cd159b264e5a7c10244413..afec5f8aea875daa4790a25da610ab9fdab77f3a 100644 --- a/mobile/lib/modules/album/views/asset_selection_page.dart +++ b/mobile/lib/modules/album/views/asset_selection_page.dart @@ -73,9 +73,12 @@ class AssetSelectionPage extends HookConsumerWidget { AutoRouter.of(context) .popForced(payload); }, - child: const Text( + child: Text( "share_add", - style: TextStyle(fontWeight: FontWeight.bold), + style: TextStyle( + fontWeight: FontWeight.bold, + color: Theme.of(context).primaryColor, + ), ).tr(), ), ], diff --git a/mobile/lib/modules/album/views/create_album_page.dart b/mobile/lib/modules/album/views/create_album_page.dart index 4a4e7a9e5b84f3bc3eca7af06389ead7337ae849..e1f0d65e7b7cece496e85a7c5c009c9ea62c8248 100644 --- a/mobile/lib/modules/album/views/create_album_page.dart +++ b/mobile/lib/modules/album/views/create_album_page.dart @@ -30,7 +30,8 @@ class CreateAlbumPage extends HookConsumerWidget { final albumTitleTextFieldFocusNode = useFocusNode(); final isAlbumTitleTextFieldFocus = useState(false); final isAlbumTitleEmpty = useState(true); - final selectedAssets = useState>(initialAssets != null ? Set.from(initialAssets!) : const {}); + final selectedAssets = useState>( + initialAssets != null ? Set.from(initialAssets!) : const {},); final isDarkTheme = Theme.of(context).brightness == Brightness.dark; showSelectUserPage() async { @@ -248,8 +249,9 @@ class CreateAlbumPage extends HookConsumerWidget { : null, child: Text( 'create_shared_album_page_create'.tr(), - style: const TextStyle( + style: TextStyle( fontWeight: FontWeight.bold, + color: Theme.of(context).primaryColor, ), ), ), diff --git a/mobile/lib/modules/album/views/library_page.dart b/mobile/lib/modules/album/views/library_page.dart index a2e35f715e3639cb09085b41786dcc03050ca9c0..c22129a50172d287e48a43ce502261842a4faece 100644 --- a/mobile/lib/modules/album/views/library_page.dart +++ b/mobile/lib/modules/album/views/library_page.dart @@ -60,7 +60,7 @@ class LibraryPage extends HookConsumerWidget { Widget buildSortButton() { final options = [ "library_page_sort_created".tr(), - "library_page_sort_title".tr() + "library_page_sort_title".tr(), ]; return PopupMenuButton( @@ -87,7 +87,7 @@ class LibraryPage extends HookConsumerWidget { color: selected ? Theme.of(context).primaryColor : null, fontSize: 12.0, ), - ) + ), ], ), ); diff --git a/mobile/lib/modules/album/views/select_additional_user_for_sharing_page.dart b/mobile/lib/modules/album/views/select_additional_user_for_sharing_page.dart index ec7ddb17e428222076ba2a6496f219344d65749a..e13637d23d0051a3440aa6474155220a1f51b29b 100644 --- a/mobile/lib/modules/album/views/select_additional_user_for_sharing_page.dart +++ b/mobile/lib/modules/album/views/select_additional_user_for_sharing_page.dart @@ -7,6 +7,7 @@ import 'package:immich_mobile/modules/album/providers/suggested_shared_users.pro import 'package:immich_mobile/shared/models/album.dart'; import 'package:immich_mobile/shared/models/user.dart'; import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; +import 'package:immich_mobile/shared/ui/user_circle_avatar.dart'; class SelectAdditionalUserForSharingPage extends HookConsumerWidget { final Album album; @@ -35,10 +36,8 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget { ), ); } else { - return CircleAvatar( - backgroundImage: - const AssetImage('assets/immich-logo-no-outline.png'), - backgroundColor: Theme.of(context).primaryColor.withAlpha(50), + return UserCircleAvatar( + user: user, ); } } @@ -103,7 +102,7 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget { } else { sharedUsersList.value = { ...sharedUsersList.value, - users[index] + users[index], }; } }, @@ -136,7 +135,7 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget { "share_add", style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold), ).tr(), - ) + ), ], ), body: suggestedShareUsers.when( diff --git a/mobile/lib/modules/album/views/select_user_for_sharing_page.dart b/mobile/lib/modules/album/views/select_user_for_sharing_page.dart index eaf99164599e2b4fb396bbc2e979337ad21450d5..fe6cef40160082152643479d70c260bbd2b6652a 100644 --- a/mobile/lib/modules/album/views/select_user_for_sharing_page.dart +++ b/mobile/lib/modules/album/views/select_user_for_sharing_page.dart @@ -10,6 +10,7 @@ import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/shared/models/asset.dart'; import 'package:immich_mobile/shared/models/user.dart'; import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; +import 'package:immich_mobile/shared/ui/user_circle_avatar.dart'; class SelectUserForSharingPage extends HookConsumerWidget { const SelectUserForSharingPage({Key? key, required this.assets}) @@ -56,10 +57,8 @@ class SelectUserForSharingPage extends HookConsumerWidget { ), ); } else { - return CircleAvatar( - backgroundImage: - const AssetImage('assets/immich-logo-no-outline.png'), - backgroundColor: Theme.of(context).primaryColor.withAlpha(50), + return UserCircleAvatar( + user: user, ); } } @@ -124,7 +123,7 @@ class SelectUserForSharingPage extends HookConsumerWidget { } else { sharedUsersList.value = { ...sharedUsersList.value, - users[index] + users[index], }; } }, @@ -164,7 +163,7 @@ class SelectUserForSharingPage extends HookConsumerWidget { // color: Theme.of(context).primaryColor, ), ).tr(), - ) + ), ], ), body: suggestedShareUsers.when( diff --git a/mobile/lib/modules/album/views/sharing_page.dart b/mobile/lib/modules/album/views/sharing_page.dart index b87cbdb06a0f8e2279ed8d3853ca4a309e91f2b9..1f1745d497ebcfbcb66fc6515db9d8afc716a3ce 100644 --- a/mobile/lib/modules/album/views/sharing_page.dart +++ b/mobile/lib/modules/album/views/sharing_page.dart @@ -160,7 +160,7 @@ class SharingPage extends HookConsumerWidget { maxLines: 1, ).tr(), ), - ) + ), ], ), ); diff --git a/mobile/lib/modules/archive/views/archive_page.dart b/mobile/lib/modules/archive/views/archive_page.dart index f5ca80406b40cc7da28b3eb62858c38850e741fc..2e1c1cd4a6fdd3f445097edecb73f11ecd4eb3ce 100644 --- a/mobile/lib/modules/archive/views/archive_page.dart +++ b/mobile/lib/modules/archive/views/archive_page.dart @@ -2,14 +2,12 @@ import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/modules/archive/providers/archive_asset_provider.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart'; import 'package:immich_mobile/shared/models/asset.dart'; -import 'package:immich_mobile/shared/providers/asset.provider.dart'; import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; -import 'package:immich_mobile/shared/ui/immich_toast.dart'; +import 'package:immich_mobile/utils/selection_handlers.dart'; class ArchivePage extends HookConsumerWidget { const ArchivePage({super.key}); @@ -68,30 +66,18 @@ class ArchivePage extends HookConsumerWidget { : () async { processing.value = true; try { - if (selection.value.isNotEmpty) { - await ref - .watch(assetProvider.notifier) - .toggleArchive( - selection.value.toList(), - false, - ); - - final assetOrAssets = selection.value.length > 1 - ? 'assets' - : 'asset'; - ImmichToast.show( - context: context, - msg: - 'Moved ${selection.value.length} $assetOrAssets to library', - gravity: ToastGravity.CENTER, - ); - } + await handleArchiveAssets( + ref, + context, + selection.value.toList(), + shouldArchive: false, + ); } finally { processing.value = false; selectionEnabledHook.value = false; } }, - ) + ), ], ), ), @@ -124,7 +110,7 @@ class ArchivePage extends HookConsumerWidget { ), if (selectionEnabledHook.value) buildBottomBar(), if (processing.value) - const Center(child: ImmichLoadingIndicator()) + const Center(child: ImmichLoadingIndicator()), ], ), ), diff --git a/mobile/lib/modules/asset_viewer/ui/exif_bottom_sheet.dart b/mobile/lib/modules/asset_viewer/ui/exif_bottom_sheet.dart index 6bace49afdcc54b7f8469c6314b0fbfbca6d7d00..fcc1d4440d565796cddc33733d1c57813b44607d 100644 --- a/mobile/lib/modules/asset_viewer/ui/exif_bottom_sheet.dart +++ b/mobile/lib/modules/asset_viewer/ui/exif_bottom_sheet.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/modules/asset_viewer/ui/description_input.dart'; +import 'package:immich_mobile/modules/map/ui/map_thumbnail.dart'; import 'package:immich_mobile/shared/models/asset.dart'; import 'package:immich_mobile/shared/ui/drag_sheet.dart'; import 'package:latlong2/latlong.dart'; @@ -16,16 +17,35 @@ class ExifBottomSheet extends HookConsumerWidget { const ExifBottomSheet({Key? key, required this.asset}) : super(key: key); - bool get showMap => + bool get hasCoordinates => asset.exifInfo?.latitude != null && asset.exifInfo?.longitude != null; - Future _createCoordinatesUri(double latitude, double longitude) async { - const zoomLevel = 5; + String get formattedDateTime { + final fileCreatedAt = asset.fileCreatedAt.toLocal(); + final date = DateFormat.yMMMEd().format(fileCreatedAt); + final time = DateFormat.jm().format(fileCreatedAt); + + return '$date • $time'; + } + + Future _createCoordinatesUri() async { + if (!hasCoordinates) { + return null; + } + + double latitude = asset.exifInfo!.latitude!; + double longitude = asset.exifInfo!.longitude!; + + const zoomLevel = 16; + if (Platform.isAndroid) { Uri uri = Uri( scheme: 'geo', host: '$latitude,$longitude', - queryParameters: {'z': '$zoomLevel', 'q': '$latitude,$longitude'}, + queryParameters: { + 'z': '$zoomLevel', + 'q': '$latitude,$longitude($formattedDateTime)', + }, ); if (await canLaunchUrl(uri)) { return uri; @@ -33,16 +53,20 @@ class ExifBottomSheet extends HookConsumerWidget { } else if (Platform.isIOS) { var params = { 'll': '$latitude,$longitude', - 'q': '$latitude, $longitude', + 'q': formattedDateTime, + 'z': '$zoomLevel', }; Uri uri = Uri.https('maps.apple.com', '/', params); - if (!await canLaunchUrl(uri)) { + if (await canLaunchUrl(uri)) { return uri; } } - return Uri.https( - 'www.google.com', - '/maps/place/$latitude,$longitude/@$latitude,$longitude,${zoomLevel}z', + + return Uri( + scheme: 'https', + host: 'openstreetmap.org', + queryParameters: {'mlat': '$latitude', 'mlon': '$longitude'}, + fragment: 'map=$zoomLevel/$latitude/$longitude', ); } @@ -57,67 +81,35 @@ class ExifBottomSheet extends HookConsumerWidget { padding: const EdgeInsets.symmetric(vertical: 16.0), child: LayoutBuilder( builder: (context, constraints) { - return Container( - height: 150, - width: constraints.maxWidth, - decoration: const BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(15)), + return MapThumbnail( + coords: LatLng( + exifInfo?.latitude ?? 0, + exifInfo?.longitude ?? 0, ), - child: FlutterMap( - options: MapOptions( - interactiveFlags: InteractiveFlag.none, - center: LatLng( + height: 150, + zoom: 16.0, + markers: [ + Marker( + anchorPos: AnchorPos.align(AnchorAlign.top), + point: LatLng( exifInfo?.latitude ?? 0, exifInfo?.longitude ?? 0, ), - zoom: 16.0, - onTap: (tapPosition, latLong) async { - if (exifInfo != null && - exifInfo.latitude != null && - exifInfo.longitude != null) { - launchUrl( - await _createCoordinatesUri( - exifInfo.latitude!, - exifInfo.longitude!, - ), - ); - } - }, - ), - nonRotatedChildren: [ - RichAttributionWidget( - attributions: [ - TextSourceAttribution( - 'OpenStreetMap contributors', - onTap: () => launchUrl( - Uri.parse('https://openstreetmap.org/copyright'), - ), - ), - ], - ), - ], - children: [ - TileLayer( - urlTemplate: - "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", - subdomains: const ['a', 'b', 'c'], + builder: (ctx) => const Image( + image: AssetImage('assets/location-pin.png'), ), - MarkerLayer( - markers: [ - Marker( - anchorPos: AnchorPos.align(AnchorAlign.top), - point: LatLng( - exifInfo?.latitude ?? 0, - exifInfo?.longitude ?? 0, - ), - builder: (ctx) => const Image( - image: AssetImage('assets/location-pin.png'), - ), - ), - ], - ), - ], - ), + ), + ], + onTap: (tapPosition, latLong) async { + Uri? uri = await _createCoordinatesUri(); + + if (uri == null) { + return; + } + + debugPrint('Opening Map Uri: $uri'); + launchUrl(uri); + }, ); }, ), @@ -151,7 +143,7 @@ class ExifBottomSheet extends HookConsumerWidget { buildLocation() { // Guard no lat/lng - if (!showMap) { + if (!hasCoordinates) { return Container(); } @@ -199,7 +191,7 @@ class ExifBottomSheet extends HookConsumerWidget { Text( "${exifInfo!.latitude!.toStringAsFixed(4)}, ${exifInfo.longitude!.toStringAsFixed(4)}", style: const TextStyle(fontSize: 12), - ) + ), ], ), ], @@ -207,12 +199,8 @@ class ExifBottomSheet extends HookConsumerWidget { } buildDate() { - final fileCreatedAt = asset.fileCreatedAt.toLocal(); - final date = DateFormat.yMMMEd().format(fileCreatedAt); - final time = DateFormat.jm().format(fileCreatedAt); - return Text( - '$date • $time', + formattedDateTime, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 14, @@ -306,7 +294,7 @@ class ExifBottomSheet extends HookConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Flexible( - flex: showMap ? 5 : 0, + flex: hasCoordinates ? 5 : 0, child: Padding( padding: const EdgeInsets.only(right: 8.0), child: buildLocation(), @@ -336,7 +324,7 @@ class ExifBottomSheet extends HookConsumerWidget { if (asset.isRemote) DescriptionInput(asset: asset), const SizedBox(height: 8.0), buildLocation(), - SizedBox(height: showMap ? 16.0 : 0.0), + SizedBox(height: hasCoordinates ? 16.0 : 0.0), buildDetail(), const SizedBox(height: 50), ], diff --git a/mobile/lib/modules/asset_viewer/ui/top_control_app_bar.dart b/mobile/lib/modules/asset_viewer/ui/top_control_app_bar.dart index 4cc0b6f42c3c5b7233bf09df9c2adadc4650a6c2..08e9b79af6e2a750351a4da15e4526af442c9a65 100644 --- a/mobile/lib/modules/asset_viewer/ui/top_control_app_bar.dart +++ b/mobile/lib/modules/asset_viewer/ui/top_control_app_bar.dart @@ -128,7 +128,7 @@ class TopControlAppBar extends HookConsumerWidget { if (asset.isLocal && !asset.isRemote) buildUploadButton(), if (asset.isRemote && !asset.isLocal) buildDownloadButton(), if (asset.isRemote) buildAddToAlbumButtom(), - buildMoreInfoButton() + buildMoreInfoButton(), ], ); } diff --git a/mobile/lib/modules/asset_viewer/views/video_viewer_page.dart b/mobile/lib/modules/asset_viewer/views/video_viewer_page.dart index 72a1dce61a5c0c9dd6abdfc30cbbbc8e22f6b3d2..a56d65595d835ca21c14e15b52ce635cb26dad77 100644 --- a/mobile/lib/modules/asset_viewer/views/video_viewer_page.dart +++ b/mobile/lib/modules/asset_viewer/views/video_viewer_page.dart @@ -11,7 +11,7 @@ import 'package:immich_mobile/shared/models/store.dart'; import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; import 'package:photo_manager/photo_manager.dart'; import 'package:video_player/video_player.dart'; -import 'package:wakelock/wakelock.dart'; +import 'package:wakelock_plus/wakelock_plus.dart'; // ignore: must_be_immutable class VideoViewerPage extends HookConsumerWidget { @@ -136,16 +136,16 @@ class _VideoPlayerState extends State { videoPlayerController.addListener(() { if (videoPlayerController.value.isInitialized) { if (videoPlayerController.value.isPlaying) { - Wakelock.enable(); + WakelockPlus.enable(); widget.onPlaying?.call(); } else if (!videoPlayerController.value.isPlaying) { - Wakelock.disable(); + WakelockPlus.disable(); widget.onPaused?.call(); } if (videoPlayerController.value.position == videoPlayerController.value.duration) { - Wakelock.disable(); + WakelockPlus.disable(); widget.onVideoEnded(); } } @@ -155,8 +155,8 @@ class _VideoPlayerState extends State { Future initializePlayer() async { try { videoPlayerController = widget.file == null - ? VideoPlayerController.network( - widget.url!, + ? VideoPlayerController.networkUrl( + Uri.parse(widget.url!), httpHeaders: {"Authorization": "Bearer ${widget.jwtToken}"}, ) : VideoPlayerController.file(widget.file!); @@ -210,8 +210,7 @@ class _VideoPlayerState extends State { child: Center( child: Stack( children: [ - if (widget.placeholder != null) - widget.placeholder!, + if (widget.placeholder != null) widget.placeholder!, const Center( child: ImmichLoadingIndicator(), ), diff --git a/mobile/lib/modules/backup/background_service/background.service.dart b/mobile/lib/modules/backup/background_service/background.service.dart index 823a7c2bcbe1d6a48174735adf411fe3eb823095..0d0501c3024beb42076af4bb0b9db5517e759afc 100644 --- a/mobile/lib/modules/backup/background_service/background.service.dart +++ b/mobile/lib/modules/backup/background_service/background.service.dart @@ -90,7 +90,7 @@ class BackgroundService { requireUnmetered, requireCharging, triggerUpdateDelay, - triggerMaxDelay + triggerMaxDelay, ], ); return ok; diff --git a/mobile/lib/modules/backup/providers/backup.provider.dart b/mobile/lib/modules/backup/providers/backup.provider.dart index 0c38a6831bac2d9e4c868e965434706d88d40bf7..2aacf49be5c6081a153ce6d40584a15f79499dbb 100644 --- a/mobile/lib/modules/backup/providers/backup.provider.dart +++ b/mobile/lib/modules/backup/providers/backup.provider.dart @@ -511,7 +511,7 @@ class BackupNotifier extends StateNotifier { state = state.copyWith( selectedAlbumsBackupAssetsIds: { ...state.selectedAlbumsBackupAssetsIds, - deviceAssetId + deviceAssetId, }, allAssetsInDatabase: [...state.allAssetsInDatabase, deviceAssetId], ); diff --git a/mobile/lib/modules/backup/providers/manual_upload.provider.dart b/mobile/lib/modules/backup/providers/manual_upload.provider.dart index 3a335a7b1f883e2bca138e28d9092238dfee25a4..6d585e289ac9eb9e52694322f47dfa090a5e279e 100644 --- a/mobile/lib/modules/backup/providers/manual_upload.provider.dart +++ b/mobile/lib/modules/backup/providers/manual_upload.provider.dart @@ -149,16 +149,30 @@ class ManualUploadNotifier extends StateNotifier { } Future _startUpload(Iterable allManualUploads) async { + bool hasErrors = false; try { _backupProvider.updateBackupProgress(BackUpProgressEnum.manualInProgress); if (ref.read(galleryPermissionNotifier.notifier).hasPermission) { await PhotoManager.clearFileCache(); - Set allUploadAssets = allManualUploads - .where((e) => e.isLocal && e.local != null) - .map((e) => e.local!) - .toSet(); + // We do not have 1:1 mapping of all AssetEntity fields to Asset. This results in cases + // where platform specific fields such as `subtype` used to detect platform specific assets such as + // LivePhoto in iOS is lost when we directly fetch the local asset from Asset using Asset.local + List allAssetsFromDevice = await Future.wait( + allManualUploads + // Filter local only assets + .where((e) => e.isLocal && !e.isRemote) + .map((e) => e.local!.obtainForNewProperties()), + ); + + if (allAssetsFromDevice.length != allManualUploads.length) { + _log.warning( + '[_startUpload] Refreshed upload list -> ${allManualUploads.length - allAssetsFromDevice.length} asset will not be uploaded', + ); + } + + Set allUploadAssets = allAssetsFromDevice.nonNulls.toSet(); if (allUploadAssets.isEmpty) { debugPrint("[_startUpload] No Assets to upload - Abort Process"); @@ -213,7 +227,7 @@ class ManualUploadNotifier extends StateNotifier { '[_startUpload] Manual Upload Completed - success: ${state.successfulUploads},' ' failed: ${state.totalAssetsToUpload - state.successfulUploads}', ); - bool hasErrors = false; + // User cancelled upload if (!ok && state.cancelToken.isCancelled) { await _localNotificationService.showOrUpdateManualUploadStatus( @@ -237,32 +251,29 @@ class ManualUploadNotifier extends StateNotifier { presentBanner: true, ); } - - _backupProvider.updateBackupProgress(BackUpProgressEnum.idle); - _handleAppInActivity(); - await _backupProvider.notifyBackgroundServiceCanRun(); - return !hasErrors; } else { openAppSettings(); debugPrint("[_startUpload] Do not have permission to the gallery"); } } catch (e) { debugPrint("ERROR _startUpload: ${e.toString()}"); + hasErrors = true; + } finally { + _backupProvider.updateBackupProgress(BackUpProgressEnum.idle); + _handleAppInActivity(); + await _localNotificationService.closeNotification( + LocalNotificationService.manualUploadDetailedNotificationID, + ); + await _backupProvider.notifyBackgroundServiceCanRun(); } - _backupProvider.updateBackupProgress(BackUpProgressEnum.idle); - _handleAppInActivity(); - await _localNotificationService.closeNotification( - LocalNotificationService.manualUploadDetailedNotificationID, - ); - await _backupProvider.notifyBackgroundServiceCanRun(); - return false; + return !hasErrors; } void _handleAppInActivity() { final appState = ref.read(appStateProvider.notifier).getAppState(); // The app is currently in background. Perform the necessary cleanups which // are on-hold for upload completion - if (appState != AppStateEnum.active || appState != AppStateEnum.resumed) { + if (appState != AppStateEnum.active && appState != AppStateEnum.resumed) { ref.read(appStateProvider.notifier).handleAppInactivity(); } } diff --git a/mobile/lib/modules/backup/services/backup.service.dart b/mobile/lib/modules/backup/services/backup.service.dart index 520c8310bf7067e94dcfdd0722ae9bbf2676db67..8535496e10c81849139a561febca7b67306bbadc 100644 --- a/mobile/lib/modules/backup/services/backup.service.dart +++ b/mobile/lib/modules/backup/services/backup.service.dart @@ -218,7 +218,18 @@ class BackupService { bool anyErrors = false; final List duplicatedAssetIds = []; - for (var entity in assetList) { + // Upload images before video assets + // these are further sorted by using their creation date so the upload goes as follows + // older images -> latest images -> older videos -> latest videos + List sortedAssets = assetList.sorted( + (a, b) { + final cmp = a.typeInt - b.typeInt; + if (cmp != 0) return cmp; + return a.createDateTime.compareTo(b.createDateTime); + }, + ); + + for (var entity in sortedAssets) { try { if (entity.type == AssetType.video) { file = await entity.originFile; @@ -248,9 +259,10 @@ class BackupService { req.fields['deviceAssetId'] = entity.id; req.fields['deviceId'] = deviceId; - req.fields['fileCreatedAt'] = entity.createDateTime.toIso8601String(); + req.fields['fileCreatedAt'] = + entity.createDateTime.toUtc().toIso8601String(); req.fields['fileModifiedAt'] = - entity.modifiedDateTime.toIso8601String(); + entity.modifiedDateTime.toUtc().toIso8601String(); req.fields['isFavorite'] = entity.isFavorite.toString(); req.fields['duration'] = entity.videoDuration.toString(); diff --git a/mobile/lib/modules/backup/ui/album_info_card.dart b/mobile/lib/modules/backup/ui/album_info_card.dart index a8fcde8ee2b97aa31a98b794d64e8a430a603170..eaace503ee70144e937eb91c21adbfe8a03c77e2 100644 --- a/mobile/lib/modules/backup/ui/album_info_card.dart +++ b/mobile/lib/modules/backup/ui/album_info_card.dart @@ -174,7 +174,7 @@ class AlbumInfoCard extends HookConsumerWidget { bottom: 10, right: 25, child: buildSelectedTextBox(), - ) + ), ], ), ), @@ -218,7 +218,7 @@ class AlbumInfoCard extends HookConsumerWidget { }), future: albumInfo.assetCount, ), - ) + ), ], ), ), diff --git a/mobile/lib/modules/backup/ui/current_backup_asset_info_box.dart b/mobile/lib/modules/backup/ui/current_backup_asset_info_box.dart index 9c20c95bb600afc59b556f6f2aaca82301c069e5..23bdf11edf0627df35875f021d7302110e73c35d 100644 --- a/mobile/lib/modules/backup/ui/current_backup_asset_info_box.dart +++ b/mobile/lib/modules/backup/ui/current_backup_asset_info_box.dart @@ -212,7 +212,7 @@ class CurrentUploadingAssetInfoBox extends HookConsumerWidget { Text( " ${uploadProgress.toStringAsFixed(0)}%", style: const TextStyle(fontSize: 12), - ) + ), ], ), ), diff --git a/mobile/lib/modules/backup/views/backup_album_selection_page.dart b/mobile/lib/modules/backup/views/backup_album_selection_page.dart index 80259d06f7ae0153a66afc7976a98064dcbbdd76..9e4118038e1f1f73c46c44e9ba45f5469b0082e0 100644 --- a/mobile/lib/modules/backup/views/backup_album_selection_page.dart +++ b/mobile/lib/modules/backup/views/backup_album_selection_page.dart @@ -247,7 +247,7 @@ class BackupAlbumSelectionPage extends HookConsumerWidget { child: Wrap( children: [ ...buildSelectedAlbumNameChip(), - ...buildExcludedAlbumNameChip() + ...buildExcludedAlbumNameChip(), ], ), ), @@ -301,7 +301,7 @@ class BackupAlbumSelectionPage extends HookConsumerWidget { .watch(backupProvider) .availableAlbums .length - .toString() + .toString(), ], ), style: const TextStyle( diff --git a/mobile/lib/modules/backup/views/backup_controller_page.dart b/mobile/lib/modules/backup/views/backup_controller_page.dart index c689a3141e5f5a1c5d4caa4be3ee1c860d5ef561..33584bb357ce790801d11d111e357374f5c430ab 100644 --- a/mobile/lib/modules/backup/views/backup_controller_page.dart +++ b/mobile/lib/modules/backup/views/backup_controller_page.dart @@ -26,7 +26,7 @@ import 'package:immich_mobile/shared/ui/confirm_dialog.dart'; import 'package:immich_mobile/shared/ui/immich_toast.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:url_launcher/url_launcher.dart'; -import 'package:wakelock/wakelock.dart'; +import 'package:wakelock_plus/wakelock_plus.dart'; class BackupControllerPage extends HookConsumerWidget { const BackupControllerPage({Key? key}) : super(key: key); @@ -114,7 +114,7 @@ class BackupControllerPage extends HookConsumerWidget { ); return; } - Wakelock.enable(); + WakelockPlus.enable(); const limit = 100; final toDelete = await ref .read(backupVerificationServiceProvider) @@ -140,7 +140,7 @@ class BackupControllerPage extends HookConsumerWidget { ); } } finally { - Wakelock.disable(); + WakelockPlus.disable(); checkInProgress.value = false; } } @@ -202,7 +202,7 @@ class BackupControllerPage extends HookConsumerWidget { child: const Text('backup_controller_page_storage_format').tr( args: [ backupState.serverInfo.diskUse, - backupState.serverInfo.diskSize + backupState.serverInfo.diskSize, ], ), ), @@ -256,7 +256,7 @@ class BackupControllerPage extends HookConsumerWidget { ), ), ), - ) + ), ], ), ), @@ -624,7 +624,7 @@ class BackupControllerPage extends HookConsumerWidget { style: TextStyle(fontSize: 12), ).tr(), buildSelectedAlbumName(), - buildExcludedAlbumName() + buildExcludedAlbumName(), ], ), ), @@ -776,7 +776,7 @@ class BackupControllerPage extends HookConsumerWidget { const Divider(), const CurrentUploadingAssetInfoBox(), if (!hasExclusiveAccess) buildBackgroundBackupInfo(), - buildBackupButton() + buildBackupButton(), ], ), ), diff --git a/mobile/lib/modules/backup/views/failed_backup_status_page.dart b/mobile/lib/modules/backup/views/failed_backup_status_page.dart index 7028591a717b31eef94286076a9592d4ab23f176..c55383cf3a0b7df1138271f19cfb3d24ba6446c4 100644 --- a/mobile/lib/modules/backup/views/failed_backup_status_page.dart +++ b/mobile/lib/modules/backup/views/failed_backup_status_page.dart @@ -129,7 +129,7 @@ class FailedBackupStatusPage extends HookConsumerWidget { ], ), ), - ) + ), ], ), ), diff --git a/mobile/lib/modules/favorite/views/favorites_page.dart b/mobile/lib/modules/favorite/views/favorites_page.dart index fa68ff03c2af0e93a21c9973e13f4fba3fa238f1..62f8763bbcf5eee49596962763a0cd9c7cc3d7dd 100644 --- a/mobile/lib/modules/favorite/views/favorites_page.dart +++ b/mobile/lib/modules/favorite/views/favorites_page.dart @@ -2,13 +2,11 @@ import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/modules/favorite/providers/favorite_provider.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart'; import 'package:immich_mobile/shared/models/asset.dart'; -import 'package:immich_mobile/shared/providers/asset.provider.dart'; -import 'package:immich_mobile/shared/ui/immich_toast.dart'; +import 'package:immich_mobile/utils/selection_handlers.dart'; class FavoritesPage extends HookConsumerWidget { const FavoritesPage({Key? key}) : super(key: key); @@ -44,16 +42,11 @@ class FavoritesPage extends HookConsumerWidget { void unfavorite() async { try { if (selection.value.isNotEmpty) { - await ref.watch(assetProvider.notifier).toggleFavorite( - selection.value.toList(), - false, - ); - final assetOrAssets = selection.value.length > 1 ? 'assets' : 'asset'; - ImmichToast.show( - context: context, - msg: - 'Removed ${selection.value.length} $assetOrAssets from favorites', - gravity: ToastGravity.CENTER, + await handleFavoriteAssets( + ref, + context, + selection.value.toList(), + shouldFavorite: false, ); } } finally { @@ -83,7 +76,7 @@ class FavoritesPage extends HookConsumerWidget { style: TextStyle(fontSize: 14), ), onTap: processing.value ? null : unfavorite, - ) + ), ], ), ), @@ -108,7 +101,7 @@ class FavoritesPage extends HookConsumerWidget { selectionActive: selectionEnabledHook.value, listener: selectionListener, ), - if (selectionEnabledHook.value) buildBottomBar() + if (selectionEnabledHook.value) buildBottomBar(), ], ), ), diff --git a/mobile/lib/modules/home/ui/asset_grid/group_divider_title.dart b/mobile/lib/modules/home/ui/asset_grid/group_divider_title.dart index 6d9b293dff1db0db319c3026bf7a1c243d6ad7f0..d63b0631e08fb43d57737156eeb50edf36d2b378 100644 --- a/mobile/lib/modules/home/ui/asset_grid/group_divider_title.dart +++ b/mobile/lib/modules/home/ui/asset_grid/group_divider_title.dart @@ -57,7 +57,7 @@ class GroupDividerTitle extends ConsumerWidget { Icons.check_circle_outline_rounded, color: Colors.grey, ), - ) + ), ], ), ); diff --git a/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid.dart b/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid.dart index 891bde10044a14c1a4b3e9481401cdf6d30880e4..c4a6d527ed6eec546d4472bea33474e2daacf872 100644 --- a/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid.dart +++ b/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid.dart @@ -30,6 +30,8 @@ class ImmichAssetGrid extends HookConsumerWidget { final void Function(ItemPosition start, ItemPosition end)? visibleItemsListener; final Widget? topWidget; + final bool shrinkWrap; + final bool showDragScroll; const ImmichAssetGrid({ super.key, @@ -47,6 +49,8 @@ class ImmichAssetGrid extends HookConsumerWidget { this.showMultiSelectIndicator = true, this.visibleItemsListener, this.topWidget, + this.shrinkWrap = false, + this.showDragScroll = true, }); @override @@ -89,7 +93,7 @@ class ImmichAssetGrid extends HookConsumerWidget { perRow.value = 7 - scaleFactor.value.toInt(); } }; - }) + }), }, child: ImmichAssetGridView( onRefresh: onRefresh, @@ -108,6 +112,8 @@ class ImmichAssetGrid extends HookConsumerWidget { visibleItemsListener: visibleItemsListener, topWidget: topWidget, heroOffset: heroOffset(), + shrinkWrap: shrinkWrap, + showDragScroll: showDragScroll, ), ); } diff --git a/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid_view.dart b/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid_view.dart index 1ad7f3a28f77bd7ebdeef6eca890b2f8757ef43f..8f50c28832bfbe72b055e9dd08fd4f88fb8529f0 100644 --- a/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid_view.dart +++ b/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid_view.dart @@ -35,6 +35,8 @@ class ImmichAssetGridView extends StatefulWidget { visibleItemsListener; final Widget? topWidget; final int heroOffset; + final bool shrinkWrap; + final bool showDragScroll; const ImmichAssetGridView({ super.key, @@ -52,6 +54,8 @@ class ImmichAssetGridView extends StatefulWidget { this.visibleItemsListener, this.topWidget, this.heroOffset = 0, + this.shrinkWrap = false, + this.showDragScroll = true, }); @override @@ -225,7 +229,7 @@ class ImmichAssetGridViewState extends State { right: i + 1 == num ? 0.0 : widget.margin, ), color: Colors.grey, - ) + ), ], ); } @@ -300,7 +304,13 @@ class ImmichAssetGridViewState extends State { } Text _labelBuilder(int pos) { - final date = widget.renderList.elements[pos].date; + final maxLength = widget.renderList.elements.length; + if (pos < 0 || pos >= maxLength) { + return const Text(""); + } + + final date = widget.renderList.elements[pos % maxLength].date; + return Text( DateFormat.yMMMM().format(date), style: const TextStyle( @@ -318,7 +328,8 @@ class ImmichAssetGridViewState extends State { } Widget _buildAssetGrid() { - final useDragScrolling = widget.renderList.totalAssets >= 20; + final useDragScrolling = + widget.showDragScroll && widget.renderList.totalAssets >= 20; void dragScrolling(bool active) { if (active != _scrolling) { @@ -335,8 +346,10 @@ class ImmichAssetGridViewState extends State { itemBuilder: _itemBuilder, itemPositionsListener: _itemPositionsListener, itemScrollController: _itemScrollController, - itemCount: widget.renderList.elements.length + (widget.topWidget != null ? 1 : 0), + itemCount: widget.renderList.elements.length + + (widget.topWidget != null ? 1 : 0), addRepaintBoundaries: true, + shrinkWrap: widget.shrinkWrap, ); final child = useDragScrolling diff --git a/mobile/lib/modules/home/ui/control_bottom_app_bar.dart b/mobile/lib/modules/home/ui/control_bottom_app_bar.dart index 1d1632ef7a877dccbdd4e9a76a542e2022c4d859..24e2e7ce783ff2a16ff9d10bdd51119373c48b74 100644 --- a/mobile/lib/modules/home/ui/control_bottom_app_bar.dart +++ b/mobile/lib/modules/home/ui/control_bottom_app_bar.dart @@ -155,7 +155,7 @@ class ControlBottomAppBar extends ConsumerWidget { if (hasRemote) const SliverToBoxAdapter( child: SizedBox(height: 200), - ) + ), ], ), ); diff --git a/mobile/lib/modules/home/ui/home_page_app_bar.dart b/mobile/lib/modules/home/ui/home_page_app_bar.dart index cff37b0bb1a749f6d3abb7e9ff0e1497d5fe4ff9..ce038d327edb96d46f6eec441b1fa8d5d08ea6a1 100644 --- a/mobile/lib/modules/home/ui/home_page_app_bar.dart +++ b/mobile/lib/modules/home/ui/home_page_app_bar.dart @@ -1,7 +1,8 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/modules/home/ui/user_circle_avatar.dart'; +import 'package:immich_mobile/shared/models/store.dart'; +import 'package:immich_mobile/shared/ui/user_circle_avatar.dart'; import 'package:immich_mobile/modules/login/models/authentication_state.model.dart'; import 'package:immich_mobile/modules/login/providers/authentication.provider.dart'; @@ -29,9 +30,9 @@ class HomePageAppBar extends ConsumerWidget implements PreferredSizeWidget { backupState.backgroundBackup || backupState.autoBackup; final ServerInfoState serverInfoState = ref.watch(serverInfoProvider); AuthenticationState authState = ref.watch(authenticationProvider); - + final user = Store.tryGet(StoreKey.currentUser); buildProfilePhoto() { - if (authState.profileImagePath.isEmpty) { + if (authState.profileImagePath.isEmpty || user == null) { return IconButton( splashRadius: 25, icon: const Icon( @@ -47,9 +48,10 @@ class HomePageAppBar extends ConsumerWidget implements PreferredSizeWidget { onTap: () { Scaffold.of(context).openDrawer(); }, - child: const UserCircleAvatar( + child: UserCircleAvatar( radius: 18, size: 33, + user: user, ), ); } diff --git a/mobile/lib/modules/home/ui/profile_drawer/profile_drawer.dart b/mobile/lib/modules/home/ui/profile_drawer/profile_drawer.dart index 5d41a684265bed3ef6b04edae600b06fa7885e66..fbc9b4ed79f76cc77d598a94ed06a90631aa24a9 100644 --- a/mobile/lib/modules/home/ui/profile_drawer/profile_drawer.dart +++ b/mobile/lib/modules/home/ui/profile_drawer/profile_drawer.dart @@ -108,7 +108,7 @@ class ProfileDrawer extends HookConsumerWidget { buildSignOutButton(), ], ), - const ServerInfoBox() + const ServerInfoBox(), ], ), ); diff --git a/mobile/lib/modules/home/ui/profile_drawer/profile_drawer_header.dart b/mobile/lib/modules/home/ui/profile_drawer/profile_drawer_header.dart index da8a2c6546e42f2c9a9a560972c87749fde8fae0..328d9cbc1f04c9713b9772cee4cbc7f51ae3de86 100644 --- a/mobile/lib/modules/home/ui/profile_drawer/profile_drawer_header.dart +++ b/mobile/lib/modules/home/ui/profile_drawer/profile_drawer_header.dart @@ -3,7 +3,8 @@ import 'package:flutter_hooks/flutter_hooks.dart' hide Store; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:image_picker/image_picker.dart'; import 'package:immich_mobile/modules/home/providers/upload_profile_image.provider.dart'; -import 'package:immich_mobile/modules/home/ui/user_circle_avatar.dart'; +import 'package:immich_mobile/shared/models/store.dart'; +import 'package:immich_mobile/shared/ui/user_circle_avatar.dart'; import 'package:immich_mobile/modules/login/models/authentication_state.model.dart'; import 'package:immich_mobile/modules/login/providers/authentication.provider.dart'; import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; @@ -19,14 +20,10 @@ class ProfileDrawerHeader extends HookConsumerWidget { final uploadProfileImageStatus = ref.watch(uploadProfileImageProvider).status; final isDarkMode = Theme.of(context).brightness == Brightness.dark; + final user = Store.tryGet(StoreKey.currentUser); buildUserProfileImage() { - var userImage = const UserCircleAvatar( - radius: 35, - size: 66, - ); - - if (authState.profileImagePath.isEmpty) { + if (authState.profileImagePath.isEmpty || user == null) { return const CircleAvatar( radius: 35, backgroundImage: AssetImage('assets/immich-logo-no-outline.png'), @@ -34,6 +31,12 @@ class ProfileDrawerHeader extends HookConsumerWidget { ); } + var userImage = UserCircleAvatar( + radius: 35, + size: 66, + user: user, + ); + if (uploadProfileImageStatus == UploadProfileStatus.idle) { if (authState.profileImagePath.isNotEmpty) { return userImage; @@ -153,7 +156,7 @@ class ProfileDrawerHeader extends HookConsumerWidget { Text( authState.userEmail, style: Theme.of(context).textTheme.labelMedium, - ) + ), ], ), ); diff --git a/mobile/lib/modules/home/ui/user_circle_avatar.dart b/mobile/lib/modules/home/ui/user_circle_avatar.dart deleted file mode 100644 index 441aab5cda1d65c5ba8335cf01568bb782e5f438..0000000000000000000000000000000000000000 --- a/mobile/lib/modules/home/ui/user_circle_avatar.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'dart:math'; - -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/modules/login/models/authentication_state.model.dart'; -import 'package:immich_mobile/modules/login/providers/authentication.provider.dart'; -import 'package:immich_mobile/shared/models/store.dart'; -import 'package:immich_mobile/shared/ui/transparent_image.dart'; - -class UserCircleAvatar extends ConsumerWidget { - final double radius; - final double size; - const UserCircleAvatar({super.key, required this.radius, required this.size}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - AuthenticationState authState = ref.watch(authenticationProvider); - - var profileImageUrl = - '${Store.get(StoreKey.serverEndpoint)}/user/profile-image/${authState.userId}?d=${Random().nextInt(1024)}'; - return CircleAvatar( - backgroundColor: Theme.of(context).primaryColor, - radius: radius, - child: ClipRRect( - borderRadius: BorderRadius.circular(50), - child: FadeInImage( - fit: BoxFit.cover, - placeholder: MemoryImage(kTransparentImage), - width: size, - height: size, - image: NetworkImage( - profileImageUrl, - headers: { - "Authorization": "Bearer ${Store.get(StoreKey.accessToken)}" - }, - ), - fadeInDuration: const Duration(milliseconds: 200), - imageErrorBuilder: (context, error, stackTrace) => - Image.memory(kTransparentImage), - ), - ), - ); - } -} diff --git a/mobile/lib/modules/home/views/home_page.dart b/mobile/lib/modules/home/views/home_page.dart index c3226f228e8254c8437a1b0f42707be8f788b3e1..e37491440b7fbdab25c151692b9658cc90d8b8b8 100644 --- a/mobile/lib/modules/home/views/home_page.dart +++ b/mobile/lib/modules/home/views/home_page.dart @@ -25,10 +25,9 @@ import 'package:immich_mobile/shared/providers/asset.provider.dart'; import 'package:immich_mobile/shared/providers/server_info.provider.dart'; import 'package:immich_mobile/shared/providers/user.provider.dart'; import 'package:immich_mobile/shared/providers/websocket.provider.dart'; -import 'package:immich_mobile/shared/services/share.service.dart'; import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; import 'package:immich_mobile/shared/ui/immich_toast.dart'; -import 'package:immich_mobile/shared/ui/share_dialog.dart'; +import 'package:immich_mobile/utils/selection_handlers.dart'; class HomePage extends HookConsumerWidget { const HomePage({Key? key}) : super(key: key); @@ -88,17 +87,7 @@ class HomePage extends HookConsumerWidget { } void onShareAssets() { - showDialog( - context: context, - builder: (BuildContext buildContext) { - ref - .watch(shareServiceProvider) - .shareAssets(selection.value.toList()) - .then((_) => Navigator.of(buildContext).pop()); - return const ShareDialog(); - }, - barrierDismissible: false, - ); + handleShareAssets(ref, context, selection.value.toList()); selectionEnabledHook.value = false; } @@ -126,16 +115,7 @@ class HomePage extends HookConsumerWidget { localErrorMessage: 'home_page_favorite_err_local'.tr(), ); if (remoteAssets.isNotEmpty) { - await ref - .watch(assetProvider.notifier) - .toggleFavorite(remoteAssets, true); - - final assetOrAssets = remoteAssets.length > 1 ? 'assets' : 'asset'; - ImmichToast.show( - context: context, - msg: 'Added ${remoteAssets.length} $assetOrAssets to favorites', - gravity: ToastGravity.BOTTOM, - ); + await handleFavoriteAssets(ref, context, remoteAssets); } } finally { processing.value = false; @@ -149,18 +129,7 @@ class HomePage extends HookConsumerWidget { final remoteAssets = remoteOnlySelection( localErrorMessage: 'home_page_archive_err_local'.tr(), ); - if (remoteAssets.isNotEmpty) { - await ref - .read(assetProvider.notifier) - .toggleArchive(remoteAssets, true); - - final assetOrAssets = remoteAssets.length > 1 ? 'assets' : 'asset'; - ImmichToast.show( - context: context, - msg: 'Moved ${remoteAssets.length} $assetOrAssets to archive', - gravity: ToastGravity.CENTER, - ); - } + await handleArchiveAssets(ref, context, remoteAssets); } finally { processing.value = false; selectionEnabledHook.value = false; @@ -221,7 +190,7 @@ class HomePage extends HookConsumerWidget { namedArgs: { "album": album.name, "added": result.successfullyAdded.toString(), - "failed": result.alreadyInAlbum.length.toString() + "failed": result.alreadyInAlbum.length.toString(), }, ), ); @@ -323,7 +292,7 @@ class HomePage extends HookConsumerWidget { ).tr(), ), ), - ) + ), ], ), ); @@ -365,7 +334,7 @@ class HomePage extends HookConsumerWidget { enabled: !processing.value, selectionAssetState: selectionAssetState.value, ), - if (processing.value) const Center(child: ImmichLoadingIndicator()) + if (processing.value) const Center(child: ImmichLoadingIndicator()), ], ), ); diff --git a/mobile/lib/modules/login/ui/change_password_form.dart b/mobile/lib/modules/login/ui/change_password_form.dart index 904c59563bb7ca8cbdb99109646f26cf0d943428..555dd6c0d30bed68472a327d9419856013aab731 100644 --- a/mobile/lib/modules/login/ui/change_password_form.dart +++ b/mobile/lib/modules/login/ui/change_password_form.dart @@ -94,7 +94,7 @@ class ChangePasswordForm extends HookConsumerWidget { ), ], ), - ) + ), ], ), ), diff --git a/mobile/lib/modules/map/models/map_page_event.model.dart b/mobile/lib/modules/map/models/map_page_event.model.dart new file mode 100644 index 0000000000000000000000000000000000000000..63665173d94f2734801444cdc3e35f11ba00897d --- /dev/null +++ b/mobile/lib/modules/map/models/map_page_event.model.dart @@ -0,0 +1,40 @@ +import 'package:immich_mobile/shared/models/asset.dart'; + +enum MapPageEventType { + mapTap, + bottomSheetScrolled, + assetsInBoundUpdated, + zoomToAsset, + zoomToCurrentLocation, +} + +class MapPageEventBase { + final MapPageEventType type; + + const MapPageEventBase(this.type); +} + +class MapPageOnTapEvent extends MapPageEventBase { + const MapPageOnTapEvent() : super(MapPageEventType.mapTap); +} + +class MapPageAssetsInBoundUpdated extends MapPageEventBase { + List assets; + MapPageAssetsInBoundUpdated(this.assets) + : super(MapPageEventType.assetsInBoundUpdated); +} + +class MapPageBottomSheetScrolled extends MapPageEventBase { + Asset? asset; + MapPageBottomSheetScrolled(this.asset) + : super(MapPageEventType.bottomSheetScrolled); +} + +class MapPageZoomToAsset extends MapPageEventBase { + Asset? asset; + MapPageZoomToAsset(this.asset) : super(MapPageEventType.zoomToAsset); +} + +class MapPageZoomToLocation extends MapPageEventBase { + const MapPageZoomToLocation() : super(MapPageEventType.zoomToCurrentLocation); +} diff --git a/mobile/lib/modules/map/models/map_state.model.dart b/mobile/lib/modules/map/models/map_state.model.dart new file mode 100644 index 0000000000000000000000000000000000000000..ed2b033fdf1077f1d8bb6a202d81f1d5faa558bb --- /dev/null +++ b/mobile/lib/modules/map/models/map_state.model.dart @@ -0,0 +1,45 @@ +class MapState { + final bool isDarkTheme; + final bool showFavoriteOnly; + final int relativeTime; + + MapState({ + this.isDarkTheme = false, + this.showFavoriteOnly = false, + this.relativeTime = 0, + }); + + MapState copyWith({ + bool? isDarkTheme, + bool? showFavoriteOnly, + int? relativeTime, + }) { + return MapState( + isDarkTheme: isDarkTheme ?? this.isDarkTheme, + showFavoriteOnly: showFavoriteOnly ?? this.showFavoriteOnly, + relativeTime: relativeTime ?? this.relativeTime, + ); + } + + @override + String toString() { + return 'MapSettingsState(isDarkTheme: $isDarkTheme, showFavoriteOnly: $showFavoriteOnly, relativeTime: $relativeTime)'; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is MapState && + other.isDarkTheme == isDarkTheme && + other.showFavoriteOnly == showFavoriteOnly && + other.relativeTime == relativeTime; + } + + @override + int get hashCode { + return isDarkTheme.hashCode ^ + showFavoriteOnly.hashCode ^ + relativeTime.hashCode; + } +} diff --git a/mobile/lib/modules/map/providers/map_marker.provider.dart b/mobile/lib/modules/map/providers/map_marker.provider.dart new file mode 100644 index 0000000000000000000000000000000000000000..30343f2806d966af1dbbdbef6ab4f48d6dea0f6a --- /dev/null +++ b/mobile/lib/modules/map/providers/map_marker.provider.dart @@ -0,0 +1,58 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/modules/map/providers/map_state.provider.dart'; +import 'package:immich_mobile/modules/map/services/map.service.dart'; +import 'package:immich_mobile/shared/models/asset.dart'; +import 'package:latlong2/latlong.dart'; + +final mapMarkersProvider = + FutureProvider.autoDispose>((ref) async { + final service = ref.read(mapServiceProvider); + final mapState = ref.read(mapStateNotifier); + DateTime? fileCreatedAfter; + bool? isFavorite; + + if (mapState.relativeTime != 0) { + fileCreatedAfter = + DateTime.now().subtract(Duration(days: mapState.relativeTime)); + } + + if (mapState.showFavoriteOnly) { + isFavorite = true; + } + + final markers = await service.getMapMarkers( + isFavorite: isFavorite, + fileCreatedAfter: fileCreatedAfter, + ); + + final assetMarkerData = await Future.wait( + markers.map((e) async { + final asset = await service.getAssetForMarkerId(e.id); + bool hasInvalidCoords = e.lat < -90 || e.lat > 90; + hasInvalidCoords = hasInvalidCoords || (e.lon < -180 || e.lon > 180); + if (asset == null || hasInvalidCoords) return null; + return AssetMarkerData(asset, LatLng(e.lat, e.lon)); + }), + ); + + return assetMarkerData.nonNulls.toSet(); +}); + +class AssetMarkerData { + final LatLng point; + final Asset asset; + + const AssetMarkerData(this.asset, this.point); + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is AssetMarkerData && other.asset.remoteId == asset.remoteId; + } + + @override + int get hashCode { + return asset.remoteId.hashCode; + } +} diff --git a/mobile/lib/modules/map/providers/map_state.provider.dart b/mobile/lib/modules/map/providers/map_state.provider.dart new file mode 100644 index 0000000000000000000000000000000000000000..7fd7d60614a9b8753271bfe75e81d454894842cc --- /dev/null +++ b/mobile/lib/modules/map/providers/map_state.provider.dart @@ -0,0 +1,51 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/modules/map/models/map_state.model.dart'; +import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart'; +import 'package:immich_mobile/modules/settings/services/app_settings.service.dart'; + +class MapStateNotifier extends StateNotifier { + MapStateNotifier(this.appSettingsProvider) + : super( + MapState( + isDarkTheme: appSettingsProvider + .getSetting(AppSettingsEnum.mapThemeMode), + showFavoriteOnly: appSettingsProvider + .getSetting(AppSettingsEnum.mapShowFavoriteOnly), + relativeTime: appSettingsProvider + .getSetting(AppSettingsEnum.mapRelativeDate), + ), + ); + + final AppSettingsService appSettingsProvider; + + bool get isDarkTheme => state.isDarkTheme; + + void switchTheme(bool isDarkTheme) { + appSettingsProvider.setSetting( + AppSettingsEnum.mapThemeMode, + isDarkTheme, + ); + state = state.copyWith(isDarkTheme: isDarkTheme); + } + + void switchFavoriteOnly(bool isFavoriteOnly) { + appSettingsProvider.setSetting( + AppSettingsEnum.mapShowFavoriteOnly, + appSettingsProvider, + ); + state = state.copyWith(showFavoriteOnly: isFavoriteOnly); + } + + void setRelativeTime(int relativeTime) { + appSettingsProvider.setSetting( + AppSettingsEnum.mapRelativeDate, + relativeTime, + ); + state = state.copyWith(relativeTime: relativeTime); + } +} + +final mapStateNotifier = + StateNotifierProvider((ref) { + return MapStateNotifier(ref.watch(appSettingsServiceProvider)); +}); diff --git a/mobile/lib/modules/map/services/map.service.dart b/mobile/lib/modules/map/services/map.service.dart new file mode 100644 index 0000000000000000000000000000000000000000..ec8dbbb39eca53bf2aeb1cf1d4d50b04f756fc11 --- /dev/null +++ b/mobile/lib/modules/map/services/map.service.dart @@ -0,0 +1,62 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/shared/models/asset.dart'; +import 'package:immich_mobile/shared/providers/api.provider.dart'; +import 'package:immich_mobile/shared/providers/db.provider.dart'; +import 'package:immich_mobile/shared/services/api.service.dart'; +import 'package:isar/isar.dart'; +import 'package:logging/logging.dart'; +import 'package:openapi/api.dart'; + +final mapServiceProvider = Provider( + (ref) => MapSerivce( + ref.read(apiServiceProvider), + ref.read(dbProvider), + ), +); + +class MapSerivce { + final ApiService _apiService; + final Isar _db; + final log = Logger("MapService"); + + MapSerivce(this._apiService, this._db); + + Future> getMapMarkers({ + bool? isFavorite, + DateTime? fileCreatedAfter, + DateTime? fileCreatedBefore, + }) async { + try { + final markers = await _apiService.assetApi.getMapMarkers( + isFavorite: isFavorite, + fileCreatedAfter: fileCreatedAfter, + fileCreatedBefore: fileCreatedBefore, + ); + + return markers ?? []; + } catch (error, stack) { + log.severe("Cannot get map markers ${error.toString()}", error, stack); + return []; + } + } + + Future getAssetForMarkerId(String remoteId) async { + try { + final assets = await _db.assets.getAllByRemoteId([remoteId]); + if (assets.isNotEmpty) return assets[0]; + + final dto = await _apiService.assetApi.getAssetById(remoteId); + if (dto == null) { + return null; + } + return Asset.remote(dto); + } catch (error, stack) { + log.severe( + "Cannot get asset for marker ${error.toString()}", + error, + stack, + ); + return null; + } + } +} diff --git a/mobile/lib/modules/map/ui/asset_marker_icon.dart b/mobile/lib/modules/map/ui/asset_marker_icon.dart new file mode 100644 index 0000000000000000000000000000000000000000..db6d1a10ebcb73d8093d950a05af6f8ce2ff77f4 --- /dev/null +++ b/mobile/lib/modules/map/ui/asset_marker_icon.dart @@ -0,0 +1,144 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:immich_mobile/shared/models/store.dart'; +import 'package:immich_mobile/utils/image_url_builder.dart'; + +class AssetMarkerIcon extends StatelessWidget { + const AssetMarkerIcon({ + Key? key, + required this.id, + this.isDarkTheme = false, + }) : super(key: key); + + final String id; + final bool isDarkTheme; + + @override + Widget build(BuildContext context) { + final imageUrl = getThumbnailUrlForRemoteId(id); + final cacheKey = getThumbnailCacheKeyForRemoteId(id); + return LayoutBuilder( + builder: (context, constraints) { + return Stack( + children: [ + Positioned( + bottom: 0, + left: constraints.maxWidth * 0.5, + child: CustomPaint( + painter: _PinPainter( + primaryColor: isDarkTheme ? Colors.white : Colors.black, + secondaryColor: isDarkTheme ? Colors.black : Colors.white, + primaryRadius: constraints.maxHeight * 0.06, + secondaryRadius: constraints.maxHeight * 0.038, + ), + child: SizedBox( + height: constraints.maxHeight * 0.14, + width: constraints.maxWidth * 0.14, + ), + ), + ), + Positioned( + top: constraints.maxHeight * 0.07, + left: constraints.maxWidth * 0.17, + child: CircleAvatar( + radius: constraints.maxHeight * 0.40, + backgroundColor: isDarkTheme ? Colors.white : Colors.black, + child: CircleAvatar( + radius: constraints.maxHeight * 0.37, + backgroundImage: CachedNetworkImageProvider( + imageUrl, + cacheKey: cacheKey, + headers: { + "Authorization": + "Bearer ${Store.get(StoreKey.accessToken)}", + }, + errorListener: () => + const Icon(Icons.image_not_supported_outlined), + ), + ), + ), + ), + ], + ); + }, + ); + } +} + +class _PinPainter extends CustomPainter { + final Color primaryColor; + final Color secondaryColor; + final double primaryRadius; + final double secondaryRadius; + + _PinPainter({ + this.primaryColor = Colors.black, + this.secondaryColor = Colors.white, + required this.primaryRadius, + required this.secondaryRadius, + }); + + @override + void paint(Canvas canvas, Size size) { + Paint primaryBrush = Paint() + ..color = primaryColor + ..style = PaintingStyle.fill; + + Paint secondaryBrush = Paint() + ..color = secondaryColor + ..style = PaintingStyle.fill; + + Paint lineBrush = Paint() + ..color = primaryColor + ..style = PaintingStyle.stroke + ..strokeWidth = 2; + + canvas.drawCircle( + Offset(size.width / 2, size.height), + primaryRadius, + primaryBrush, + ); + canvas.drawCircle( + Offset(size.width / 2, size.height), + secondaryRadius, + secondaryBrush, + ); + canvas.drawPath(getTrianglePath(size.width, size.height), primaryBrush); + // The line is to make the above triangluar path more prominent since it has a slight curve + canvas.drawLine( + Offset(size.width / 2, 0), + Offset( + size.width / 2, + size.height, + ), + lineBrush, + ); + } + + Path getTrianglePath(double x, double y) { + final firstEndPoint = Offset(x / 2, y); + final controlPoint = Offset(x / 2, y * 0.3); + final secondEndPoint = Offset(x, 0); + + return Path() + ..quadraticBezierTo( + controlPoint.dx, + controlPoint.dy, + firstEndPoint.dx, + firstEndPoint.dy, + ) + ..quadraticBezierTo( + controlPoint.dx, + controlPoint.dy, + secondEndPoint.dx, + secondEndPoint.dy, + ) + ..lineTo(0, 0); + } + + @override + bool shouldRepaint(_PinPainter old) { + return old.primaryColor != primaryColor || + old.secondaryColor != secondaryColor; + } +} diff --git a/mobile/lib/modules/map/ui/location_dialog.dart b/mobile/lib/modules/map/ui/location_dialog.dart new file mode 100644 index 0000000000000000000000000000000000000000..a55202e145ede695e2416aae91dc3415a593aa60 --- /dev/null +++ b/mobile/lib/modules/map/ui/location_dialog.dart @@ -0,0 +1,30 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:geolocator/geolocator.dart'; +import 'package:immich_mobile/shared/ui/confirm_dialog.dart'; + +class LocationServiceDisabledDialog extends ConfirmDialog { + LocationServiceDisabledDialog({Key? key}) + : super( + key: key, + title: 'map_location_service_disabled_title'.tr(), + content: 'map_location_service_disabled_content'.tr(), + cancel: 'map_location_dialog_cancel'.tr(), + ok: 'map_location_dialog_yes'.tr(), + onOk: () async { + await Geolocator.openLocationSettings(); + }, + ); +} + +class LocationPermissionDisabledDialog extends ConfirmDialog { + LocationPermissionDisabledDialog({Key? key}) + : super( + key: key, + title: 'map_no_location_permission_title'.tr(), + content: 'map_no_location_permission_content'.tr(), + cancel: 'map_location_dialog_cancel'.tr(), + ok: 'map_location_dialog_yes'.tr(), + onOk: () {}, + ); +} diff --git a/mobile/lib/modules/map/ui/map_page_app_bar.dart b/mobile/lib/modules/map/ui/map_page_app_bar.dart new file mode 100644 index 0000000000000000000000000000000000000000..c43cd9d3c44af17c5096fd449e2109bb943127ac --- /dev/null +++ b/mobile/lib/modules/map/ui/map_page_app_bar.dart @@ -0,0 +1,138 @@ +import 'dart:io'; + +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:immich_mobile/modules/home/ui/asset_grid/disable_multi_select_button.dart'; +import 'package:immich_mobile/modules/map/ui/map_settings_dialog.dart'; + +class MapAppBar extends HookWidget implements PreferredSizeWidget { + final ValueNotifier selectionEnabled; + final int selectedAssetsLength; + final bool isDarkTheme; + + final void Function() onShare; + final void Function() onFavorite; + final void Function() onArchive; + + const MapAppBar({ + super.key, + required this.selectionEnabled, + required this.selectedAssetsLength, + required this.onShare, + required this.onArchive, + required this.onFavorite, + this.isDarkTheme = false, + }); + + List buildNonSelectionWidgets(BuildContext context) { + return [ + Padding( + padding: const EdgeInsets.only(left: 15, top: 15), + child: ElevatedButton( + onPressed: () => AutoRouter.of(context).pop(), + style: ElevatedButton.styleFrom( + shape: const CircleBorder(), + padding: const EdgeInsets.all(12), + ), + child: const Icon(Icons.arrow_back_ios_new_rounded, size: 22), + ), + ), + Padding( + padding: const EdgeInsets.only(right: 15, top: 15), + child: ElevatedButton( + onPressed: () => showDialog( + context: context, + builder: (BuildContext _) { + return const MapSettingsDialog(); + }, + ), + style: ElevatedButton.styleFrom( + shape: const CircleBorder(), + padding: const EdgeInsets.all(12), + ), + child: const Icon(Icons.more_vert_rounded, size: 22), + ), + ), + ]; + } + + List buildSelectionWidgets() { + return [ + DisableMultiSelectButton( + onPressed: () { + selectionEnabled.value = false; + }, + selectedItemCount: selectedAssetsLength, + ), + Row( + children: [ + // Share button + Padding( + padding: const EdgeInsets.only(top: 15), + child: ElevatedButton( + onPressed: onShare, + style: ElevatedButton.styleFrom( + shape: const CircleBorder(), + padding: const EdgeInsets.all(12), + ), + child: Icon( + Platform.isAndroid + ? Icons.share_rounded + : Icons.ios_share_rounded, + size: 22, + ), + ), + ), + // Favorite button + Padding( + padding: const EdgeInsets.only(top: 15), + child: ElevatedButton( + onPressed: onFavorite, + style: ElevatedButton.styleFrom( + shape: const CircleBorder(), + padding: const EdgeInsets.all(12), + ), + child: const Icon( + Icons.favorite, + size: 22, + ), + ), + ), + // Archive Button + Padding( + padding: const EdgeInsets.only(right: 10, top: 15), + child: ElevatedButton( + onPressed: onArchive, + style: ElevatedButton.styleFrom( + shape: const CircleBorder(), + padding: const EdgeInsets.all(12), + ), + child: const Icon( + Icons.archive, + size: 22, + ), + ), + ), + ], + ), + ]; + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(top: 30), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (!selectionEnabled.value) ...buildNonSelectionWidgets(context), + if (selectionEnabled.value) ...buildSelectionWidgets(), + ], + ), + ); + } + + @override + Size get preferredSize => const Size.fromHeight(100); +} diff --git a/mobile/lib/modules/map/ui/map_page_bottom_sheet.dart b/mobile/lib/modules/map/ui/map_page_bottom_sheet.dart new file mode 100644 index 0000000000000000000000000000000000000000..f74df4331c69cc9723f1b06addd3011bbc75f75b --- /dev/null +++ b/mobile/lib/modules/map/ui/map_page_bottom_sheet.dart @@ -0,0 +1,356 @@ +import 'dart:async'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/modules/asset_viewer/providers/render_list.provider.dart'; +import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart'; +import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart'; +import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid_view.dart'; +import 'package:immich_mobile/modules/map/models/map_page_event.model.dart'; +import 'package:immich_mobile/shared/models/asset.dart'; +import 'package:immich_mobile/shared/ui/drag_sheet.dart'; +import 'package:immich_mobile/utils/color_filter_generator.dart'; +import 'package:immich_mobile/utils/debounce.dart'; +import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class MapPageBottomSheet extends StatefulHookConsumerWidget { + final Stream mapPageEventStream; + final StreamController bottomSheetEventSC; + final bool selectionEnabled; + final ImmichAssetGridSelectionListener selectionlistener; + final bool isDarkTheme; + + const MapPageBottomSheet({ + super.key, + required this.mapPageEventStream, + required this.bottomSheetEventSC, + required this.selectionEnabled, + required this.selectionlistener, + this.isDarkTheme = false, + }); + + @override + AssetsInBoundBottomSheetState createState() => + AssetsInBoundBottomSheetState(); +} + +class AssetsInBoundBottomSheetState extends ConsumerState { + // Non-State variables + bool userTappedOnMap = false; + RenderList? _cachedRenderList; + int lastAssetOffsetInSheet = -1; + late final DraggableScrollableController bottomSheetController; + late final Debounce debounce; + + @override + void initState() { + super.initState(); + bottomSheetController = DraggableScrollableController(); + debounce = Debounce( + const Duration(milliseconds: 200), + ); + } + + @override + Widget build(BuildContext context) { + var isDarkMode = Theme.of(context).brightness == Brightness.dark; + double maxHeight = MediaQuery.of(context).size.height; + final isSheetScrolled = useState(false); + final isSheetExpanded = useState(false); + final assetsInBound = useState([]); + final currentExtend = useState(0.1); + + void handleMapPageEvents(dynamic event) { + if (event is MapPageAssetsInBoundUpdated) { + assetsInBound.value = event.assets; + } else if (event is MapPageOnTapEvent) { + userTappedOnMap = true; + lastAssetOffsetInSheet = -1; + bottomSheetController.animateTo( + 0.1, + duration: const Duration(milliseconds: 200), + curve: Curves.linearToEaseOut, + ); + isSheetScrolled.value = false; + } + } + + useEffect( + () { + final mapPageEventSubscription = + widget.mapPageEventStream.listen(handleMapPageEvents); + return mapPageEventSubscription.cancel; + }, + [widget.mapPageEventStream], + ); + + void handleVisibleItems(ItemPosition start, ItemPosition end) { + final renderElement = _cachedRenderList?.elements[start.index]; + if (renderElement == null) { + return; + } + final rowOffset = renderElement.offset; + if ((-start.itemLeadingEdge) != 0) { + var columnOffset = -start.itemLeadingEdge ~/ 0.05; + columnOffset = columnOffset < renderElement.totalCount + ? columnOffset + : renderElement.totalCount - 1; + lastAssetOffsetInSheet = rowOffset + columnOffset; + final asset = _cachedRenderList?.allAssets?[lastAssetOffsetInSheet]; + userTappedOnMap = false; + if (!userTappedOnMap && isSheetExpanded.value) { + widget.bottomSheetEventSC.add( + MapPageBottomSheetScrolled(asset), + ); + } + if (isSheetExpanded.value) { + isSheetScrolled.value = true; + } + } + } + + void visibleItemsListener(ItemPosition start, ItemPosition end) { + if (_cachedRenderList == null) { + debounce.dispose(); + return; + } + debounce.call(() => handleVisibleItems(start, end)); + } + + Widget buildNoPhotosWidget() { + const image = Image( + image: AssetImage('assets/lighthouse.png'), + ); + + return isSheetExpanded.value + ? Column( + children: [ + const SizedBox( + height: 80, + ), + SizedBox( + height: 150, + width: 150, + child: isDarkMode + ? const InvertionFilter( + child: SaturationFilter( + saturation: -1, + child: BrightnessFilter( + brightness: -5, + child: image, + ), + ), + ) + : image, + ), + const SizedBox( + height: 20, + ), + Text( + "map_zoom_to_see_photos".tr(), + style: TextStyle( + fontSize: 20, + color: Theme.of(context).textTheme.displayLarge?.color, + ), + ), + ], + ) + : const SizedBox.shrink(); + } + + void onTapMapButton() { + if (lastAssetOffsetInSheet != -1) { + widget.bottomSheetEventSC.add( + MapPageZoomToAsset( + _cachedRenderList?.allAssets?[lastAssetOffsetInSheet], + ), + ); + } + } + + Widget buildDragHandle(ScrollController scrollController) { + final textToDisplay = assetsInBound.value.isNotEmpty + ? "${assetsInBound.value.length} photo${assetsInBound.value.length > 1 ? "s" : ""}" + : "map_no_assets_in_bounds".tr(); + final dragHandle = Container( + height: 75, + width: double.infinity, + decoration: BoxDecoration( + color: isDarkMode ? Colors.grey[900] : Colors.grey[100], + ), + child: Stack( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(height: 12), + const CustomDraggingHandle(), + const SizedBox(height: 12), + Text( + textToDisplay, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).textTheme.displayLarge?.color, + fontWeight: FontWeight.bold, + ), + ), + Divider( + color: Theme.of(context) + .textTheme + .displayLarge + ?.color + ?.withOpacity(0.5), + ), + ], + ), + if (isSheetExpanded.value && isSheetScrolled.value) + Positioned( + top: 5, + right: 10, + child: IconButton( + icon: Icon( + Icons.map_outlined, + color: Theme.of(context).textTheme.displayLarge?.color, + ), + iconSize: 20, + tooltip: 'Zoom to bounds', + onPressed: onTapMapButton, + ), + ), + ], + ), + ); + return SingleChildScrollView( + controller: scrollController, + child: dragHandle, + ); + } + + return NotificationListener( + onNotification: (DraggableScrollableNotification notification) { + final sheetExtended = notification.extent > 0.2; + isSheetExpanded.value = sheetExtended; + currentExtend.value = notification.extent; + if (!sheetExtended) { + // reset state + userTappedOnMap = false; + lastAssetOffsetInSheet = -1; + isSheetScrolled.value = false; + } + + return true; + }, + child: Stack( + children: [ + DraggableScrollableSheet( + controller: bottomSheetController, + initialChildSize: 0.1, + minChildSize: 0.1, + maxChildSize: 0.55, + snap: true, + builder: ( + BuildContext context, + ScrollController scrollController, + ) { + return Card( + color: isDarkMode ? Colors.grey[900] : Colors.grey[100], + surfaceTintColor: Colors.transparent, + elevation: 18.0, + margin: const EdgeInsets.all(0), + child: Column( + children: [ + buildDragHandle(scrollController), + if (isSheetExpanded.value && assetsInBound.value.isNotEmpty) + ref + .watch( + renderListProvider( + assetsInBound.value, + ), + ) + .when( + data: (renderList) { + _cachedRenderList = renderList; + final assetGrid = ImmichAssetGrid( + shrinkWrap: true, + renderList: renderList, + showDragScroll: false, + selectionActive: widget.selectionEnabled, + showMultiSelectIndicator: false, + listener: widget.selectionlistener, + visibleItemsListener: visibleItemsListener, + ); + + return Expanded(child: assetGrid); + }, + error: (error, stackTrace) { + log.warning( + "Cannot get assets in the current map bounds ${error.toString()}", + error, + stackTrace, + ); + return const SizedBox.shrink(); + }, + loading: () => const SizedBox.shrink(), + ), + if (isSheetExpanded.value && assetsInBound.value.isEmpty) + Expanded( + child: SingleChildScrollView( + child: buildNoPhotosWidget(), + ), + ), + ], + ), + ); + }, + ), + Positioned( + bottom: maxHeight * currentExtend.value, + left: 0, + child: GestureDetector( + onTap: () => launchUrl( + Uri.parse('https://openstreetmap.org/copyright'), + ), + child: ColoredBox( + color: + (widget.isDarkTheme ? Colors.grey[900] : Colors.grey[100])!, + child: Padding( + padding: const EdgeInsets.all(3), + child: Text( + '© OpenStreetMap contributors', + style: TextStyle( + fontSize: 6, + color: !widget.isDarkTheme + ? Colors.grey[900] + : Colors.grey[100], + ), + ), + ), + ), + ), + ), + Positioned( + bottom: maxHeight * (0.14 + (currentExtend.value - 0.1)), + right: 15, + child: ElevatedButton( + onPressed: () => + widget.bottomSheetEventSC.add(const MapPageZoomToLocation()), + style: ElevatedButton.styleFrom( + shape: const CircleBorder(), + padding: const EdgeInsets.all(12), + ), + child: const Icon( + Icons.my_location, + size: 22, + fill: 1, + ), + ), + ), + ], + ), + ); + } +} diff --git a/mobile/lib/modules/map/ui/map_settings_dialog.dart b/mobile/lib/modules/map/ui/map_settings_dialog.dart new file mode 100644 index 0000000000000000000000000000000000000000..d04ff2b85b909461134c35fca9c564de74500fc6 --- /dev/null +++ b/mobile/lib/modules/map/ui/map_settings_dialog.dart @@ -0,0 +1,193 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/modules/map/providers/map_state.provider.dart'; + +class MapSettingsDialog extends HookConsumerWidget { + const MapSettingsDialog({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final mapSettingsNotifier = ref.read(mapStateNotifier.notifier); + final mapSettings = ref.read(mapStateNotifier); + final isDarkMode = useState(mapSettings.isDarkTheme); + final showFavoriteOnly = useState(mapSettings.showFavoriteOnly); + final showRelativeDate = useState(mapSettings.relativeTime); + final ThemeData theme = Theme.of(context); + + Widget buildMapThemeSetting() { + return SwitchListTile.adaptive( + value: isDarkMode.value, + onChanged: (value) { + isDarkMode.value = value; + }, + activeColor: theme.primaryColor, + dense: true, + title: Text( + "map_settings_dark_mode".tr(), + style: + theme.textTheme.labelLarge?.copyWith(fontWeight: FontWeight.bold), + ), + ); + } + + Widget buildFavoriteOnlySetting() { + return SwitchListTile.adaptive( + value: showFavoriteOnly.value, + onChanged: (value) { + showFavoriteOnly.value = value; + }, + activeColor: theme.primaryColor, + dense: true, + title: Text( + "map_settings_only_show_favorites".tr(), + style: + theme.textTheme.labelLarge?.copyWith(fontWeight: FontWeight.bold), + ), + ); + } + + Widget buildDateRangeSetting() { + final now = DateTime.now(); + return DropdownMenu( + enableSearch: false, + enableFilter: false, + initialSelection: showRelativeDate.value, + onSelected: (value) { + showRelativeDate.value = value!; + }, + dropdownMenuEntries: [ + const DropdownMenuEntry(value: 0, label: "All"), + const DropdownMenuEntry( + value: 1, + label: "Past 24 hours", + ), + const DropdownMenuEntry( + value: 7, + label: "Past 7 days", + ), + const DropdownMenuEntry( + value: 30, + label: "Past 30 days", + ), + DropdownMenuEntry( + value: now + .difference( + DateTime( + now.year - 1, + now.month, + now.day, + now.hour, + now.minute, + now.second, + ), + ) + .inDays, + label: "Past year", + ), + DropdownMenuEntry( + value: now + .difference( + DateTime( + now.year - 3, + now.month, + now.day, + now.hour, + now.minute, + now.second, + ), + ) + .inDays, + label: "Past 3 years", + ), + ], + ); + } + + List getDialogActions() { + return [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + style: TextButton.styleFrom( + backgroundColor: + mapSettings.isDarkTheme ? Colors.grey[100] : Colors.grey[700], + ), + child: Text( + "map_settings_dialog_cancel".tr(), + style: theme.textTheme.labelSmall?.copyWith( + fontWeight: FontWeight.bold, + color: + mapSettings.isDarkTheme ? Colors.grey[900] : Colors.grey[100], + ), + ), + ), + TextButton( + onPressed: () { + mapSettingsNotifier.switchTheme(isDarkMode.value); + mapSettingsNotifier.switchFavoriteOnly(showFavoriteOnly.value); + mapSettingsNotifier.setRelativeTime(showRelativeDate.value); + Navigator.of(context).pop(); + }, + style: TextButton.styleFrom( + backgroundColor: theme.primaryColor, + ), + child: Text( + "map_settings_dialog_save".tr(), + style: theme.textTheme.labelSmall?.copyWith( + fontWeight: FontWeight.bold, + color: theme.primaryTextTheme.labelLarge?.color, + ), + ), + ), + ]; + } + + return AlertDialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + title: Center( + child: Text( + "map_settings_dialog_title".tr(), + style: TextStyle( + color: theme.primaryColor, + fontWeight: FontWeight.bold, + fontSize: 18, + ), + ), + ), + content: SizedBox( + width: double.maxFinite, + child: ConstrainedBox( + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height * 0.6, + ), + child: ListView( + shrinkWrap: true, + children: [ + buildMapThemeSetting(), + buildFavoriteOnlySetting(), + const SizedBox( + height: 10, + ), + Padding( + padding: const EdgeInsets.only(left: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "map_settings_only_relative_range".tr(), + style: const TextStyle(fontWeight: FontWeight.bold), + ), + buildDateRangeSetting(), + ], + ), + ), + ].toList(), + ), + ), + ), + actions: getDialogActions(), + actionsAlignment: MainAxisAlignment.spaceEvenly, + ); + } +} diff --git a/mobile/lib/modules/map/ui/map_thumbnail.dart b/mobile/lib/modules/map/ui/map_thumbnail.dart new file mode 100644 index 0000000000000000000000000000000000000000..78998276d8679a5bdaa64a8c9a63e0b5d2bfb499 --- /dev/null +++ b/mobile/lib/modules/map/ui/map_thumbnail.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_map/plugin_api.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/utils/color_filter_generator.dart'; +import 'package:latlong2/latlong.dart'; +import 'package:url_launcher/url_launcher.dart'; + +// A non-interactive thumbnail of a map in the given coordinates with optional markers +class MapThumbnail extends HookConsumerWidget { + final Function(TapPosition, LatLng)? onTap; + final LatLng coords; + final double zoom; + final List markers; + final double height; + final bool showAttribution; + final bool isDarkTheme; + + const MapThumbnail({ + super.key, + required this.coords, + required this.height, + this.onTap, + this.zoom = 1, + this.showAttribution = true, + this.isDarkTheme = false, + this.markers = const [], + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final tileLayer = TileLayer( + urlTemplate: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", + subdomains: const ['a', 'b', 'c'], + ); + + return SizedBox( + height: height, + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(15)), + child: FlutterMap( + options: MapOptions( + interactiveFlags: InteractiveFlag.none, + center: coords, + zoom: zoom, + onTap: onTap, + ), + nonRotatedChildren: [ + if (showAttribution) + RichAttributionWidget( + animationConfig: const ScaleRAWA(), + attributions: [ + TextSourceAttribution( + 'OpenStreetMap contributors', + onTap: () => launchUrl( + Uri.parse('https://openstreetmap.org/copyright'), + ), + ), + ], + ), + ], + children: [ + isDarkTheme + ? InvertionFilter( + child: SaturationFilter( + saturation: -1, + child: tileLayer, + ), + ) + : tileLayer, + if (markers.isNotEmpty) MarkerLayer(markers: markers), + ], + ), + ), + ); + } +} diff --git a/mobile/lib/modules/map/views/map_page.dart b/mobile/lib/modules/map/views/map_page.dart new file mode 100644 index 0000000000000000000000000000000000000000..379b209d995d88dcfcbc614cd3ca71e38361662b --- /dev/null +++ b/mobile/lib/modules/map/views/map_page.dart @@ -0,0 +1,499 @@ +import 'dart:async'; + +import 'package:auto_route/auto_route.dart'; +import 'package:collection/collection.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_map/plugin_api.dart'; +import 'package:flutter_map_heatmap/flutter_map_heatmap.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:geolocator/geolocator.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/modules/map/models/map_page_event.model.dart'; +import 'package:immich_mobile/modules/map/providers/map_marker.provider.dart'; +import 'package:immich_mobile/modules/map/providers/map_state.provider.dart'; +import 'package:immich_mobile/modules/map/ui/asset_marker_icon.dart'; +import 'package:immich_mobile/modules/map/ui/location_dialog.dart'; +import 'package:immich_mobile/modules/map/ui/map_page_bottom_sheet.dart'; +import 'package:immich_mobile/modules/map/ui/map_page_app_bar.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/shared/models/asset.dart'; +import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; +import 'package:immich_mobile/shared/ui/immich_toast.dart'; +import 'package:immich_mobile/utils/color_filter_generator.dart'; +import 'package:immich_mobile/utils/debounce.dart'; +import 'package:immich_mobile/utils/flutter_map_extensions.dart'; +import 'package:immich_mobile/utils/immich_app_theme.dart'; +import 'package:immich_mobile/utils/selection_handlers.dart'; +import 'package:latlong2/latlong.dart'; +import 'package:logging/logging.dart'; + +class MapPage extends StatefulHookConsumerWidget { + const MapPage({super.key}); + + @override + MapPageState createState() => MapPageState(); +} + +class MapPageState extends ConsumerState { + // Non-State variables + late final MapController mapController; + // Streams are used instead of callbacks to prevent unnecessary rebuilds on events + final StreamController mapPageEventSC = + StreamController.broadcast(); + final StreamController bottomSheetEventSC = + StreamController.broadcast(); + late final Stream bottomSheetEventStream; + // Making assets in bounds as a state variable will result in un-necessary rebuilds of the bottom sheet + // resulting in it getting reloaded each time a map move occurs + Set assetsInBounds = {}; + // TODO: Migrate the handling to MapEventMove#id when flutter_map is upgraded + // https://github.com/fleaflet/flutter_map/issues/1542 + // The below is used instead of MapEventMove#id to handle event from controller + // in onMapEvent() since MapEventMove#id is not populated properly in the + // current version of flutter_map(4.0.0) used + bool forceAssetUpdate = false; + late final Debounce debounce; + + @override + void initState() { + super.initState(); + mapController = MapController(); + bottomSheetEventStream = bottomSheetEventSC.stream; + // Map zoom events and move events are triggered often. Throttle the call to limit rebuilds + debounce = Debounce( + const Duration(milliseconds: 300), + ); + } + + @override + void dispose() { + debounce.dispose(); + super.dispose(); + } + + void reloadAssetsInBound( + Set? assetMarkers, { + bool forceReload = false, + }) { + final bounds = mapController.bounds; + if (bounds != null) { + final oldAssetsInBounds = assetsInBounds.toSet(); + assetsInBounds = + assetMarkers?.where((e) => bounds.contains(e.point)).toSet() ?? {}; + final shouldReload = forceReload || + assetsInBounds.difference(oldAssetsInBounds).isNotEmpty || + assetsInBounds.length != oldAssetsInBounds.length; + if (shouldReload) { + mapPageEventSC.add( + MapPageAssetsInBoundUpdated( + assetsInBounds.map((e) => e.asset).toList(), + ), + ); + } + } + } + + void openAssetInViewer(Asset asset) { + AutoRouter.of(context).push( + GalleryViewerRoute( + initialIndex: 0, + loadAsset: (index) => asset, + totalAssets: 1, + heroOffset: 0, + ), + ); + } + + @override + Widget build(BuildContext context) { + final log = Logger("MapService"); + final isDarkTheme = + ref.watch(mapStateNotifier.select((state) => state.isDarkTheme)); + final ValueNotifier> mapMarkerData = + useState({}); + final ValueNotifier closestAssetMarker = useState(null); + final selectionEnabledHook = useState(false); + final selectedAssets = useState({}); + final showLoadingIndicator = useState(false); + final refetchMarkers = useState(true); + + if (refetchMarkers.value) { + mapMarkerData.value = ref.watch(mapMarkersProvider).when( + skipLoadingOnRefresh: false, + error: (error, stackTrace) { + log.warning( + "Cannot get map markers ${error.toString()}", + error, + stackTrace, + ); + showLoadingIndicator.value = false; + return {}; + }, + loading: () { + showLoadingIndicator.value = true; + return {}; + }, + data: (data) { + showLoadingIndicator.value = false; + refetchMarkers.value = false; + closestAssetMarker.value = null; + debounce( + () => reloadAssetsInBound( + mapMarkerData.value, + forceReload: true, + ), + ); + return data; + }, + ); + } + + ref.listen(mapStateNotifier, (previous, next) { + bool shouldRefetch = + previous?.showFavoriteOnly != next.showFavoriteOnly || + previous?.relativeTime != next.relativeTime; + if (shouldRefetch) { + refetchMarkers.value = shouldRefetch; + ref.invalidate(mapMarkersProvider); + } + }); + + void onZoomToAssetEvent(Asset? assetInBottomSheet) { + if (assetInBottomSheet != null) { + final mapMarker = mapMarkerData.value + .firstWhereOrNull((e) => e.asset.id == assetInBottomSheet.id); + if (mapMarker != null) { + LatLng? newCenter = mapController.centerBoundsWithPadding( + mapMarker.point, + const Offset(0, -120), + zoomLevel: 6, + ); + if (newCenter != null) { + forceAssetUpdate = true; + mapController.move(newCenter, 6); + } + } + } + } + + void onZoomToLocation() async { + try { + bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); + if (!serviceEnabled) { + showDialog( + context: context, + builder: (context) => Theme( + data: isDarkTheme ? immichDarkTheme : immichLightTheme, + child: LocationServiceDisabledDialog(), + ), + ); + return; + } + + LocationPermission permission = await Geolocator.checkPermission(); + bool shouldRequestPermission = false; + + if (permission == LocationPermission.denied) { + shouldRequestPermission = await showDialog( + context: context, + builder: (context) => Theme( + data: isDarkTheme ? immichDarkTheme : immichLightTheme, + child: LocationPermissionDisabledDialog(), + ), + ); + if (shouldRequestPermission) { + permission = await Geolocator.requestPermission(); + } + } + + if (permission == LocationPermission.denied || + permission == LocationPermission.deniedForever) { + // Open app settings only if you did not request for permission before + if (permission == LocationPermission.deniedForever && + !shouldRequestPermission) { + await Geolocator.openAppSettings(); + } + return; + } + + Position currentUserLocation = await Geolocator.getCurrentPosition( + desiredAccuracy: LocationAccuracy.medium, + timeLimit: const Duration(seconds: 5), + ); + + forceAssetUpdate = true; + mapController.move( + LatLng(currentUserLocation.latitude, currentUserLocation.longitude), + 12, + ); + } catch (error) { + log.severe( + "Cannot get user's current location due to ${error.toString()}", + ); + if (context.mounted) { + ImmichToast.show( + context: context, + gravity: ToastGravity.BOTTOM, + toastType: ToastType.error, + msg: "map_cannot_get_user_location".tr(), + ); + } + } + } + + void handleBottomSheetEvents(dynamic event) { + if (event is MapPageBottomSheetScrolled) { + final assetInBottomSheet = event.asset; + if (assetInBottomSheet != null) { + final mapMarker = mapMarkerData.value + .firstWhereOrNull((e) => e.asset.id == assetInBottomSheet.id); + closestAssetMarker.value = mapMarker; + if (mapMarker != null && mapController.zoom >= 5) { + LatLng? newCenter = mapController.centerBoundsWithPadding( + mapMarker.point, + const Offset(0, -120), + ); + if (newCenter != null) { + mapController.move( + newCenter, + mapController.zoom, + ); + } + } + } + } else if (event is MapPageZoomToAsset) { + onZoomToAssetEvent(event.asset); + } else if (event is MapPageZoomToLocation) { + onZoomToLocation(); + } + } + + useEffect( + () { + final bottomSheetEventSubscription = + bottomSheetEventStream.listen(handleBottomSheetEvents); + return bottomSheetEventSubscription.cancel; + }, + [bottomSheetEventStream], + ); + + void handleMapTapEvent(LatLng tapPosition) { + const d = Distance(); + final assetsInBoundsList = assetsInBounds.toList(); + assetsInBoundsList.sort( + (a, b) => d + .distance(a.point, tapPosition) + .compareTo(d.distance(b.point, tapPosition)), + ); + // First asset less than the threshold from the tap point + final nearestAsset = assetsInBoundsList.firstWhereOrNull( + (element) => + d.distance(element.point, tapPosition) < + mapController.getTapThresholdForZoomLevel(), + ); + // Reset marker if no assets are near the tap point + if (nearestAsset == null && closestAssetMarker.value != null) { + selectionEnabledHook.value = false; + mapPageEventSC.add( + const MapPageOnTapEvent(), + ); + } + closestAssetMarker.value = nearestAsset; + } + + void onMapEvent(MapEvent mapEvent) { + if (mapEvent is MapEventMove || mapEvent is MapEventDoubleTapZoom) { + if (forceAssetUpdate || + mapEvent.source != MapEventSource.mapController) { + debounce(() { + if (selectionEnabledHook.value) { + selectionEnabledHook.value = false; + } + reloadAssetsInBound( + mapMarkerData.value, + forceReload: forceAssetUpdate, + ); + forceAssetUpdate = false; + }); + } + } else if (mapEvent is MapEventTap) { + handleMapTapEvent(mapEvent.tapPosition); + } + } + + void onShareAsset() { + handleShareAssets(ref, context, selectedAssets.value.toList()); + selectionEnabledHook.value = false; + } + + void onFavoriteAsset() async { + showLoadingIndicator.value = true; + try { + await handleFavoriteAssets(ref, context, selectedAssets.value.toList()); + } finally { + showLoadingIndicator.value = false; + selectionEnabledHook.value = false; + refetchMarkers.value = true; + } + } + + void onArchiveAsset() async { + showLoadingIndicator.value = true; + try { + await handleArchiveAssets(ref, context, selectedAssets.value.toList()); + } finally { + showLoadingIndicator.value = false; + selectionEnabledHook.value = false; + refetchMarkers.value = true; + } + } + + void selectionListener(bool isMultiSelect, Set selection) { + selectionEnabledHook.value = isMultiSelect; + selectedAssets.value = selection; + } + + final tileLayer = TileLayer( + urlTemplate: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", + subdomains: const ['a', 'b', 'c'], + maxNativeZoom: 19, + maxZoom: 19, + ); + + final darkTileLayer = InvertionFilter( + child: SaturationFilter( + saturation: -1, + child: BrightnessFilter( + brightness: -1, + child: tileLayer, + ), + ), + ); + + final markerLayer = MarkerLayer( + markers: [ + if (closestAssetMarker.value != null) + AssetMarker( + remoteId: closestAssetMarker.value!.asset.remoteId!, + anchorPos: AnchorPos.align(AnchorAlign.top), + point: closestAssetMarker.value!.point, + width: 100, + height: 100, + builder: (ctx) => GestureDetector( + onTap: () => openAssetInViewer(closestAssetMarker.value!.asset), + child: AssetMarkerIcon( + isDarkTheme: isDarkTheme, + id: closestAssetMarker.value!.asset.remoteId!, + ), + ), + ), + ], + ); + + final heatMapLayer = mapMarkerData.value.isNotEmpty + ? HeatMapLayer( + heatMapDataSource: InMemoryHeatMapDataSource( + data: mapMarkerData.value + .map( + (e) => WeightedLatLng( + LatLng(e.point.latitude, e.point.longitude), + 1, + ), + ) + .toList(), + ), + heatMapOptions: HeatMapOptions( + radius: 60, + layerOpacity: 0.5, + gradient: { + 0.20: Colors.deepPurple, + 0.40: Colors.blue, + 0.60: Colors.green, + 0.95: Colors.yellow, + 1.0: Colors.deepOrange, + }, + ), + ) + : const SizedBox.shrink(); + + return AnnotatedRegion( + value: SystemUiOverlayStyle( + statusBarColor: Colors.black.withOpacity(0.5), + statusBarIconBrightness: Brightness.light, + ), + child: Theme( + // Override app theme based on map theme + data: isDarkTheme ? immichDarkTheme : immichLightTheme, + child: Scaffold( + appBar: MapAppBar( + isDarkTheme: isDarkTheme, + selectionEnabled: selectionEnabledHook, + selectedAssetsLength: selectedAssets.value.length, + onShare: onShareAsset, + onArchive: onArchiveAsset, + onFavorite: onFavoriteAsset, + ), + extendBodyBehindAppBar: true, + body: Stack( + children: [ + FlutterMap( + mapController: mapController, + options: MapOptions( + maxBounds: + LatLngBounds(LatLng(-90, -180.0), LatLng(90.0, 180.0)), + interactiveFlags: InteractiveFlag.doubleTapZoom | + InteractiveFlag.drag | + InteractiveFlag.flingAnimation | + InteractiveFlag.pinchMove | + InteractiveFlag.pinchZoom, + center: LatLng(20, 20), + zoom: 2, + minZoom: 1, + maxZoom: 18, // max level supported by OSM, + onMapReady: () { + mapController.mapEventStream.listen(onMapEvent); + }, + ), + children: [ + isDarkTheme ? darkTileLayer : tileLayer, + heatMapLayer, + markerLayer, + ], + ), + MapPageBottomSheet( + mapPageEventStream: mapPageEventSC.stream, + bottomSheetEventSC: bottomSheetEventSC, + selectionEnabled: selectionEnabledHook.value, + selectionlistener: selectionListener, + isDarkTheme: isDarkTheme, + ), + if (showLoadingIndicator.value) + Positioned( + top: MediaQuery.of(context).size.height * 0.35, + left: MediaQuery.of(context).size.width * 0.425, + child: const ImmichLoadingIndicator(), + ), + ], + ), + ), + ), + ); + } +} + +class AssetMarker extends Marker { + String remoteId; + + AssetMarker({ + super.key, + required this.remoteId, + super.anchorPos, + required super.point, + super.width = 100.0, + super.height = 100.0, + required super.builder, + }); +} diff --git a/mobile/lib/modules/memories/ui/memory_card.dart b/mobile/lib/modules/memories/ui/memory_card.dart index 0dbe5749b50af38b979e991568bf382376d8b38b..8ef06e0d1f1c873a940104e07089be488493b629 100644 --- a/mobile/lib/modules/memories/ui/memory_card.dart +++ b/mobile/lib/modules/memories/ui/memory_card.dart @@ -110,7 +110,7 @@ class MemoryCard extends HookConsumerWidget { left: 18.0, bottom: 18.0, child: buildTitle(), - ) + ), ], ), ); diff --git a/mobile/lib/modules/onboarding/views/permission_onboarding_page.dart b/mobile/lib/modules/onboarding/views/permission_onboarding_page.dart index cab16d82d0b83e523aafdd86fdfaac88214be7ae..efbbc78a3c2a0d41233a935e292c9b18d42dbcba 100644 --- a/mobile/lib/modules/onboarding/views/permission_onboarding_page.dart +++ b/mobile/lib/modules/onboarding/views/permission_onboarding_page.dart @@ -153,6 +153,7 @@ class PermissionOnboardingPage extends HookConsumerWidget { child = buildRequestPermission(); break; case PermissionStatus.granted: + case PermissionStatus.provisional: child = buildPermissionGranted(); break; case PermissionStatus.restricted: @@ -183,7 +184,7 @@ class PermissionOnboardingPage extends HookConsumerWidget { ), TextButton( child: const Text('permission_onboarding_log_out').tr(), - onPressed: () { + onPressed: () { ref.read(authenticationProvider.notifier).logout(); AutoRouter.of(context).replace( const LoginRoute(), diff --git a/mobile/lib/modules/partner/views/partner_page.dart b/mobile/lib/modules/partner/views/partner_page.dart index 789f036c4adf7e640f56aa7050ddd7d4c4bef535..61c63974662bbf9d2aebf0e2f6f7604b55d32c8e 100644 --- a/mobile/lib/modules/partner/views/partner_page.dart +++ b/mobile/lib/modules/partner/views/partner_page.dart @@ -44,7 +44,7 @@ class PartnerPage extends HookConsumerWidget { Text("${u.firstName} ${u.lastName}"), ], ), - ) + ), ], ); }, @@ -151,7 +151,7 @@ class PartnerPage extends HookConsumerWidget { availableUsers.whenOrNull(data: (data) => addNewUsersHandler), icon: const Icon(Icons.person_add), tooltip: "partner_page_add_partner".tr(), - ) + ), ], ), body: buildUserList(partners), diff --git a/mobile/lib/modules/search/ui/curated_people_row.dart b/mobile/lib/modules/search/ui/curated_people_row.dart index 18a1da2c3868e8912f0d9296dec46b0792c607f0..8a65c25f79524bc197a8930875a6a36aef1d57fb 100644 --- a/mobile/lib/modules/search/ui/curated_people_row.dart +++ b/mobile/lib/modules/search/ui/curated_people_row.dart @@ -50,7 +50,7 @@ class CuratedPeopleRow extends StatelessWidget { itemBuilder: (context, index) { final person = content[index]; final headers = { - "Authorization": "Bearer ${Store.get(StoreKey.accessToken)}" + "Authorization": "Bearer ${Store.get(StoreKey.accessToken)}", }; return Padding( padding: const EdgeInsets.only(right: 18.0), @@ -102,7 +102,7 @@ class CuratedPeopleRow extends StatelessWidget { fontSize: 13.0, ), ), - ) + ), ], ), ), diff --git a/mobile/lib/modules/search/ui/curated_places_row.dart b/mobile/lib/modules/search/ui/curated_places_row.dart new file mode 100644 index 0000000000000000000000000000000000000000..ef394b83b9b4d60bf146eaf339357b37edbbf3eb --- /dev/null +++ b/mobile/lib/modules/search/ui/curated_places_row.dart @@ -0,0 +1,110 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:immich_mobile/modules/map/ui/map_thumbnail.dart'; +import 'package:immich_mobile/modules/search/ui/curated_row.dart'; +import 'package:immich_mobile/modules/search/ui/thumbnail_with_info.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/shared/models/store.dart'; +import 'package:latlong2/latlong.dart'; + +class CuratedPlacesRow extends CuratedRow { + const CuratedPlacesRow({ + super.key, + required super.content, + super.imageSize, + super.onTap, + }); + + @override + Widget build(BuildContext context) { + Widget buildMapThumbnail() { + return GestureDetector( + onTap: () => AutoRouter.of(context).push( + const MapRoute(), + ), + child: SizedBox( + height: imageSize, + width: imageSize, + child: Stack( + children: [ + Padding( + padding: const EdgeInsets.only(right: 10.0), + child: MapThumbnail( + zoom: 2, + coords: LatLng( + 47, + 5, + ), + height: imageSize, + showAttribution: false, + isDarkTheme: Theme.of(context).brightness == Brightness.dark, + ), + ), + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.black, + gradient: LinearGradient( + begin: FractionalOffset.topCenter, + end: FractionalOffset.bottomCenter, + colors: [ + Colors.blueGrey.withOpacity(0.0), + Colors.black.withOpacity(0.4), + ], + stops: const [0.0, 1.0], + ), + ), + ), + const Align( + alignment: Alignment.bottomCenter, + child: Padding( + padding: EdgeInsets.only(bottom: 10), + child: Text( + "Your Map", + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + ), + ), + ], + ), + ), + ); + } + + return ListView.builder( + scrollDirection: Axis.horizontal, + padding: const EdgeInsets.symmetric( + horizontal: 16, + ), + itemBuilder: (context, index) { + // Injecting Map thumbnail as the first element + if (index == 0) { + return buildMapThumbnail(); + } + // The actual index is 1 less than the virutal index since we inject map into the first position + final actualIndex = index - 1; + final object = content[actualIndex]; + final thumbnailRequestUrl = + '${Store.get(StoreKey.serverEndpoint)}/asset/thumbnail/${object.id}'; + return SizedBox( + width: imageSize, + height: imageSize, + child: Padding( + padding: const EdgeInsets.only(right: 10.0), + child: ThumbnailWithInfo( + imageUrl: thumbnailRequestUrl, + textInfo: object.label, + onTap: () => onTap?.call(object, actualIndex), + ), + ), + ); + }, + // Adding 1 to inject map thumbnail as first element + itemCount: content.length + 1, + ); + } +} diff --git a/mobile/lib/modules/search/ui/search_suggestion_list.dart b/mobile/lib/modules/search/ui/search_suggestion_list.dart index e4c6061953bc9232e90a17a64a2f36d1944b18ae..b66be410fdde5465a885366467cc381b28eecad4 100644 --- a/mobile/lib/modules/search/ui/search_suggestion_list.dart +++ b/mobile/lib/modules/search/ui/search_suggestion_list.dart @@ -39,7 +39,7 @@ class SearchSuggestionList extends ConsumerWidget { color: Theme.of(context).primaryColor, fontWeight: FontWeight.bold, ), - ) + ), ], ), ), diff --git a/mobile/lib/modules/search/ui/thumbnail_with_info.dart b/mobile/lib/modules/search/ui/thumbnail_with_info.dart index d16d548bf87687f3a63573a96616220e4c7b4e26..bbb7e6834c89ed0c4a9eafb0304dc152b2a14b47 100644 --- a/mobile/lib/modules/search/ui/thumbnail_with_info.dart +++ b/mobile/lib/modules/search/ui/thumbnail_with_info.dart @@ -46,7 +46,7 @@ class ThumbnailWithInfo extends StatelessWidget { imageUrl: imageUrl!, httpHeaders: { "Authorization": - "Bearer ${Store.get(StoreKey.accessToken)}" + "Bearer ${Store.get(StoreKey.accessToken)}", }, errorWidget: (context, url, error) => const Icon(Icons.image_not_supported_outlined), diff --git a/mobile/lib/modules/search/views/person_result_page.dart b/mobile/lib/modules/search/views/person_result_page.dart index bcdae6185490e57b871942cd8d50aa400471aa49..01483f0bd39f133646c37799a8364f1d79897eca 100644 --- a/mobile/lib/modules/search/views/person_result_page.dart +++ b/mobile/lib/modules/search/views/person_result_page.dart @@ -56,7 +56,7 @@ class PersonResultPage extends HookConsumerWidget { style: TextStyle(fontWeight: FontWeight.bold), ), onTap: showEditNameDialog, - ) + ), ], ), ); @@ -134,7 +134,7 @@ class PersonResultPage extends HookConsumerWidget { getFaceThumbnailUrl(personId), headers: { "Authorization": - "Bearer ${isar_store.Store.get(isar_store.StoreKey.accessToken)}" + "Bearer ${isar_store.Store.get(isar_store.StoreKey.accessToken)}", }, ), ), diff --git a/mobile/lib/modules/search/views/search_page.dart b/mobile/lib/modules/search/views/search_page.dart index b94806730afffd48d675986b530923780dade876..a7edcd90e273a2cebec21ed67e2be8cbade79e15 100644 --- a/mobile/lib/modules/search/views/search_page.dart +++ b/mobile/lib/modules/search/views/search_page.dart @@ -7,7 +7,7 @@ import 'package:immich_mobile/modules/search/models/curated_content.dart'; import 'package:immich_mobile/modules/search/providers/people.provider.dart'; import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart'; import 'package:immich_mobile/modules/search/ui/curated_people_row.dart'; -import 'package:immich_mobile/modules/search/ui/curated_row.dart'; +import 'package:immich_mobile/modules/search/ui/curated_places_row.dart'; import 'package:immich_mobile/modules/search/ui/immich_search_bar.dart'; import 'package:immich_mobile/modules/search/ui/person_name_edit_form.dart'; import 'package:immich_mobile/modules/search/ui/search_row_title.dart'; @@ -69,7 +69,7 @@ class SearchPage extends HookConsumerWidget { buildPeople() { return SizedBox( - height: MediaQuery.of(context).size.width / 3, + height: imageSize, child: curatedPeople.when( loading: () => const Center(child: ImmichLoadingIndicator()), error: (err, stack) => Center(child: Text('Error: $err')), @@ -105,7 +105,7 @@ class SearchPage extends HookConsumerWidget { child: curatedLocation.when( loading: () => const Center(child: ImmichLoadingIndicator()), error: (err, stack) => Center(child: Text('Error: $err')), - data: (locations) => CuratedRow( + data: (locations) => CuratedPlacesRow( content: locations .map( (o) => CuratedContent( @@ -155,6 +155,7 @@ class SearchPage extends HookConsumerWidget { ), top: 0, ), + const SizedBox(height: 10.0), buildPlaces(), const SizedBox(height: 24.0), Padding( diff --git a/mobile/lib/modules/settings/services/app_settings.service.dart b/mobile/lib/modules/settings/services/app_settings.service.dart index e54e6b60e0924f8ce3c5336cf95d107587172f04..7ad93ea08a9b022276445b0a93d8cd9706060fa8 100644 --- a/mobile/lib/modules/settings/services/app_settings.service.dart +++ b/mobile/lib/modules/settings/services/app_settings.service.dart @@ -46,6 +46,9 @@ enum AppSettingsEnum { advancedTroubleshooting(StoreKey.advancedTroubleshooting, null, false), logLevel(StoreKey.logLevel, null, 5), // Level.INFO = 5 preferRemoteImage(StoreKey.preferRemoteImage, null, false), + mapThemeMode(StoreKey.mapThemeMode, null, false), + mapShowFavoriteOnly(StoreKey.mapShowFavoriteOnly, null, false), + mapRelativeDate(StoreKey.mapRelativeDate, null, 0), ; const AppSettingsEnum(this.storeKey, this.hiveKey, this.defaultValue); diff --git a/mobile/lib/modules/settings/views/settings_page.dart b/mobile/lib/modules/settings/views/settings_page.dart index 6e5ddec70cf13199599df44df78c9dbf76747d9a..798d9916a70e0a4bb20fa0bd4ccba103c81085ea 100644 --- a/mobile/lib/modules/settings/views/settings_page.dart +++ b/mobile/lib/modules/settings/views/settings_page.dart @@ -42,7 +42,7 @@ class SettingsPage extends HookConsumerWidget { const AssetListSettings(), const NotificationSetting(), // const ExperimentalSettings(), - const AdvancedSettings() + const AdvancedSettings(), ], ).toList(), ], diff --git a/mobile/lib/routing/router.dart b/mobile/lib/routing/router.dart index 06f462ae5585f8e435356d59df88a6f67e5613a0..56885aeaf339b856b3d096737de4ec39dcc8df8f 100644 --- a/mobile/lib/routing/router.dart +++ b/mobile/lib/routing/router.dart @@ -2,10 +2,12 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/modules/album/models/asset_selection_page_result.model.dart'; +import 'package:immich_mobile/modules/album/views/album_options_part.dart'; import 'package:immich_mobile/modules/album/views/album_viewer_page.dart'; import 'package:immich_mobile/modules/album/views/asset_selection_page.dart'; import 'package:immich_mobile/modules/album/views/create_album_page.dart'; import 'package:immich_mobile/modules/album/views/library_page.dart'; +import 'package:immich_mobile/modules/map/views/map_page.dart'; import 'package:immich_mobile/modules/memories/models/memory.dart'; import 'package:immich_mobile/modules/memories/views/memory_page.dart'; import 'package:immich_mobile/modules/partner/views/partner_detail_page.dart'; @@ -74,7 +76,7 @@ part 'router.gr.dart'; AutoRoute(page: HomePage, guards: [AuthGuard, DuplicateGuard]), AutoRoute(page: SearchPage, guards: [AuthGuard, DuplicateGuard]), AutoRoute(page: SharingPage, guards: [AuthGuard, DuplicateGuard]), - AutoRoute(page: LibraryPage, guards: [AuthGuard, DuplicateGuard]) + AutoRoute(page: LibraryPage, guards: [AuthGuard, DuplicateGuard]), ], transitionsBuilder: TransitionsBuilders.fadeIn, ), @@ -152,6 +154,8 @@ part 'router.gr.dart'; ), AutoRoute(page: AllPeoplePage, guards: [AuthGuard, DuplicateGuard]), AutoRoute(page: MemoryPage, guards: [AuthGuard, DuplicateGuard]), + AutoRoute(page: MapPage, guards: [AuthGuard, DuplicateGuard]), + AutoRoute(page: AlbumOptionsPage, guards: [AuthGuard, DuplicateGuard]), ], ) class AppRouter extends _$AppRouter { diff --git a/mobile/lib/routing/router.gr.dart b/mobile/lib/routing/router.gr.dart index 7e92aebed467b2a0b7943422cc05bf4849907d4b..4aef3beabbe1f9ca8466059171361483edb02471 100644 --- a/mobile/lib/routing/router.gr.dart +++ b/mobile/lib/routing/router.gr.dart @@ -296,6 +296,22 @@ class _$AppRouter extends RootStackRouter { ), ); }, + MapRoute.name: (routeData) { + return MaterialPageX( + routeData: routeData, + child: const MapPage(), + ); + }, + AlbumOptionsRoute.name: (routeData) { + final args = routeData.argsAs(); + return MaterialPageX( + routeData: routeData, + child: AlbumOptionsPage( + key: args.key, + album: args.album, + ), + ); + }, HomeRoute.name: (routeData) { return MaterialPageX( routeData: routeData, @@ -595,6 +611,22 @@ class _$AppRouter extends RootStackRouter { duplicateGuard, ], ), + RouteConfig( + MapRoute.name, + path: '/map-page', + guards: [ + authGuard, + duplicateGuard, + ], + ), + RouteConfig( + AlbumOptionsRoute.name, + path: '/album-options-page', + guards: [ + authGuard, + duplicateGuard, + ], + ), ]; } @@ -1319,6 +1351,51 @@ class MemoryRouteArgs { } } +/// [MapPage] +class MapRoute extends PageRouteInfo { + const MapRoute() + : super( + MapRoute.name, + path: '/map-page', + ); + + static const String name = 'MapRoute'; +} + +/// generated route for +/// [AlbumOptionsPage] +class AlbumOptionsRoute extends PageRouteInfo { + AlbumOptionsRoute({ + Key? key, + required Album album, + }) : super( + AlbumOptionsRoute.name, + path: '/album-options-page', + args: AlbumOptionsRouteArgs( + key: key, + album: album, + ), + ); + + static const String name = 'AlbumOptionsRoute'; +} + +class AlbumOptionsRouteArgs { + const AlbumOptionsRouteArgs({ + this.key, + required this.album, + }); + + final Key? key; + + final Album album; + + @override + String toString() { + return 'AlbumOptionsRouteArgs{key: $key, album: $album}'; + } +} + /// generated route for /// [HomePage] class HomeRoute extends PageRouteInfo { diff --git a/mobile/lib/shared/models/server_info_state.model.dart b/mobile/lib/shared/models/server_info_state.model.dart index 7b1ea9c918717db786821a86ee95291a63ba786a..6623ac16660624548ac05e2868f0c62c088c8c92 100644 --- a/mobile/lib/shared/models/server_info_state.model.dart +++ b/mobile/lib/shared/models/server_info_state.model.dart @@ -1,7 +1,7 @@ import 'package:openapi/api.dart'; class ServerInfoState { - final ServerVersionReponseDto serverVersion; + final ServerVersionResponseDto serverVersion; final bool isVersionMismatch; final String versionMismatchErrorMessage; @@ -12,7 +12,7 @@ class ServerInfoState { }); ServerInfoState copyWith({ - ServerVersionReponseDto? serverVersion, + ServerVersionResponseDto? serverVersion, bool? isVersionMismatch, String? versionMismatchErrorMessage, }) { diff --git a/mobile/lib/shared/models/store.dart b/mobile/lib/shared/models/store.dart index ebbef904e8438653e725b96dc41c42d748eff0e2..f67b2b4115f831416dc0331f3f08da11443a05c6 100644 --- a/mobile/lib/shared/models/store.dart +++ b/mobile/lib/shared/models/store.dart @@ -174,6 +174,10 @@ enum StoreKey { advancedTroubleshooting(114, type: bool), logLevel(115, type: int), preferRemoteImage(116, type: bool), + // map related settings + mapThemeMode(117, type: bool), + mapShowFavoriteOnly(118, type: bool), + mapRelativeDate(119, type: int), ; const StoreKey( diff --git a/mobile/lib/shared/models/user.g.dart b/mobile/lib/shared/models/user.g.dart index 461168f6ebf35d1b2b9c612e1d1287677bbcd9a4..687a784c090ff2d93ca9e9a2bee120827801b4a3 100644 --- a/mobile/lib/shared/models/user.g.dart +++ b/mobile/lib/shared/models/user.g.dart @@ -157,7 +157,7 @@ User _userDeserialize( isPartnerSharedBy: reader.readBoolOrNull(offsets[4]) ?? false, isPartnerSharedWith: reader.readBoolOrNull(offsets[5]) ?? false, lastName: reader.readString(offsets[6]), - memoryEnabled: reader.readBoolOrNull(offsets[7]) ?? true, + memoryEnabled: reader.readBoolOrNull(offsets[7]), profileImagePath: reader.readStringOrNull(offsets[8]) ?? '', updatedAt: reader.readDateTime(offsets[9]), ); @@ -186,7 +186,7 @@ P _userDeserializeProp

( case 6: return (reader.readString(offset)) as P; case 7: - return (reader.readBoolOrNull(offset) ?? true) as P; + return (reader.readBoolOrNull(offset)) as P; case 8: return (reader.readStringOrNull(offset) ?? '') as P; case 9: @@ -979,8 +979,24 @@ extension UserQueryFilter on QueryBuilder { }); } + QueryBuilder memoryEnabledIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'memoryEnabled', + )); + }); + } + + QueryBuilder memoryEnabledIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'memoryEnabled', + )); + }); + } + QueryBuilder memoryEnabledEqualTo( - bool value) { + bool? value) { return QueryBuilder.apply(this, (query) { return query.addFilterCondition(FilterCondition.equalTo( property: r'memoryEnabled', @@ -1661,7 +1677,7 @@ extension UserQueryProperty on QueryBuilder { }); } - QueryBuilder memoryEnabledProperty() { + QueryBuilder memoryEnabledProperty() { return QueryBuilder.apply(this, (query) { return query.addPropertyName(r'memoryEnabled'); }); diff --git a/mobile/lib/shared/providers/app_state.provider.dart b/mobile/lib/shared/providers/app_state.provider.dart index 7dc898be37d3b2386f082c00aa2deea241bcb0af..3cebc4d2a867b5b906d06823c0d5ae483d35dd1b 100644 --- a/mobile/lib/shared/providers/app_state.provider.dart +++ b/mobile/lib/shared/providers/app_state.provider.dart @@ -21,6 +21,7 @@ enum AppStateEnum { paused, resumed, detached, + hidden, } class AppStateNotiifer extends StateNotifier { @@ -84,6 +85,10 @@ class AppStateNotiifer extends StateNotifier { state = AppStateEnum.detached; ref.watch(manualUploadProvider.notifier).cancelBackup(); } + + void handleAppHidden() { + state = AppStateEnum.hidden; + } } final appStateProvider = diff --git a/mobile/lib/shared/providers/server_info.provider.dart b/mobile/lib/shared/providers/server_info.provider.dart index 2613b85224d0a4395e84da5f5550e64b1cad9323..ff0945ead0cc06558b022cc9ae52733b322cd0da 100644 --- a/mobile/lib/shared/providers/server_info.provider.dart +++ b/mobile/lib/shared/providers/server_info.provider.dart @@ -10,7 +10,7 @@ class ServerInfoNotifier extends StateNotifier { ServerInfoNotifier(this._serverInfoService) : super( ServerInfoState( - serverVersion: ServerVersionReponseDto( + serverVersion: ServerVersionResponseDto( major: 0, patch_: 0, minor: 0, @@ -23,7 +23,7 @@ class ServerInfoNotifier extends StateNotifier { final ServerInfoService _serverInfoService; getServerVersion() async { - ServerVersionReponseDto? serverVersion = + ServerVersionResponseDto? serverVersion = await _serverInfoService.getServerVersion(); if (serverVersion == null) { diff --git a/mobile/lib/shared/services/asset.service.dart b/mobile/lib/shared/services/asset.service.dart index 9165e6308f612fca92f4476ce6a44022df9eba8f..8e0fc1847fcad89dd5906f85aa8d4d7e5a4f60f5 100644 --- a/mobile/lib/shared/services/asset.service.dart +++ b/mobile/lib/shared/services/asset.service.dart @@ -69,7 +69,6 @@ class AssetService { await _apiService.assetApi.getAllAssetsWithETag( eTag: etag, userId: user.id, - withoutThumbs: true, ); if (assets == null) { return null; diff --git a/mobile/lib/shared/services/local_notification.service.dart b/mobile/lib/shared/services/local_notification.service.dart index f37da8a4c971bca8c310834dfa351e7963898fdb..ed0065528e4d9b8e61e6498a60bdac7a2572f108 100644 --- a/mobile/lib/shared/services/local_notification.service.dart +++ b/mobile/lib/shared/services/local_notification.service.dart @@ -102,7 +102,7 @@ class LocalNotificationService { cancelUploadActionID, 'Cancel', showsUserInterface: true, - ) + ), ] : null, ) diff --git a/mobile/lib/shared/services/server_info.service.dart b/mobile/lib/shared/services/server_info.service.dart index b112fc46ca7d889b74dc17ae2dc5e00d9f21d013..bf923dfab0363985db4ecd3be6e653da94f57fe5 100644 --- a/mobile/lib/shared/services/server_info.service.dart +++ b/mobile/lib/shared/services/server_info.service.dart @@ -24,7 +24,7 @@ class ServerInfoService { } } - Future getServerVersion() async { + Future getServerVersion() async { try { return await _apiService.serverInfoApi.getServerVersion(); } catch (e) { diff --git a/mobile/lib/shared/ui/confirm_dialog.dart b/mobile/lib/shared/ui/confirm_dialog.dart index 87d77ecd01a535325bb4fe1c98c881431c8da496..773007f73c4ab56ba5d9df12835fb4bdbd13e508 100644 --- a/mobile/lib/shared/ui/confirm_dialog.dart +++ b/mobile/lib/shared/ui/confirm_dialog.dart @@ -26,7 +26,7 @@ class ConfirmDialog extends ConsumerWidget { content: Text(content).tr(), actions: [ TextButton( - onPressed: () => Navigator.of(context).pop(), + onPressed: () => Navigator.of(context).pop(false), child: Text( cancel, style: TextStyle( @@ -38,7 +38,7 @@ class ConfirmDialog extends ConsumerWidget { TextButton( onPressed: () { onOk(); - Navigator.of(context).pop(); + Navigator.of(context).pop(true); }, child: Text( ok, diff --git a/mobile/lib/shared/ui/photo_view/src/utils/ignorable_change_notifier.dart b/mobile/lib/shared/ui/photo_view/src/utils/ignorable_change_notifier.dart index 95f6552be3e8970bc953ffc7ab30f1832b9caccc..d061b7b76cd90018a1e4e7d86d3ce11c47ef0228 100644 --- a/mobile/lib/shared/ui/photo_view/src/utils/ignorable_change_notifier.dart +++ b/mobile/lib/shared/ui/photo_view/src/utils/ignorable_change_notifier.dart @@ -16,7 +16,7 @@ class IgnorableChangeNotifier extends ChangeNotifier { if (_ignorableListeners == null) { AssertionError([ 'A $runtimeType was used after being disposed.', - 'Once you have called dispose() on a $runtimeType, it can no longer be used.' + 'Once you have called dispose() on a $runtimeType, it can no longer be used.', ]); } return true; diff --git a/mobile/lib/shared/ui/share_dialog.dart b/mobile/lib/shared/ui/share_dialog.dart index 887dcd86f588960af386b80739a477f7976ef715..d8ea664a6edab58d20b6d866fb2027bc5727122b 100644 --- a/mobile/lib/shared/ui/share_dialog.dart +++ b/mobile/lib/shared/ui/share_dialog.dart @@ -15,7 +15,7 @@ class ShareDialog extends StatelessWidget { margin: const EdgeInsets.only(top: 12), child: const Text('share_dialog_preparing') .tr(), - ) + ), ], ), ); diff --git a/mobile/lib/shared/ui/user_circle_avatar.dart b/mobile/lib/shared/ui/user_circle_avatar.dart new file mode 100644 index 0000000000000000000000000000000000000000..39f27dbd9f2a4b9daf37d2f1a5b766f416f5aed4 --- /dev/null +++ b/mobile/lib/shared/ui/user_circle_avatar.dart @@ -0,0 +1,75 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/shared/models/store.dart'; +import 'package:immich_mobile/shared/models/user.dart'; +import 'package:immich_mobile/shared/ui/transparent_image.dart'; + +// ignore: must_be_immutable +class UserCircleAvatar extends ConsumerWidget { + final User user; + double radius; + double size; + bool useRandomBackgroundColor; + + UserCircleAvatar({ + super.key, + this.radius = 22, + this.size = 44, + this.useRandomBackgroundColor = false, + required this.user, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final randomColors = [ + Colors.red[200], + Colors.blue[200], + Colors.green[200], + Colors.yellow[200], + Colors.purple[200], + Colors.orange[200], + Colors.pink[200], + Colors.teal[200], + Colors.indigo[200], + Colors.cyan[200], + Colors.brown[200], + ]; + + final profileImageUrl = + '${Store.get(StoreKey.serverEndpoint)}/user/profile-image/${user.id}?d=${Random().nextInt(1024)}'; + return CircleAvatar( + backgroundColor: useRandomBackgroundColor + ? randomColors[Random().nextInt(randomColors.length)] + : Theme.of(context).primaryColor, + radius: radius, + child: user.profileImagePath == "" + ? Text( + user.firstName[0], + style: const TextStyle( + fontWeight: FontWeight.bold, + color: Colors.black, + ), + ) + : ClipRRect( + borderRadius: BorderRadius.circular(50), + child: FadeInImage( + fit: BoxFit.cover, + placeholder: MemoryImage(kTransparentImage), + width: size, + height: size, + image: NetworkImage( + profileImageUrl, + headers: { + "Authorization": "Bearer ${Store.get(StoreKey.accessToken)}", + }, + ), + fadeInDuration: const Duration(milliseconds: 200), + imageErrorBuilder: (context, error, stackTrace) => + Image.memory(kTransparentImage), + ), + ), + ); + } +} diff --git a/mobile/lib/shared/views/app_log_detail_page.dart b/mobile/lib/shared/views/app_log_detail_page.dart index 212787aa6bdb588ef0dafe338d0ebe8a957dac1c..0963605b43c9435546f656f1b76857abfa7a1079 100644 --- a/mobile/lib/shared/views/app_log_detail_page.dart +++ b/mobile/lib/shared/views/app_log_detail_page.dart @@ -47,7 +47,7 @@ class AppLogDetailPage extends HookConsumerWidget { size: 16.0, color: Theme.of(context).primaryColor, ), - ) + ), ], ), Container( @@ -106,7 +106,7 @@ class AppLogDetailPage extends HookConsumerWidget { size: 16.0, color: Theme.of(context).primaryColor, ), - ) + ), ], ), Container( @@ -181,7 +181,7 @@ class AppLogDetailPage extends HookConsumerWidget { if (logMessage.context1 != null) buildLogContext1(logMessage.context1.toString()), if (logMessage.context2 != null) - buildStackMessage(logMessage.context2.toString()) + buildStackMessage(logMessage.context2.toString()), ], ), ), diff --git a/mobile/lib/shared/views/tab_controller_page.dart b/mobile/lib/shared/views/tab_controller_page.dart index f718c480d020e20e71c5e1f591c5b885840bf6d5..cb5a3c36212b88c3954d9ec45dcb28a9b074cd64 100644 --- a/mobile/lib/shared/views/tab_controller_page.dart +++ b/mobile/lib/shared/views/tab_controller_page.dart @@ -148,7 +148,7 @@ class TabControllerPage extends HookConsumerWidget { color: Theme.of(context).primaryColor, ), ), - ) + ), ], ); } @@ -159,7 +159,7 @@ class TabControllerPage extends HookConsumerWidget { const HomeRoute(), SearchRoute(), const SharingRoute(), - const LibraryRoute() + const LibraryRoute(), ], builder: (context, child, animation) { final tabsRouter = AutoTabsRouter.of(context); diff --git a/mobile/lib/shared/views/version_announcement_overlay.dart b/mobile/lib/shared/views/version_announcement_overlay.dart index 7cb8213fc8c7bb250628ba71315d51472151afae..7a01ef304d4762e74a6494f9058e73bf2e358c00 100644 --- a/mobile/lib/shared/views/version_announcement_overlay.dart +++ b/mobile/lib/shared/views/version_announcement_overlay.dart @@ -99,7 +99,7 @@ class VersionAnnouncementOverlay extends HookConsumerWidget { text: "version_announcement_overlay_text_3" .tr(), - ) + ), ], ), ), @@ -126,7 +126,7 @@ class VersionAnnouncementOverlay extends HookConsumerWidget { ), ).tr(), ), - ) + ), ], ), ), diff --git a/mobile/lib/utils/color_filter_generator.dart b/mobile/lib/utils/color_filter_generator.dart new file mode 100644 index 0000000000000000000000000000000000000000..c1558232646d42bc1633e70b84bd71b7b2a5854e --- /dev/null +++ b/mobile/lib/utils/color_filter_generator.dart @@ -0,0 +1,104 @@ +import 'package:flutter/widgets.dart'; + +class InvertionFilter extends StatelessWidget { + final Widget? child; + const InvertionFilter({super.key, this.child}); + + @override + Widget build(BuildContext context) { + return ColorFiltered( + colorFilter: const ColorFilter.matrix([ + -1, 0, 0, 0, 255, // + 0, -1, 0, 0, 255, // + 0, 0, -1, 0, 255, // + 0, 0, 0, 1, 0, // + ]), + child: child, + ); + } +} + +// -1 - darkest, 1 - brightest, 0 - unchanged +class BrightnessFilter extends StatelessWidget { + final Widget? child; + final double brightness; + const BrightnessFilter({super.key, this.child, this.brightness = 0}); + + @override + Widget build(BuildContext context) { + return ColorFiltered( + colorFilter: ColorFilter.matrix( + _ColorFilterGenerator.brightnessAdjustMatrix(brightness), + ), + child: child, + ); + } +} + +// -1 - greyscale, 1 - most saturated, 0 - unchanged +class SaturationFilter extends StatelessWidget { + final Widget? child; + final double saturation; + const SaturationFilter({super.key, this.child, this.saturation = 0}); + + @override + Widget build(BuildContext context) { + return ColorFiltered( + colorFilter: ColorFilter.matrix( + _ColorFilterGenerator.saturationAdjustMatrix(saturation), + ), + child: child, + ); + } +} + +class _ColorFilterGenerator { + static List brightnessAdjustMatrix(double value) { + value = value * 10; + + if (value == 0) { + return [ + 1, 0, 0, 0, 0, // + 0, 1, 0, 0, 0, // + 0, 0, 1, 0, 0, // + 0, 0, 0, 1, 0, // + ]; + } + + return List.from([ + 1, 0, 0, 0, value, 0, 1, 0, 0, value, 0, 0, 1, 0, value, 0, 0, 0, 1, 0, // + ]).map((i) => i.toDouble()).toList(); + } + + static List saturationAdjustMatrix(double value) { + value = value * 100; + + if (value == 0) { + return [ + 1, 0, 0, 0, 0, // + 0, 1, 0, 0, 0, // + 0, 0, 1, 0, 0, // + 0, 0, 0, 1, 0, // + ]; + } + + double x = + ((1 + ((value > 0) ? ((3 * value) / 100) : (value / 100)))).toDouble(); + double lumR = 0.3086; + double lumG = 0.6094; + double lumB = 0.082; + + return List.from([ + (lumR * (1 - x)) + x, lumG * (1 - x), lumB * (1 - x), // + 0, 0, // + lumR * (1 - x), // + (lumG * (1 - x)) + x, // + lumB * (1 - x), // + 0, 0, // + lumR * (1 - x), // + lumG * (1 - x), // + (lumB * (1 - x)) + x, // + 0, 0, 0, 0, 0, 1, 0, // + ]).map((i) => i.toDouble()).toList(); + } +} diff --git a/mobile/lib/utils/debounce.dart b/mobile/lib/utils/debounce.dart new file mode 100644 index 0000000000000000000000000000000000000000..273ee8ba95b26e4099fff91640945165e5e996c4 --- /dev/null +++ b/mobile/lib/utils/debounce.dart @@ -0,0 +1,26 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +class Debounce { + Debounce(Duration interval) : _interval = interval.inMilliseconds; + final int _interval; + Timer? _timer; + VoidCallback? action; + + void call(VoidCallback? action) { + this.action = action; + _timer?.cancel(); + _timer = Timer(Duration(milliseconds: _interval), _callAndRest); + } + + void _callAndRest() { + action?.call(); + _timer = null; + } + + void dispose() { + _timer?.cancel(); + _timer = null; + } +} diff --git a/mobile/lib/utils/flutter_map_extensions.dart b/mobile/lib/utils/flutter_map_extensions.dart new file mode 100644 index 0000000000000000000000000000000000000000..4fc812b4a7fe88ca0fe8e8b646f48ca94d3d1243 --- /dev/null +++ b/mobile/lib/utils/flutter_map_extensions.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:latlong2/latlong.dart'; +import 'dart:math' as math; + +extension MoveByBounds on MapController { + // TODO: Remove this in favor of built-in method when upgrading flutter_map to 5.0.0 + LatLng? centerBoundsWithPadding( + LatLng coordinates, + Offset offset, { + double? zoomLevel, + }) { + const crs = Epsg3857(); + final oldCenterPt = crs.latLngToPoint(coordinates, zoomLevel ?? zoom); + final mapCenterPoint = _rotatePoint( + oldCenterPt, + oldCenterPt - CustomPoint(offset.dx, offset.dy), + ); + return crs.pointToLatLng(mapCenterPoint, zoomLevel ?? zoom); + } + + CustomPoint _rotatePoint( + CustomPoint mapCenter, + CustomPoint point, { + bool counterRotation = true, + }) { + final counterRotationFactor = counterRotation ? -1 : 1; + + final m = Matrix4.identity() + ..translate(mapCenter.x, mapCenter.y) + ..rotateZ(degToRadian(rotation) * counterRotationFactor) + ..translate(-mapCenter.x, -mapCenter.y); + + final tp = MatrixUtils.transformPoint(m, Offset(point.x, point.y)); + + return CustomPoint(tp.dx, tp.dy); + } + + double getTapThresholdForZoomLevel() { + const scale = [ + 25000000, + 15000000, + 8000000, + 4000000, + 2000000, + 1000000, + 500000, + 250000, + 100000, + 50000, + 25000, + 15000, + 8000, + 4000, + 2000, + 1000, + 500, + 250, + 100, + 50, + 25, + 10, + 5, + ]; + return scale[math.max(0, math.min(20, zoom.round() + 2))].toDouble() / 6; + } +} diff --git a/mobile/lib/utils/image_url_builder.dart b/mobile/lib/utils/image_url_builder.dart index 3fe68f131dcdafd8994371331a86cbf83a407f32..2056237cbd24d6e24fca1e9fdbc7f53072abc2c6 100644 --- a/mobile/lib/utils/image_url_builder.dart +++ b/mobile/lib/utils/image_url_builder.dart @@ -7,17 +7,20 @@ String getThumbnailUrl( final Asset asset, { ThumbnailFormat type = ThumbnailFormat.WEBP, }) { - return _getThumbnailUrl(asset.remoteId!, type: type); + return getThumbnailUrlForRemoteId(asset.remoteId!, type: type); } String getThumbnailCacheKey( final Asset asset, { ThumbnailFormat type = ThumbnailFormat.WEBP, }) { - return _getThumbnailCacheKey(asset.remoteId!, type); + return getThumbnailCacheKeyForRemoteId(asset.remoteId!, type: type); } -String _getThumbnailCacheKey(final String id, final ThumbnailFormat type) { +String getThumbnailCacheKeyForRemoteId( + final String id, { + ThumbnailFormat type = ThumbnailFormat.WEBP, +}) { if (type == ThumbnailFormat.WEBP) { return 'thumbnail-image-$id'; } else { @@ -32,7 +35,8 @@ String getAlbumThumbnailUrl( if (album.thumbnail.value?.remoteId == null) { return ''; } - return _getThumbnailUrl(album.thumbnail.value!.remoteId!, type: type); + return getThumbnailUrlForRemoteId(album.thumbnail.value!.remoteId!, + type: type,); } String getAlbumThumbNailCacheKey( @@ -42,7 +46,10 @@ String getAlbumThumbNailCacheKey( if (album.thumbnail.value?.remoteId == null) { return ''; } - return _getThumbnailCacheKey(album.thumbnail.value!.remoteId!, type); + return getThumbnailCacheKeyForRemoteId( + album.thumbnail.value!.remoteId!, + type: type, + ); } String getImageUrl(final Asset asset) { @@ -53,7 +60,7 @@ String getImageCacheKey(final Asset asset) { return '${asset.id}_fullStage'; } -String _getThumbnailUrl( +String getThumbnailUrlForRemoteId( final String id, { ThumbnailFormat type = ThumbnailFormat.WEBP, }) { diff --git a/mobile/lib/utils/openapi_extensions.dart b/mobile/lib/utils/openapi_extensions.dart index 0fd3f2be5daf91d5efacc107119798fa8beb502f..bd4458e4a5b422e8ddcf36b7e28e49ce2397e890 100644 --- a/mobile/lib/utils/openapi_extensions.dart +++ b/mobile/lib/utils/openapi_extensions.dart @@ -17,14 +17,12 @@ extension WithETag on AssetApi { String? userId, bool? isFavorite, bool? isArchived, - bool? withoutThumbs, }) async { final response = await getAllAssetsWithHttpInfo( ifNoneMatch: eTag, userId: userId, isFavorite: isFavorite, isArchived: isArchived, - withoutThumbs: withoutThumbs, ); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); diff --git a/mobile/lib/utils/selection_handlers.dart b/mobile/lib/utils/selection_handlers.dart new file mode 100644 index 0000000000000000000000000000000000000000..08de7961b67eeaac079f225072d5e547096f487f --- /dev/null +++ b/mobile/lib/utils/selection_handlers.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/shared/models/asset.dart'; +import 'package:immich_mobile/shared/providers/asset.provider.dart'; +import 'package:immich_mobile/shared/services/share.service.dart'; +import 'package:immich_mobile/shared/ui/immich_toast.dart'; +import 'package:immich_mobile/shared/ui/share_dialog.dart'; + +void handleShareAssets( + WidgetRef ref, + BuildContext context, + List selection, +) { + showDialog( + context: context, + builder: (BuildContext buildContext) { + ref + .watch(shareServiceProvider) + .shareAssets(selection.toList()) + .then((_) => Navigator.of(buildContext).pop()); + return const ShareDialog(); + }, + barrierDismissible: false, + ); +} + +Future handleArchiveAssets( + WidgetRef ref, + BuildContext context, + List selection, { + bool shouldArchive = true, + ToastGravity toastGravity = ToastGravity.BOTTOM, +}) async { + if (selection.isNotEmpty) { + await ref + .read(assetProvider.notifier) + .toggleArchive(selection, shouldArchive); + + final assetOrAssets = selection.length > 1 ? 'assets' : 'asset'; + final archiveOrLibrary = shouldArchive ? 'archive' : 'library'; + if (context.mounted) { + ImmichToast.show( + context: context, + msg: 'Moved ${selection.length} $assetOrAssets to $archiveOrLibrary', + gravity: toastGravity, + ); + } + } +} + +Future handleFavoriteAssets( + WidgetRef ref, + BuildContext context, + List selection, { + bool shouldFavorite = true, + ToastGravity toastGravity = ToastGravity.BOTTOM, +}) async { + if (selection.isNotEmpty) { + await ref + .watch(assetProvider.notifier) + .toggleFavorite(selection, shouldFavorite); + + final assetOrAssets = selection.length > 1 ? 'assets' : 'asset'; + final toastMessage = shouldFavorite + ? 'Added ${selection.length} $assetOrAssets to favorites' + : 'Removed ${selection.length} $assetOrAssets from favorites'; + if (context.mounted) { + ImmichToast.show( + context: context, + msg: toastMessage, + gravity: ToastGravity.BOTTOM, + ); + } + } +} diff --git a/mobile/openapi/.openapi-generator/FILES b/mobile/openapi/.openapi-generator/FILES index bf485ef08b552397ca3d47f4077c137d8eaf8b60..e62417987a7bd4ad2443e8efea55e2cad46071ef 100644 --- a/mobile/openapi/.openapi-generator/FILES +++ b/mobile/openapi/.openapi-generator/FILES @@ -15,6 +15,7 @@ doc/AlbumCountResponseDto.md doc/AlbumResponseDto.md doc/AllJobStatusResponseDto.md doc/AssetApi.md +doc/AssetBulkUpdateDto.md doc/AssetBulkUploadCheckDto.md doc/AssetBulkUploadCheckItem.md doc/AssetBulkUploadCheckResponseDto.md @@ -22,10 +23,14 @@ doc/AssetBulkUploadCheckResult.md doc/AssetFileUploadResponseDto.md doc/AssetIdsDto.md doc/AssetIdsResponseDto.md +doc/AssetJobName.md +doc/AssetJobsDto.md doc/AssetResponseDto.md doc/AssetStatsResponseDto.md doc/AssetTypeEnum.md doc/AudioCodec.md +doc/AuditApi.md +doc/AuditDeletesResponseDto.md doc/AuthDeviceResponseDto.md doc/AuthenticationApi.md doc/BulkIdResponseDto.md @@ -47,6 +52,7 @@ doc/DeleteAssetStatus.md doc/DownloadArchiveInfo.md doc/DownloadInfoDto.md doc/DownloadResponseDto.md +doc/EntityType.md doc/ExifResponseDto.md doc/ImportAssetDto.md doc/JobApi.md @@ -78,18 +84,18 @@ doc/SearchAlbumResponseDto.md doc/SearchApi.md doc/SearchAssetDto.md doc/SearchAssetResponseDto.md -doc/SearchConfigResponseDto.md doc/SearchExploreItem.md doc/SearchExploreResponseDto.md doc/SearchFacetCountResponseDto.md doc/SearchFacetResponseDto.md doc/SearchResponseDto.md +doc/ServerFeaturesDto.md doc/ServerInfoApi.md doc/ServerInfoResponseDto.md doc/ServerMediaTypesResponseDto.md doc/ServerPingResponse.md doc/ServerStatsResponseDto.md -doc/ServerVersionReponseDto.md +doc/ServerVersionResponseDto.md doc/SharedLinkApi.md doc/SharedLinkCreateDto.md doc/SharedLinkEditDto.md @@ -101,6 +107,7 @@ doc/SystemConfigApi.md doc/SystemConfigDto.md doc/SystemConfigFFmpegDto.md doc/SystemConfigJobDto.md +doc/SystemConfigMachineLearningDto.md doc/SystemConfigOAuthDto.md doc/SystemConfigPasswordLoginDto.md doc/SystemConfigStorageTemplateDto.md @@ -130,6 +137,7 @@ lib/api.dart lib/api/album_api.dart lib/api/api_key_api.dart lib/api/asset_api.dart +lib/api/audit_api.dart lib/api/authentication_api.dart lib/api/job_api.dart lib/api/o_auth_api.dart @@ -158,6 +166,7 @@ lib/model/api_key_create_dto.dart lib/model/api_key_create_response_dto.dart lib/model/api_key_response_dto.dart lib/model/api_key_update_dto.dart +lib/model/asset_bulk_update_dto.dart lib/model/asset_bulk_upload_check_dto.dart lib/model/asset_bulk_upload_check_item.dart lib/model/asset_bulk_upload_check_response_dto.dart @@ -165,10 +174,13 @@ lib/model/asset_bulk_upload_check_result.dart lib/model/asset_file_upload_response_dto.dart lib/model/asset_ids_dto.dart lib/model/asset_ids_response_dto.dart +lib/model/asset_job_name.dart +lib/model/asset_jobs_dto.dart lib/model/asset_response_dto.dart lib/model/asset_stats_response_dto.dart lib/model/asset_type_enum.dart lib/model/audio_codec.dart +lib/model/audit_deletes_response_dto.dart lib/model/auth_device_response_dto.dart lib/model/bulk_id_response_dto.dart lib/model/bulk_ids_dto.dart @@ -189,6 +201,7 @@ lib/model/delete_asset_status.dart lib/model/download_archive_info.dart lib/model/download_info_dto.dart lib/model/download_response_dto.dart +lib/model/entity_type.dart lib/model/exif_response_dto.dart lib/model/import_asset_dto.dart lib/model/job_command.dart @@ -215,17 +228,17 @@ lib/model/queue_status_dto.dart lib/model/search_album_response_dto.dart lib/model/search_asset_dto.dart lib/model/search_asset_response_dto.dart -lib/model/search_config_response_dto.dart lib/model/search_explore_item.dart lib/model/search_explore_response_dto.dart lib/model/search_facet_count_response_dto.dart lib/model/search_facet_response_dto.dart lib/model/search_response_dto.dart +lib/model/server_features_dto.dart lib/model/server_info_response_dto.dart lib/model/server_media_types_response_dto.dart lib/model/server_ping_response.dart lib/model/server_stats_response_dto.dart -lib/model/server_version_reponse_dto.dart +lib/model/server_version_response_dto.dart lib/model/shared_link_create_dto.dart lib/model/shared_link_edit_dto.dart lib/model/shared_link_response_dto.dart @@ -235,6 +248,7 @@ lib/model/smart_info_response_dto.dart lib/model/system_config_dto.dart lib/model/system_config_f_fmpeg_dto.dart lib/model/system_config_job_dto.dart +lib/model/system_config_machine_learning_dto.dart lib/model/system_config_o_auth_dto.dart lib/model/system_config_password_login_dto.dart lib/model/system_config_storage_template_dto.dart @@ -270,6 +284,7 @@ test/api_key_create_response_dto_test.dart test/api_key_response_dto_test.dart test/api_key_update_dto_test.dart test/asset_api_test.dart +test/asset_bulk_update_dto_test.dart test/asset_bulk_upload_check_dto_test.dart test/asset_bulk_upload_check_item_test.dart test/asset_bulk_upload_check_response_dto_test.dart @@ -277,10 +292,14 @@ test/asset_bulk_upload_check_result_test.dart test/asset_file_upload_response_dto_test.dart test/asset_ids_dto_test.dart test/asset_ids_response_dto_test.dart +test/asset_job_name_test.dart +test/asset_jobs_dto_test.dart test/asset_response_dto_test.dart test/asset_stats_response_dto_test.dart test/asset_type_enum_test.dart test/audio_codec_test.dart +test/audit_api_test.dart +test/audit_deletes_response_dto_test.dart test/auth_device_response_dto_test.dart test/authentication_api_test.dart test/bulk_id_response_dto_test.dart @@ -302,6 +321,7 @@ test/delete_asset_status_test.dart test/download_archive_info_test.dart test/download_info_dto_test.dart test/download_response_dto_test.dart +test/entity_type_test.dart test/exif_response_dto_test.dart test/import_asset_dto_test.dart test/job_api_test.dart @@ -333,18 +353,18 @@ test/search_album_response_dto_test.dart test/search_api_test.dart test/search_asset_dto_test.dart test/search_asset_response_dto_test.dart -test/search_config_response_dto_test.dart test/search_explore_item_test.dart test/search_explore_response_dto_test.dart test/search_facet_count_response_dto_test.dart test/search_facet_response_dto_test.dart test/search_response_dto_test.dart +test/server_features_dto_test.dart test/server_info_api_test.dart test/server_info_response_dto_test.dart test/server_media_types_response_dto_test.dart test/server_ping_response_test.dart test/server_stats_response_dto_test.dart -test/server_version_reponse_dto_test.dart +test/server_version_response_dto_test.dart test/shared_link_api_test.dart test/shared_link_create_dto_test.dart test/shared_link_edit_dto_test.dart @@ -356,6 +376,7 @@ test/system_config_api_test.dart test/system_config_dto_test.dart test/system_config_f_fmpeg_dto_test.dart test/system_config_job_dto_test.dart +test/system_config_machine_learning_dto_test.dart test/system_config_o_auth_dto_test.dart test/system_config_password_login_dto_test.dart test/system_config_storage_template_dto_test.dart diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 9167613b255d463913bc2f4e4a1af0025c9aadaa..148eaa7e400a4d47fc97b560bbd6b60ab423611d 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -3,7 +3,7 @@ Immich API This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project: -- API version: 1.73.0 +- API version: 1.75.2 - Build package: org.openapitools.codegen.languages.DartClientCodegen ## Requirements @@ -107,10 +107,13 @@ Class | Method | HTTP request | Description *AssetApi* | [**getTimeBuckets**](doc//AssetApi.md#gettimebuckets) | **GET** /asset/time-buckets | *AssetApi* | [**getUserAssetsByDeviceId**](doc//AssetApi.md#getuserassetsbydeviceid) | **GET** /asset/{deviceId} | *AssetApi* | [**importFile**](doc//AssetApi.md#importfile) | **POST** /asset/import | +*AssetApi* | [**runAssetJobs**](doc//AssetApi.md#runassetjobs) | **POST** /asset/jobs | *AssetApi* | [**searchAsset**](doc//AssetApi.md#searchasset) | **POST** /asset/search | *AssetApi* | [**serveFile**](doc//AssetApi.md#servefile) | **GET** /asset/file/{id} | *AssetApi* | [**updateAsset**](doc//AssetApi.md#updateasset) | **PUT** /asset/{id} | +*AssetApi* | [**updateAssets**](doc//AssetApi.md#updateassets) | **PUT** /asset | *AssetApi* | [**uploadFile**](doc//AssetApi.md#uploadfile) | **POST** /asset/upload | +*AuditApi* | [**getAuditDeletes**](doc//AuditApi.md#getauditdeletes) | **GET** /audit/deletes | *AuthenticationApi* | [**adminSignUp**](doc//AuthenticationApi.md#adminsignup) | **POST** /auth/admin-sign-up | *AuthenticationApi* | [**changePassword**](doc//AuthenticationApi.md#changepassword) | **POST** /auth/change-password | *AuthenticationApi* | [**getAuthDevices**](doc//AuthenticationApi.md#getauthdevices) | **GET** /auth/devices | @@ -137,8 +140,8 @@ Class | Method | HTTP request | Description *PersonApi* | [**updatePeople**](doc//PersonApi.md#updatepeople) | **PUT** /person | *PersonApi* | [**updatePerson**](doc//PersonApi.md#updateperson) | **PUT** /person/{id} | *SearchApi* | [**getExploreData**](doc//SearchApi.md#getexploredata) | **GET** /search/explore | -*SearchApi* | [**getSearchConfig**](doc//SearchApi.md#getsearchconfig) | **GET** /search/config | *SearchApi* | [**search**](doc//SearchApi.md#search) | **GET** /search | +*ServerInfoApi* | [**getServerFeatures**](doc//ServerInfoApi.md#getserverfeatures) | **GET** /server-info/features | *ServerInfoApi* | [**getServerInfo**](doc//ServerInfoApi.md#getserverinfo) | **GET** /server-info | *ServerInfoApi* | [**getServerVersion**](doc//ServerInfoApi.md#getserverversion) | **GET** /server-info/version | *ServerInfoApi* | [**getStats**](doc//ServerInfoApi.md#getstats) | **GET** /server-info/stats | @@ -187,6 +190,7 @@ Class | Method | HTTP request | Description - [AlbumCountResponseDto](doc//AlbumCountResponseDto.md) - [AlbumResponseDto](doc//AlbumResponseDto.md) - [AllJobStatusResponseDto](doc//AllJobStatusResponseDto.md) + - [AssetBulkUpdateDto](doc//AssetBulkUpdateDto.md) - [AssetBulkUploadCheckDto](doc//AssetBulkUploadCheckDto.md) - [AssetBulkUploadCheckItem](doc//AssetBulkUploadCheckItem.md) - [AssetBulkUploadCheckResponseDto](doc//AssetBulkUploadCheckResponseDto.md) @@ -194,10 +198,13 @@ Class | Method | HTTP request | Description - [AssetFileUploadResponseDto](doc//AssetFileUploadResponseDto.md) - [AssetIdsDto](doc//AssetIdsDto.md) - [AssetIdsResponseDto](doc//AssetIdsResponseDto.md) + - [AssetJobName](doc//AssetJobName.md) + - [AssetJobsDto](doc//AssetJobsDto.md) - [AssetResponseDto](doc//AssetResponseDto.md) - [AssetStatsResponseDto](doc//AssetStatsResponseDto.md) - [AssetTypeEnum](doc//AssetTypeEnum.md) - [AudioCodec](doc//AudioCodec.md) + - [AuditDeletesResponseDto](doc//AuditDeletesResponseDto.md) - [AuthDeviceResponseDto](doc//AuthDeviceResponseDto.md) - [BulkIdResponseDto](doc//BulkIdResponseDto.md) - [BulkIdsDto](doc//BulkIdsDto.md) @@ -218,6 +225,7 @@ Class | Method | HTTP request | Description - [DownloadArchiveInfo](doc//DownloadArchiveInfo.md) - [DownloadInfoDto](doc//DownloadInfoDto.md) - [DownloadResponseDto](doc//DownloadResponseDto.md) + - [EntityType](doc//EntityType.md) - [ExifResponseDto](doc//ExifResponseDto.md) - [ImportAssetDto](doc//ImportAssetDto.md) - [JobCommand](doc//JobCommand.md) @@ -244,17 +252,17 @@ Class | Method | HTTP request | Description - [SearchAlbumResponseDto](doc//SearchAlbumResponseDto.md) - [SearchAssetDto](doc//SearchAssetDto.md) - [SearchAssetResponseDto](doc//SearchAssetResponseDto.md) - - [SearchConfigResponseDto](doc//SearchConfigResponseDto.md) - [SearchExploreItem](doc//SearchExploreItem.md) - [SearchExploreResponseDto](doc//SearchExploreResponseDto.md) - [SearchFacetCountResponseDto](doc//SearchFacetCountResponseDto.md) - [SearchFacetResponseDto](doc//SearchFacetResponseDto.md) - [SearchResponseDto](doc//SearchResponseDto.md) + - [ServerFeaturesDto](doc//ServerFeaturesDto.md) - [ServerInfoResponseDto](doc//ServerInfoResponseDto.md) - [ServerMediaTypesResponseDto](doc//ServerMediaTypesResponseDto.md) - [ServerPingResponse](doc//ServerPingResponse.md) - [ServerStatsResponseDto](doc//ServerStatsResponseDto.md) - - [ServerVersionReponseDto](doc//ServerVersionReponseDto.md) + - [ServerVersionResponseDto](doc//ServerVersionResponseDto.md) - [SharedLinkCreateDto](doc//SharedLinkCreateDto.md) - [SharedLinkEditDto](doc//SharedLinkEditDto.md) - [SharedLinkResponseDto](doc//SharedLinkResponseDto.md) @@ -264,6 +272,7 @@ Class | Method | HTTP request | Description - [SystemConfigDto](doc//SystemConfigDto.md) - [SystemConfigFFmpegDto](doc//SystemConfigFFmpegDto.md) - [SystemConfigJobDto](doc//SystemConfigJobDto.md) + - [SystemConfigMachineLearningDto](doc//SystemConfigMachineLearningDto.md) - [SystemConfigOAuthDto](doc//SystemConfigOAuthDto.md) - [SystemConfigPasswordLoginDto](doc//SystemConfigPasswordLoginDto.md) - [SystemConfigStorageTemplateDto](doc//SystemConfigStorageTemplateDto.md) diff --git a/mobile/openapi/doc/AssetApi.md b/mobile/openapi/doc/AssetApi.md index 5c1c7bb4bfb2c1f80706cbea11443790ffa228ec..4bec9fa2ac7986ed8027eecdd07f77f852377fc0 100644 --- a/mobile/openapi/doc/AssetApi.md +++ b/mobile/openapi/doc/AssetApi.md @@ -29,9 +29,11 @@ Method | HTTP request | Description [**getTimeBuckets**](AssetApi.md#gettimebuckets) | **GET** /asset/time-buckets | [**getUserAssetsByDeviceId**](AssetApi.md#getuserassetsbydeviceid) | **GET** /asset/{deviceId} | [**importFile**](AssetApi.md#importfile) | **POST** /asset/import | +[**runAssetJobs**](AssetApi.md#runassetjobs) | **POST** /asset/jobs | [**searchAsset**](AssetApi.md#searchasset) | **POST** /asset/search | [**serveFile**](AssetApi.md#servefile) | **GET** /asset/file/{id} | [**updateAsset**](AssetApi.md#updateasset) | **PUT** /asset/{id} | +[**updateAssets**](AssetApi.md#updateassets) | **PUT** /asset | [**uploadFile**](AssetApi.md#uploadfile) | **POST** /asset/upload | @@ -378,7 +380,7 @@ Name | Type | Description | Notes [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **getAllAssets** -> List getAllAssets(userId, isFavorite, isArchived, withoutThumbs, skip, ifNoneMatch) +> List getAllAssets(userId, isFavorite, isArchived, skip, updatedAfter, ifNoneMatch) @@ -406,12 +408,12 @@ final api_instance = AssetApi(); final userId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String | final isFavorite = true; // bool | final isArchived = true; // bool | -final withoutThumbs = true; // bool | Include assets without thumbnails final skip = 8.14; // num | +final updatedAfter = 2013-10-20T19:20:30+01:00; // DateTime | final ifNoneMatch = ifNoneMatch_example; // String | ETag of data already cached on the client try { - final result = api_instance.getAllAssets(userId, isFavorite, isArchived, withoutThumbs, skip, ifNoneMatch); + final result = api_instance.getAllAssets(userId, isFavorite, isArchived, skip, updatedAfter, ifNoneMatch); print(result); } catch (e) { print('Exception when calling AssetApi->getAllAssets: $e\n'); @@ -425,8 +427,8 @@ Name | Type | Description | Notes **userId** | **String**| | [optional] **isFavorite** | **bool**| | [optional] **isArchived** | **bool**| | [optional] - **withoutThumbs** | **bool**| Include assets without thumbnails | [optional] **skip** | **num**| | [optional] + **updatedAfter** | **DateTime**| | [optional] **ifNoneMatch** | **String**| ETag of data already cached on the client | [optional] ### Return type @@ -1191,6 +1193,60 @@ Name | Type | Description | Notes [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) +# **runAssetJobs** +> runAssetJobs(assetJobsDto) + + + +### Example +```dart +import 'package:openapi/api.dart'; +// TODO Configure API key authorization: cookie +//defaultApiClient.getAuthentication('cookie').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('cookie').apiKeyPrefix = 'Bearer'; +// TODO Configure API key authorization: api_key +//defaultApiClient.getAuthentication('api_key').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('api_key').apiKeyPrefix = 'Bearer'; +// TODO Configure HTTP Bearer authorization: bearer +// Case 1. Use String Token +//defaultApiClient.getAuthentication('bearer').setAccessToken('YOUR_ACCESS_TOKEN'); +// Case 2. Use Function which generate token. +// String yourTokenGeneratorFunction() { ... } +//defaultApiClient.getAuthentication('bearer').setAccessToken(yourTokenGeneratorFunction); + +final api_instance = AssetApi(); +final assetJobsDto = AssetJobsDto(); // AssetJobsDto | + +try { + api_instance.runAssetJobs(assetJobsDto); +} catch (e) { + print('Exception when calling AssetApi->runAssetJobs: $e\n'); +} +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **assetJobsDto** | [**AssetJobsDto**](AssetJobsDto.md)| | + +### Return type + +void (empty response body) + +### Authorization + +[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer) + +### HTTP request headers + + - **Content-Type**: application/json + - **Accept**: Not defined + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + # **searchAsset** > List searchAsset(searchAssetDto) @@ -1366,6 +1422,60 @@ Name | Type | Description | Notes [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) +# **updateAssets** +> updateAssets(assetBulkUpdateDto) + + + +### Example +```dart +import 'package:openapi/api.dart'; +// TODO Configure API key authorization: cookie +//defaultApiClient.getAuthentication('cookie').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('cookie').apiKeyPrefix = 'Bearer'; +// TODO Configure API key authorization: api_key +//defaultApiClient.getAuthentication('api_key').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('api_key').apiKeyPrefix = 'Bearer'; +// TODO Configure HTTP Bearer authorization: bearer +// Case 1. Use String Token +//defaultApiClient.getAuthentication('bearer').setAccessToken('YOUR_ACCESS_TOKEN'); +// Case 2. Use Function which generate token. +// String yourTokenGeneratorFunction() { ... } +//defaultApiClient.getAuthentication('bearer').setAccessToken(yourTokenGeneratorFunction); + +final api_instance = AssetApi(); +final assetBulkUpdateDto = AssetBulkUpdateDto(); // AssetBulkUpdateDto | + +try { + api_instance.updateAssets(assetBulkUpdateDto); +} catch (e) { + print('Exception when calling AssetApi->updateAssets: $e\n'); +} +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **assetBulkUpdateDto** | [**AssetBulkUpdateDto**](AssetBulkUpdateDto.md)| | + +### Return type + +void (empty response body) + +### Authorization + +[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer) + +### HTTP request headers + + - **Content-Type**: application/json + - **Accept**: Not defined + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + # **uploadFile** > AssetFileUploadResponseDto uploadFile(assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, key, duration, isArchived, isReadOnly, isVisible, livePhotoData, sidecarData) diff --git a/mobile/openapi/doc/AssetBulkUpdateDto.md b/mobile/openapi/doc/AssetBulkUpdateDto.md new file mode 100644 index 0000000000000000000000000000000000000000..b48268464201011b2492698dbf5b53493bead375 --- /dev/null +++ b/mobile/openapi/doc/AssetBulkUpdateDto.md @@ -0,0 +1,17 @@ +# openapi.model.AssetBulkUpdateDto + +## Load the model package +```dart +import 'package:openapi/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**ids** | **List** | | [default to const []] +**isArchived** | **bool** | | [optional] +**isFavorite** | **bool** | | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/mobile/openapi/doc/SearchConfigResponseDto.md b/mobile/openapi/doc/AssetJobName.md similarity index 83% rename from mobile/openapi/doc/SearchConfigResponseDto.md rename to mobile/openapi/doc/AssetJobName.md index 25020ea756675228b281e11b45d19dd908c9ac37..d9612705ac4bb1cc255c18854dfe6aa832d41674 100644 --- a/mobile/openapi/doc/SearchConfigResponseDto.md +++ b/mobile/openapi/doc/AssetJobName.md @@ -1,4 +1,4 @@ -# openapi.model.SearchConfigResponseDto +# openapi.model.AssetJobName ## Load the model package ```dart @@ -8,7 +8,6 @@ import 'package:openapi/api.dart'; ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**enabled** | **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) diff --git a/mobile/openapi/doc/AssetJobsDto.md b/mobile/openapi/doc/AssetJobsDto.md new file mode 100644 index 0000000000000000000000000000000000000000..e1d04388a1b62a4456e527b96c329651f22084f5 --- /dev/null +++ b/mobile/openapi/doc/AssetJobsDto.md @@ -0,0 +1,16 @@ +# openapi.model.AssetJobsDto + +## Load the model package +```dart +import 'package:openapi/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**assetIds** | **List** | | [default to const []] +**name** | [**AssetJobName**](AssetJobName.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) + + diff --git a/mobile/openapi/doc/AuditApi.md b/mobile/openapi/doc/AuditApi.md new file mode 100644 index 0000000000000000000000000000000000000000..63a1c97a3bb5088631cca0c12aaa2a58e65b6260 --- /dev/null +++ b/mobile/openapi/doc/AuditApi.md @@ -0,0 +1,73 @@ +# openapi.api.AuditApi + +## Load the API package +```dart +import 'package:openapi/api.dart'; +``` + +All URIs are relative to */api* + +Method | HTTP request | Description +------------- | ------------- | ------------- +[**getAuditDeletes**](AuditApi.md#getauditdeletes) | **GET** /audit/deletes | + + +# **getAuditDeletes** +> AuditDeletesResponseDto getAuditDeletes(entityType, after, userId) + + + +### Example +```dart +import 'package:openapi/api.dart'; +// TODO Configure API key authorization: cookie +//defaultApiClient.getAuthentication('cookie').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('cookie').apiKeyPrefix = 'Bearer'; +// TODO Configure API key authorization: api_key +//defaultApiClient.getAuthentication('api_key').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('api_key').apiKeyPrefix = 'Bearer'; +// TODO Configure HTTP Bearer authorization: bearer +// Case 1. Use String Token +//defaultApiClient.getAuthentication('bearer').setAccessToken('YOUR_ACCESS_TOKEN'); +// Case 2. Use Function which generate token. +// String yourTokenGeneratorFunction() { ... } +//defaultApiClient.getAuthentication('bearer').setAccessToken(yourTokenGeneratorFunction); + +final api_instance = AuditApi(); +final entityType = ; // EntityType | +final after = 2013-10-20T19:20:30+01:00; // DateTime | +final userId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String | + +try { + final result = api_instance.getAuditDeletes(entityType, after, userId); + print(result); +} catch (e) { + print('Exception when calling AuditApi->getAuditDeletes: $e\n'); +} +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **entityType** | [**EntityType**](.md)| | + **after** | **DateTime**| | + **userId** | **String**| | [optional] + +### Return type + +[**AuditDeletesResponseDto**](AuditDeletesResponseDto.md) + +### Authorization + +[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer) + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + diff --git a/mobile/openapi/doc/AuditDeletesResponseDto.md b/mobile/openapi/doc/AuditDeletesResponseDto.md new file mode 100644 index 0000000000000000000000000000000000000000..c7c9594f1bb291121d9aa962122ab1d20d6575fc --- /dev/null +++ b/mobile/openapi/doc/AuditDeletesResponseDto.md @@ -0,0 +1,16 @@ +# openapi.model.AuditDeletesResponseDto + +## Load the model package +```dart +import 'package:openapi/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**ids** | **List** | | [default to const []] +**needsFullSync** | **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) + + diff --git a/mobile/openapi/doc/EntityType.md b/mobile/openapi/doc/EntityType.md new file mode 100644 index 0000000000000000000000000000000000000000..a01b3cf5e3d7ee0cdaf5fcd0ce3e510c12661bc8 --- /dev/null +++ b/mobile/openapi/doc/EntityType.md @@ -0,0 +1,14 @@ +# openapi.model.EntityType + +## 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) + + diff --git a/mobile/openapi/doc/PeopleUpdateItem.md b/mobile/openapi/doc/PeopleUpdateItem.md index 43a1b0225947e94ac08387dbbdf83ff0fa47c05a..25152c4e4bad112cea6c264953a058306e4db359 100644 --- a/mobile/openapi/doc/PeopleUpdateItem.md +++ b/mobile/openapi/doc/PeopleUpdateItem.md @@ -8,6 +8,7 @@ import 'package:openapi/api.dart'; ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- +**birthDate** | [**DateTime**](DateTime.md) | Person date of birth. | [optional] **featureFaceAssetId** | **String** | Asset is used to get the feature face thumbnail. | [optional] **id** | **String** | Person id. | **isHidden** | **bool** | Person visibility | [optional] diff --git a/mobile/openapi/doc/PersonResponseDto.md b/mobile/openapi/doc/PersonResponseDto.md index e43d67a6114227349c2df10a002633f5ad09d6ab..c2acbacd1b5ab260cd688438b654c156088439ff 100644 --- a/mobile/openapi/doc/PersonResponseDto.md +++ b/mobile/openapi/doc/PersonResponseDto.md @@ -8,6 +8,7 @@ import 'package:openapi/api.dart'; ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- +**birthDate** | [**DateTime**](DateTime.md) | | **id** | **String** | | **isHidden** | **bool** | | **name** | **String** | | diff --git a/mobile/openapi/doc/PersonUpdateDto.md b/mobile/openapi/doc/PersonUpdateDto.md index 935b4348c389ed85759c2346b84dd2aecab83d27..a4df668785624bebb9b4a6ac81aa5ddedba18f15 100644 --- a/mobile/openapi/doc/PersonUpdateDto.md +++ b/mobile/openapi/doc/PersonUpdateDto.md @@ -8,6 +8,7 @@ import 'package:openapi/api.dart'; ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- +**birthDate** | [**DateTime**](DateTime.md) | Person date of birth. | [optional] **featureFaceAssetId** | **String** | Asset is used to get the feature face thumbnail. | [optional] **isHidden** | **bool** | Person visibility | [optional] **name** | **String** | Person name. | [optional] diff --git a/mobile/openapi/doc/SearchApi.md b/mobile/openapi/doc/SearchApi.md index 74840a73cda5b4e0c245fd509706e8cbd5a5f662..5cc36956a0ff70ab0cdc892d7b815b8cb8910ca2 100644 --- a/mobile/openapi/doc/SearchApi.md +++ b/mobile/openapi/doc/SearchApi.md @@ -10,7 +10,6 @@ All URIs are relative to */api* Method | HTTP request | Description ------------- | ------------- | ------------- [**getExploreData**](SearchApi.md#getexploredata) | **GET** /search/explore | -[**getSearchConfig**](SearchApi.md#getsearchconfig) | **GET** /search/config | [**search**](SearchApi.md#search) | **GET** /search | @@ -65,57 +64,6 @@ This endpoint does not need any parameter. [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) -# **getSearchConfig** -> SearchConfigResponseDto getSearchConfig() - - - -### Example -```dart -import 'package:openapi/api.dart'; -// TODO Configure API key authorization: cookie -//defaultApiClient.getAuthentication('cookie').apiKey = 'YOUR_API_KEY'; -// uncomment below to setup prefix (e.g. Bearer) for API key, if needed -//defaultApiClient.getAuthentication('cookie').apiKeyPrefix = 'Bearer'; -// TODO Configure API key authorization: api_key -//defaultApiClient.getAuthentication('api_key').apiKey = 'YOUR_API_KEY'; -// uncomment below to setup prefix (e.g. Bearer) for API key, if needed -//defaultApiClient.getAuthentication('api_key').apiKeyPrefix = 'Bearer'; -// TODO Configure HTTP Bearer authorization: bearer -// Case 1. Use String Token -//defaultApiClient.getAuthentication('bearer').setAccessToken('YOUR_ACCESS_TOKEN'); -// Case 2. Use Function which generate token. -// String yourTokenGeneratorFunction() { ... } -//defaultApiClient.getAuthentication('bearer').setAccessToken(yourTokenGeneratorFunction); - -final api_instance = SearchApi(); - -try { - final result = api_instance.getSearchConfig(); - print(result); -} catch (e) { - print('Exception when calling SearchApi->getSearchConfig: $e\n'); -} -``` - -### Parameters -This endpoint does not need any parameter. - -### Return type - -[**SearchConfigResponseDto**](SearchConfigResponseDto.md) - -### Authorization - -[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer) - -### HTTP request headers - - - **Content-Type**: Not defined - - **Accept**: application/json - -[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) - # **search** > SearchResponseDto search(q, query, clip, type, isFavorite, isArchived, exifInfoPeriodCity, exifInfoPeriodState, exifInfoPeriodCountry, exifInfoPeriodMake, exifInfoPeriodModel, exifInfoPeriodProjectionType, smartInfoPeriodObjects, smartInfoPeriodTags, recent, motion) diff --git a/mobile/openapi/doc/ServerFeaturesDto.md b/mobile/openapi/doc/ServerFeaturesDto.md new file mode 100644 index 0000000000000000000000000000000000000000..168479b9908cb3af0bdc0cb7005bed5a264b1016 --- /dev/null +++ b/mobile/openapi/doc/ServerFeaturesDto.md @@ -0,0 +1,23 @@ +# openapi.model.ServerFeaturesDto + +## Load the model package +```dart +import 'package:openapi/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**clipEncode** | **bool** | | +**configFile** | **bool** | | +**facialRecognition** | **bool** | | +**oauth** | **bool** | | +**oauthAutoLaunch** | **bool** | | +**passwordLogin** | **bool** | | +**search** | **bool** | | +**sidecar** | **bool** | | +**tagImage** | **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) + + diff --git a/mobile/openapi/doc/ServerInfoApi.md b/mobile/openapi/doc/ServerInfoApi.md index 0666c7d414b8d744e0adf61e3eb5235630239875..62758afbe818a36d325e208eba391e79c9466965 100644 --- a/mobile/openapi/doc/ServerInfoApi.md +++ b/mobile/openapi/doc/ServerInfoApi.md @@ -9,6 +9,7 @@ All URIs are relative to */api* Method | HTTP request | Description ------------- | ------------- | ------------- +[**getServerFeatures**](ServerInfoApi.md#getserverfeatures) | **GET** /server-info/features | [**getServerInfo**](ServerInfoApi.md#getserverinfo) | **GET** /server-info | [**getServerVersion**](ServerInfoApi.md#getserverversion) | **GET** /server-info/version | [**getStats**](ServerInfoApi.md#getstats) | **GET** /server-info/stats | @@ -16,6 +17,43 @@ Method | HTTP request | Description [**pingServer**](ServerInfoApi.md#pingserver) | **GET** /server-info/ping | +# **getServerFeatures** +> ServerFeaturesDto getServerFeatures() + + + +### Example +```dart +import 'package:openapi/api.dart'; + +final api_instance = ServerInfoApi(); + +try { + final result = api_instance.getServerFeatures(); + print(result); +} catch (e) { + print('Exception when calling ServerInfoApi->getServerFeatures: $e\n'); +} +``` + +### Parameters +This endpoint does not need any parameter. + +### Return type + +[**ServerFeaturesDto**](ServerFeaturesDto.md) + +### Authorization + +No authorization required + +### 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) + # **getServerInfo** > ServerInfoResponseDto getServerInfo() @@ -68,7 +106,7 @@ This endpoint does not need any parameter. [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **getServerVersion** -> ServerVersionReponseDto getServerVersion() +> ServerVersionResponseDto getServerVersion() @@ -91,7 +129,7 @@ This endpoint does not need any parameter. ### Return type -[**ServerVersionReponseDto**](ServerVersionReponseDto.md) +[**ServerVersionResponseDto**](ServerVersionResponseDto.md) ### Authorization diff --git a/mobile/openapi/doc/ServerVersionReponseDto.md b/mobile/openapi/doc/ServerVersionResponseDto.md similarity index 91% rename from mobile/openapi/doc/ServerVersionReponseDto.md rename to mobile/openapi/doc/ServerVersionResponseDto.md index 68dfa972cc2b03831e9d8f56aaa3251cd83a0a59..b48291f12f531e13d59024c6d86c0d25e4cbb7ba 100644 --- a/mobile/openapi/doc/ServerVersionReponseDto.md +++ b/mobile/openapi/doc/ServerVersionResponseDto.md @@ -1,4 +1,4 @@ -# openapi.model.ServerVersionReponseDto +# openapi.model.ServerVersionResponseDto ## Load the model package ```dart diff --git a/mobile/openapi/doc/SystemConfigDto.md b/mobile/openapi/doc/SystemConfigDto.md index ebccc31ebe0e34eb7735d394e9e9edabc6c3012c..6fc9808528d8d90648e7729f451c5d2f8cc66c71 100644 --- a/mobile/openapi/doc/SystemConfigDto.md +++ b/mobile/openapi/doc/SystemConfigDto.md @@ -10,6 +10,7 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **ffmpeg** | [**SystemConfigFFmpegDto**](SystemConfigFFmpegDto.md) | | **job** | [**SystemConfigJobDto**](SystemConfigJobDto.md) | | +**machineLearning** | [**SystemConfigMachineLearningDto**](SystemConfigMachineLearningDto.md) | | **oauth** | [**SystemConfigOAuthDto**](SystemConfigOAuthDto.md) | | **passwordLogin** | [**SystemConfigPasswordLoginDto**](SystemConfigPasswordLoginDto.md) | | **storageTemplate** | [**SystemConfigStorageTemplateDto**](SystemConfigStorageTemplateDto.md) | | diff --git a/mobile/openapi/doc/SystemConfigMachineLearningDto.md b/mobile/openapi/doc/SystemConfigMachineLearningDto.md new file mode 100644 index 0000000000000000000000000000000000000000..9b2c596e2e4e4ed6c2a43a3ba6f46002a344a8ca --- /dev/null +++ b/mobile/openapi/doc/SystemConfigMachineLearningDto.md @@ -0,0 +1,19 @@ +# openapi.model.SystemConfigMachineLearningDto + +## Load the model package +```dart +import 'package:openapi/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**clipEncodeEnabled** | **bool** | | +**enabled** | **bool** | | +**facialRecognitionEnabled** | **bool** | | +**tagImageEnabled** | **bool** | | +**url** | **String** | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index a38d8784e34111d5cc8a58a44c15517c2187811d..d1b51ea8e687b6ea4bba27567d797fa457f7a6f8 100644 --- a/mobile/openapi/lib/api.dart +++ b/mobile/openapi/lib/api.dart @@ -31,6 +31,7 @@ part 'auth/http_bearer_auth.dart'; part 'api/api_key_api.dart'; part 'api/album_api.dart'; part 'api/asset_api.dart'; +part 'api/audit_api.dart'; part 'api/authentication_api.dart'; part 'api/job_api.dart'; part 'api/o_auth_api.dart'; @@ -52,6 +53,7 @@ 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_bulk_update_dto.dart'; part 'model/asset_bulk_upload_check_dto.dart'; part 'model/asset_bulk_upload_check_item.dart'; part 'model/asset_bulk_upload_check_response_dto.dart'; @@ -59,10 +61,13 @@ part 'model/asset_bulk_upload_check_result.dart'; part 'model/asset_file_upload_response_dto.dart'; part 'model/asset_ids_dto.dart'; part 'model/asset_ids_response_dto.dart'; +part 'model/asset_job_name.dart'; +part 'model/asset_jobs_dto.dart'; part 'model/asset_response_dto.dart'; part 'model/asset_stats_response_dto.dart'; part 'model/asset_type_enum.dart'; part 'model/audio_codec.dart'; +part 'model/audit_deletes_response_dto.dart'; part 'model/auth_device_response_dto.dart'; part 'model/bulk_id_response_dto.dart'; part 'model/bulk_ids_dto.dart'; @@ -83,6 +88,7 @@ part 'model/delete_asset_status.dart'; part 'model/download_archive_info.dart'; part 'model/download_info_dto.dart'; part 'model/download_response_dto.dart'; +part 'model/entity_type.dart'; part 'model/exif_response_dto.dart'; part 'model/import_asset_dto.dart'; part 'model/job_command.dart'; @@ -109,17 +115,17 @@ part 'model/queue_status_dto.dart'; part 'model/search_album_response_dto.dart'; part 'model/search_asset_dto.dart'; part 'model/search_asset_response_dto.dart'; -part 'model/search_config_response_dto.dart'; part 'model/search_explore_item.dart'; part 'model/search_explore_response_dto.dart'; part 'model/search_facet_count_response_dto.dart'; part 'model/search_facet_response_dto.dart'; part 'model/search_response_dto.dart'; +part 'model/server_features_dto.dart'; part 'model/server_info_response_dto.dart'; part 'model/server_media_types_response_dto.dart'; part 'model/server_ping_response.dart'; part 'model/server_stats_response_dto.dart'; -part 'model/server_version_reponse_dto.dart'; +part 'model/server_version_response_dto.dart'; part 'model/shared_link_create_dto.dart'; part 'model/shared_link_edit_dto.dart'; part 'model/shared_link_response_dto.dart'; @@ -129,6 +135,7 @@ part 'model/smart_info_response_dto.dart'; part 'model/system_config_dto.dart'; part 'model/system_config_f_fmpeg_dto.dart'; part 'model/system_config_job_dto.dart'; +part 'model/system_config_machine_learning_dto.dart'; part 'model/system_config_o_auth_dto.dart'; part 'model/system_config_password_login_dto.dart'; part 'model/system_config_storage_template_dto.dart'; diff --git a/mobile/openapi/lib/api/asset_api.dart b/mobile/openapi/lib/api/asset_api.dart index ba9f1d5a1f598d6c69e82ec83dea422a23b8142d..9067f8dd07084f46ef257138a2bc18c6f44d8981 100644 --- a/mobile/openapi/lib/api/asset_api.dart +++ b/mobile/openapi/lib/api/asset_api.dart @@ -353,14 +353,13 @@ class AssetApi { /// /// * [bool] isArchived: /// - /// * [bool] withoutThumbs: - /// Include assets without thumbnails - /// /// * [num] skip: /// + /// * [DateTime] updatedAfter: + /// /// * [String] ifNoneMatch: /// ETag of data already cached on the client - Future getAllAssetsWithHttpInfo({ String? userId, bool? isFavorite, bool? isArchived, bool? withoutThumbs, num? skip, String? ifNoneMatch, }) async { + Future getAllAssetsWithHttpInfo({ String? userId, bool? isFavorite, bool? isArchived, num? skip, DateTime? updatedAfter, String? ifNoneMatch, }) async { // ignore: prefer_const_declarations final path = r'/asset'; @@ -380,12 +379,12 @@ class AssetApi { if (isArchived != null) { queryParams.addAll(_queryParams('', 'isArchived', isArchived)); } - if (withoutThumbs != null) { - queryParams.addAll(_queryParams('', 'withoutThumbs', withoutThumbs)); - } if (skip != null) { queryParams.addAll(_queryParams('', 'skip', skip)); } + if (updatedAfter != null) { + queryParams.addAll(_queryParams('', 'updatedAfter', updatedAfter)); + } if (ifNoneMatch != null) { headerParams[r'if-none-match'] = parameterToString(ifNoneMatch); @@ -415,15 +414,14 @@ class AssetApi { /// /// * [bool] isArchived: /// - /// * [bool] withoutThumbs: - /// Include assets without thumbnails - /// /// * [num] skip: /// + /// * [DateTime] updatedAfter: + /// /// * [String] ifNoneMatch: /// ETag of data already cached on the client - Future?> getAllAssets({ String? userId, bool? isFavorite, bool? isArchived, bool? withoutThumbs, num? skip, String? ifNoneMatch, }) async { - final response = await getAllAssetsWithHttpInfo( userId: userId, isFavorite: isFavorite, isArchived: isArchived, withoutThumbs: withoutThumbs, skip: skip, ifNoneMatch: ifNoneMatch, ); + Future?> getAllAssets({ String? userId, bool? isFavorite, bool? isArchived, num? skip, DateTime? updatedAfter, String? ifNoneMatch, }) async { + final response = await getAllAssetsWithHttpInfo( userId: userId, isFavorite: isFavorite, isArchived: isArchived, skip: skip, updatedAfter: updatedAfter, ifNoneMatch: ifNoneMatch, ); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -1227,6 +1225,45 @@ class AssetApi { return null; } + /// Performs an HTTP 'POST /asset/jobs' operation and returns the [Response]. + /// Parameters: + /// + /// * [AssetJobsDto] assetJobsDto (required): + Future runAssetJobsWithHttpInfo(AssetJobsDto assetJobsDto,) async { + // ignore: prefer_const_declarations + final path = r'/asset/jobs'; + + // ignore: prefer_final_locals + Object? postBody = assetJobsDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + path, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Parameters: + /// + /// * [AssetJobsDto] assetJobsDto (required): + Future runAssetJobs(AssetJobsDto assetJobsDto,) async { + final response = await runAssetJobsWithHttpInfo(assetJobsDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + /// Performs an HTTP 'POST /asset/search' operation and returns the [Response]. /// Parameters: /// @@ -1404,6 +1441,45 @@ class AssetApi { return null; } + /// Performs an HTTP 'PUT /asset' operation and returns the [Response]. + /// Parameters: + /// + /// * [AssetBulkUpdateDto] assetBulkUpdateDto (required): + Future updateAssetsWithHttpInfo(AssetBulkUpdateDto assetBulkUpdateDto,) async { + // ignore: prefer_const_declarations + final path = r'/asset'; + + // ignore: prefer_final_locals + Object? postBody = assetBulkUpdateDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + path, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Parameters: + /// + /// * [AssetBulkUpdateDto] assetBulkUpdateDto (required): + Future updateAssets(AssetBulkUpdateDto assetBulkUpdateDto,) async { + final response = await updateAssetsWithHttpInfo(assetBulkUpdateDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + /// Performs an HTTP 'POST /asset/upload' operation and returns the [Response]. /// Parameters: /// diff --git a/mobile/openapi/lib/api/audit_api.dart b/mobile/openapi/lib/api/audit_api.dart new file mode 100644 index 0000000000000000000000000000000000000000..4eabd17c9c0fd681e82a5132a10f47fe027f60fc --- /dev/null +++ b/mobile/openapi/lib/api/audit_api.dart @@ -0,0 +1,79 @@ +// +// 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 AuditApi { + AuditApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Performs an HTTP 'GET /audit/deletes' operation and returns the [Response]. + /// Parameters: + /// + /// * [EntityType] entityType (required): + /// + /// * [DateTime] after (required): + /// + /// * [String] userId: + Future getAuditDeletesWithHttpInfo(EntityType entityType, DateTime after, { String? userId, }) async { + // ignore: prefer_const_declarations + final path = r'/audit/deletes'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + queryParams.addAll(_queryParams('', 'entityType', entityType)); + if (userId != null) { + queryParams.addAll(_queryParams('', 'userId', userId)); + } + queryParams.addAll(_queryParams('', 'after', after)); + + const contentTypes = []; + + + return apiClient.invokeAPI( + path, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Parameters: + /// + /// * [EntityType] entityType (required): + /// + /// * [DateTime] after (required): + /// + /// * [String] userId: + Future getAuditDeletes(EntityType entityType, DateTime after, { String? userId, }) async { + final response = await getAuditDeletesWithHttpInfo(entityType, after, userId: userId, ); + 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), 'AuditDeletesResponseDto',) as AuditDeletesResponseDto; + + } + return null; + } +} diff --git a/mobile/openapi/lib/api/search_api.dart b/mobile/openapi/lib/api/search_api.dart index 8178395e077478f806a303487d57c8c2d14e8ba2..9393b5c61dcf68eb4231eb7529faa1e0c3504fd7 100644 --- a/mobile/openapi/lib/api/search_api.dart +++ b/mobile/openapi/lib/api/search_api.dart @@ -60,47 +60,6 @@ class SearchApi { return null; } - /// Performs an HTTP 'GET /search/config' operation and returns the [Response]. - Future getSearchConfigWithHttpInfo() async { - // ignore: prefer_const_declarations - final path = r'/search/config'; - - // ignore: prefer_final_locals - Object? postBody; - - final queryParams = []; - final headerParams = {}; - final formParams = {}; - - const contentTypes = []; - - - return apiClient.invokeAPI( - path, - 'GET', - queryParams, - postBody, - headerParams, - formParams, - contentTypes.isEmpty ? null : contentTypes.first, - ); - } - - Future getSearchConfig() async { - final response = await getSearchConfigWithHttpInfo(); - 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), 'SearchConfigResponseDto',) as SearchConfigResponseDto; - - } - return null; - } - /// Performs an HTTP 'GET /search' operation and returns the [Response]. /// Parameters: /// diff --git a/mobile/openapi/lib/api/server_info_api.dart b/mobile/openapi/lib/api/server_info_api.dart index fb34e09ce866e5939102add22182e894f575b904..3b7899cc264fde989628cfd36cd903aa35d92bfc 100644 --- a/mobile/openapi/lib/api/server_info_api.dart +++ b/mobile/openapi/lib/api/server_info_api.dart @@ -16,6 +16,47 @@ class ServerInfoApi { final ApiClient apiClient; + /// Performs an HTTP 'GET /server-info/features' operation and returns the [Response]. + Future getServerFeaturesWithHttpInfo() async { + // ignore: prefer_const_declarations + final path = r'/server-info/features'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + path, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + Future getServerFeatures() async { + final response = await getServerFeaturesWithHttpInfo(); + 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), 'ServerFeaturesDto',) as ServerFeaturesDto; + + } + return null; + } + /// Performs an HTTP 'GET /server-info' operation and returns the [Response]. Future getServerInfoWithHttpInfo() async { // ignore: prefer_const_declarations @@ -83,7 +124,7 @@ class ServerInfoApi { ); } - Future getServerVersion() async { + Future getServerVersion() async { final response = await getServerVersionWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); @@ -92,7 +133,7 @@ class ServerInfoApi { // 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), 'ServerVersionReponseDto',) as ServerVersionReponseDto; + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'ServerVersionResponseDto',) as ServerVersionResponseDto; } return null; diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index eb76c12c58dfc6c6bd2c4ce621db67f0255838a7..3e819692032a15a64963f1015d1c2968d1b67731 100644 --- a/mobile/openapi/lib/api_client.dart +++ b/mobile/openapi/lib/api_client.dart @@ -199,6 +199,8 @@ class ApiClient { return AlbumResponseDto.fromJson(value); case 'AllJobStatusResponseDto': return AllJobStatusResponseDto.fromJson(value); + case 'AssetBulkUpdateDto': + return AssetBulkUpdateDto.fromJson(value); case 'AssetBulkUploadCheckDto': return AssetBulkUploadCheckDto.fromJson(value); case 'AssetBulkUploadCheckItem': @@ -213,6 +215,10 @@ class ApiClient { return AssetIdsDto.fromJson(value); case 'AssetIdsResponseDto': return AssetIdsResponseDto.fromJson(value); + case 'AssetJobName': + return AssetJobNameTypeTransformer().decode(value); + case 'AssetJobsDto': + return AssetJobsDto.fromJson(value); case 'AssetResponseDto': return AssetResponseDto.fromJson(value); case 'AssetStatsResponseDto': @@ -221,6 +227,8 @@ class ApiClient { return AssetTypeEnumTypeTransformer().decode(value); case 'AudioCodec': return AudioCodecTypeTransformer().decode(value); + case 'AuditDeletesResponseDto': + return AuditDeletesResponseDto.fromJson(value); case 'AuthDeviceResponseDto': return AuthDeviceResponseDto.fromJson(value); case 'BulkIdResponseDto': @@ -261,6 +269,8 @@ class ApiClient { return DownloadInfoDto.fromJson(value); case 'DownloadResponseDto': return DownloadResponseDto.fromJson(value); + case 'EntityType': + return EntityTypeTypeTransformer().decode(value); case 'ExifResponseDto': return ExifResponseDto.fromJson(value); case 'ImportAssetDto': @@ -313,8 +323,6 @@ class ApiClient { return SearchAssetDto.fromJson(value); case 'SearchAssetResponseDto': return SearchAssetResponseDto.fromJson(value); - case 'SearchConfigResponseDto': - return SearchConfigResponseDto.fromJson(value); case 'SearchExploreItem': return SearchExploreItem.fromJson(value); case 'SearchExploreResponseDto': @@ -325,6 +333,8 @@ class ApiClient { return SearchFacetResponseDto.fromJson(value); case 'SearchResponseDto': return SearchResponseDto.fromJson(value); + case 'ServerFeaturesDto': + return ServerFeaturesDto.fromJson(value); case 'ServerInfoResponseDto': return ServerInfoResponseDto.fromJson(value); case 'ServerMediaTypesResponseDto': @@ -333,8 +343,8 @@ class ApiClient { return ServerPingResponse.fromJson(value); case 'ServerStatsResponseDto': return ServerStatsResponseDto.fromJson(value); - case 'ServerVersionReponseDto': - return ServerVersionReponseDto.fromJson(value); + case 'ServerVersionResponseDto': + return ServerVersionResponseDto.fromJson(value); case 'SharedLinkCreateDto': return SharedLinkCreateDto.fromJson(value); case 'SharedLinkEditDto': @@ -353,6 +363,8 @@ class ApiClient { return SystemConfigFFmpegDto.fromJson(value); case 'SystemConfigJobDto': return SystemConfigJobDto.fromJson(value); + case 'SystemConfigMachineLearningDto': + return SystemConfigMachineLearningDto.fromJson(value); case 'SystemConfigOAuthDto': return SystemConfigOAuthDto.fromJson(value); case 'SystemConfigPasswordLoginDto': diff --git a/mobile/openapi/lib/api_helper.dart b/mobile/openapi/lib/api_helper.dart index bc1dfd7bc73342592a8db6001355e4b13f80dbba..a9df71cfd69e073fe0feb08b753c4b11da0fcfb6 100644 --- a/mobile/openapi/lib/api_helper.dart +++ b/mobile/openapi/lib/api_helper.dart @@ -55,6 +55,9 @@ String parameterToString(dynamic value) { if (value is DateTime) { return value.toUtc().toIso8601String(); } + if (value is AssetJobName) { + return AssetJobNameTypeTransformer().encode(value).toString(); + } if (value is AssetTypeEnum) { return AssetTypeEnumTypeTransformer().encode(value).toString(); } @@ -64,6 +67,9 @@ String parameterToString(dynamic value) { if (value is DeleteAssetStatus) { return DeleteAssetStatusTypeTransformer().encode(value).toString(); } + if (value is EntityType) { + return EntityTypeTypeTransformer().encode(value).toString(); + } if (value is JobCommand) { return JobCommandTypeTransformer().encode(value).toString(); } diff --git a/mobile/openapi/lib/model/asset_bulk_update_dto.dart b/mobile/openapi/lib/model/asset_bulk_update_dto.dart new file mode 100644 index 0000000000000000000000000000000000000000..7eb0e31afce8a999e467e19213606ce938026212 --- /dev/null +++ b/mobile/openapi/lib/model/asset_bulk_update_dto.dart @@ -0,0 +1,134 @@ +// +// 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 AssetBulkUpdateDto { + /// Returns a new [AssetBulkUpdateDto] instance. + AssetBulkUpdateDto({ + this.ids = const [], + this.isArchived, + this.isFavorite, + }); + + List ids; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isArchived; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isFavorite; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetBulkUpdateDto && + other.ids == ids && + other.isArchived == isArchived && + other.isFavorite == isFavorite; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (ids.hashCode) + + (isArchived == null ? 0 : isArchived!.hashCode) + + (isFavorite == null ? 0 : isFavorite!.hashCode); + + @override + String toString() => 'AssetBulkUpdateDto[ids=$ids, isArchived=$isArchived, isFavorite=$isFavorite]'; + + Map toJson() { + final json = {}; + json[r'ids'] = this.ids; + if (this.isArchived != null) { + json[r'isArchived'] = this.isArchived; + } else { + // json[r'isArchived'] = null; + } + if (this.isFavorite != null) { + json[r'isFavorite'] = this.isFavorite; + } else { + // json[r'isFavorite'] = null; + } + return json; + } + + /// Returns a new [AssetBulkUpdateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetBulkUpdateDto? fromJson(dynamic value) { + if (value is Map) { + final json = value.cast(); + + return AssetBulkUpdateDto( + ids: json[r'ids'] is List + ? (json[r'ids'] as List).cast() + : const [], + isArchived: mapValueOfType(json, r'isArchived'), + isFavorite: mapValueOfType(json, r'isFavorite'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetBulkUpdateDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetBulkUpdateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetBulkUpdateDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetBulkUpdateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'ids', + }; +} + diff --git a/mobile/openapi/lib/model/asset_job_name.dart b/mobile/openapi/lib/model/asset_job_name.dart new file mode 100644 index 0000000000000000000000000000000000000000..61334df089336300bc63cd0b6aabe322b11b7b1a --- /dev/null +++ b/mobile/openapi/lib/model/asset_job_name.dart @@ -0,0 +1,88 @@ +// +// 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 AssetJobName { + /// Instantiate a new enum with the provided [value]. + const AssetJobName._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const regenerateThumbnail = AssetJobName._(r'regenerate-thumbnail'); + static const refreshMetadata = AssetJobName._(r'refresh-metadata'); + static const transcodeVideo = AssetJobName._(r'transcode-video'); + + /// List of all possible values in this [enum][AssetJobName]. + static const values = [ + regenerateThumbnail, + refreshMetadata, + transcodeVideo, + ]; + + static AssetJobName? fromJson(dynamic value) => AssetJobNameTypeTransformer().decode(value); + + static List? listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetJobName.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [AssetJobName] to String, +/// and [decode] dynamic data back to [AssetJobName]. +class AssetJobNameTypeTransformer { + factory AssetJobNameTypeTransformer() => _instance ??= const AssetJobNameTypeTransformer._(); + + const AssetJobNameTypeTransformer._(); + + String encode(AssetJobName data) => data.value; + + /// Decodes a [dynamic value][data] to a AssetJobName. + /// + /// 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. + AssetJobName? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'regenerate-thumbnail': return AssetJobName.regenerateThumbnail; + case r'refresh-metadata': return AssetJobName.refreshMetadata; + case r'transcode-video': return AssetJobName.transcodeVideo; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [AssetJobNameTypeTransformer] instance. + static AssetJobNameTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/asset_jobs_dto.dart b/mobile/openapi/lib/model/asset_jobs_dto.dart new file mode 100644 index 0000000000000000000000000000000000000000..3c88438b979120e65ea574eef6744714cef256d9 --- /dev/null +++ b/mobile/openapi/lib/model/asset_jobs_dto.dart @@ -0,0 +1,108 @@ +// +// 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 AssetJobsDto { + /// Returns a new [AssetJobsDto] instance. + AssetJobsDto({ + this.assetIds = const [], + required this.name, + }); + + List assetIds; + + AssetJobName name; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetJobsDto && + other.assetIds == assetIds && + other.name == name; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assetIds.hashCode) + + (name.hashCode); + + @override + String toString() => 'AssetJobsDto[assetIds=$assetIds, name=$name]'; + + Map toJson() { + final json = {}; + json[r'assetIds'] = this.assetIds; + json[r'name'] = this.name; + return json; + } + + /// Returns a new [AssetJobsDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetJobsDto? fromJson(dynamic value) { + if (value is Map) { + final json = value.cast(); + + return AssetJobsDto( + assetIds: json[r'assetIds'] is List + ? (json[r'assetIds'] as List).cast() + : const [], + name: AssetJobName.fromJson(json[r'name'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetJobsDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetJobsDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetJobsDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetJobsDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assetIds', + 'name', + }; +} + diff --git a/mobile/openapi/lib/model/search_config_response_dto.dart b/mobile/openapi/lib/model/audit_deletes_response_dto.dart similarity index 53% rename from mobile/openapi/lib/model/search_config_response_dto.dart rename to mobile/openapi/lib/model/audit_deletes_response_dto.dart index 31927662c4bc0ec78ef14b1623ac376d0b57d6b6..a0bc0dd4d929e9b41615ef036517283fc0d0f748 100644 --- a/mobile/openapi/lib/model/search_config_response_dto.dart +++ b/mobile/openapi/lib/model/audit_deletes_response_dto.dart @@ -10,51 +10,60 @@ part of openapi.api; -class SearchConfigResponseDto { - /// Returns a new [SearchConfigResponseDto] instance. - SearchConfigResponseDto({ - required this.enabled, +class AuditDeletesResponseDto { + /// Returns a new [AuditDeletesResponseDto] instance. + AuditDeletesResponseDto({ + this.ids = const [], + required this.needsFullSync, }); - bool enabled; + List ids; + + bool needsFullSync; @override - bool operator ==(Object other) => identical(this, other) || other is SearchConfigResponseDto && - other.enabled == enabled; + bool operator ==(Object other) => identical(this, other) || other is AuditDeletesResponseDto && + other.ids == ids && + other.needsFullSync == needsFullSync; @override int get hashCode => // ignore: unnecessary_parenthesis - (enabled.hashCode); + (ids.hashCode) + + (needsFullSync.hashCode); @override - String toString() => 'SearchConfigResponseDto[enabled=$enabled]'; + String toString() => 'AuditDeletesResponseDto[ids=$ids, needsFullSync=$needsFullSync]'; Map toJson() { final json = {}; - json[r'enabled'] = this.enabled; + json[r'ids'] = this.ids; + json[r'needsFullSync'] = this.needsFullSync; return json; } - /// Returns a new [SearchConfigResponseDto] instance and imports its values from + /// Returns a new [AuditDeletesResponseDto] instance and imports its values from /// [value] if it's a [Map], null otherwise. // ignore: prefer_constructors_over_static_methods - static SearchConfigResponseDto? fromJson(dynamic value) { + static AuditDeletesResponseDto? fromJson(dynamic value) { if (value is Map) { final json = value.cast(); - return SearchConfigResponseDto( - enabled: mapValueOfType(json, r'enabled')!, + return AuditDeletesResponseDto( + ids: json[r'ids'] is List + ? (json[r'ids'] as List).cast() + : const [], + needsFullSync: mapValueOfType(json, r'needsFullSync')!, ); } return null; } - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; if (json is List && json.isNotEmpty) { for (final row in json) { - final value = SearchConfigResponseDto.fromJson(row); + final value = AuditDeletesResponseDto.fromJson(row); if (value != null) { result.add(value); } @@ -63,12 +72,12 @@ class SearchConfigResponseDto { return result.toList(growable: growable); } - static Map mapFromJson(dynamic json) { - final map = {}; + static Map mapFromJson(dynamic json) { + final map = {}; if (json is Map && json.isNotEmpty) { json = json.cast(); // ignore: parameter_assignments for (final entry in json.entries) { - final value = SearchConfigResponseDto.fromJson(entry.value); + final value = AuditDeletesResponseDto.fromJson(entry.value); if (value != null) { map[entry.key] = value; } @@ -77,14 +86,14 @@ class SearchConfigResponseDto { return map; } - // maps a json object with a list of SearchConfigResponseDto-objects as value to a dart map - static Map> mapListFromJson(dynamic json, {bool growable = false,}) { - final map = >{}; + // maps a json object with a list of AuditDeletesResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; if (json is Map && json.isNotEmpty) { // ignore: parameter_assignments json = json.cast(); for (final entry in json.entries) { - map[entry.key] = SearchConfigResponseDto.listFromJson(entry.value, growable: growable,); + map[entry.key] = AuditDeletesResponseDto.listFromJson(entry.value, growable: growable,); } } return map; @@ -92,7 +101,8 @@ class SearchConfigResponseDto { /// The list of required keys that must be present in a JSON. static const requiredKeys = { - 'enabled', + 'ids', + 'needsFullSync', }; } diff --git a/mobile/openapi/lib/model/entity_type.dart b/mobile/openapi/lib/model/entity_type.dart new file mode 100644 index 0000000000000000000000000000000000000000..1a45e1b2e90448e01463a29fd7a2dd008f208fb1 --- /dev/null +++ b/mobile/openapi/lib/model/entity_type.dart @@ -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 EntityType { + /// Instantiate a new enum with the provided [value]. + const EntityType._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const ASSET = EntityType._(r'ASSET'); + static const ALBUM = EntityType._(r'ALBUM'); + + /// List of all possible values in this [enum][EntityType]. + static const values = [ + ASSET, + ALBUM, + ]; + + static EntityType? fromJson(dynamic value) => EntityTypeTypeTransformer().decode(value); + + static List? listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = EntityType.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [EntityType] to String, +/// and [decode] dynamic data back to [EntityType]. +class EntityTypeTypeTransformer { + factory EntityTypeTypeTransformer() => _instance ??= const EntityTypeTypeTransformer._(); + + const EntityTypeTypeTransformer._(); + + String encode(EntityType data) => data.value; + + /// Decodes a [dynamic value][data] to a EntityType. + /// + /// 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. + EntityType? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'ASSET': return EntityType.ASSET; + case r'ALBUM': return EntityType.ALBUM; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [EntityTypeTypeTransformer] instance. + static EntityTypeTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/people_update_item.dart b/mobile/openapi/lib/model/people_update_item.dart index 3a35c8a58ebabb568b0e62797a3b9476bcc9a0cb..0abb7a474c2918709de706833eb9c9f7e650feb4 100644 --- a/mobile/openapi/lib/model/people_update_item.dart +++ b/mobile/openapi/lib/model/people_update_item.dart @@ -13,12 +13,16 @@ part of openapi.api; class PeopleUpdateItem { /// Returns a new [PeopleUpdateItem] instance. PeopleUpdateItem({ + this.birthDate, this.featureFaceAssetId, required this.id, this.isHidden, this.name, }); + /// Person date of birth. + DateTime? birthDate; + /// Asset is used to get the feature face thumbnail. /// /// Please note: This property should have been non-nullable! Since the specification file @@ -51,6 +55,7 @@ class PeopleUpdateItem { @override bool operator ==(Object other) => identical(this, other) || other is PeopleUpdateItem && + other.birthDate == birthDate && other.featureFaceAssetId == featureFaceAssetId && other.id == id && other.isHidden == isHidden && @@ -59,16 +64,22 @@ class PeopleUpdateItem { @override int get hashCode => // ignore: unnecessary_parenthesis + (birthDate == null ? 0 : birthDate!.hashCode) + (featureFaceAssetId == null ? 0 : featureFaceAssetId!.hashCode) + (id.hashCode) + (isHidden == null ? 0 : isHidden!.hashCode) + (name == null ? 0 : name!.hashCode); @override - String toString() => 'PeopleUpdateItem[featureFaceAssetId=$featureFaceAssetId, id=$id, isHidden=$isHidden, name=$name]'; + String toString() => 'PeopleUpdateItem[birthDate=$birthDate, featureFaceAssetId=$featureFaceAssetId, id=$id, isHidden=$isHidden, name=$name]'; Map toJson() { final json = {}; + if (this.birthDate != null) { + json[r'birthDate'] = _dateFormatter.format(this.birthDate!.toUtc()); + } else { + // json[r'birthDate'] = null; + } if (this.featureFaceAssetId != null) { json[r'featureFaceAssetId'] = this.featureFaceAssetId; } else { @@ -96,6 +107,7 @@ class PeopleUpdateItem { final json = value.cast(); return PeopleUpdateItem( + birthDate: mapDateTime(json, r'birthDate', ''), featureFaceAssetId: mapValueOfType(json, r'featureFaceAssetId'), id: mapValueOfType(json, r'id')!, isHidden: mapValueOfType(json, r'isHidden'), diff --git a/mobile/openapi/lib/model/person_response_dto.dart b/mobile/openapi/lib/model/person_response_dto.dart index 21120f23b87b18cdf642fea4ba82ac5dac99e538..5e65d947a47088804da87b362824a5b838194f5c 100644 --- a/mobile/openapi/lib/model/person_response_dto.dart +++ b/mobile/openapi/lib/model/person_response_dto.dart @@ -13,12 +13,15 @@ part of openapi.api; class PersonResponseDto { /// Returns a new [PersonResponseDto] instance. PersonResponseDto({ + required this.birthDate, required this.id, required this.isHidden, required this.name, required this.thumbnailPath, }); + DateTime? birthDate; + String id; bool isHidden; @@ -29,6 +32,7 @@ class PersonResponseDto { @override bool operator ==(Object other) => identical(this, other) || other is PersonResponseDto && + other.birthDate == birthDate && other.id == id && other.isHidden == isHidden && other.name == name && @@ -37,16 +41,22 @@ class PersonResponseDto { @override int get hashCode => // ignore: unnecessary_parenthesis + (birthDate == null ? 0 : birthDate!.hashCode) + (id.hashCode) + (isHidden.hashCode) + (name.hashCode) + (thumbnailPath.hashCode); @override - String toString() => 'PersonResponseDto[id=$id, isHidden=$isHidden, name=$name, thumbnailPath=$thumbnailPath]'; + String toString() => 'PersonResponseDto[birthDate=$birthDate, id=$id, isHidden=$isHidden, name=$name, thumbnailPath=$thumbnailPath]'; Map toJson() { final json = {}; + if (this.birthDate != null) { + json[r'birthDate'] = _dateFormatter.format(this.birthDate!.toUtc()); + } else { + // json[r'birthDate'] = null; + } json[r'id'] = this.id; json[r'isHidden'] = this.isHidden; json[r'name'] = this.name; @@ -62,6 +72,7 @@ class PersonResponseDto { final json = value.cast(); return PersonResponseDto( + birthDate: mapDateTime(json, r'birthDate', ''), id: mapValueOfType(json, r'id')!, isHidden: mapValueOfType(json, r'isHidden')!, name: mapValueOfType(json, r'name')!, @@ -113,6 +124,7 @@ class PersonResponseDto { /// The list of required keys that must be present in a JSON. static const requiredKeys = { + 'birthDate', 'id', 'isHidden', 'name', diff --git a/mobile/openapi/lib/model/person_update_dto.dart b/mobile/openapi/lib/model/person_update_dto.dart index baa985b1c724342f605d7543690563183e25f16d..fc384c842e67c3e05e5f5de7f5acb56d0330ed2f 100644 --- a/mobile/openapi/lib/model/person_update_dto.dart +++ b/mobile/openapi/lib/model/person_update_dto.dart @@ -13,11 +13,15 @@ part of openapi.api; class PersonUpdateDto { /// Returns a new [PersonUpdateDto] instance. PersonUpdateDto({ + this.birthDate, this.featureFaceAssetId, this.isHidden, this.name, }); + /// Person date of birth. + DateTime? birthDate; + /// Asset is used to get the feature face thumbnail. /// /// Please note: This property should have been non-nullable! Since the specification file @@ -47,6 +51,7 @@ class PersonUpdateDto { @override bool operator ==(Object other) => identical(this, other) || other is PersonUpdateDto && + other.birthDate == birthDate && other.featureFaceAssetId == featureFaceAssetId && other.isHidden == isHidden && other.name == name; @@ -54,15 +59,21 @@ class PersonUpdateDto { @override int get hashCode => // ignore: unnecessary_parenthesis + (birthDate == null ? 0 : birthDate!.hashCode) + (featureFaceAssetId == null ? 0 : featureFaceAssetId!.hashCode) + (isHidden == null ? 0 : isHidden!.hashCode) + (name == null ? 0 : name!.hashCode); @override - String toString() => 'PersonUpdateDto[featureFaceAssetId=$featureFaceAssetId, isHidden=$isHidden, name=$name]'; + String toString() => 'PersonUpdateDto[birthDate=$birthDate, featureFaceAssetId=$featureFaceAssetId, isHidden=$isHidden, name=$name]'; Map toJson() { final json = {}; + if (this.birthDate != null) { + json[r'birthDate'] = _dateFormatter.format(this.birthDate!.toUtc()); + } else { + // json[r'birthDate'] = null; + } if (this.featureFaceAssetId != null) { json[r'featureFaceAssetId'] = this.featureFaceAssetId; } else { @@ -89,6 +100,7 @@ class PersonUpdateDto { final json = value.cast(); return PersonUpdateDto( + birthDate: mapDateTime(json, r'birthDate', ''), featureFaceAssetId: mapValueOfType(json, r'featureFaceAssetId'), isHidden: mapValueOfType(json, r'isHidden'), name: mapValueOfType(json, r'name'), diff --git a/mobile/openapi/lib/model/server_features_dto.dart b/mobile/openapi/lib/model/server_features_dto.dart new file mode 100644 index 0000000000000000000000000000000000000000..60827add6c45034a9a22fbf9cdf4eea2037812c1 --- /dev/null +++ b/mobile/openapi/lib/model/server_features_dto.dart @@ -0,0 +1,162 @@ +// +// 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 ServerFeaturesDto { + /// Returns a new [ServerFeaturesDto] instance. + ServerFeaturesDto({ + required this.clipEncode, + required this.configFile, + required this.facialRecognition, + required this.oauth, + required this.oauthAutoLaunch, + required this.passwordLogin, + required this.search, + required this.sidecar, + required this.tagImage, + }); + + bool clipEncode; + + bool configFile; + + bool facialRecognition; + + bool oauth; + + bool oauthAutoLaunch; + + bool passwordLogin; + + bool search; + + bool sidecar; + + bool tagImage; + + @override + bool operator ==(Object other) => identical(this, other) || other is ServerFeaturesDto && + other.clipEncode == clipEncode && + other.configFile == configFile && + other.facialRecognition == facialRecognition && + other.oauth == oauth && + other.oauthAutoLaunch == oauthAutoLaunch && + other.passwordLogin == passwordLogin && + other.search == search && + other.sidecar == sidecar && + other.tagImage == tagImage; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (clipEncode.hashCode) + + (configFile.hashCode) + + (facialRecognition.hashCode) + + (oauth.hashCode) + + (oauthAutoLaunch.hashCode) + + (passwordLogin.hashCode) + + (search.hashCode) + + (sidecar.hashCode) + + (tagImage.hashCode); + + @override + String toString() => 'ServerFeaturesDto[clipEncode=$clipEncode, configFile=$configFile, facialRecognition=$facialRecognition, oauth=$oauth, oauthAutoLaunch=$oauthAutoLaunch, passwordLogin=$passwordLogin, search=$search, sidecar=$sidecar, tagImage=$tagImage]'; + + Map toJson() { + final json = {}; + json[r'clipEncode'] = this.clipEncode; + json[r'configFile'] = this.configFile; + json[r'facialRecognition'] = this.facialRecognition; + json[r'oauth'] = this.oauth; + json[r'oauthAutoLaunch'] = this.oauthAutoLaunch; + json[r'passwordLogin'] = this.passwordLogin; + json[r'search'] = this.search; + json[r'sidecar'] = this.sidecar; + json[r'tagImage'] = this.tagImage; + return json; + } + + /// Returns a new [ServerFeaturesDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static ServerFeaturesDto? fromJson(dynamic value) { + if (value is Map) { + final json = value.cast(); + + return ServerFeaturesDto( + clipEncode: mapValueOfType(json, r'clipEncode')!, + configFile: mapValueOfType(json, r'configFile')!, + facialRecognition: mapValueOfType(json, r'facialRecognition')!, + oauth: mapValueOfType(json, r'oauth')!, + oauthAutoLaunch: mapValueOfType(json, r'oauthAutoLaunch')!, + passwordLogin: mapValueOfType(json, r'passwordLogin')!, + search: mapValueOfType(json, r'search')!, + sidecar: mapValueOfType(json, r'sidecar')!, + tagImage: mapValueOfType(json, r'tagImage')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = ServerFeaturesDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = ServerFeaturesDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of ServerFeaturesDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = ServerFeaturesDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'clipEncode', + 'configFile', + 'facialRecognition', + 'oauth', + 'oauthAutoLaunch', + 'passwordLogin', + 'search', + 'sidecar', + 'tagImage', + }; +} + diff --git a/mobile/openapi/lib/model/server_version_reponse_dto.dart b/mobile/openapi/lib/model/server_version_response_dto.dart similarity index 63% rename from mobile/openapi/lib/model/server_version_reponse_dto.dart rename to mobile/openapi/lib/model/server_version_response_dto.dart index a1ec0e68e5208667dc703ba95f5f034655ec0d88..353d65049b25e067219cfb2b5254e616abdeac5d 100644 --- a/mobile/openapi/lib/model/server_version_reponse_dto.dart +++ b/mobile/openapi/lib/model/server_version_response_dto.dart @@ -10,9 +10,9 @@ part of openapi.api; -class ServerVersionReponseDto { - /// Returns a new [ServerVersionReponseDto] instance. - ServerVersionReponseDto({ +class ServerVersionResponseDto { + /// Returns a new [ServerVersionResponseDto] instance. + ServerVersionResponseDto({ required this.major, required this.minor, required this.patch_, @@ -25,7 +25,7 @@ class ServerVersionReponseDto { int patch_; @override - bool operator ==(Object other) => identical(this, other) || other is ServerVersionReponseDto && + bool operator ==(Object other) => identical(this, other) || other is ServerVersionResponseDto && other.major == major && other.minor == minor && other.patch_ == patch_; @@ -38,7 +38,7 @@ class ServerVersionReponseDto { (patch_.hashCode); @override - String toString() => 'ServerVersionReponseDto[major=$major, minor=$minor, patch_=$patch_]'; + String toString() => 'ServerVersionResponseDto[major=$major, minor=$minor, patch_=$patch_]'; Map toJson() { final json = {}; @@ -48,14 +48,14 @@ class ServerVersionReponseDto { return json; } - /// Returns a new [ServerVersionReponseDto] instance and imports its values from + /// Returns a new [ServerVersionResponseDto] instance and imports its values from /// [value] if it's a [Map], null otherwise. // ignore: prefer_constructors_over_static_methods - static ServerVersionReponseDto? fromJson(dynamic value) { + static ServerVersionResponseDto? fromJson(dynamic value) { if (value is Map) { final json = value.cast(); - return ServerVersionReponseDto( + return ServerVersionResponseDto( major: mapValueOfType(json, r'major')!, minor: mapValueOfType(json, r'minor')!, patch_: mapValueOfType(json, r'patch')!, @@ -64,11 +64,11 @@ class ServerVersionReponseDto { return null; } - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; if (json is List && json.isNotEmpty) { for (final row in json) { - final value = ServerVersionReponseDto.fromJson(row); + final value = ServerVersionResponseDto.fromJson(row); if (value != null) { result.add(value); } @@ -77,12 +77,12 @@ class ServerVersionReponseDto { return result.toList(growable: growable); } - static Map mapFromJson(dynamic json) { - final map = {}; + static Map mapFromJson(dynamic json) { + final map = {}; if (json is Map && json.isNotEmpty) { json = json.cast(); // ignore: parameter_assignments for (final entry in json.entries) { - final value = ServerVersionReponseDto.fromJson(entry.value); + final value = ServerVersionResponseDto.fromJson(entry.value); if (value != null) { map[entry.key] = value; } @@ -91,14 +91,14 @@ class ServerVersionReponseDto { return map; } - // maps a json object with a list of ServerVersionReponseDto-objects as value to a dart map - static Map> mapListFromJson(dynamic json, {bool growable = false,}) { - final map = >{}; + // maps a json object with a list of ServerVersionResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; if (json is Map && json.isNotEmpty) { // ignore: parameter_assignments json = json.cast(); for (final entry in json.entries) { - map[entry.key] = ServerVersionReponseDto.listFromJson(entry.value, growable: growable,); + map[entry.key] = ServerVersionResponseDto.listFromJson(entry.value, growable: growable,); } } return map; diff --git a/mobile/openapi/lib/model/system_config_dto.dart b/mobile/openapi/lib/model/system_config_dto.dart index aefc97d1ba42ddb815d2f2c83c1e06327a46a5d1..da0500ebf8ddf71da93c76334d1ce91042d1e7fa 100644 --- a/mobile/openapi/lib/model/system_config_dto.dart +++ b/mobile/openapi/lib/model/system_config_dto.dart @@ -15,6 +15,7 @@ class SystemConfigDto { SystemConfigDto({ required this.ffmpeg, required this.job, + required this.machineLearning, required this.oauth, required this.passwordLogin, required this.storageTemplate, @@ -25,6 +26,8 @@ class SystemConfigDto { SystemConfigJobDto job; + SystemConfigMachineLearningDto machineLearning; + SystemConfigOAuthDto oauth; SystemConfigPasswordLoginDto passwordLogin; @@ -37,6 +40,7 @@ class SystemConfigDto { bool operator ==(Object other) => identical(this, other) || other is SystemConfigDto && other.ffmpeg == ffmpeg && other.job == job && + other.machineLearning == machineLearning && other.oauth == oauth && other.passwordLogin == passwordLogin && other.storageTemplate == storageTemplate && @@ -47,18 +51,20 @@ class SystemConfigDto { // ignore: unnecessary_parenthesis (ffmpeg.hashCode) + (job.hashCode) + + (machineLearning.hashCode) + (oauth.hashCode) + (passwordLogin.hashCode) + (storageTemplate.hashCode) + (thumbnail.hashCode); @override - String toString() => 'SystemConfigDto[ffmpeg=$ffmpeg, job=$job, oauth=$oauth, passwordLogin=$passwordLogin, storageTemplate=$storageTemplate, thumbnail=$thumbnail]'; + String toString() => 'SystemConfigDto[ffmpeg=$ffmpeg, job=$job, machineLearning=$machineLearning, oauth=$oauth, passwordLogin=$passwordLogin, storageTemplate=$storageTemplate, thumbnail=$thumbnail]'; Map toJson() { final json = {}; json[r'ffmpeg'] = this.ffmpeg; json[r'job'] = this.job; + json[r'machineLearning'] = this.machineLearning; json[r'oauth'] = this.oauth; json[r'passwordLogin'] = this.passwordLogin; json[r'storageTemplate'] = this.storageTemplate; @@ -76,6 +82,7 @@ class SystemConfigDto { return SystemConfigDto( ffmpeg: SystemConfigFFmpegDto.fromJson(json[r'ffmpeg'])!, job: SystemConfigJobDto.fromJson(json[r'job'])!, + machineLearning: SystemConfigMachineLearningDto.fromJson(json[r'machineLearning'])!, oauth: SystemConfigOAuthDto.fromJson(json[r'oauth'])!, passwordLogin: SystemConfigPasswordLoginDto.fromJson(json[r'passwordLogin'])!, storageTemplate: SystemConfigStorageTemplateDto.fromJson(json[r'storageTemplate'])!, @@ -129,6 +136,7 @@ class SystemConfigDto { static const requiredKeys = { 'ffmpeg', 'job', + 'machineLearning', 'oauth', 'passwordLogin', 'storageTemplate', diff --git a/mobile/openapi/lib/model/system_config_machine_learning_dto.dart b/mobile/openapi/lib/model/system_config_machine_learning_dto.dart new file mode 100644 index 0000000000000000000000000000000000000000..c8d70d92cf43c01e8c7d3a4711c2e53317ee79e6 --- /dev/null +++ b/mobile/openapi/lib/model/system_config_machine_learning_dto.dart @@ -0,0 +1,130 @@ +// +// 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 SystemConfigMachineLearningDto { + /// Returns a new [SystemConfigMachineLearningDto] instance. + SystemConfigMachineLearningDto({ + required this.clipEncodeEnabled, + required this.enabled, + required this.facialRecognitionEnabled, + required this.tagImageEnabled, + required this.url, + }); + + bool clipEncodeEnabled; + + bool enabled; + + bool facialRecognitionEnabled; + + bool tagImageEnabled; + + String url; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigMachineLearningDto && + other.clipEncodeEnabled == clipEncodeEnabled && + other.enabled == enabled && + other.facialRecognitionEnabled == facialRecognitionEnabled && + other.tagImageEnabled == tagImageEnabled && + other.url == url; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (clipEncodeEnabled.hashCode) + + (enabled.hashCode) + + (facialRecognitionEnabled.hashCode) + + (tagImageEnabled.hashCode) + + (url.hashCode); + + @override + String toString() => 'SystemConfigMachineLearningDto[clipEncodeEnabled=$clipEncodeEnabled, enabled=$enabled, facialRecognitionEnabled=$facialRecognitionEnabled, tagImageEnabled=$tagImageEnabled, url=$url]'; + + Map toJson() { + final json = {}; + json[r'clipEncodeEnabled'] = this.clipEncodeEnabled; + json[r'enabled'] = this.enabled; + json[r'facialRecognitionEnabled'] = this.facialRecognitionEnabled; + json[r'tagImageEnabled'] = this.tagImageEnabled; + json[r'url'] = this.url; + return json; + } + + /// Returns a new [SystemConfigMachineLearningDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigMachineLearningDto? fromJson(dynamic value) { + if (value is Map) { + final json = value.cast(); + + return SystemConfigMachineLearningDto( + clipEncodeEnabled: mapValueOfType(json, r'clipEncodeEnabled')!, + enabled: mapValueOfType(json, r'enabled')!, + facialRecognitionEnabled: mapValueOfType(json, r'facialRecognitionEnabled')!, + tagImageEnabled: mapValueOfType(json, r'tagImageEnabled')!, + url: mapValueOfType(json, r'url')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigMachineLearningDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigMachineLearningDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigMachineLearningDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigMachineLearningDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'clipEncodeEnabled', + 'enabled', + 'facialRecognitionEnabled', + 'tagImageEnabled', + 'url', + }; +} + diff --git a/mobile/openapi/test/asset_api_test.dart b/mobile/openapi/test/asset_api_test.dart index ebb472c4a05228ea212ef5ed9f972e33f225a724..ea45fc7d19acb6cc86bde3c0d52773a5ec4c387f 100644 --- a/mobile/openapi/test/asset_api_test.dart +++ b/mobile/openapi/test/asset_api_test.dart @@ -55,7 +55,7 @@ void main() { // Get all AssetEntity belong to the user // - //Future> getAllAssets({ String userId, bool isFavorite, bool isArchived, bool withoutThumbs, num skip, String ifNoneMatch }) async + //Future> getAllAssets({ String userId, bool isFavorite, bool isArchived, num skip, DateTime updatedAfter, String ifNoneMatch }) async test('test getAllAssets', () async { // TODO }); @@ -129,6 +129,11 @@ void main() { // TODO }); + //Future runAssetJobs(AssetJobsDto assetJobsDto) async + test('test runAssetJobs', () async { + // TODO + }); + //Future> searchAsset(SearchAssetDto searchAssetDto) async test('test searchAsset', () async { // TODO @@ -146,6 +151,11 @@ void main() { // TODO }); + //Future updateAssets(AssetBulkUpdateDto assetBulkUpdateDto) async + test('test updateAssets', () async { + // TODO + }); + //Future uploadFile(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, bool isFavorite, { String key, String duration, bool isArchived, bool isReadOnly, bool isVisible, MultipartFile livePhotoData, MultipartFile sidecarData }) async test('test uploadFile', () async { // TODO diff --git a/mobile/openapi/test/asset_bulk_update_dto_test.dart b/mobile/openapi/test/asset_bulk_update_dto_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..cb23751e084df51ec7770e01d42fb9558d75edc1 --- /dev/null +++ b/mobile/openapi/test/asset_bulk_update_dto_test.dart @@ -0,0 +1,37 @@ +// +// 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 AssetBulkUpdateDto +void main() { + // final instance = AssetBulkUpdateDto(); + + group('test AssetBulkUpdateDto', () { + // List ids (default value: const []) + test('to test the property `ids`', () async { + // TODO + }); + + // bool isArchived + test('to test the property `isArchived`', () async { + // TODO + }); + + // bool isFavorite + test('to test the property `isFavorite`', () async { + // TODO + }); + + + }); + +} diff --git a/mobile/openapi/test/search_config_response_dto_test.dart b/mobile/openapi/test/asset_job_name_test.dart similarity index 61% rename from mobile/openapi/test/search_config_response_dto_test.dart rename to mobile/openapi/test/asset_job_name_test.dart index 2a49752011abe63a3d6e7040d2a1a9314ec351e5..dc6313c9274eff4623459bb29cb332397b1f3096 100644 --- a/mobile/openapi/test/search_config_response_dto_test.dart +++ b/mobile/openapi/test/asset_job_name_test.dart @@ -11,16 +11,10 @@ import 'package:openapi/api.dart'; import 'package:test/test.dart'; -// tests for SearchConfigResponseDto +// tests for AssetJobName void main() { - // final instance = SearchConfigResponseDto(); - - group('test SearchConfigResponseDto', () { - // bool enabled - test('to test the property `enabled`', () async { - // TODO - }); + group('test AssetJobName', () { }); diff --git a/mobile/openapi/test/asset_jobs_dto_test.dart b/mobile/openapi/test/asset_jobs_dto_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..e114d9fb2c64aae40f15e1f761cc4147fda0448a --- /dev/null +++ b/mobile/openapi/test/asset_jobs_dto_test.dart @@ -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 AssetJobsDto +void main() { + // final instance = AssetJobsDto(); + + group('test AssetJobsDto', () { + // List assetIds (default value: const []) + test('to test the property `assetIds`', () async { + // TODO + }); + + // AssetJobName name + test('to test the property `name`', () async { + // TODO + }); + + + }); + +} diff --git a/mobile/openapi/test/audit_api_test.dart b/mobile/openapi/test/audit_api_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..68ffede19c056ff71bb12146dffb13c14309f26c --- /dev/null +++ b/mobile/openapi/test/audit_api_test.dart @@ -0,0 +1,26 @@ +// +// 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 AuditApi +void main() { + // final instance = AuditApi(); + + group('tests for AuditApi', () { + //Future getAuditDeletes(EntityType entityType, DateTime after, { String userId }) async + test('test getAuditDeletes', () async { + // TODO + }); + + }); +} diff --git a/mobile/openapi/test/audit_deletes_response_dto_test.dart b/mobile/openapi/test/audit_deletes_response_dto_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..45dbccc28d7430d301a0380f015d78f827ea7dd9 --- /dev/null +++ b/mobile/openapi/test/audit_deletes_response_dto_test.dart @@ -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 AuditDeletesResponseDto +void main() { + // final instance = AuditDeletesResponseDto(); + + group('test AuditDeletesResponseDto', () { + // List ids (default value: const []) + test('to test the property `ids`', () async { + // TODO + }); + + // bool needsFullSync + test('to test the property `needsFullSync`', () async { + // TODO + }); + + + }); + +} diff --git a/mobile/openapi/test/entity_type_test.dart b/mobile/openapi/test/entity_type_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..81f023308c2edf844a6eeb6591ab1e24a28e4ee6 --- /dev/null +++ b/mobile/openapi/test/entity_type_test.dart @@ -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 EntityType +void main() { + + group('test EntityType', () { + + }); + +} diff --git a/mobile/openapi/test/people_update_item_test.dart b/mobile/openapi/test/people_update_item_test.dart index 9c366e4ebebe42af4945c7cdad036aa97991f621..4c91143bd5ec0f7093d7801858ecd6569de5cd28 100644 --- a/mobile/openapi/test/people_update_item_test.dart +++ b/mobile/openapi/test/people_update_item_test.dart @@ -16,6 +16,12 @@ void main() { // final instance = PeopleUpdateItem(); group('test PeopleUpdateItem', () { + // Person date of birth. + // DateTime birthDate + test('to test the property `birthDate`', () async { + // TODO + }); + // Asset is used to get the feature face thumbnail. // String featureFaceAssetId test('to test the property `featureFaceAssetId`', () async { diff --git a/mobile/openapi/test/person_response_dto_test.dart b/mobile/openapi/test/person_response_dto_test.dart index 8b9f7bec8a17ab37c324f58a3ebe668abe4fd326..0ba73061177b6dcf9a78484a1aa8561565769c02 100644 --- a/mobile/openapi/test/person_response_dto_test.dart +++ b/mobile/openapi/test/person_response_dto_test.dart @@ -16,6 +16,11 @@ void main() { // final instance = PersonResponseDto(); group('test PersonResponseDto', () { + // DateTime birthDate + test('to test the property `birthDate`', () async { + // TODO + }); + // String id test('to test the property `id`', () async { // TODO diff --git a/mobile/openapi/test/person_update_dto_test.dart b/mobile/openapi/test/person_update_dto_test.dart index b515c2c8e1e98b24babc02120751e48339a8d00d..80c46e44f2fe2f78892c1a8f6fe2c1ca914bc81a 100644 --- a/mobile/openapi/test/person_update_dto_test.dart +++ b/mobile/openapi/test/person_update_dto_test.dart @@ -16,6 +16,12 @@ void main() { // final instance = PersonUpdateDto(); group('test PersonUpdateDto', () { + // Person date of birth. + // DateTime birthDate + test('to test the property `birthDate`', () async { + // TODO + }); + // Asset is used to get the feature face thumbnail. // String featureFaceAssetId test('to test the property `featureFaceAssetId`', () async { diff --git a/mobile/openapi/test/search_api_test.dart b/mobile/openapi/test/search_api_test.dart index a6bbacfa2a9bde4621ef5743abc56a66beca7070..8365eff0762ed55dd694cc315151b85130724ae0 100644 --- a/mobile/openapi/test/search_api_test.dart +++ b/mobile/openapi/test/search_api_test.dart @@ -22,11 +22,6 @@ void main() { // TODO }); - //Future getSearchConfig() async - test('test getSearchConfig', () async { - // TODO - }); - //Future search({ String q, String query, bool clip, String type, bool isFavorite, bool isArchived, String exifInfoPeriodCity, String exifInfoPeriodState, String exifInfoPeriodCountry, String exifInfoPeriodMake, String exifInfoPeriodModel, String exifInfoPeriodProjectionType, List smartInfoPeriodObjects, List smartInfoPeriodTags, bool recent, bool motion }) async test('test search', () async { // TODO diff --git a/mobile/openapi/test/server_features_dto_test.dart b/mobile/openapi/test/server_features_dto_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..2cd1387ba86e9e22570dea0858379de9041e5fde --- /dev/null +++ b/mobile/openapi/test/server_features_dto_test.dart @@ -0,0 +1,67 @@ +// +// 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 ServerFeaturesDto +void main() { + // final instance = ServerFeaturesDto(); + + group('test ServerFeaturesDto', () { + // bool clipEncode + test('to test the property `clipEncode`', () async { + // TODO + }); + + // bool configFile + test('to test the property `configFile`', () async { + // TODO + }); + + // bool facialRecognition + test('to test the property `facialRecognition`', () async { + // TODO + }); + + // bool oauth + test('to test the property `oauth`', () async { + // TODO + }); + + // bool oauthAutoLaunch + test('to test the property `oauthAutoLaunch`', () async { + // TODO + }); + + // bool passwordLogin + test('to test the property `passwordLogin`', () async { + // TODO + }); + + // bool search + test('to test the property `search`', () async { + // TODO + }); + + // bool sidecar + test('to test the property `sidecar`', () async { + // TODO + }); + + // bool tagImage + test('to test the property `tagImage`', () async { + // TODO + }); + + + }); + +} diff --git a/mobile/openapi/test/server_info_api_test.dart b/mobile/openapi/test/server_info_api_test.dart index 4fee682a2552353d272a2b126ba67cdbd30acc6f..8ca3e30ef1979221ac931e937941aa8e3b49114c 100644 --- a/mobile/openapi/test/server_info_api_test.dart +++ b/mobile/openapi/test/server_info_api_test.dart @@ -17,12 +17,17 @@ void main() { // final instance = ServerInfoApi(); group('tests for ServerInfoApi', () { + //Future getServerFeatures() async + test('test getServerFeatures', () async { + // TODO + }); + //Future getServerInfo() async test('test getServerInfo', () async { // TODO }); - //Future getServerVersion() async + //Future getServerVersion() async test('test getServerVersion', () async { // TODO }); diff --git a/mobile/openapi/test/server_version_reponse_dto_test.dart b/mobile/openapi/test/server_version_response_dto_test.dart similarity index 82% rename from mobile/openapi/test/server_version_reponse_dto_test.dart rename to mobile/openapi/test/server_version_response_dto_test.dart index 3095e7a4629873c6da15e0c5fba4e9bb0b0389d0..add42ccd66713cf6651c877df0867ce91a2789a7 100644 --- a/mobile/openapi/test/server_version_reponse_dto_test.dart +++ b/mobile/openapi/test/server_version_response_dto_test.dart @@ -11,11 +11,11 @@ import 'package:openapi/api.dart'; import 'package:test/test.dart'; -// tests for ServerVersionReponseDto +// tests for ServerVersionResponseDto void main() { - // final instance = ServerVersionReponseDto(); + // final instance = ServerVersionResponseDto(); - group('test ServerVersionReponseDto', () { + group('test ServerVersionResponseDto', () { // int major test('to test the property `major`', () async { // TODO diff --git a/mobile/openapi/test/system_config_dto_test.dart b/mobile/openapi/test/system_config_dto_test.dart index 946324282e7eb8a8d0baa39c5da700cf6a4b1763..4ea0b98bde8bcbcb9e644d364878d387311f1c4e 100644 --- a/mobile/openapi/test/system_config_dto_test.dart +++ b/mobile/openapi/test/system_config_dto_test.dart @@ -26,6 +26,11 @@ void main() { // TODO }); + // SystemConfigMachineLearningDto machineLearning + test('to test the property `machineLearning`', () async { + // TODO + }); + // SystemConfigOAuthDto oauth test('to test the property `oauth`', () async { // TODO diff --git a/mobile/openapi/test/system_config_machine_learning_dto_test.dart b/mobile/openapi/test/system_config_machine_learning_dto_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..e8003763fc52d291595270884ce7ce957c9fff63 --- /dev/null +++ b/mobile/openapi/test/system_config_machine_learning_dto_test.dart @@ -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 SystemConfigMachineLearningDto +void main() { + // final instance = SystemConfigMachineLearningDto(); + + group('test SystemConfigMachineLearningDto', () { + // bool clipEncodeEnabled + test('to test the property `clipEncodeEnabled`', () async { + // TODO + }); + + // bool enabled + test('to test the property `enabled`', () async { + // TODO + }); + + // bool facialRecognitionEnabled + test('to test the property `facialRecognitionEnabled`', () async { + // TODO + }); + + // bool tagImageEnabled + test('to test the property `tagImageEnabled`', () async { + // TODO + }); + + // String url + test('to test the property `url`', () async { + // TODO + }); + + + }); + +} diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 5881a8e103c0f8b17ebfa67f7b098a67be9b5af7..03d7f020c93e04f2a981f4fa1a5fee3291bda534 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -5,18 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "405666cd3cf0ee0a48d21ec67e65406aad2c726d9fa58840d3375e7bdcd32a07" + sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a url: "https://pub.dev" source: hosted - version: "60.0.0" + version: "61.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "1952250bd005bacb895a01bf1b4dc00e3ba1c526cf47dca54dfe24979c65f5b3" + sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 url: "https://pub.dev" source: hosted - version: "5.12.0" + version: "5.13.0" archive: dependency: transitive description: @@ -29,10 +29,10 @@ packages: dependency: transitive description: name: args - sha256: c372bb384f273f0c2a8aaaa226dad84dc27c8519a691b888725dec59518ad53a + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" async: dependency: transitive description: @@ -77,10 +77,10 @@ packages: dependency: transitive description: name: build - sha256: "43865b79fbb78532e4bff7c33087aa43b1d488c4fdef014eaef568af6d8016dc" + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" build_config: dependency: transitive description: @@ -101,26 +101,26 @@ packages: dependency: transitive description: name: build_resolvers - sha256: db49b8609ef8c81cca2b310618c3017c00f03a92af44c04d310b907b2d692d95 + sha256: "6c4dd11d05d056e76320b828a1db0fc01ccd376922526f8e9d6c796a5adbac20" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.2.1" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "220ae4553e50d7c21a17c051afc7b183d28a24a420502e842f303f8e4e6edced" + sha256: "10c6bcdbf9d049a0b666702cf1cee4ddfdc38f02a19d35ae392863b47519848b" url: "https://pub.dev" source: hosted - version: "2.4.4" + version: "2.4.6" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: "30859c90e9ddaccc484f56303931f477b1f1ba2bab74aa32ed5d6ce15870f8cf" + sha256: "6d6ee4276b1c5f34f21fdf39425202712d2be82019983d52f351c94aafbc2c41" url: "https://pub.dev" source: hosted - version: "7.2.8" + version: "7.2.10" built_collection: dependency: transitive description: @@ -133,10 +133,10 @@ packages: dependency: transitive description: name: built_value - sha256: "2f17434bd5d52a26762043d6b43bb53b3acd029b4d9071a329f46d67ef297e6d" + sha256: "598a2a682e2a7a90f08ba39c0aaa9374c5112340f0a2e275f61b59389543d166" url: "https://pub.dev" source: hosted - version: "8.5.0" + version: "8.6.1" cached_network_image: dependency: "direct main" description: @@ -165,10 +165,10 @@ packages: dependency: transitive description: name: cancellation_token - sha256: "7bacc556338b9f84e4db991805fdfa37fa1eda3689b94185bdc7459099455d71" + sha256: ad95acf9d4b2f3563e25dc937f63587e46a70ce534e910b65d10e115490f1027 url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.0.1" cancellation_token_http: dependency: "direct main" description: @@ -197,10 +197,10 @@ packages: dependency: "direct main" description: name: chewie - sha256: "745e81e84c6d7f3835f89f85bb49771c0a66099e4caf8f8e9e9a372bc66fb2c1" + sha256: "60701da1f22ed20cd2d40e856fd1f2249dacf5b629d9fa50676443a18a4857b8" url: "https://pub.dev" source: hosted - version: "1.5.0" + version: "1.7.0" clock: dependency: transitive description: @@ -213,26 +213,26 @@ packages: dependency: transitive description: name: code_builder - sha256: "0d43dd1288fd145de1ecc9a3948ad4a6d5a82f0a14c4fdd0892260787d975cbe" + sha256: "4ad01d6e56db961d29661561effde45e519939fdaeb46c351275b182eac70189" url: "https://pub.dev" source: hosted - version: "4.4.0" + version: "4.5.0" collection: dependency: "direct main" description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" connectivity_plus: dependency: "direct main" description: name: connectivity_plus - sha256: "8599ae9edca5ff96163fca3e36f8e481ea917d1e71cdad912c084b5579913f34" + sha256: "77a180d6938f78ca7d2382d2240eb626c0f6a735d0bfdce227d8ffb80f95c48b" url: "https://pub.dev" source: hosted - version: "4.0.1" + version: "4.0.2" connectivity_plus_platform_interface: dependency: transitive description: @@ -269,10 +269,10 @@ packages: dependency: transitive description: name: csslib - sha256: b36c7f7e24c0bdf1bf9a3da461c837d1de64b9f8beb190c9011d8c72a3dfd745 + sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb" url: "https://pub.dev" source: hosted - version: "0.17.2" + version: "1.0.0" cupertino_icons: dependency: transitive description: @@ -285,18 +285,18 @@ packages: dependency: transitive description: name: dart_style - sha256: f4f1f73ab3fd2afcbcca165ee601fe980d966af6a21b5970c6c9376955c528ad + sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" dartx: dependency: transitive description: name: dartx - sha256: "45d7176701f16c5a5e00a4798791c1964bc231491b879369c818dd9a9c764871" + sha256: "8b25435617027257d43e6508b5fe061012880ddfdaa75a71d607c3de2a13d244" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.0" dbus: dependency: transitive description: @@ -325,10 +325,10 @@ packages: dependency: "direct main" description: name: easy_image_viewer - sha256: "8eddbbfc20c9f4f276e112326b955e518c950b5c5a1348d4e42565030e83d01d" + sha256: "8d11a4630e9beb7aacf043c98da2dd4b3bc3b47aa4073d2016ba696376161272" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" easy_localization: dependency: "direct main" description: @@ -357,10 +357,10 @@ packages: dependency: transitive description: name: ffi - sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99 + sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.1.0" file: dependency: transitive description: @@ -369,6 +369,38 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.4" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "770eb1ab057b5ae4326d1c24cc57710758b9a46026349d021d6311bd27580046" + url: "https://pub.dev" + source: hosted + version: "0.9.2" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: "4ada532862917bf16e3adb3891fe3a5917a58bae03293e497082203a80909412" + url: "https://pub.dev" + source: hosted + version: "0.9.3+1" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: "412705a646a0ae90f33f37acfae6a0f7cbc02222d6cd34e479421c3e74d3853c" + url: "https://pub.dev" + source: hosted + version: "2.6.0" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "1372760c6b389842b77156203308940558a2817360154084368608413835fc26" + url: "https://pub.dev" + source: hosted + version: "0.9.3" fixnum: dependency: transitive description: @@ -394,10 +426,10 @@ packages: dependency: "direct main" description: name: flutter_cache_manager - sha256: "32cd900555219333326a2d0653aaaf8671264c29befa65bbd9856d204a4c9fb3" + sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba" url: "https://pub.dev" source: hosted - version: "3.3.0" + version: "3.3.1" flutter_displaymode: dependency: "direct main" description: @@ -431,10 +463,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" flutter_local_notifications: dependency: "direct main" description: @@ -472,6 +504,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.0" + flutter_map_heatmap: + dependency: "direct main" + description: + name: flutter_map_heatmap + sha256: "2d16cf5bf41f40a79ae79bcdf2afc92ec45fea0cc311b3a51e3eae661392df88" + url: "https://pub.dev" + source: hosted + version: "0.0.4+2" flutter_native_splash: dependency: "direct dev" description: @@ -492,10 +532,10 @@ packages: dependency: transitive description: name: flutter_riverpod - sha256: b83ac5827baadefd331ea1d85110f34645827ea234ccabf53a655f41901a9bf4 + sha256: b6cb0041c6c11cefb2dcb97ef436eba43c6d41287ac6d8ca93e02a497f53a4f3 url: "https://pub.dev" source: hosted - version: "2.3.6" + version: "2.3.7" flutter_test: dependency: "direct dev" description: flutter @@ -526,10 +566,10 @@ packages: dependency: "direct main" description: name: fluttertoast - sha256: "2f9c4d3f4836421f7067a28f8939814597b27614e021da9d63e5d3fb6e212d25" + sha256: "474f7d506230897a3cd28c965ec21c5328ae5605fc9c400cd330e9e9d6ac175c" url: "https://pub.dev" source: hosted - version: "8.2.1" + version: "8.2.2" frontend_server_client: dependency: transitive description: @@ -543,38 +583,86 @@ packages: description: flutter source: sdk version: "0.0.0" + geolocator: + dependency: "direct main" + description: + name: geolocator + sha256: "9d6eff112971b9f195271834b390fc0e1899a9a6c96225ead72efd5d4aaa80c7" + url: "https://pub.dev" + source: hosted + version: "10.0.0" + geolocator_android: + dependency: transitive + description: + name: geolocator_android + sha256: "835ff5b4888a2f8eba128996494faf9c5d422785322a81dc0565b99e0f6c379d" + url: "https://pub.dev" + source: hosted + version: "4.2.2" + geolocator_apple: + dependency: transitive + description: + name: geolocator_apple + sha256: "36527c555f4c425f7d8fa8c7c07d67b78e3ff7590d40448051959e1860c1cfb4" + url: "https://pub.dev" + source: hosted + version: "2.2.7" + geolocator_platform_interface: + dependency: transitive + description: + name: geolocator_platform_interface + sha256: af4d69231452f9620718588f41acc4cb58312368716bfff2e92e770b46ce6386 + url: "https://pub.dev" + source: hosted + version: "4.0.7" + geolocator_web: + dependency: transitive + description: + name: geolocator_web + sha256: f68a122da48fcfff68bbc9846bb0b74ef651afe84a1b1f6ec20939de4d6860e1 + url: "https://pub.dev" + source: hosted + version: "2.1.6" + geolocator_windows: + dependency: transitive + description: + name: geolocator_windows + sha256: "463045515b08bd83f73e014359c4ad063b902eb3899952cfb784497ae6c6583b" + url: "https://pub.dev" + source: hosted + version: "0.2.0" glob: dependency: transitive description: name: glob - sha256: "4515b5b6ddb505ebdd242a5f2cc5d22d3d6a80013789debfbda7777f47ea308c" + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" graphs: dependency: transitive description: name: graphs - sha256: "772db3d53d23361d4ffcf5a9bb091cf3ee9b22f2be52cd107cd7a2683a89ba0e" + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.3.1" hooks_riverpod: dependency: "direct main" description: name: hooks_riverpod - sha256: be68cf7653fcab798500f9047ac58c3f109287a1595012412f4a0d654a9bb9c5 + sha256: "2bb8ae6a729e1334f71f1ef68dd5f0400dca8f01de8cbdcde062584a68017b18" url: "https://pub.dev" source: hosted - version: "2.3.6" + version: "2.3.8" html: dependency: transitive description: name: html - sha256: "58e3491f7bf0b6a4ea5110c0c688877460d1a6366731155c4a4580e7ded773e8" + sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" url: "https://pub.dev" source: hosted - version: "0.15.3" + version: "0.15.4" http: dependency: "direct main" description: @@ -611,42 +699,66 @@ packages: dependency: "direct main" description: name: image_picker - sha256: "9978d3510af4e6a902e545ce19229b926e6de6a1828d6134d3aab2e129a4d270" + sha256: b6951e25b795d053a6ba03af5f710069c99349de9341af95155d52665cb4607c url: "https://pub.dev" source: hosted - version: "0.8.7+5" + version: "0.8.9" image_picker_android: dependency: transitive description: name: image_picker_android - sha256: "364967c8d581f5d75fc05f6c79fcf1115e3c05db3d3eee1aaca52e0da3f7501c" + sha256: "8179b54039b50eee561676232304f487602e2950ffb3e8995ed9034d6505ca34" url: "https://pub.dev" source: hosted - version: "0.8.6+15" + version: "0.8.7+4" image_picker_for_web: dependency: transitive description: name: image_picker_for_web - sha256: "98f50d6b9f294c8ba35e25cc0d13b04bfddd25dbc8d32fa9d566a6572f2c081c" + sha256: "869fe8a64771b7afbc99fc433a5f7be2fea4d1cb3d7c11a48b6b579eb9c797f0" url: "https://pub.dev" source: hosted - version: "2.1.12" + version: "2.2.0" image_picker_ios: dependency: transitive description: name: image_picker_ios - sha256: d779210bda268a03b57e923fb1e410f32f5c5e708ad256348bcbf1f44f558fd0 + sha256: b3e2f21feb28b24dd73a35d7ad6e83f568337c70afab5eabac876e23803f264b url: "https://pub.dev" source: hosted - version: "0.8.7+4" + version: "0.8.8" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "02cbc21fe1706b97942b575966e5fbbeaac535e76deef70d3a242e4afb857831" + url: "https://pub.dev" + source: hosted + version: "0.2.1" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: cee2aa86c56780c13af2c77b5f2f72973464db204569e1ba2dd744459a065af4 + url: "https://pub.dev" + source: hosted + version: "0.2.1" image_picker_platform_interface: dependency: transitive description: name: image_picker_platform_interface - sha256: "1991219d9dbc42a99aff77e663af8ca51ced592cd6685c9485e3458302d3d4f8" + sha256: c1134543ae2187e85299996d21c526b2f403854994026d575ae4cf30d7bb2a32 url: "https://pub.dev" source: hosted - version: "2.6.3" + version: "2.9.0" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: c3066601ea42113922232c7b7b3330a2d86f029f685bba99d82c30e799914952 + url: "https://pub.dev" + source: hosted + version: "0.2.1" integration_test: dependency: "direct dev" description: flutter @@ -656,10 +768,10 @@ packages: dependency: "direct main" description: name: intl - sha256: a3715e3bc90294e971cb7dc063fbf3cd9ee0ebf8604ffeafabd9e6f16abbdbe6 + sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" url: "https://pub.dev" source: hosted - version: "0.18.0" + version: "0.18.1" io: dependency: transitive description: @@ -712,18 +824,18 @@ packages: dependency: "direct main" description: name: latlong2 - sha256: "408993a0e3f46e79ce1f129e4cb0386eef6d48dfa6394939ecacfbd7049154ec" + sha256: "08ef7282ba9f76e8495e49e2dc4d653015ac929dce5f92b375a415d30b407ea0" url: "https://pub.dev" source: hosted - version: "0.8.1" + version: "0.8.2" lints: dependency: transitive description: name: lints - sha256: "6b0206b0bf4f04961fc5438198ccb3a885685cd67d4d4a32cc20ad7f8adbe015" + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" lists: dependency: transitive description: @@ -736,26 +848,26 @@ packages: dependency: "direct main" description: name: logging - sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d" + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.2.0" matcher: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -784,10 +896,10 @@ packages: dependency: "direct dev" description: name: mockito - sha256: dd61809f04da1838a680926de50a9e87385c1de91c6579629c3d1723946e8059 + sha256: "7d5b53bcd556c1bc7ffbe4e4d5a19c3e112b7e925e9e172dd7c6ad0630812616" url: "https://pub.dev" source: hosted - version: "5.4.0" + version: "5.4.2" nested: dependency: transitive description: @@ -831,10 +943,10 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: "10259b111176fba5c505b102e3a5b022b51dd97e30522e906d6922c745584745" + sha256: "6ff267fcd9d48cb61c8df74a82680e8b82e940231bb5f68356672fde0397334a" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "4.1.0" package_info_plus_platform_interface: dependency: transitive description: @@ -855,26 +967,26 @@ packages: dependency: "direct main" description: name: path_provider - sha256: "3087813781ab814e4157b172f1a11c46be20179fcc9bea043e0fba36bc0acaa2" + sha256: "909b84830485dbcd0308edf6f7368bc8fd76afa26a270420f34cabea2a6467a0" url: "https://pub.dev" source: hosted - version: "2.0.15" + version: "2.1.0" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "2cec049d282c7f13c594b4a73976b0b4f2d7a1838a6dd5aaf7bd9719196bee86" + sha256: "5d44fc3314d969b84816b569070d7ace0f1dea04bd94a83f74c4829615d22ad8" url: "https://pub.dev" source: hosted - version: "2.0.27" + version: "2.1.0" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "1995d88ec2948dac43edf8fe58eb434d35d22a2940ecee1a9fefcd62beee6eb3" + sha256: "1b744d3d774e5a879bb76d6cd1ecee2ba2c6960c03b1020cd35212f6aa267ac5" url: "https://pub.dev" source: hosted - version: "2.2.3" + version: "2.3.0" path_provider_ios: dependency: "direct main" description: @@ -887,74 +999,66 @@ packages: dependency: transitive description: name: path_provider_linux - sha256: "2ae08f2216225427e64ad224a24354221c2c7907e448e6e0e8b57b1eb9f10ad1" + sha256: ba2b77f0c52a33db09fc8caf85b12df691bf28d983e84cf87ff6d693cfa007b3 url: "https://pub.dev" source: hosted - version: "2.1.10" + version: "2.2.0" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface - sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec" + sha256: bced5679c7df11190e1ddc35f3222c858f328fff85c3942e46e7f5589bf9eb84 url: "https://pub.dev" source: hosted - version: "2.0.6" + version: "2.1.0" path_provider_windows: dependency: transitive description: name: path_provider_windows - sha256: d3f80b32e83ec208ac95253e0cd4d298e104fbc63cb29c5c69edaed43b0c69d6 - url: "https://pub.dev" - source: hosted - version: "2.1.6" - pedantic: - dependency: transitive - description: - name: pedantic - sha256: "67fc27ed9639506c856c840ccce7594d0bdcd91bc8d53d6e52359449a1d50602" + sha256: ee0e0d164516b90ae1f970bdf29f726f1aa730d7cfc449ecc74c495378b705da url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "2.2.0" permission_handler: dependency: "direct main" description: name: permission_handler - sha256: "33c6a1253d1f95fd06fa74b65b7ba907ae9811f9d5c1d3150e51417d04b8d6a8" + sha256: "63e5216aae014a72fe9579ccd027323395ce7a98271d9defa9d57320d001af81" url: "https://pub.dev" source: hosted - version: "10.2.0" + version: "10.4.3" permission_handler_android: dependency: transitive description: name: permission_handler_android - sha256: d8cc6a62ded6d0f49c6eac337e080b066ee3bce4d405bd9439a61e1f1927bfe8 + sha256: "2ffaf52a21f64ac9b35fe7369bb9533edbd4f698e5604db8645b1064ff4cf221" url: "https://pub.dev" source: hosted - version: "10.2.1" + version: "10.3.3" permission_handler_apple: dependency: transitive description: name: permission_handler_apple - sha256: ee96ac32f5a8e6f80756e25b25b9f8e535816c8e6665a96b6d70681f8c4f7e85 + sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5" url: "https://pub.dev" source: hosted - version: "9.0.8" + version: "9.1.4" permission_handler_platform_interface: dependency: transitive description: name: permission_handler_platform_interface - sha256: "68abbc472002b5e6dfce47fe9898c6b7d8328d58b5d2524f75e277c07a97eb84" + sha256: "7c6b1500385dd1d2ca61bb89e2488ca178e274a69144d26bbd65e33eae7c02a9" url: "https://pub.dev" source: hosted - version: "3.9.0" + version: "3.11.3" permission_handler_windows: dependency: transitive description: name: permission_handler_windows - sha256: f67cab14b4328574938ecea2db3475dad7af7ead6afab6338772c5f88963e38b + sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098 url: "https://pub.dev" source: hosted - version: "0.1.2" + version: "0.1.3" petitparser: dependency: transitive description: @@ -967,10 +1071,10 @@ packages: dependency: "direct main" description: name: photo_manager - sha256: bdc4ab1fa9fb064d8ccfea6ab44119f55b220293d7ce2e19eb5a5f998db86c88 + sha256: b2d81bd197323697d1b335e2e04cea2f67e11624ced77cfd02917a10afaeba73 url: "https://pub.dev" source: hosted - version: "2.6.0" + version: "2.7.1" platform: dependency: transitive description: @@ -983,10 +1087,10 @@ packages: dependency: transitive description: name: plugin_platform_interface - sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc" + sha256: "43798d895c929056255600343db8f049921cbec94d31ec87f1dc5c16c01935dd" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" pointycastle: dependency: transitive description: @@ -1055,10 +1159,10 @@ packages: dependency: transitive description: name: riverpod - sha256: "80e48bebc83010d5e67a11c9514af6b44bbac1ec77b4333c8ea65dbc79e2d8ef" + sha256: b0657b5b30c81a3184bdaab353045f0a403ebd60bb381591a8b7ad77dcade793 url: "https://pub.dev" source: hosted - version: "2.3.6" + version: "2.3.7" rxdart: dependency: transitive description: @@ -1079,74 +1183,74 @@ packages: dependency: "direct main" description: name: share_plus - sha256: b1f15232d41e9701ab2f04181f21610c36c83a12ae426b79b4bd011c567934b1 + sha256: "6cec740fa0943a826951223e76218df002804adb588235a8910dc3d6b0654e11" url: "https://pub.dev" source: hosted - version: "6.3.4" + version: "7.1.0" share_plus_platform_interface: dependency: transitive description: name: share_plus_platform_interface - sha256: "0c6e61471bd71b04a138b8b588fa388e66d8b005e6f2deda63371c5c505a0981" + sha256: "357412af4178d8e11d14f41723f80f12caea54cf0d5cd29af9dcdab85d58aea7" url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.3.0" shared_preferences: dependency: transitive description: name: shared_preferences - sha256: "16d3fb6b3692ad244a695c0183fca18cf81fd4b821664394a781de42386bf022" + sha256: "0344316c947ffeb3a529eac929e1978fcd37c26be4e8468628bac399365a3ca1" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.2.0" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "6478c6bbbecfe9aced34c483171e90d7c078f5883558b30ec3163cf18402c749" + sha256: fe8401ec5b6dcd739a0fe9588802069e608c3fdbfd3c3c93e546cf2f90438076 url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: e014107bb79d6d3297196f4f2d0db54b5d1f85b8ea8ff63b8e8b391a02700feb + sha256: d29753996d8eb8f7619a1f13df6ce65e34bc107bef6330739ed76f18b22310ef url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.3.3" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux - sha256: "9d387433ca65717bbf1be88f4d5bb18f10508917a8fa2fb02e0fd0d7479a9afa" + sha256: "71d6806d1449b0a9d4e85e0c7a917771e672a3d5dc61149cc9fac871115018e1" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.0" shared_preferences_platform_interface: dependency: transitive description: name: shared_preferences_platform_interface - sha256: fb5cf25c0235df2d0640ac1b1174f6466bd311f621574997ac59018a6664548d + sha256: "23b052f17a25b90ff2b61aad4cc962154da76fb62848a9ce088efe30d7c50ab1" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.0" shared_preferences_web: dependency: transitive description: name: shared_preferences_web - sha256: "74083203a8eae241e0de4a0d597dbedab3b8fef5563f33cf3c12d7e93c655ca5" + sha256: "7347b194fb0bbeb4058e6a4e87ee70350b6b2b90f8ac5f8bd5b3a01548f6d33a" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.2.0" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows - sha256: "5e588e2efef56916a3b229c3bfe81e6a525665a454519ca51dbcc4236a274173" + sha256: f95e6a43162bce43c9c3405f3eb6f39e5b5d11f65fab19196cf8225e2777624d url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.0" shelf: dependency: transitive description: @@ -1172,10 +1276,10 @@ packages: dependency: "direct main" description: name: socket_io_client - sha256: "5d2a0a12c2a4a5f48d14e5b6aef7db78d3b425a9b084071059fa54bd12d2576c" + sha256: ede469f3e4c55e8528b4e023bdedbc20832e8811ab9b61679d1ba3ed5f01f23b url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.0.3+1" socket_io_common: dependency: transitive description: @@ -1188,34 +1292,34 @@ packages: dependency: transitive description: name: source_gen - sha256: "373f96cf5a8744bc9816c1ff41cf5391bbdbe3d7a96fe98c622b6738a8a7bd33" + sha256: fc0da689e5302edb6177fdd964efcb7f58912f43c28c2047a808f5bfff643d16 url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.4.0" source_span: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" sqflite: dependency: transitive description: name: sqflite - sha256: b4d6710e1200e96845747e37338ea8a819a12b51689a3bcf31eff0003b37a0b9 + sha256: "591f1602816e9c31377d5f008c2d9ef7b8aca8941c3f89cc5fd9d84da0c38a9a" url: "https://pub.dev" source: hosted - version: "2.2.8+4" + version: "2.3.0" sqflite_common: dependency: transitive description: name: sqflite_common - sha256: e77abf6ff961d69dfef41daccbb66b51e9983cdd5cb35bf30733598057401555 + sha256: "1b92f368f44b0dee2425bb861cfa17b6f6cf3961f762ff6f941d20b33355660a" url: "https://pub.dev" source: hosted - version: "2.4.5" + version: "2.5.0" stack_trace: dependency: transitive description: @@ -1228,10 +1332,10 @@ packages: dependency: transitive description: name: state_notifier - sha256: "8fe42610f179b843b12371e40db58c9444f8757f8b69d181c97e50787caed289" + sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb url: "https://pub.dev" source: hosted - version: "0.7.2+1" + version: "1.0.0" stream_channel: dependency: transitive description: @@ -1284,10 +1388,10 @@ packages: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.6.0" time: dependency: transitive description: @@ -1316,10 +1420,10 @@ packages: dependency: transitive description: name: tuple - sha256: "0ea99cd2f9352b2586583ab2ce6489d1f95a5f6de6fb9492faaf97ae2060f0aa" + sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151 url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" typed_data: dependency: transitive description: @@ -1340,26 +1444,26 @@ packages: dependency: transitive description: name: universal_io - sha256: "06866290206d196064fd61df4c7aea1ffe9a4e7c4ccaa8fcded42dd41948005d" + sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.2.2" url_launcher: dependency: "direct main" description: name: url_launcher - sha256: eb1e00ab44303d50dd487aab67ebc575456c146c6af44422f9c13889984c00f3 + sha256: "781bd58a1eb16069412365c98597726cd8810ae27435f04b3b4d3a470bacd61e" url: "https://pub.dev" source: hosted - version: "6.1.11" + version: "6.1.12" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "1a5848f598acc5b7d8f7c18b8cb834ab667e59a13edc3c93e9d09cf38cc6bc87" + sha256: "3dd2388cc0c42912eee04434531a26a82512b9cb1827e0214430c9bcbddfe025" url: "https://pub.dev" source: hosted - version: "6.0.34" + version: "6.0.38" url_launcher_ios: dependency: transitive description: @@ -1380,34 +1484,34 @@ packages: dependency: transitive description: name: url_launcher_macos - sha256: "91ee3e75ea9dadf38036200c5d3743518f4a5eb77a8d13fda1ee5764373f185e" + sha256: "1c4fdc0bfea61a70792ce97157e5cc17260f61abbe4f39354513f39ec6fd73b1" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.6" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - sha256: "6c9ca697a5ae218ce56cece69d46128169a58aa8653c1b01d26fcd4aad8c4370" + sha256: bfdfa402f1f3298637d71ca8ecfe840b4696698213d5346e9d12d4ab647ee2ea url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: "81fe91b6c4f84f222d186a9d23c73157dc4c8e1c71489c4d08be1ad3b228f1aa" + sha256: cc26720eefe98c1b71d85f9dc7ef0cada5132617046369d9dc296b3ecaa5cbb4 url: "https://pub.dev" source: hosted - version: "2.0.16" + version: "2.0.18" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "254708f17f7c20a9c8c471f67d86d76d4a3f9c1591aad1e15292008aceb82771" + sha256: "7967065dd2b5fccc18c653b97958fdf839c5478c28e767c61ee879f4e7882422" url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.0.7" uuid: dependency: transitive description: @@ -1428,34 +1532,34 @@ packages: dependency: "direct main" description: name: video_player - sha256: de95f0e9405f29b5582573d4166132e71f83b3158aac14e8ee5767a54f4f1fbd + sha256: "3fd106c74da32f336dc7feb65021da9b0207cb3124392935f1552834f7cce822" url: "https://pub.dev" source: hosted - version: "2.6.1" + version: "2.7.0" video_player_android: dependency: transitive description: name: video_player_android - sha256: ae1c7d9a71c236a1bf9e567bd7ed4c90887e389a5f233b2192593f7f7395005c + sha256: f338a5a396c845f4632959511cad3542cdf3167e1b2a1a948ef07f7123c03608 url: "https://pub.dev" source: hosted - version: "2.4.8" + version: "2.4.9" video_player_avfoundation: dependency: transitive description: name: video_player_avfoundation - sha256: "4c274e439f349a0ee5cb3c42978393ede173a443b98f50de6ffe6900eaa19216" + sha256: f5f5b7fe8c865be8a57fe80c2dca130772e1db775b7af4e5c5aa1905069cfc6c url: "https://pub.dev" source: hosted - version: "2.4.6" + version: "2.4.9" video_player_platform_interface: dependency: transitive description: name: video_player_platform_interface - sha256: a8c4dcae2a7a6e7cc1d7f9808294d968eca1993af34a98e95b9bdfa959bec684 + sha256: "1ca9acd7a0fb15fb1a990cb554e6f004465c6f37c99d2285766f08a4b2802988" url: "https://pub.dev" source: hosted - version: "6.1.0" + version: "6.2.0" video_player_web: dependency: transitive description: @@ -1468,50 +1572,26 @@ packages: dependency: transitive description: name: vm_service - sha256: f6deed8ed625c52864792459709183da231ebf66ff0cf09e69b573227c377efe + sha256: c620a6f783fa22436da68e42db7ebbf18b8c44b9a46ab911f666ff09ffd9153f url: "https://pub.dev" source: hosted - version: "11.3.0" - wakelock: + version: "11.7.1" + wakelock_plus: dependency: "direct main" description: - name: wakelock - sha256: "769ecf42eb2d07128407b50cb93d7c10bd2ee48f0276ef0119db1d25cc2f87db" + name: wakelock_plus + sha256: aac3f3258f01781ec9212df94eecef1eb9ba9350e106728def405baa096ba413 url: "https://pub.dev" source: hosted - version: "0.6.2" - wakelock_macos: - dependency: transitive - description: - name: wakelock_macos - sha256: "047c6be2f88cb6b76d02553bca5a3a3b95323b15d30867eca53a19a0a319d4cd" - url: "https://pub.dev" - source: hosted - version: "0.4.0" - wakelock_platform_interface: - dependency: transitive - description: - name: wakelock_platform_interface - sha256: "1f4aeb81fb592b863da83d2d0f7b8196067451e4df91046c26b54a403f9de621" - url: "https://pub.dev" - source: hosted - version: "0.3.0" - wakelock_web: - dependency: transitive - description: - name: wakelock_web - sha256: "1b256b811ee3f0834888efddfe03da8d18d0819317f20f6193e2922b41a501b5" - url: "https://pub.dev" - source: hosted - version: "0.4.0" - wakelock_windows: + version: "1.1.1" + wakelock_plus_platform_interface: dependency: transitive description: - name: wakelock_windows - sha256: "857f77b3fe6ae82dd045455baa626bc4b93cb9bb6c86bf3f27c182167c3a5567" + name: wakelock_plus_platform_interface + sha256: "40fabed5da06caff0796dc638e1f07ee395fb18801fbff3255a2372db2d80385" url: "https://pub.dev" source: hosted - version: "0.2.1" + version: "1.1.0" watcher: dependency: transitive description: @@ -1520,6 +1600,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" web_socket_channel: dependency: transitive description: @@ -1540,10 +1628,10 @@ packages: dependency: transitive description: name: win32 - sha256: a6f0236dbda0f63aa9a25ad1ff9a9d8a4eaaa5012da0dc59d21afdb1dc361ca4 + sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "4.1.4" wkt_parser: dependency: transitive description: @@ -1556,10 +1644,10 @@ packages: dependency: transitive description: name: xdg_directories - sha256: ee1505df1426458f7f60aac270645098d318a8b4766d85fde75f76f2e21807d1 + sha256: f0c26453a2d47aa4c2570c6a033246a3fc62da2fe23c7ffdd0a7495086dc0247 url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.0.2" xml: dependency: transitive description: @@ -1585,5 +1673,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.0.0 <4.0.0" - flutter: ">=3.3.0" + dart: ">=3.1.0-185.0.dev <4.0.0" + flutter: ">=3.10.0" diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index dc90a5373c357245570b0213907e75b26bb345ab..1bb5f3738490623fa3af3769463c5e0151197cd8 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -2,11 +2,11 @@ name: immich_mobile description: Immich - selfhosted backup media file on mobile phone publish_to: "none" -version: 1.73.0+96 +version: 1.75.2+98 isar_version: &isar_version 3.1.0+1 environment: - sdk: ">=3.0.0-0 <4.0.0" + sdk: ">=3.0.0 <4.0.0" dependencies: flutter: @@ -20,19 +20,21 @@ dependencies: flutter_cache_manager: ^3.3.0 intl: ^0.18.0 auto_route: ^5.0.1 - fluttertoast: ^8.0.8 + fluttertoast: ^8.2.2 video_player: ^2.2.18 chewie: ^1.4.0 badges: ^2.0.2 socket_io_client: ^2.0.0-beta.4-nullsafety.0 flutter_map: ^4.0.0 + flutter_map_heatmap: ^0.0.4 + geolocator: ^10.0.0 # used to move to current location in map view flutter_udid: ^2.0.0 - package_info_plus: ^3.1.2 + package_info_plus: ^4.1.0 url_launcher: ^6.1.3 http: 0.13.5 cancellation_token_http: ^1.1.0 easy_localization: ^3.0.1 - share_plus: ^6.3.0 + share_plus: ^7.1.0 flutter_displaymode: ^0.4.0 scrollable_positioned_list: ^0.3.4 path: ^1.8.1 @@ -48,7 +50,7 @@ dependencies: device_info_plus: ^8.1.0 connectivity_plus: ^4.0.1 crypto: ^3.0.3 # TODO remove once native crypto is used on iOS - wakelock: ^0.6.2 + wakelock_plus: ^1.1.1 flutter_local_notifications: ^15.1.0+1 openapi: diff --git a/mobile/test/sync_service_test.dart b/mobile/test/sync_service_test.dart index 63b5614bd5fa2502668658801c081c062e4d0761..8f98e88b615d288ccaf65743300bae0e85c40598 100644 --- a/mobile/test/sync_service_test.dart +++ b/mobile/test/sync_service_test.dart @@ -45,7 +45,7 @@ void main() { AlbumSchema, UserSchema, StoreValueSchema, - LoggerMessageSchema + LoggerMessageSchema, ], maxSizeMiB: 256, directory: ".", diff --git a/nginx/10-listen-on-ipv6-by-default.sh b/nginx/10-listen-on-ipv6-by-default.sh index f836aadd0ee1bc6657e1cdeed7e98912c6b7585e..e4d85c7cfa81d2fcb26855211c5890d1020d79fd 100755 --- a/nginx/10-listen-on-ipv6-by-default.sh +++ b/nginx/10-listen-on-ipv6-by-default.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/usr/bin/env sh # vim:sw=4:ts=4:et set -e diff --git a/nginx/15-set-env-variables.envsh b/nginx/15-set-env-variables.envsh index 664de1a20455bcd812915026be6d4c62eaa402b3..5d4acb1ba7f3cf75ea94fbadabf9dec8029084ca 100755 --- a/nginx/15-set-env-variables.envsh +++ b/nginx/15-set-env-variables.envsh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/usr/bin/env sh set -e export IMMICH_WEB_URL="${IMMICH_WEB_URL:-http://immich-web:3000}" diff --git a/server/Dockerfile b/server/Dockerfile index 2146c7cc8dd138d735f3b2dbbef7346368179e8f..4199cc89a70392173cb9133d1ce29a778ca9b9a9 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -13,7 +13,7 @@ mesa-va-drivers libmimalloc2.0 $(if [ $(arch) = "x86_64" ]; then echo "intel-med # debian build for imagemagick has broken RAW support, so build manually ENV LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH ENV LD_RUN_PATH=/usr/local/lib:$LD_RUN_PATH -COPY bin/build-libraw.sh bin/build-imagemagick.sh bin/build-libvips.sh ./ +COPY bin/build-libraw.sh bin/build-imagemagick.sh bin/build-libvips.sh bin/use-camera-wb.patch ./ RUN ./build-libraw.sh RUN ./build-imagemagick.sh RUN ./build-libvips.sh diff --git a/server/bin/admin-cli.sh b/server/bin/admin-cli.sh new file mode 100755 index 0000000000000000000000000000000000000000..b63e331eb08bf3802fb0b64ff2cf6099b16b3542 --- /dev/null +++ b/server/bin/admin-cli.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env sh +./start.sh admin-cli $1 diff --git a/server/bin/build-imagemagick.sh b/server/bin/build-imagemagick.sh index 01225eb46a0fbd9311896758630c7b230f3579b1..e4a57a211841f3c888be4a79cfefa7583e41b832 100755 --- a/server/bin/build-imagemagick.sh +++ b/server/bin/build-imagemagick.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e @@ -13,6 +13,7 @@ sha256sum -c imagemagick.sha256 tar -xvf ${IMAGEMAGICK_VERSION}.tar.gz -C ImageMagick --strip-components=1 rm ${IMAGEMAGICK_VERSION}.tar.gz rm imagemagick.sha256 +patch -u ImageMagick/coders/dng.c -i use-camera-wb.patch cd ImageMagick ./configure --with-modules make -j$(nproc) diff --git a/server/bin/build-libraw.sh b/server/bin/build-libraw.sh index 510753faed82554564d5e9392b091746c8b44f53..50bc049ea13d884a728b3792d9c0afeabc5400d6 100755 --- a/server/bin/build-libraw.sh +++ b/server/bin/build-libraw.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e diff --git a/server/bin/build-libvips.sh b/server/bin/build-libvips.sh index bed60e415eade9de8b4826183b5d11d89e4d75c0..2c5bc55baae34e625255eefa240f96ba6eda780b 100755 --- a/server/bin/build-libvips.sh +++ b/server/bin/build-libvips.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e diff --git a/server/bin/cli.sh b/server/bin/cli.sh index 9f3783805e891f5e12b375c3fbfb40139a91c69f..148954d9ac7bfb16cbb67762b3ea5e702a54951c 100755 --- a/server/bin/cli.sh +++ b/server/bin/cli.sh @@ -1 +1,2 @@ +#!/usr/bin/env bash node ./node_modules/immich/bin/index "$@" diff --git a/server/bin/install-ffmpeg.sh b/server/bin/install-ffmpeg.sh index 8e60b3b70f51563373312898a12913b8dc39ca75..46b9e51ef8ff5582baafce6fc03d4521a0544120 100755 --- a/server/bin/install-ffmpeg.sh +++ b/server/bin/install-ffmpeg.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e diff --git a/server/bin/use-camera-wb.patch b/server/bin/use-camera-wb.patch new file mode 100755 index 0000000000000000000000000000000000000000..507afeb3e6c295f618ddddd7d304c82ac81e782c --- /dev/null +++ b/server/bin/use-camera-wb.patch @@ -0,0 +1,9 @@ +@@ -339,6 +339,8 @@ + option=GetImageOption(image_info,"dng:use_camera_wb"); + if (option != (const char *) NULL) + raw_info->params.use_camera_wb=IsStringTrue(option); ++ else ++ raw_info->params.use_camera_wb=MagickTrue; + option=GetImageOption(image_info,"dng:use-auto-wb"); + if (option == (const char *) NULL) + option=GetImageOption(image_info,"dng:use_auto_wb"); diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index f193eb25e4a71cbd4eb129c77bc476adda8649c8..29af0fe58694c56b13670ae1a5d4121ab00b0e16 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -753,20 +753,20 @@ } }, { - "name": "withoutThumbs", + "name": "skip", "required": false, "in": "query", - "description": "Include assets without thumbnails", "schema": { - "type": "boolean" + "type": "number" } }, { - "name": "skip", + "name": "updatedAfter", "required": false, "in": "query", "schema": { - "type": "number" + "format": "date-time", + "type": "string" } }, { @@ -808,6 +808,39 @@ "tags": [ "Asset" ] + }, + "put": { + "operationId": "updateAssets", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AssetBulkUpdateDto" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "" + } + }, + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ], + "tags": [ + "Asset" + ] } }, "/asset/assetById/{id}": { @@ -1334,6 +1367,41 @@ ] } }, + "/asset/jobs": { + "post": { + "operationId": "runAssetJobs", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AssetJobsDto" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "" + } + }, + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ], + "tags": [ + "Asset" + ] + } + }, "/asset/map-marker": { "get": { "operationId": "getMapMarkers", @@ -2003,6 +2071,65 @@ ] } }, + "/audit/deletes": { + "get": { + "operationId": "getAuditDeletes", + "parameters": [ + { + "name": "entityType", + "required": true, + "in": "query", + "schema": { + "$ref": "#/components/schemas/EntityType" + } + }, + { + "name": "userId", + "required": false, + "in": "query", + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "name": "after", + "required": true, + "in": "query", + "schema": { + "format": "date-time", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuditDeletesResponseDto" + } + } + }, + "description": "" + } + }, + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ], + "tags": [ + "Audit" + ] + } + }, "/auth/admin-sign-up": { "post": { "operationId": "adminSignUp", @@ -3116,16 +3243,19 @@ ] } }, - "/search/config": { + "/search/explore": { "get": { - "operationId": "getSearchConfig", + "operationId": "getExploreData", "parameters": [], "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SearchConfigResponseDto" + "items": { + "$ref": "#/components/schemas/SearchExploreResponseDto" + }, + "type": "array" } } }, @@ -3148,19 +3278,16 @@ ] } }, - "/search/explore": { + "/server-info": { "get": { - "operationId": "getExploreData", + "operationId": "getServerInfo", "parameters": [], "responses": { "200": { "content": { "application/json": { "schema": { - "items": { - "$ref": "#/components/schemas/SearchExploreResponseDto" - }, - "type": "array" + "$ref": "#/components/schemas/ServerInfoResponseDto" } } }, @@ -3179,37 +3306,26 @@ } ], "tags": [ - "Search" + "Server Info" ] } }, - "/server-info": { + "/server-info/features": { "get": { - "operationId": "getServerInfo", + "operationId": "getServerFeatures", "parameters": [], "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ServerInfoResponseDto" + "$ref": "#/components/schemas/ServerFeaturesDto" } } }, "description": "" } }, - "security": [ - { - "bearer": [] - }, - { - "cookie": [] - }, - { - "api_key": [] - } - ], "tags": [ "Server Info" ] @@ -3298,7 +3414,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ServerVersionReponseDto" + "$ref": "#/components/schemas/ServerVersionResponseDto" } } }, @@ -4564,7 +4680,7 @@ "info": { "title": "Immich", "description": "Immich API", - "version": "1.73.0", + "version": "1.75.2", "contact": {} }, "tags": [], @@ -4841,6 +4957,27 @@ ], "type": "object" }, + "AssetBulkUpdateDto": { + "properties": { + "ids": { + "items": { + "format": "uuid", + "type": "string" + }, + "type": "array" + }, + "isArchived": { + "type": "boolean" + }, + "isFavorite": { + "type": "boolean" + } + }, + "required": [ + "ids" + ], + "type": "object" + }, "AssetBulkUploadCheckDto": { "properties": { "assets": { @@ -4967,6 +5104,33 @@ ], "type": "object" }, + "AssetJobName": { + "enum": [ + "regenerate-thumbnail", + "refresh-metadata", + "transcode-video" + ], + "type": "string" + }, + "AssetJobsDto": { + "properties": { + "assetIds": { + "items": { + "format": "uuid", + "type": "string" + }, + "type": "array" + }, + "name": { + "$ref": "#/components/schemas/AssetJobName" + } + }, + "required": [ + "assetIds", + "name" + ], + "type": "object" + }, "AssetResponseDto": { "properties": { "checksum": { @@ -5102,6 +5266,24 @@ ], "type": "string" }, + "AuditDeletesResponseDto": { + "properties": { + "ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "needsFullSync": { + "type": "boolean" + } + }, + "required": [ + "needsFullSync", + "ids" + ], + "type": "object" + }, "AuthDeviceResponseDto": { "properties": { "createdAt": { @@ -5564,6 +5746,13 @@ ], "type": "object" }, + "EntityType": { + "enum": [ + "ASSET", + "ALBUM" + ], + "type": "string" + }, "ExifResponseDto": { "properties": { "city": { @@ -6039,6 +6228,12 @@ }, "PeopleUpdateItem": { "properties": { + "birthDate": { + "description": "Person date of birth.", + "format": "date", + "nullable": true, + "type": "string" + }, "featureFaceAssetId": { "description": "Asset is used to get the feature face thumbnail.", "type": "string" @@ -6063,6 +6258,11 @@ }, "PersonResponseDto": { "properties": { + "birthDate": { + "format": "date", + "nullable": true, + "type": "string" + }, "id": { "type": "string" }, @@ -6077,6 +6277,7 @@ } }, "required": [ + "birthDate", "id", "name", "thumbnailPath", @@ -6086,6 +6287,12 @@ }, "PersonUpdateDto": { "properties": { + "birthDate": { + "description": "Person date of birth.", + "format": "date", + "nullable": true, + "type": "string" + }, "featureFaceAssetId": { "description": "Asset is used to get the feature face thumbnail.", "type": "string" @@ -6185,17 +6392,6 @@ ], "type": "object" }, - "SearchConfigResponseDto": { - "properties": { - "enabled": { - "type": "boolean" - } - }, - "required": [ - "enabled" - ], - "type": "object" - }, "SearchExploreItem": { "properties": { "data": { @@ -6277,6 +6473,49 @@ ], "type": "object" }, + "ServerFeaturesDto": { + "properties": { + "clipEncode": { + "type": "boolean" + }, + "configFile": { + "type": "boolean" + }, + "facialRecognition": { + "type": "boolean" + }, + "oauth": { + "type": "boolean" + }, + "oauthAutoLaunch": { + "type": "boolean" + }, + "passwordLogin": { + "type": "boolean" + }, + "search": { + "type": "boolean" + }, + "sidecar": { + "type": "boolean" + }, + "tagImage": { + "type": "boolean" + } + }, + "required": [ + "configFile", + "clipEncode", + "facialRecognition", + "sidecar", + "search", + "tagImage", + "oauth", + "oauthAutoLaunch", + "passwordLogin" + ], + "type": "object" + }, "ServerInfoResponseDto": { "properties": { "diskAvailable": { @@ -6396,7 +6635,7 @@ ], "type": "object" }, - "ServerVersionReponseDto": { + "ServerVersionResponseDto": { "properties": { "major": { "type": "integer" @@ -6602,6 +6841,9 @@ "job": { "$ref": "#/components/schemas/SystemConfigJobDto" }, + "machineLearning": { + "$ref": "#/components/schemas/SystemConfigMachineLearningDto" + }, "oauth": { "$ref": "#/components/schemas/SystemConfigOAuthDto" }, @@ -6617,6 +6859,7 @@ }, "required": [ "ffmpeg", + "machineLearning", "oauth", "passwordLogin", "storageTemplate", @@ -6723,6 +6966,33 @@ ], "type": "object" }, + "SystemConfigMachineLearningDto": { + "properties": { + "clipEncodeEnabled": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + }, + "facialRecognitionEnabled": { + "type": "boolean" + }, + "tagImageEnabled": { + "type": "boolean" + }, + "url": { + "type": "string" + } + }, + "required": [ + "enabled", + "url", + "clipEncodeEnabled", + "facialRecognitionEnabled", + "tagImageEnabled" + ], + "type": "object" + }, "SystemConfigOAuthDto": { "properties": { "autoLaunch": { diff --git a/server/package-lock.json b/server/package-lock.json index e6e2de6aba3786d77042c0860d5c929003ddbdd0..df1e0b9ab1792a047a1734e29912fe34cf1aceb9 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -1,12 +1,12 @@ { "name": "immich", - "version": "1.73.0", + "version": "1.75.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "immich", - "version": "1.73.0", + "version": "1.75.2", "license": "UNLICENSED", "dependencies": { "@babel/runtime": "^7.20.13", @@ -54,7 +54,7 @@ }, "bin": { "immich": "bin/cli.sh", - "immich-admin": "start.sh admin-cli" + "immich-admin": "bin/admin-cli.sh" }, "devDependencies": { "@nestjs/cli": "^9.1.8", diff --git a/server/package.json b/server/package.json index cdfb87d59ac63049121d600845b8eb5e055aae20..4f79e2dc1c7c35fc0b94807531efb7c5984c3576 100644 --- a/server/package.json +++ b/server/package.json @@ -1,13 +1,13 @@ { "name": "immich", - "version": "1.73.0", + "version": "1.75.2", "description": "", "author": "", "private": true, "license": "UNLICENSED", "bin": { "immich": "./bin/cli.sh", - "immich-admin": "./start.sh admin-cli" + "immich-admin": "./bin/admin-cli.sh" }, "scripts": { "build": "nest build", diff --git a/server/src/domain/asset/asset.repository.ts b/server/src/domain/asset/asset.repository.ts index faf21361e23af3de80512072bc0a7285e653d8da..d1e7a8fe64cec07102cbbc9c15a06b1c93d78a5a 100644 --- a/server/src/domain/asset/asset.repository.ts +++ b/server/src/domain/asset/asset.repository.ts @@ -79,6 +79,7 @@ export interface IAssetRepository { getLastUpdatedAssetForAlbumId(albumId: string): Promise; deleteAll(ownerId: string): Promise; getAll(pagination: PaginationOptions, options?: AssetSearchOptions): Paginated; + updateAll(ids: string[], options: Partial): Promise; save(asset: Partial): Promise; findLivePhotoMatch(options: LivePhotoSearchOptions): Promise; getMapMarkers(ownerId: string, options?: MapMarkerSearchOptions): Promise; diff --git a/server/src/domain/asset/asset.service.spec.ts b/server/src/domain/asset/asset.service.spec.ts index 4c91ce3425231fccb3d09ea618ca222a006a0b48..9de81a4f073ed524237fdd95e7e061abf068a9bb 100644 --- a/server/src/domain/asset/asset.service.spec.ts +++ b/server/src/domain/asset/asset.service.spec.ts @@ -7,15 +7,17 @@ import { newAccessRepositoryMock, newAssetRepositoryMock, newCryptoRepositoryMock, + newJobRepositoryMock, newStorageRepositoryMock, } from '@test'; import { when } from 'jest-when'; import { Readable } from 'stream'; import { ICryptoRepository } from '../crypto'; +import { IJobRepository, JobName } from '../job'; import { IStorageRepository } from '../storage'; import { AssetStats, IAssetRepository } from './asset.repository'; import { AssetService, UploadFieldName } from './asset.service'; -import { AssetStatsResponseDto, DownloadResponseDto } from './dto'; +import { AssetJobName, AssetStatsResponseDto, DownloadResponseDto } from './dto'; import { mapAsset } from './response-dto'; const downloadResponse: DownloadResponseDto = { @@ -145,6 +147,7 @@ describe(AssetService.name, () => { let accessMock: IAccessRepositoryMock; let assetMock: jest.Mocked; let cryptoMock: jest.Mocked; + let jobMock: jest.Mocked; let storageMock: jest.Mocked; it('should work', () => { @@ -155,8 +158,9 @@ describe(AssetService.name, () => { accessMock = newAccessRepositoryMock(); assetMock = newAssetRepositoryMock(); cryptoMock = newCryptoRepositoryMock(); + jobMock = newJobRepositoryMock(); storageMock = newStorageRepositoryMock(); - sut = new AssetService(accessMock, assetMock, cryptoMock, storageMock); + sut = new AssetService(accessMock, assetMock, cryptoMock, jobMock, storageMock); }); describe('canUpload', () => { @@ -514,4 +518,42 @@ describe(AssetService.name, () => { expect(assetMock.getStatistics).toHaveBeenCalledWith(authStub.admin.id, {}); }); }); + + describe('updateAll', () => { + it('should require asset write access for all ids', async () => { + accessMock.asset.hasOwnerAccess.mockResolvedValue(false); + await expect( + sut.updateAll(authStub.admin, { + ids: ['asset-1'], + isArchived: false, + }), + ).rejects.toBeInstanceOf(BadRequestException); + }); + + it('should update all assets', async () => { + accessMock.asset.hasOwnerAccess.mockResolvedValue(true); + await sut.updateAll(authStub.admin, { ids: ['asset-1', 'asset-2'], isArchived: true }); + expect(assetMock.updateAll).toHaveBeenCalledWith(['asset-1', 'asset-2'], { isArchived: true }); + }); + }); + + describe('run', () => { + it('should run the refresh metadata job', async () => { + accessMock.asset.hasOwnerAccess.mockResolvedValue(true); + await sut.run(authStub.admin, { assetIds: ['asset-1'], name: AssetJobName.REFRESH_METADATA }), + expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.METADATA_EXTRACTION, data: { id: 'asset-1' } }); + }); + + it('should run the refresh thumbnails job', async () => { + accessMock.asset.hasOwnerAccess.mockResolvedValue(true); + await sut.run(authStub.admin, { assetIds: ['asset-1'], name: AssetJobName.REGENERATE_THUMBNAIL }), + expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.GENERATE_JPEG_THUMBNAIL, data: { id: 'asset-1' } }); + }); + + it('should run the transcode video', async () => { + accessMock.asset.hasOwnerAccess.mockResolvedValue(true); + await sut.run(authStub.admin, { assetIds: ['asset-1'], name: AssetJobName.TRANSCODE_VIDEO }), + expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.VIDEO_CONVERSION, data: { id: 'asset-1' } }); + }); + }); }); diff --git a/server/src/domain/asset/asset.service.ts b/server/src/domain/asset/asset.service.ts index 2d4051b0fa042a87ee2cc0a880624339fff6a63b..8f681d9bdb3efd2d94dd52d97cfa0ff60b4d52b4 100644 --- a/server/src/domain/asset/asset.service.ts +++ b/server/src/domain/asset/asset.service.ts @@ -8,22 +8,31 @@ import { AuthUserDto } from '../auth'; import { ICryptoRepository } from '../crypto'; import { mimeTypes } from '../domain.constant'; import { HumanReadableSize, usePagination } from '../domain.util'; +import { IJobRepository, JobName } from '../job'; import { ImmichReadStream, IStorageRepository, StorageCore, StorageFolder } from '../storage'; import { IAssetRepository } from './asset.repository'; import { + AssetBulkUpdateDto, AssetIdsDto, + AssetJobName, + AssetJobsDto, + AssetStatsDto, DownloadArchiveInfo, DownloadInfoDto, DownloadResponseDto, + MapMarkerDto, + mapStats, MemoryLaneDto, TimeBucketAssetDto, TimeBucketDto, } from './dto'; -import { AssetStatsDto, mapStats } from './dto/asset-statistics.dto'; -import { MapMarkerDto } from './dto/map-marker.dto'; -import { AssetResponseDto, mapAsset, MapMarkerResponseDto } from './response-dto'; -import { MemoryLaneResponseDto } from './response-dto/memory-lane-response.dto'; -import { TimeBucketResponseDto } from './response-dto/time-bucket-response.dto'; +import { + AssetResponseDto, + mapAsset, + MapMarkerResponseDto, + MemoryLaneResponseDto, + TimeBucketResponseDto, +} from './response-dto'; export enum UploadFieldName { ASSET_DATA = 'assetData', @@ -53,6 +62,7 @@ export class AssetService { @Inject(IAccessRepository) accessRepository: IAccessRepository, @Inject(IAssetRepository) private assetRepository: IAssetRepository, @Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository, + @Inject(IJobRepository) private jobRepository: IJobRepository, @Inject(IStorageRepository) private storageRepository: IStorageRepository, ) { this.access = new AccessCore(accessRepository); @@ -268,4 +278,30 @@ export class AssetService { const stats = await this.assetRepository.getStatistics(authUser.id, dto); return mapStats(stats); } + + async updateAll(authUser: AuthUserDto, dto: AssetBulkUpdateDto) { + const { ids, ...options } = dto; + await this.access.requirePermission(authUser, Permission.ASSET_UPDATE, ids); + await this.assetRepository.updateAll(ids, options); + } + + async run(authUser: AuthUserDto, dto: AssetJobsDto) { + await this.access.requirePermission(authUser, Permission.ASSET_UPDATE, dto.assetIds); + + for (const id of dto.assetIds) { + switch (dto.name) { + case AssetJobName.REFRESH_METADATA: + await this.jobRepository.queue({ name: JobName.METADATA_EXTRACTION, data: { id } }); + break; + + case AssetJobName.REGENERATE_THUMBNAIL: + await this.jobRepository.queue({ name: JobName.GENERATE_JPEG_THUMBNAIL, data: { id } }); + break; + + case AssetJobName.TRANSCODE_VIDEO: + await this.jobRepository.queue({ name: JobName.VIDEO_CONVERSION, data: { id } }); + break; + } + } + } } diff --git a/server/src/domain/asset/dto/asset-ids.dto.ts b/server/src/domain/asset/dto/asset-ids.dto.ts index 6d2c58528d1c7c59cadc33b3db493d277101aa6a..5ee988bb44ea9ca72c331d4c143072c66ed20f91 100644 --- a/server/src/domain/asset/dto/asset-ids.dto.ts +++ b/server/src/domain/asset/dto/asset-ids.dto.ts @@ -1,6 +1,20 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum } from 'class-validator'; import { ValidateUUID } from '../../domain.util'; export class AssetIdsDto { @ValidateUUID({ each: true }) assetIds!: string[]; } + +export enum AssetJobName { + REGENERATE_THUMBNAIL = 'regenerate-thumbnail', + REFRESH_METADATA = 'refresh-metadata', + TRANSCODE_VIDEO = 'transcode-video', +} + +export class AssetJobsDto extends AssetIdsDto { + @ApiProperty({ enumName: 'AssetJobName', enum: AssetJobName }) + @IsEnum(AssetJobName) + name!: AssetJobName; +} diff --git a/server/src/domain/asset/dto/asset.dto.ts b/server/src/domain/asset/dto/asset.dto.ts new file mode 100644 index 0000000000000000000000000000000000000000..1e4c3faa985486bdc693aefdbfc5d1592e7a84fa --- /dev/null +++ b/server/src/domain/asset/dto/asset.dto.ts @@ -0,0 +1,12 @@ +import { IsBoolean, IsOptional } from 'class-validator'; +import { BulkIdsDto } from '../response-dto'; + +export class AssetBulkUpdateDto extends BulkIdsDto { + @IsOptional() + @IsBoolean() + isFavorite?: boolean; + + @IsOptional() + @IsBoolean() + isArchived?: boolean; +} diff --git a/server/src/domain/asset/dto/index.ts b/server/src/domain/asset/dto/index.ts index 8e9440f027196f1264130ef085fa55044221d487..8e780869a56babfc8b148023706b0edfaed14232 100644 --- a/server/src/domain/asset/dto/index.ts +++ b/server/src/domain/asset/dto/index.ts @@ -1,5 +1,6 @@ export * from './asset-ids.dto'; export * from './asset-statistics.dto'; +export * from './asset.dto'; export * from './download.dto'; export * from './map-marker.dto'; export * from './memory-lane.dto'; diff --git a/server/src/domain/asset/response-dto/asset-response.dto.ts b/server/src/domain/asset/response-dto/asset-response.dto.ts index 226cc77a9e20d5362b4cde5a4f5b871b72c19d9d..1a018c233af96050e31f65963966e15faafd9ac5 100644 --- a/server/src/domain/asset/response-dto/asset-response.dto.ts +++ b/server/src/domain/asset/response-dto/asset-response.dto.ts @@ -84,3 +84,8 @@ export function mapAssetWithoutExif(entity: AssetEntity): AssetResponseDto { checksum: entity.checksum.toString('base64'), }; } + +export class MemoryLaneResponseDto { + title!: string; + assets!: AssetResponseDto[]; +} diff --git a/server/src/domain/asset/response-dto/memory-lane-response.dto.ts b/server/src/domain/asset/response-dto/memory-lane-response.dto.ts deleted file mode 100644 index 875a0d5b756507400a0d59bf11038dd9e330c950..0000000000000000000000000000000000000000 --- a/server/src/domain/asset/response-dto/memory-lane-response.dto.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { AssetResponseDto } from './asset-response.dto'; - -export class MemoryLaneResponseDto { - title!: string; - assets!: AssetResponseDto[]; -} diff --git a/server/src/domain/audit/audi.service.spec.ts b/server/src/domain/audit/audi.service.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..32601caf07a348ec74e77882987e289fa63dc378 --- /dev/null +++ b/server/src/domain/audit/audi.service.spec.ts @@ -0,0 +1,61 @@ +import { DatabaseAction, EntityType } from '@app/infra/entities'; +import { auditStub, authStub, IAccessRepositoryMock, newAccessRepositoryMock, newAuditRepositoryMock } from '@test'; +import { IAuditRepository } from './audit.repository'; +import { AuditService } from './audit.service'; + +describe(AuditService.name, () => { + let sut: AuditService; + let accessMock: IAccessRepositoryMock; + let auditMock: jest.Mocked; + + beforeEach(async () => { + accessMock = newAccessRepositoryMock(); + auditMock = newAuditRepositoryMock(); + sut = new AuditService(accessMock, auditMock); + }); + + it('should work', () => { + expect(sut).toBeDefined(); + }); + + describe('handleCleanup', () => { + it('should delete old audit entries', async () => { + await expect(sut.handleCleanup()).resolves.toBe(true); + expect(auditMock.removeBefore).toBeCalledWith(expect.any(Date)); + }); + }); + + describe('getDeletes', () => { + it('should require full sync if the request is older than 100 days', async () => { + auditMock.getAfter.mockResolvedValue([]); + + const date = new Date(2022, 0, 1); + await expect(sut.getDeletes(authStub.admin, { after: date, entityType: EntityType.ASSET })).resolves.toEqual({ + needsFullSync: true, + ids: [], + }); + + expect(auditMock.getAfter).toHaveBeenCalledWith(date, { + action: DatabaseAction.DELETE, + ownerId: authStub.admin.id, + entityType: EntityType.ASSET, + }); + }); + + it('should get any new or updated assets and deleted ids', async () => { + auditMock.getAfter.mockResolvedValue([auditStub.delete]); + + const date = new Date(); + await expect(sut.getDeletes(authStub.admin, { after: date, entityType: EntityType.ASSET })).resolves.toEqual({ + needsFullSync: false, + ids: ['asset-deleted'], + }); + + expect(auditMock.getAfter).toHaveBeenCalledWith(date, { + action: DatabaseAction.DELETE, + ownerId: authStub.admin.id, + entityType: EntityType.ASSET, + }); + }); + }); +}); diff --git a/server/src/domain/audit/audit.dto.ts b/server/src/domain/audit/audit.dto.ts new file mode 100644 index 0000000000000000000000000000000000000000..2494883e04dd170fd3680d4d6f4233456a17c632 --- /dev/null +++ b/server/src/domain/audit/audit.dto.ts @@ -0,0 +1,24 @@ +import { EntityType } from '@app/infra/entities'; +import { ApiProperty } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { IsDate, IsEnum, IsOptional, IsUUID } from 'class-validator'; + +export class AuditDeletesDto { + @IsDate() + @Type(() => Date) + after!: Date; + + @ApiProperty({ enum: EntityType, enumName: 'EntityType' }) + @IsEnum(EntityType) + entityType!: EntityType; + + @IsOptional() + @IsUUID('4') + @ApiProperty({ format: 'uuid' }) + userId?: string; +} + +export class AuditDeletesResponseDto { + needsFullSync!: boolean; + ids!: string[]; +} diff --git a/server/src/domain/audit/audit.repository.ts b/server/src/domain/audit/audit.repository.ts new file mode 100644 index 0000000000000000000000000000000000000000..774ab1e4224211c1eccb4c5c68436b9b2c5fd218 --- /dev/null +++ b/server/src/domain/audit/audit.repository.ts @@ -0,0 +1,14 @@ +import { AuditEntity, DatabaseAction, EntityType } from '@app/infra/entities'; + +export const IAuditRepository = 'IAuditRepository'; + +export interface AuditSearch { + action?: DatabaseAction; + entityType?: EntityType; + ownerId?: string; +} + +export interface IAuditRepository { + getAfter(since: Date, options: AuditSearch): Promise; + removeBefore(before: Date): Promise; +} diff --git a/server/src/domain/audit/audit.service.ts b/server/src/domain/audit/audit.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..47d98e68865b1f3d518b68598a80e589c6987103 --- /dev/null +++ b/server/src/domain/audit/audit.service.ts @@ -0,0 +1,43 @@ +import { DatabaseAction } from '@app/infra/entities'; +import { Inject, Injectable } from '@nestjs/common'; +import { DateTime } from 'luxon'; +import { AccessCore, IAccessRepository, Permission } from '../access'; +import { AuthUserDto } from '../auth'; +import { AUDIT_LOG_MAX_DURATION } from '../domain.constant'; +import { AuditDeletesDto, AuditDeletesResponseDto } from './audit.dto'; +import { IAuditRepository } from './audit.repository'; + +@Injectable() +export class AuditService { + private access: AccessCore; + + constructor( + @Inject(IAccessRepository) accessRepository: IAccessRepository, + @Inject(IAuditRepository) private repository: IAuditRepository, + ) { + this.access = new AccessCore(accessRepository); + } + + async handleCleanup(): Promise { + await this.repository.removeBefore(DateTime.now().minus(AUDIT_LOG_MAX_DURATION).toJSDate()); + return true; + } + + async getDeletes(authUser: AuthUserDto, dto: AuditDeletesDto): Promise { + const userId = dto.userId || authUser.id; + await this.access.requirePermission(authUser, Permission.LIBRARY_READ, userId); + + const audits = await this.repository.getAfter(dto.after, { + ownerId: userId, + entityType: dto.entityType, + action: DatabaseAction.DELETE, + }); + + const duration = DateTime.now().diff(DateTime.fromJSDate(dto.after)); + + return { + needsFullSync: duration > AUDIT_LOG_MAX_DURATION, + ids: audits.map(({ entityId }) => entityId), + }; + } +} diff --git a/server/src/domain/audit/index.ts b/server/src/domain/audit/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..2074b86f385747cb66a65230785694fdcfd07f30 --- /dev/null +++ b/server/src/domain/audit/index.ts @@ -0,0 +1,3 @@ +export * from './audit.dto'; +export * from './audit.repository'; +export * from './audit.service'; diff --git a/server/src/domain/domain.constant.spec.ts b/server/src/domain/domain.constant.spec.ts index 36cec5e1b6a7889ec4da11f201a64a8e54cfb088..aa958d5820e038d8195f97b27376ef4c4807b5f5 100644 --- a/server/src/domain/domain.constant.spec.ts +++ b/server/src/domain/domain.constant.spec.ts @@ -37,6 +37,7 @@ describe('mimeTypes', () => { { mimetype: 'image/sr2', extension: '.sr2' }, { mimetype: 'image/srf', extension: '.srf' }, { mimetype: 'image/srw', extension: '.srw' }, + { mimetype: 'image/tiff', extension: '.tif' }, { mimetype: 'image/tiff', extension: '.tiff' }, { mimetype: 'image/webp', extension: '.webp' }, { mimetype: 'image/x-adobe-dng', extension: '.dng' }, @@ -82,7 +83,7 @@ describe('mimeTypes', () => { { mimetype: 'video/x-ms-wmv', extension: '.wmv' }, { mimetype: 'video/x-msvideo', extension: '.avi' }, ]) { - it(`should map ${extension} to ${mimetype}`, async () => { + it(`should map ${extension} to ${mimetype}`, () => { expect({ ...mimeTypes.image, ...mimeTypes.video }[extension]).toContain(mimetype); }); } diff --git a/server/src/domain/domain.constant.ts b/server/src/domain/domain.constant.ts index b06720613cc8c732eb6f324a8f8d9e1446b00779..2e076ad217b26fbcdd6a8385a1e80b84ab58ffcf 100644 --- a/server/src/domain/domain.constant.ts +++ b/server/src/domain/domain.constant.ts @@ -1,8 +1,10 @@ import { AssetType } from '@app/infra/entities'; -import { BadRequestException } from '@nestjs/common'; +import { Duration } from 'luxon'; import { extname } from 'node:path'; import pkg from 'src/../../package.json'; +export const AUDIT_LOG_MAX_DURATION = Duration.fromObject({ days: 100 }); + const [major, minor, patch] = pkg.version.split('.'); export interface IServerVersion { @@ -21,15 +23,6 @@ export const SERVER_VERSION = `${serverVersion.major}.${serverVersion.minor}.${s export const APP_MEDIA_LOCATION = process.env.IMMICH_MEDIA_LOCATION || './upload'; -export const MACHINE_LEARNING_URL = process.env.IMMICH_MACHINE_LEARNING_URL || 'http://immich-machine-learning:3003'; -export const MACHINE_LEARNING_ENABLED = MACHINE_LEARNING_URL !== 'false'; - -export function assertMachineLearningEnabled() { - if (!MACHINE_LEARNING_ENABLED) { - throw new BadRequestException('Machine learning is not enabled.'); - } -} - const image: Record = { '.3fr': ['image/3fr', 'image/x-hasselblad-3fr'], '.ari': ['image/ari', 'image/x-arriflex-ari'], @@ -66,6 +59,7 @@ const image: Record = { '.sr2': ['image/sr2', 'image/x-sony-sr2'], '.srf': ['image/srf', 'image/x-sony-srf'], '.srw': ['image/srw', 'image/x-samsung-srw'], + '.tif': ['image/tiff'], '.tiff': ['image/tiff'], '.webp': ['image/webp'], '.x3f': ['image/x3f', 'image/x-sigma-x3f'], diff --git a/server/src/domain/domain.module.ts b/server/src/domain/domain.module.ts index 72d53008362292b8d5e404d0fd0c6b00a735d021..a2efd8796a92f2788a07b1d754295d9573921d15 100644 --- a/server/src/domain/domain.module.ts +++ b/server/src/domain/domain.module.ts @@ -2,6 +2,7 @@ import { DynamicModule, Global, Module, ModuleMetadata, OnApplicationShutdown, P import { AlbumService } from './album'; import { APIKeyService } from './api-key'; import { AssetService } from './asset'; +import { AuditService } from './audit'; import { AuthService } from './auth'; import { FacialRecognitionService } from './facial-recognition'; import { JobService } from './job'; @@ -23,6 +24,7 @@ const providers: Provider[] = [ AlbumService, APIKeyService, AssetService, + AuditService, AuthService, FacialRecognitionService, JobService, diff --git a/server/src/domain/facial-recognition/facial-recognition.service.spec.ts b/server/src/domain/facial-recognition/facial-recognition.service.spec.ts index 537d5e5fe6f1ad0b9670599209bfeb5d5c2dadc6..3f57dc9bf6dbe73f0adb5f6f98e2385a31f09622 100644 --- a/server/src/domain/facial-recognition/facial-recognition.service.spec.ts +++ b/server/src/domain/facial-recognition/facial-recognition.service.spec.ts @@ -9,6 +9,7 @@ import { newPersonRepositoryMock, newSearchRepositoryMock, newStorageRepositoryMock, + newSystemConfigRepositoryMock, personStub, } from '@test'; import { IAssetRepository, WithoutProperty } from '../asset'; @@ -18,6 +19,7 @@ import { IPersonRepository } from '../person'; import { ISearchRepository } from '../search'; import { IMachineLearningRepository } from '../smart-info'; import { IStorageRepository } from '../storage'; +import { ISystemConfigRepository } from '../system-config'; import { IFaceRepository } from './face.repository'; import { FacialRecognitionService } from './facial-recognition.services'; @@ -94,6 +96,7 @@ const faceSearch = { describe(FacialRecognitionService.name, () => { let sut: FacialRecognitionService; let assetMock: jest.Mocked; + let configMock: jest.Mocked; let faceMock: jest.Mocked; let jobMock: jest.Mocked; let machineLearningMock: jest.Mocked; @@ -104,6 +107,7 @@ describe(FacialRecognitionService.name, () => { beforeEach(async () => { assetMock = newAssetRepositoryMock(); + configMock = newSystemConfigRepositoryMock(); faceMock = newFaceRepositoryMock(); jobMock = newJobRepositoryMock(); machineLearningMock = newMachineLearningRepositoryMock(); @@ -116,6 +120,7 @@ describe(FacialRecognitionService.name, () => { sut = new FacialRecognitionService( assetMock, + configMock, faceMock, jobMock, machineLearningMock, @@ -174,7 +179,7 @@ describe(FacialRecognitionService.name, () => { machineLearningMock.detectFaces.mockResolvedValue([]); assetMock.getByIds.mockResolvedValue([assetStub.image]); await sut.handleRecognizeFaces({ id: assetStub.image.id }); - expect(machineLearningMock.detectFaces).toHaveBeenCalledWith({ + expect(machineLearningMock.detectFaces).toHaveBeenCalledWith('http://immich-machine-learning:3003', { imagePath: assetStub.image.resizePath, }); expect(faceMock.create).not.toHaveBeenCalled(); diff --git a/server/src/domain/facial-recognition/facial-recognition.services.ts b/server/src/domain/facial-recognition/facial-recognition.services.ts index 90dd4a646f2c00645ca9690be63b9efa5a219eb9..68886d1f2b6357b3e44d005d7f56dde6c6a1071c 100644 --- a/server/src/domain/facial-recognition/facial-recognition.services.ts +++ b/server/src/domain/facial-recognition/facial-recognition.services.ts @@ -1,7 +1,6 @@ import { Inject, Logger } from '@nestjs/common'; import { join } from 'path'; import { IAssetRepository, WithoutProperty } from '../asset'; -import { MACHINE_LEARNING_ENABLED } from '../domain.constant'; import { usePagination } from '../domain.util'; import { IBaseJob, IEntityJob, IFaceThumbnailJob, IJobRepository, JobName, JOBS_ASSET_PAGINATION_SIZE } from '../job'; import { CropOptions, FACE_THUMBNAIL_SIZE, IMediaRepository } from '../media'; @@ -9,14 +8,17 @@ import { IPersonRepository } from '../person/person.repository'; import { ISearchRepository } from '../search/search.repository'; import { IMachineLearningRepository } from '../smart-info'; import { IStorageRepository, StorageCore, StorageFolder } from '../storage'; +import { ISystemConfigRepository, SystemConfigCore } from '../system-config'; import { AssetFaceId, IFaceRepository } from './face.repository'; export class FacialRecognitionService { private logger = new Logger(FacialRecognitionService.name); private storageCore = new StorageCore(); + private configCore: SystemConfigCore; constructor( @Inject(IAssetRepository) private assetRepository: IAssetRepository, + @Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository, @Inject(IFaceRepository) private faceRepository: IFaceRepository, @Inject(IJobRepository) private jobRepository: IJobRepository, @Inject(IMachineLearningRepository) private machineLearning: IMachineLearningRepository, @@ -24,9 +26,16 @@ export class FacialRecognitionService { @Inject(IPersonRepository) private personRepository: IPersonRepository, @Inject(ISearchRepository) private searchRepository: ISearchRepository, @Inject(IStorageRepository) private storageRepository: IStorageRepository, - ) {} + ) { + this.configCore = new SystemConfigCore(configRepository); + } async handleQueueRecognizeFaces({ force }: IBaseJob) { + const { machineLearning } = await this.configCore.getConfig(); + if (!machineLearning.enabled || !machineLearning.facialRecognitionEnabled) { + return true; + } + const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => { return force ? this.assetRepository.getAll(pagination, { order: 'DESC' }) @@ -49,12 +58,17 @@ export class FacialRecognitionService { } async handleRecognizeFaces({ id }: IEntityJob) { + const { machineLearning } = await this.configCore.getConfig(); + if (!machineLearning.enabled || !machineLearning.facialRecognitionEnabled) { + return true; + } + const [asset] = await this.assetRepository.getByIds([id]); - if (!asset || !MACHINE_LEARNING_ENABLED || !asset.resizePath) { + if (!asset || !asset.resizePath) { return false; } - const faces = await this.machineLearning.detectFaces({ imagePath: asset.resizePath }); + const faces = await this.machineLearning.detectFaces(machineLearning.url, { imagePath: asset.resizePath }); this.logger.debug(`${faces.length} faces detected in ${asset.resizePath}`); this.logger.verbose(faces.map((face) => ({ ...face, embedding: `float[${face.embedding.length}]` }))); @@ -100,6 +114,11 @@ export class FacialRecognitionService { } async handleGenerateFaceThumbnail(data: IFaceThumbnailJob) { + const { machineLearning } = await this.configCore.getConfig(); + if (!machineLearning.enabled || !machineLearning.facialRecognitionEnabled) { + return true; + } + const { assetId, personId, boundingBox, imageWidth, imageHeight } = data; const [asset] = await this.assetRepository.getByIds([assetId]); diff --git a/server/src/domain/index.ts b/server/src/domain/index.ts index 201e31ff31d7dba63d92b1004889dd6feced2d29..c66c4eadc5d7fd5d5009512b0016e3d030c66f83 100644 --- a/server/src/domain/index.ts +++ b/server/src/domain/index.ts @@ -2,6 +2,7 @@ export * from './access'; export * from './album'; export * from './api-key'; export * from './asset'; +export * from './audit'; export * from './auth'; export * from './communication'; export * from './crypto'; diff --git a/server/src/domain/job/job.constants.ts b/server/src/domain/job/job.constants.ts index 02fa588c9b0e78b09b4f7fa53bb50d3886acb589..7062ab86b60632a9caaec995d2fb6192bcd8f851 100644 --- a/server/src/domain/job/job.constants.ts +++ b/server/src/domain/job/job.constants.ts @@ -55,6 +55,7 @@ export enum JobName { // cleanup DELETE_FILES = 'delete-files', + CLEAN_OLD_AUDIT_LOGS = 'clean-old-audit-logs', // search SEARCH_INDEX_ASSETS = 'search-index-assets', @@ -84,6 +85,7 @@ export const JOBS_TO_QUEUE: Record = { [JobName.USER_DELETE_CHECK]: QueueName.BACKGROUND_TASK, [JobName.USER_DELETION]: QueueName.BACKGROUND_TASK, [JobName.DELETE_FILES]: QueueName.BACKGROUND_TASK, + [JobName.CLEAN_OLD_AUDIT_LOGS]: QueueName.BACKGROUND_TASK, [JobName.PERSON_CLEANUP]: QueueName.BACKGROUND_TASK, // conversion diff --git a/server/src/domain/job/job.repository.ts b/server/src/domain/job/job.repository.ts index f605bef4b4d976a50394abc38164c12009ccc5da..a452ad4f9bb2d7eccbe8ba6daa6b6e20695be722 100644 --- a/server/src/domain/job/job.repository.ts +++ b/server/src/domain/job/job.repository.ts @@ -68,6 +68,9 @@ export type JobItem = // Filesystem | { name: JobName.DELETE_FILES; data: IDeleteFilesJob } + // Audit log cleanup + | { name: JobName.CLEAN_OLD_AUDIT_LOGS; data?: IBaseJob } + // Asset Deletion | { name: JobName.PERSON_CLEANUP; data?: IBaseJob } diff --git a/server/src/domain/job/job.service.spec.ts b/server/src/domain/job/job.service.spec.ts index 503440a5cf7f8d42e6d9a923c3a87abb3f1b0801..f8a323bbba7cd9b90725da7a8e84dce481dbe09a 100644 --- a/server/src/domain/job/job.service.spec.ts +++ b/server/src/domain/job/job.service.spec.ts @@ -51,6 +51,7 @@ describe(JobService.name, () => { [{ name: JobName.USER_DELETE_CHECK }], [{ name: JobName.PERSON_CLEANUP }], [{ name: JobName.QUEUE_GENERATE_THUMBNAILS, data: { force: false } }], + [{ name: JobName.CLEAN_OLD_AUDIT_LOGS }], ]); }); }); diff --git a/server/src/domain/job/job.service.ts b/server/src/domain/job/job.service.ts index 1c82908911cb431160d45767c67810d2e0f7ef6f..7f151689f8d608947fabd53f23d318a0a4a5126e 100644 --- a/server/src/domain/job/job.service.ts +++ b/server/src/domain/job/job.service.ts @@ -2,8 +2,7 @@ import { AssetType } from '@app/infra/entities'; import { BadRequestException, Inject, Injectable, Logger } from '@nestjs/common'; import { IAssetRepository, mapAsset } from '../asset'; import { CommunicationEvent, ICommunicationRepository } from '../communication'; -import { assertMachineLearningEnabled } from '../domain.constant'; -import { ISystemConfigRepository } from '../system-config'; +import { FeatureFlag, ISystemConfigRepository } from '../system-config'; import { SystemConfigCore } from '../system-config/system-config.core'; import { JobCommand, JobName, QueueName } from './job.constants'; import { AllJobStatusResponseDto, JobCommandDto, JobStatusDto } from './job.dto'; @@ -78,23 +77,25 @@ export class JobService { return this.jobRepository.queue({ name: JobName.STORAGE_TEMPLATE_MIGRATION }); case QueueName.OBJECT_TAGGING: - assertMachineLearningEnabled(); + await this.configCore.requireFeature(FeatureFlag.TAG_IMAGE); return this.jobRepository.queue({ name: JobName.QUEUE_OBJECT_TAGGING, data: { force } }); case QueueName.CLIP_ENCODING: - assertMachineLearningEnabled(); + await this.configCore.requireFeature(FeatureFlag.CLIP_ENCODE); return this.jobRepository.queue({ name: JobName.QUEUE_ENCODE_CLIP, data: { force } }); case QueueName.METADATA_EXTRACTION: return this.jobRepository.queue({ name: JobName.QUEUE_METADATA_EXTRACTION, data: { force } }); case QueueName.SIDECAR: + await this.configCore.requireFeature(FeatureFlag.SIDECAR); return this.jobRepository.queue({ name: JobName.QUEUE_SIDECAR, data: { force } }); case QueueName.THUMBNAIL_GENERATION: return this.jobRepository.queue({ name: JobName.QUEUE_GENERATE_THUMBNAILS, data: { force } }); case QueueName.RECOGNIZE_FACES: + await this.configCore.requireFeature(FeatureFlag.FACIAL_RECOGNITION); return this.jobRepository.queue({ name: JobName.QUEUE_RECOGNIZE_FACES, data: { force } }); default: @@ -136,6 +137,7 @@ export class JobService { await this.jobRepository.queue({ name: JobName.USER_DELETE_CHECK }); await this.jobRepository.queue({ name: JobName.PERSON_CLEANUP }); await this.jobRepository.queue({ name: JobName.QUEUE_GENERATE_THUMBNAILS, data: { force: false } }); + await this.jobRepository.queue({ name: JobName.CLEAN_OLD_AUDIT_LOGS }); } /** diff --git a/server/src/domain/person/person.dto.ts b/server/src/domain/person/person.dto.ts index f9557fbaeab190d428f337c06e358cb23921a7a9..71fe0bd41f05c6382548e08077ffb7e4e5e196ab 100644 --- a/server/src/domain/person/person.dto.ts +++ b/server/src/domain/person/person.dto.ts @@ -1,7 +1,16 @@ import { AssetFaceEntity, PersonEntity } from '@app/infra/entities'; import { ApiProperty } from '@nestjs/swagger'; import { Transform, Type } from 'class-transformer'; -import { IsArray, IsBoolean, IsNotEmpty, IsOptional, IsString, ValidateNested } from 'class-validator'; +import { + IsArray, + IsBoolean, + IsDate, + IsNotEmpty, + IsOptional, + IsString, + ValidateIf, + ValidateNested, +} from 'class-validator'; import { toBoolean, ValidateUUID } from '../domain.util'; export class PersonUpdateDto { @@ -12,6 +21,16 @@ export class PersonUpdateDto { @IsString() name?: string; + /** + * Person date of birth. + */ + @IsOptional() + @IsDate() + @Type(() => Date) + @ValidateIf((value) => value !== null) + @ApiProperty({ format: 'date' }) + birthDate?: Date | null; + /** * Asset is used to get the feature face thumbnail. */ @@ -49,6 +68,15 @@ export class PeopleUpdateItem { @IsString() name?: string; + /** + * Person date of birth. + */ + @IsOptional() + @IsDate() + @Type(() => Date) + @ApiProperty({ format: 'date' }) + birthDate?: Date | null; + /** * Asset is used to get the feature face thumbnail. */ @@ -78,6 +106,8 @@ export class PersonSearchDto { export class PersonResponseDto { id!: string; name!: string; + @ApiProperty({ format: 'date' }) + birthDate!: Date | null; thumbnailPath!: string; isHidden!: boolean; } @@ -96,6 +126,7 @@ export function mapPerson(person: PersonEntity): PersonResponseDto { return { id: person.id, name: person.name, + birthDate: person.birthDate, thumbnailPath: person.thumbnailPath, isHidden: person.isHidden, }; diff --git a/server/src/domain/person/person.service.spec.ts b/server/src/domain/person/person.service.spec.ts index e5bca7c830d3910e072e740f4e4c2369264b8ff9..b75bea23f8694ac43c1d28e7337d6a190166bee1 100644 --- a/server/src/domain/person/person.service.spec.ts +++ b/server/src/domain/person/person.service.spec.ts @@ -18,6 +18,7 @@ import { PersonService } from './person.service'; const responseDto: PersonResponseDto = { id: 'person-1', name: 'Person 1', + birthDate: null, thumbnailPath: '/path/to/thumbnail.jpg', isHidden: false, }; @@ -68,6 +69,7 @@ describe(PersonService.name, () => { { id: 'person-1', name: '', + birthDate: null, thumbnailPath: '/path/to/thumbnail.jpg', isHidden: true, }, @@ -142,6 +144,24 @@ describe(PersonService.name, () => { }); }); + it("should update a person's date of birth", async () => { + personMock.getById.mockResolvedValue(personStub.noBirthDate); + personMock.update.mockResolvedValue(personStub.withBirthDate); + personMock.getAssets.mockResolvedValue([assetStub.image]); + + await expect(sut.update(authStub.admin, 'person-1', { birthDate: new Date('1976-06-30') })).resolves.toEqual({ + id: 'person-1', + name: 'Person 1', + birthDate: new Date('1976-06-30'), + thumbnailPath: '/path/to/thumbnail.jpg', + isHidden: false, + }); + + expect(personMock.getById).toHaveBeenCalledWith('admin_id', 'person-1'); + expect(personMock.update).toHaveBeenCalledWith({ id: 'person-1', birthDate: new Date('1976-06-30') }); + expect(jobMock.queue).not.toHaveBeenCalled(); + }); + it('should update a person visibility', async () => { personMock.getById.mockResolvedValue(personStub.hidden); personMock.update.mockResolvedValue(personStub.withName); diff --git a/server/src/domain/person/person.service.ts b/server/src/domain/person/person.service.ts index 187ef3358d5712f35dbc3db1ceef305ac51a146c..07a41400b322853526c54c9aea3d7ef6e7631ae4 100644 --- a/server/src/domain/person/person.service.ts +++ b/server/src/domain/person/person.service.ts @@ -63,11 +63,13 @@ export class PersonService { async update(authUser: AuthUserDto, id: string, dto: PersonUpdateDto): Promise { let person = await this.findOrFail(authUser, id); - if (dto.name != undefined || dto.isHidden !== undefined) { - person = await this.repository.update({ id, name: dto.name, isHidden: dto.isHidden }); - const assets = await this.repository.getAssets(authUser.id, id); - const ids = assets.map((asset) => asset.id); - await this.jobRepository.queue({ name: JobName.SEARCH_INDEX_ASSET, data: { ids } }); + if (dto.name !== undefined || dto.birthDate !== undefined || dto.isHidden !== undefined) { + person = await this.repository.update({ id, name: dto.name, birthDate: dto.birthDate, isHidden: dto.isHidden }); + if (this.needsSearchIndexUpdate(dto)) { + const assets = await this.repository.getAssets(authUser.id, id); + const ids = assets.map((asset) => asset.id); + await this.jobRepository.queue({ name: JobName.SEARCH_INDEX_ASSET, data: { ids } }); + } } if (dto.featureFaceAssetId) { @@ -104,6 +106,7 @@ export class PersonService { await this.update(authUser, person.id, { isHidden: person.isHidden, name: person.name, + birthDate: person.birthDate, featureFaceAssetId: person.featureFaceAssetId, }), results.push({ id: person.id, success: true }); @@ -170,6 +173,15 @@ export class PersonService { return results; } + /** + * Returns true if the given person update is going to require an update of the search index. + * @param dto the Person going to be updated + * @private + */ + private needsSearchIndexUpdate(dto: PersonUpdateDto): boolean { + return dto.name !== undefined || dto.isHidden !== undefined; + } + private async findOrFail(authUser: AuthUserDto, id: string) { const person = await this.repository.getById(authUser.id, id); if (!person) { diff --git a/server/src/domain/search/response-dto/index.ts b/server/src/domain/search/response-dto/index.ts index e74cc29b3732bfd64519a54cce73dbc8b1c09165..f48856bca88c3d6ac0308136e182671ec221d01b 100644 --- a/server/src/domain/search/response-dto/index.ts +++ b/server/src/domain/search/response-dto/index.ts @@ -1,3 +1,2 @@ -export * from './search-config-response.dto'; export * from './search-explore.response.dto'; export * from './search-response.dto'; diff --git a/server/src/domain/search/response-dto/search-config-response.dto.ts b/server/src/domain/search/response-dto/search-config-response.dto.ts deleted file mode 100644 index 9f2f3795877a5e7b28c51b5650105cee6a88cfcd..0000000000000000000000000000000000000000 --- a/server/src/domain/search/response-dto/search-config-response.dto.ts +++ /dev/null @@ -1,3 +0,0 @@ -export class SearchConfigResponseDto { - enabled!: boolean; -} diff --git a/server/src/domain/search/search.service.spec.ts b/server/src/domain/search/search.service.spec.ts index 4ffec5832c87fb01856d6e09749f8540f9065afb..d73c269ca49c65075c7e6ccfaf0a5fe4d1f32f47 100644 --- a/server/src/domain/search/search.service.spec.ts +++ b/server/src/domain/search/search.service.spec.ts @@ -1,5 +1,3 @@ -import { BadRequestException } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; import { albumStub, assetStub, @@ -12,12 +10,14 @@ import { newJobRepositoryMock, newMachineLearningRepositoryMock, newSearchRepositoryMock, + newSystemConfigRepositoryMock, searchStub, } from '@test'; import { plainToInstance } from 'class-transformer'; import { IAlbumRepository } from '../album/album.repository'; import { IAssetRepository } from '../asset/asset.repository'; import { IFaceRepository } from '../facial-recognition'; +import { ISystemConfigRepository } from '../index'; import { JobName } from '../job'; import { IJobRepository } from '../job/job.repository'; import { IMachineLearningRepository } from '../smart-info'; @@ -31,29 +31,26 @@ describe(SearchService.name, () => { let sut: SearchService; let albumMock: jest.Mocked; let assetMock: jest.Mocked; + let configMock: jest.Mocked; let faceMock: jest.Mocked; let jobMock: jest.Mocked; let machineMock: jest.Mocked; let searchMock: jest.Mocked; - let configMock: jest.Mocked; - const makeSut = (value?: string) => { - if (value) { - configMock.get.mockReturnValue(value); - } - return new SearchService(albumMock, assetMock, faceMock, jobMock, machineMock, searchMock, configMock); - }; - - beforeEach(() => { + beforeEach(async () => { albumMock = newAlbumRepositoryMock(); assetMock = newAssetRepositoryMock(); + configMock = newSystemConfigRepositoryMock(); faceMock = newFaceRepositoryMock(); jobMock = newJobRepositoryMock(); machineMock = newMachineLearningRepositoryMock(); searchMock = newSearchRepositoryMock(); - configMock = { get: jest.fn() } as unknown as jest.Mocked; - sut = makeSut(); + sut = new SearchService(albumMock, assetMock, configMock, faceMock, jobMock, machineMock, searchMock); + + searchMock.checkMigrationStatus.mockResolvedValue({ assets: false, albums: false, faces: false }); + + await sut.init(); }); afterEach(() => { @@ -86,45 +83,18 @@ describe(SearchService.name, () => { }); }); - describe('isEnabled', () => { - it('should be enabled by default', () => { - expect(sut.isEnabled()).toBe(true); - }); - - it('should be disabled via an env variable', () => { - const sut = makeSut('false'); - - expect(sut.isEnabled()).toBe(false); - }); - }); - - describe('getConfig', () => { - it('should return the config', () => { - expect(sut.getConfig()).toEqual({ enabled: true }); - }); - - it('should return the config when search is disabled', () => { - const sut = makeSut('false'); - - expect(sut.getConfig()).toEqual({ enabled: false }); - }); - }); - describe(`init`, () => { - it('should skip when search is disabled', async () => { - const sut = makeSut('false'); + // it('should skip when search is disabled', async () => { + // await sut.init(); - await sut.init(); + // expect(searchMock.setup).not.toHaveBeenCalled(); + // expect(searchMock.checkMigrationStatus).not.toHaveBeenCalled(); + // expect(jobMock.queue).not.toHaveBeenCalled(); - expect(searchMock.setup).not.toHaveBeenCalled(); - expect(searchMock.checkMigrationStatus).not.toHaveBeenCalled(); - expect(jobMock.queue).not.toHaveBeenCalled(); - - sut.teardown(); - }); + // sut.teardown(); + // }); it('should skip schema migration if not needed', async () => { - searchMock.checkMigrationStatus.mockResolvedValue({ assets: false, albums: false, faces: false }); await sut.init(); expect(searchMock.setup).toHaveBeenCalled(); @@ -145,14 +115,14 @@ describe(SearchService.name, () => { }); describe('search', () => { - it('should throw an error is search is disabled', async () => { - const sut = makeSut('false'); + // it('should throw an error is search is disabled', async () => { + // sut['enabled'] = false; - await expect(sut.search(authStub.admin, {})).rejects.toBeInstanceOf(BadRequestException); + // await expect(sut.search(authStub.admin, {})).rejects.toBeInstanceOf(BadRequestException); - expect(searchMock.searchAlbums).not.toHaveBeenCalled(); - expect(searchMock.searchAssets).not.toHaveBeenCalled(); - }); + // expect(searchMock.searchAlbums).not.toHaveBeenCalled(); + // expect(searchMock.searchAssets).not.toHaveBeenCalled(); + // }); it('should search assets and albums', async () => { searchMock.searchAssets.mockResolvedValue(searchStub.emptyResults); @@ -205,7 +175,7 @@ describe(SearchService.name, () => { }); it('should skip if search is disabled', async () => { - const sut = makeSut('false'); + sut['enabled'] = false; await sut.handleIndexAssets(); @@ -216,7 +186,7 @@ describe(SearchService.name, () => { describe('handleIndexAsset', () => { it('should skip if search is disabled', () => { - const sut = makeSut('false'); + sut['enabled'] = false; sut.handleIndexAsset({ ids: [assetStub.image.id] }); }); @@ -227,7 +197,7 @@ describe(SearchService.name, () => { describe('handleIndexAlbums', () => { it('should skip if search is disabled', () => { - const sut = makeSut('false'); + sut['enabled'] = false; sut.handleIndexAlbums(); }); @@ -242,7 +212,7 @@ describe(SearchService.name, () => { describe('handleIndexAlbum', () => { it('should skip if search is disabled', () => { - const sut = makeSut('false'); + sut['enabled'] = false; sut.handleIndexAlbum({ ids: [albumStub.empty.id] }); }); @@ -253,7 +223,7 @@ describe(SearchService.name, () => { describe('handleRemoveAlbum', () => { it('should skip if search is disabled', () => { - const sut = makeSut('false'); + sut['enabled'] = false; sut.handleRemoveAlbum({ ids: ['album1'] }); }); @@ -264,7 +234,7 @@ describe(SearchService.name, () => { describe('handleRemoveAsset', () => { it('should skip if search is disabled', () => { - const sut = makeSut('false'); + sut['enabled'] = false; sut.handleRemoveAsset({ ids: ['asset1'] }); }); @@ -305,7 +275,7 @@ describe(SearchService.name, () => { }); it('should skip if search is disabled', async () => { - const sut = makeSut('false'); + sut['enabled'] = false; await sut.handleIndexFaces(); @@ -315,7 +285,7 @@ describe(SearchService.name, () => { describe('handleIndexAsset', () => { it('should skip if search is disabled', () => { - const sut = makeSut('false'); + sut['enabled'] = false; sut.handleIndexFace({ assetId: 'asset-1', personId: 'person-1' }); expect(searchMock.importFaces).not.toHaveBeenCalled(); @@ -333,7 +303,7 @@ describe(SearchService.name, () => { describe('handleRemoveFace', () => { it('should skip if search is disabled', () => { - const sut = makeSut('false'); + sut['enabled'] = false; sut.handleRemoveFace({ assetId: 'asset-1', personId: 'person-1' }); }); diff --git a/server/src/domain/search/search.service.ts b/server/src/domain/search/search.service.ts index 80236b8d456d252a061fc2a3d6911eb3eba7639f..66dd6ffb0ff92288f15358d8545d9fabccb0a209 100644 --- a/server/src/domain/search/search.service.ts +++ b/server/src/domain/search/search.service.ts @@ -1,18 +1,17 @@ import { AlbumEntity, AssetEntity, AssetFaceEntity } from '@app/infra/entities'; -import { BadRequestException, Inject, Injectable, Logger } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; +import { Inject, Injectable, Logger } from '@nestjs/common'; import { mapAlbumWithAssets } from '../album'; import { IAlbumRepository } from '../album/album.repository'; import { AssetResponseDto, mapAsset } from '../asset'; import { IAssetRepository } from '../asset/asset.repository'; import { AuthUserDto } from '../auth'; -import { MACHINE_LEARNING_ENABLED } from '../domain.constant'; import { usePagination } from '../domain.util'; import { AssetFaceId, IFaceRepository } from '../facial-recognition'; import { IAssetFaceJob, IBulkEntityJob, IJobRepository, JobName, JOBS_ASSET_PAGINATION_SIZE } from '../job'; import { IMachineLearningRepository } from '../smart-info'; +import { FeatureFlag, ISystemConfigRepository, SystemConfigCore } from '../system-config'; import { SearchDto } from './dto'; -import { SearchConfigResponseDto, SearchResponseDto } from './response-dto'; +import { SearchResponseDto } from './response-dto'; import { ISearchRepository, OwnedFaceEntity, @@ -30,8 +29,9 @@ interface SyncQueue { @Injectable() export class SearchService { private logger = new Logger(SearchService.name); - private enabled: boolean; + private enabled = false; private timer: NodeJS.Timer | null = null; + private configCore: SystemConfigCore; private albumQueue: SyncQueue = { upsert: new Set(), @@ -51,16 +51,13 @@ export class SearchService { constructor( @Inject(IAlbumRepository) private albumRepository: IAlbumRepository, @Inject(IAssetRepository) private assetRepository: IAssetRepository, + @Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository, @Inject(IFaceRepository) private faceRepository: IFaceRepository, @Inject(IJobRepository) private jobRepository: IJobRepository, @Inject(IMachineLearningRepository) private machineLearning: IMachineLearningRepository, @Inject(ISearchRepository) private searchRepository: ISearchRepository, - configService: ConfigService, ) { - this.enabled = configService.get('TYPESENSE_ENABLED') !== 'false'; - if (this.enabled) { - this.timer = setInterval(() => this.flush(), 5_000); - } + this.configCore = new SystemConfigCore(configRepository); } teardown() { @@ -70,17 +67,8 @@ export class SearchService { } } - isEnabled() { - return this.enabled; - } - - getConfig(): SearchConfigResponseDto { - return { - enabled: this.enabled, - }; - } - async init() { + this.enabled = await this.configCore.hasFeature(FeatureFlag.SEARCH); if (!this.enabled) { return; } @@ -101,10 +89,13 @@ export class SearchService { this.logger.debug('Queueing job to re-index all faces'); await this.jobRepository.queue({ name: JobName.SEARCH_INDEX_FACES }); } + + this.timer = setInterval(() => this.flush(), 5_000); } async getExploreData(authUser: AuthUserDto): Promise[]> { - this.assertEnabled(); + await this.configCore.requireFeature(FeatureFlag.SEARCH); + const results = await this.searchRepository.explore(authUser.id); const lookup = await this.getLookupMap( results.reduce( @@ -126,16 +117,18 @@ export class SearchService { } async search(authUser: AuthUserDto, dto: SearchDto): Promise { - this.assertEnabled(); + const { machineLearning } = await this.configCore.getConfig(); + await this.configCore.requireFeature(FeatureFlag.SEARCH); const query = dto.q || dto.query || '*'; - const strategy = dto.clip && MACHINE_LEARNING_ENABLED ? SearchStrategy.CLIP : SearchStrategy.TEXT; + const hasClip = machineLearning.enabled && machineLearning.clipEncodeEnabled; + const strategy = dto.clip && hasClip ? SearchStrategy.CLIP : SearchStrategy.TEXT; const filters = { userId: authUser.id, ...dto }; let assets: SearchResult; switch (strategy) { case SearchStrategy.CLIP: - const clip = await this.machineLearning.encodeText(query); + const clip = await this.machineLearning.encodeText(machineLearning.url, query); assets = await this.searchRepository.vectorSearch(clip, filters); break; case SearchStrategy.TEXT: @@ -333,12 +326,6 @@ export class SearchService { } } - private assertEnabled() { - if (!this.enabled) { - throw new BadRequestException('Search is disabled'); - } - } - private async idsToAlbums(ids: string[]): Promise { const entities = await this.albumRepository.getByIds(ids); return this.patchAlbums(entities); diff --git a/server/src/domain/server-info/index.ts b/server/src/domain/server-info/index.ts index 72113665a809d7e29dff506025edf40e7727f5bc..74a46a52b834bc76109dcbfc8c1ad92693d68bf3 100644 --- a/server/src/domain/server-info/index.ts +++ b/server/src/domain/server-info/index.ts @@ -1,2 +1,2 @@ -export * from './response-dto'; +export * from './server-info.dto'; export * from './server-info.service'; diff --git a/server/src/domain/server-info/response-dto/index.ts b/server/src/domain/server-info/response-dto/index.ts deleted file mode 100644 index 47cbd2ff8f84eca8d3054376a9e84635a4deb2f6..0000000000000000000000000000000000000000 --- a/server/src/domain/server-info/response-dto/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './server-info-response.dto'; -export * from './server-ping-response.dto'; -export * from './server-stats-response.dto'; -export * from './server-version-response.dto'; -export * from './usage-by-user-response.dto'; diff --git a/server/src/domain/server-info/response-dto/server-info-response.dto.ts b/server/src/domain/server-info/response-dto/server-info-response.dto.ts deleted file mode 100644 index e844da6899799bd2bcf8881108c7fcb472a38f33..0000000000000000000000000000000000000000 --- a/server/src/domain/server-info/response-dto/server-info-response.dto.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; - -export class ServerInfoResponseDto { - diskSize!: string; - diskUse!: string; - diskAvailable!: string; - - @ApiProperty({ type: 'integer', format: 'int64' }) - diskSizeRaw!: number; - - @ApiProperty({ type: 'integer', format: 'int64' }) - diskUseRaw!: number; - - @ApiProperty({ type: 'integer', format: 'int64' }) - diskAvailableRaw!: number; - - @ApiProperty({ type: 'number', format: 'float' }) - diskUsagePercentage!: number; -} diff --git a/server/src/domain/server-info/response-dto/server-ping-response.dto.ts b/server/src/domain/server-info/response-dto/server-ping-response.dto.ts deleted file mode 100644 index 8b41b4af1b8472a9152d7e86144e3717e652f01d..0000000000000000000000000000000000000000 --- a/server/src/domain/server-info/response-dto/server-ping-response.dto.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ApiResponseProperty } from '@nestjs/swagger'; - -export class ServerPingResponse { - constructor(res: string) { - this.res = res; - } - - @ApiResponseProperty({ type: String, example: 'pong' }) - res!: string; -} diff --git a/server/src/domain/server-info/response-dto/server-stats-response.dto.ts b/server/src/domain/server-info/response-dto/server-stats-response.dto.ts deleted file mode 100644 index 1459ba452ab3ee128f0d09c08d5b1ba4ca299a92..0000000000000000000000000000000000000000 --- a/server/src/domain/server-info/response-dto/server-stats-response.dto.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { UsageByUserDto } from './usage-by-user-response.dto'; - -export class ServerStatsResponseDto { - @ApiProperty({ type: 'integer' }) - photos = 0; - - @ApiProperty({ type: 'integer' }) - videos = 0; - - @ApiProperty({ type: 'integer', format: 'int64' }) - usage = 0; - - @ApiProperty({ - isArray: true, - type: UsageByUserDto, - title: 'Array of usage for each user', - example: [ - { - photos: 1, - videos: 1, - diskUsageRaw: 1, - }, - ], - }) - usageByUser: UsageByUserDto[] = []; -} - -export class ServerMediaTypesResponseDto { - video!: string[]; - image!: string[]; - sidecar!: string[]; -} diff --git a/server/src/domain/server-info/response-dto/server-version-response.dto.ts b/server/src/domain/server-info/response-dto/server-version-response.dto.ts deleted file mode 100644 index 373fa734f893895d6f603e1e25859a89c6bb6446..0000000000000000000000000000000000000000 --- a/server/src/domain/server-info/response-dto/server-version-response.dto.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { IServerVersion } from '@app/domain'; -import { ApiProperty } from '@nestjs/swagger'; - -export class ServerVersionReponseDto implements IServerVersion { - @ApiProperty({ type: 'integer' }) - major!: number; - @ApiProperty({ type: 'integer' }) - minor!: number; - @ApiProperty({ type: 'integer' }) - patch!: number; -} diff --git a/server/src/domain/server-info/response-dto/usage-by-user-response.dto.ts b/server/src/domain/server-info/response-dto/usage-by-user-response.dto.ts deleted file mode 100644 index ac3a8290778840acc41d940a0eac2bb8d10f015b..0000000000000000000000000000000000000000 --- a/server/src/domain/server-info/response-dto/usage-by-user-response.dto.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; - -export class UsageByUserDto { - @ApiProperty({ type: 'string' }) - userId!: string; - @ApiProperty({ type: 'string' }) - userFirstName!: string; - @ApiProperty({ type: 'string' }) - userLastName!: string; - @ApiProperty({ type: 'integer' }) - photos!: number; - @ApiProperty({ type: 'integer' }) - videos!: number; - @ApiProperty({ type: 'integer', format: 'int64' }) - usage!: number; -} diff --git a/server/src/domain/server-info/server-info.dto.ts b/server/src/domain/server-info/server-info.dto.ts new file mode 100644 index 0000000000000000000000000000000000000000..b9cdd181f37cb334bff55232fa43cb5969912743 --- /dev/null +++ b/server/src/domain/server-info/server-info.dto.ts @@ -0,0 +1,94 @@ +import { FeatureFlags, IServerVersion } from '@app/domain'; +import { ApiProperty, ApiResponseProperty } from '@nestjs/swagger'; + +export class ServerPingResponse { + @ApiResponseProperty({ type: String, example: 'pong' }) + res!: string; +} + +export class ServerInfoResponseDto { + diskSize!: string; + diskUse!: string; + diskAvailable!: string; + + @ApiProperty({ type: 'integer', format: 'int64' }) + diskSizeRaw!: number; + + @ApiProperty({ type: 'integer', format: 'int64' }) + diskUseRaw!: number; + + @ApiProperty({ type: 'integer', format: 'int64' }) + diskAvailableRaw!: number; + + @ApiProperty({ type: 'number', format: 'float' }) + diskUsagePercentage!: number; +} + +export class ServerVersionResponseDto implements IServerVersion { + @ApiProperty({ type: 'integer' }) + major!: number; + @ApiProperty({ type: 'integer' }) + minor!: number; + @ApiProperty({ type: 'integer' }) + patch!: number; +} + +export class UsageByUserDto { + @ApiProperty({ type: 'string' }) + userId!: string; + @ApiProperty({ type: 'string' }) + userFirstName!: string; + @ApiProperty({ type: 'string' }) + userLastName!: string; + @ApiProperty({ type: 'integer' }) + photos!: number; + @ApiProperty({ type: 'integer' }) + videos!: number; + @ApiProperty({ type: 'integer', format: 'int64' }) + usage!: number; +} + +export class ServerStatsResponseDto { + @ApiProperty({ type: 'integer' }) + photos = 0; + + @ApiProperty({ type: 'integer' }) + videos = 0; + + @ApiProperty({ type: 'integer', format: 'int64' }) + usage = 0; + + @ApiProperty({ + isArray: true, + type: UsageByUserDto, + title: 'Array of usage for each user', + example: [ + { + photos: 1, + videos: 1, + diskUsageRaw: 1, + }, + ], + }) + usageByUser: UsageByUserDto[] = []; +} + +export class ServerMediaTypesResponseDto { + video!: string[]; + image!: string[]; + sidecar!: string[]; +} + +export class ServerFeaturesDto implements FeatureFlags { + configFile!: boolean; + clipEncode!: boolean; + facialRecognition!: boolean; + sidecar!: boolean; + search!: boolean; + tagImage!: boolean; + + // TODO: use these instead of `POST oauth/config` + oauth!: boolean; + oauthAutoLaunch!: boolean; + passwordLogin!: boolean; +} diff --git a/server/src/domain/server-info/server-info.service.spec.ts b/server/src/domain/server-info/server-info.service.spec.ts index ebb0d800fbdd3cccc51e890123e5978687b586e5..6c8d194641dad359d93f5597f40a448bccc40360 100644 --- a/server/src/domain/server-info/server-info.service.spec.ts +++ b/server/src/domain/server-info/server-info.service.spec.ts @@ -1,19 +1,22 @@ -import { newStorageRepositoryMock, newUserRepositoryMock } from '@test'; +import { newStorageRepositoryMock, newSystemConfigRepositoryMock, newUserRepositoryMock } from '@test'; import { serverVersion } from '../domain.constant'; +import { ISystemConfigRepository } from '../index'; import { IStorageRepository } from '../storage'; import { IUserRepository } from '../user'; import { ServerInfoService } from './server-info.service'; describe(ServerInfoService.name, () => { let sut: ServerInfoService; + let configMock: jest.Mocked; let storageMock: jest.Mocked; let userMock: jest.Mocked; beforeEach(() => { + configMock = newSystemConfigRepositoryMock(); storageMock = newStorageRepositoryMock(); userMock = newUserRepositoryMock(); - sut = new ServerInfoService(userMock, storageMock); + sut = new ServerInfoService(configMock, userMock, storageMock); }); it('should work', () => { @@ -140,6 +143,23 @@ describe(ServerInfoService.name, () => { it('should respond the server version', () => { expect(sut.getVersion()).toEqual(serverVersion); }); + + describe('getFeatures', () => { + it('should respond the server features', async () => { + await expect(sut.getFeatures()).resolves.toEqual({ + clipEncode: true, + facialRecognition: true, + oauth: false, + oauthAutoLaunch: false, + passwordLogin: true, + search: true, + sidecar: true, + tagImage: true, + configFile: false, + }); + expect(configMock.load).toHaveBeenCalled(); + }); + }); }); describe('getStats', () => { diff --git a/server/src/domain/server-info/server-info.service.ts b/server/src/domain/server-info/server-info.service.ts index 4a7e7f22bb1a7c21ae26a7f58de05fb4f444cc59..655b21603ef904677f9e3ec11f4bc3a9662bf900 100644 --- a/server/src/domain/server-info/server-info.service.ts +++ b/server/src/domain/server-info/server-info.service.ts @@ -2,23 +2,29 @@ import { Inject, Injectable } from '@nestjs/common'; import { mimeTypes, serverVersion } from '../domain.constant'; import { asHumanReadable } from '../domain.util'; import { IStorageRepository, StorageCore, StorageFolder } from '../storage'; +import { ISystemConfigRepository, SystemConfigCore } from '../system-config'; import { IUserRepository, UserStatsQueryResponse } from '../user'; import { + ServerFeaturesDto, ServerInfoResponseDto, ServerMediaTypesResponseDto, ServerPingResponse, ServerStatsResponseDto, UsageByUserDto, -} from './response-dto'; +} from './server-info.dto'; @Injectable() export class ServerInfoService { private storageCore = new StorageCore(); + private configCore: SystemConfigCore; constructor( + @Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository, @Inject(IUserRepository) private userRepository: IUserRepository, @Inject(IStorageRepository) private storageRepository: IStorageRepository, - ) {} + ) { + this.configCore = new SystemConfigCore(configRepository); + } async getInfo(): Promise { const libraryBase = this.storageCore.getBaseFolder(StorageFolder.LIBRARY); @@ -38,13 +44,17 @@ export class ServerInfoService { } ping(): ServerPingResponse { - return new ServerPingResponse('pong'); + return { res: 'pong' }; } getVersion() { return serverVersion; } + getFeatures(): Promise { + return this.configCore.getFeatures(); + } + async getStats(): Promise { const userStats: UserStatsQueryResponse[] = await this.userRepository.getUserStats(); const serverStats = new ServerStatsResponseDto(); diff --git a/server/src/domain/smart-info/machine-learning.interface.ts b/server/src/domain/smart-info/machine-learning.interface.ts index 3f7b9b2d82756fcc048ace9d2cc75043d996eb56..7c431fd5f38872b8506ae41e0e64ed139923ebb9 100644 --- a/server/src/domain/smart-info/machine-learning.interface.ts +++ b/server/src/domain/smart-info/machine-learning.interface.ts @@ -20,8 +20,8 @@ export interface DetectFaceResult { } export interface IMachineLearningRepository { - classifyImage(input: MachineLearningInput): Promise; - encodeImage(input: MachineLearningInput): Promise; - encodeText(input: string): Promise; - detectFaces(input: MachineLearningInput): Promise; + classifyImage(url: string, input: MachineLearningInput): Promise; + encodeImage(url: string, input: MachineLearningInput): Promise; + encodeText(url: string, input: string): Promise; + detectFaces(url: string, input: MachineLearningInput): Promise; } diff --git a/server/src/domain/smart-info/smart-info.service.spec.ts b/server/src/domain/smart-info/smart-info.service.spec.ts index f6464cb021bb0236546a979ebd11b3ff183a4ad8..7461058e292dbf9f66d017f56b0159c497f55d8b 100644 --- a/server/src/domain/smart-info/smart-info.service.spec.ts +++ b/server/src/domain/smart-info/smart-info.service.spec.ts @@ -5,9 +5,11 @@ import { newJobRepositoryMock, newMachineLearningRepositoryMock, newSmartInfoRepositoryMock, + newSystemConfigRepositoryMock, } from '@test'; import { IAssetRepository, WithoutProperty } from '../asset'; import { IJobRepository, JobName } from '../job'; +import { ISystemConfigRepository } from '../system-config'; import { IMachineLearningRepository } from './machine-learning.interface'; import { ISmartInfoRepository } from './smart-info.repository'; import { SmartInfoService } from './smart-info.service'; @@ -20,16 +22,18 @@ const asset = { describe(SmartInfoService.name, () => { let sut: SmartInfoService; let assetMock: jest.Mocked; + let configMock: jest.Mocked; let jobMock: jest.Mocked; let smartMock: jest.Mocked; let machineMock: jest.Mocked; beforeEach(async () => { assetMock = newAssetRepositoryMock(); + configMock = newSystemConfigRepositoryMock(); smartMock = newSmartInfoRepositoryMock(); jobMock = newJobRepositoryMock(); machineMock = newMachineLearningRepositoryMock(); - sut = new SmartInfoService(assetMock, jobMock, smartMock, machineMock); + sut = new SmartInfoService(assetMock, configMock, jobMock, smartMock, machineMock); assetMock.getByIds.mockResolvedValue([asset]); }); @@ -80,7 +84,9 @@ describe(SmartInfoService.name, () => { await sut.handleClassifyImage({ id: asset.id }); - expect(machineMock.classifyImage).toHaveBeenCalledWith({ imagePath: 'path/to/resize.ext' }); + expect(machineMock.classifyImage).toHaveBeenCalledWith('http://immich-machine-learning:3003', { + imagePath: 'path/to/resize.ext', + }); expect(smartMock.upsert).toHaveBeenCalledWith({ assetId: 'asset-1', tags: ['tag1', 'tag2', 'tag3'], @@ -139,7 +145,9 @@ describe(SmartInfoService.name, () => { await sut.handleEncodeClip({ id: asset.id }); - expect(machineMock.encodeImage).toHaveBeenCalledWith({ imagePath: 'path/to/resize.ext' }); + expect(machineMock.encodeImage).toHaveBeenCalledWith('http://immich-machine-learning:3003', { + imagePath: 'path/to/resize.ext', + }); expect(smartMock.upsert).toHaveBeenCalledWith({ assetId: 'asset-1', clipEmbedding: [0.01, 0.02, 0.03], diff --git a/server/src/domain/smart-info/smart-info.service.ts b/server/src/domain/smart-info/smart-info.service.ts index c1341a04b602129da703933d931129190800b948..2512c4c32dd3f6718ea01adadcd98a6857dc4f28 100644 --- a/server/src/domain/smart-info/smart-info.service.ts +++ b/server/src/domain/smart-info/smart-info.service.ts @@ -1,23 +1,31 @@ -import { Inject, Injectable, Logger } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import { IAssetRepository, WithoutProperty } from '../asset'; -import { MACHINE_LEARNING_ENABLED } from '../domain.constant'; import { usePagination } from '../domain.util'; import { IBaseJob, IEntityJob, IJobRepository, JobName, JOBS_ASSET_PAGINATION_SIZE } from '../job'; +import { ISystemConfigRepository, SystemConfigCore } from '../system-config'; import { IMachineLearningRepository } from './machine-learning.interface'; import { ISmartInfoRepository } from './smart-info.repository'; @Injectable() export class SmartInfoService { - private logger = new Logger(SmartInfoService.name); + private configCore: SystemConfigCore; constructor( @Inject(IAssetRepository) private assetRepository: IAssetRepository, + @Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository, @Inject(IJobRepository) private jobRepository: IJobRepository, @Inject(ISmartInfoRepository) private repository: ISmartInfoRepository, @Inject(IMachineLearningRepository) private machineLearning: IMachineLearningRepository, - ) {} + ) { + this.configCore = new SystemConfigCore(configRepository); + } async handleQueueObjectTagging({ force }: IBaseJob) { + const { machineLearning } = await this.configCore.getConfig(); + if (!machineLearning.enabled || !machineLearning.tagImageEnabled) { + return true; + } + const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => { return force ? this.assetRepository.getAll(pagination) @@ -34,19 +42,28 @@ export class SmartInfoService { } async handleClassifyImage({ id }: IEntityJob) { - const [asset] = await this.assetRepository.getByIds([id]); + const { machineLearning } = await this.configCore.getConfig(); + if (!machineLearning.enabled || !machineLearning.tagImageEnabled) { + return true; + } - if (!MACHINE_LEARNING_ENABLED || !asset.resizePath) { + const [asset] = await this.assetRepository.getByIds([id]); + if (!asset.resizePath) { return false; } - const tags = await this.machineLearning.classifyImage({ imagePath: asset.resizePath }); + const tags = await this.machineLearning.classifyImage(machineLearning.url, { imagePath: asset.resizePath }); await this.repository.upsert({ assetId: asset.id, tags }); return true; } async handleQueueEncodeClip({ force }: IBaseJob) { + const { machineLearning } = await this.configCore.getConfig(); + if (!machineLearning.enabled || !machineLearning.clipEncodeEnabled) { + return true; + } + const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => { return force ? this.assetRepository.getAll(pagination) @@ -63,13 +80,17 @@ export class SmartInfoService { } async handleEncodeClip({ id }: IEntityJob) { - const [asset] = await this.assetRepository.getByIds([id]); + const { machineLearning } = await this.configCore.getConfig(); + if (!machineLearning.enabled || !machineLearning.clipEncodeEnabled) { + return true; + } - if (!MACHINE_LEARNING_ENABLED || !asset.resizePath) { + const [asset] = await this.assetRepository.getByIds([id]); + if (!asset.resizePath) { return false; } - const clipEmbedding = await this.machineLearning.encodeImage({ imagePath: asset.resizePath }); + const clipEmbedding = await this.machineLearning.encodeImage(machineLearning.url, { imagePath: asset.resizePath }); await this.repository.upsert({ assetId: asset.id, clipEmbedding: clipEmbedding }); return true; diff --git a/server/src/domain/system-config/dto/system-config-machine-learning.dto.ts b/server/src/domain/system-config/dto/system-config-machine-learning.dto.ts new file mode 100644 index 0000000000000000000000000000000000000000..b4063669d3859dca4171c4972fc55e31668d763f --- /dev/null +++ b/server/src/domain/system-config/dto/system-config-machine-learning.dto.ts @@ -0,0 +1,19 @@ +import { IsBoolean, IsUrl, ValidateIf } from 'class-validator'; + +export class SystemConfigMachineLearningDto { + @IsBoolean() + enabled!: boolean; + + @IsUrl({ require_tld: false }) + @ValidateIf((dto) => dto.enabled) + url!: string; + + @IsBoolean() + clipEncodeEnabled!: boolean; + + @IsBoolean() + facialRecognitionEnabled!: boolean; + + @IsBoolean() + tagImageEnabled!: boolean; +} diff --git a/server/src/domain/system-config/dto/system-config.dto.ts b/server/src/domain/system-config/dto/system-config.dto.ts index f34ebf7100f2e311c08e7284e4b5d22ec9fb38ab..c089da3df38c6072d24ae7f48f3617890fe9fa68 100644 --- a/server/src/domain/system-config/dto/system-config.dto.ts +++ b/server/src/domain/system-config/dto/system-config.dto.ts @@ -4,16 +4,22 @@ import { Type } from 'class-transformer'; import { IsObject, ValidateNested } from 'class-validator'; import { SystemConfigFFmpegDto } from './system-config-ffmpeg.dto'; import { SystemConfigJobDto } from './system-config-job.dto'; +import { SystemConfigMachineLearningDto } from './system-config-machine-learning.dto'; import { SystemConfigOAuthDto } from './system-config-oauth.dto'; import { SystemConfigPasswordLoginDto } from './system-config-password-login.dto'; import { SystemConfigStorageTemplateDto } from './system-config-storage-template.dto'; -export class SystemConfigDto { +export class SystemConfigDto implements SystemConfig { @Type(() => SystemConfigFFmpegDto) @ValidateNested() @IsObject() ffmpeg!: SystemConfigFFmpegDto; + @Type(() => SystemConfigMachineLearningDto) + @ValidateNested() + @IsObject() + machineLearning!: SystemConfigMachineLearningDto; + @Type(() => SystemConfigOAuthDto) @ValidateNested() @IsObject() diff --git a/server/src/domain/system-config/index.ts b/server/src/domain/system-config/index.ts index e5a685a30f4a1b4c145b837600faef2a90c8785d..da270886b059e81a23852a6452cefac758d0cb8d 100644 --- a/server/src/domain/system-config/index.ts +++ b/server/src/domain/system-config/index.ts @@ -1,5 +1,6 @@ export * from './dto'; export * from './response-dto'; export * from './system-config.constants'; +export * from './system-config.core'; export * from './system-config.repository'; export * from './system-config.service'; diff --git a/server/src/domain/system-config/system-config.core.ts b/server/src/domain/system-config/system-config.core.ts index 80f650571cfd8441322834ebf464f767feddaad8..5fa55a7092bf1ddd63344c1ec3d3e6378b6336ac 100644 --- a/server/src/domain/system-config/system-config.core.ts +++ b/server/src/domain/system-config/system-config.core.ts @@ -9,11 +9,14 @@ import { TranscodePolicy, VideoCodec, } from '@app/infra/entities'; -import { BadRequestException, Injectable, Logger } from '@nestjs/common'; +import { BadRequestException, ForbiddenException, Injectable, Logger } from '@nestjs/common'; +import { plainToClass } from 'class-transformer'; +import { validate } from 'class-validator'; import * as _ from 'lodash'; import { Subject } from 'rxjs'; import { DeepPartial } from 'typeorm'; import { QueueName } from '../job/job.constants'; +import { SystemConfigDto } from './dto'; import { ISystemConfigRepository } from './system-config.repository'; export type SystemConfigValidator = (config: SystemConfig) => void | Promise; @@ -44,6 +47,13 @@ export const defaults = Object.freeze({ [QueueName.THUMBNAIL_GENERATION]: { concurrency: 5 }, [QueueName.VIDEO_CONVERSION]: { concurrency: 1 }, }, + machineLearning: { + enabled: process.env.IMMICH_MACHINE_LEARNING_ENABLED !== 'false', + url: process.env.IMMICH_MACHINE_LEARNING_URL || 'http://immich-machine-learning:3003', + facialRecognitionEnabled: true, + tagImageEnabled: true, + clipEncodeEnabled: true, + }, oauth: { enabled: false, issuerUrl: '', @@ -71,17 +81,82 @@ export const defaults = Object.freeze({ }, }); +export enum FeatureFlag { + CLIP_ENCODE = 'clipEncode', + FACIAL_RECOGNITION = 'facialRecognition', + TAG_IMAGE = 'tagImage', + SIDECAR = 'sidecar', + SEARCH = 'search', + OAUTH = 'oauth', + OAUTH_AUTO_LAUNCH = 'oauthAutoLaunch', + PASSWORD_LOGIN = 'passwordLogin', + CONFIG_FILE = 'configFile', +} + +export type FeatureFlags = Record; + const singleton = new Subject(); @Injectable() export class SystemConfigCore { private logger = new Logger(SystemConfigCore.name); private validators: SystemConfigValidator[] = []; + private configCache: SystemConfig | null = null; public config$ = singleton; constructor(private repository: ISystemConfigRepository) {} + async requireFeature(feature: FeatureFlag) { + const hasFeature = await this.hasFeature(feature); + if (!hasFeature) { + switch (feature) { + case FeatureFlag.CLIP_ENCODE: + throw new BadRequestException('Clip encoding is not enabled'); + case FeatureFlag.FACIAL_RECOGNITION: + throw new BadRequestException('Facial recognition is not enabled'); + case FeatureFlag.TAG_IMAGE: + throw new BadRequestException('Image tagging is not enabled'); + case FeatureFlag.SIDECAR: + throw new BadRequestException('Sidecar is not enabled'); + case FeatureFlag.SEARCH: + throw new BadRequestException('Search is not enabled'); + case FeatureFlag.OAUTH: + throw new BadRequestException('OAuth is not enabled'); + case FeatureFlag.PASSWORD_LOGIN: + throw new BadRequestException('Password login is not enabled'); + case FeatureFlag.CONFIG_FILE: + throw new BadRequestException('Config file is not set'); + default: + throw new ForbiddenException(`Missing required feature: ${feature}`); + } + } + } + + async hasFeature(feature: FeatureFlag) { + const features = await this.getFeatures(); + return features[feature] ?? false; + } + + async getFeatures(): Promise { + const config = await this.getConfig(); + const mlEnabled = config.machineLearning.enabled; + + return { + [FeatureFlag.CLIP_ENCODE]: mlEnabled && config.machineLearning.clipEncodeEnabled, + [FeatureFlag.FACIAL_RECOGNITION]: mlEnabled && config.machineLearning.facialRecognitionEnabled, + [FeatureFlag.TAG_IMAGE]: mlEnabled && config.machineLearning.tagImageEnabled, + [FeatureFlag.SIDECAR]: true, + [FeatureFlag.SEARCH]: process.env.TYPESENSE_ENABLED !== 'false', + + // TODO: use these instead of `POST oauth/config` + [FeatureFlag.OAUTH]: config.oauth.enabled, + [FeatureFlag.OAUTH_AUTO_LAUNCH]: config.oauth.autoLaunch, + [FeatureFlag.PASSWORD_LOGIN]: config.passwordLogin.enabled, + [FeatureFlag.CONFIG_FILE]: !!process.env.IMMICH_CONFIG_FILE, + }; + } + public getDefaults(): SystemConfig { return defaults; } @@ -90,18 +165,16 @@ export class SystemConfigCore { this.validators.push(validator); } - public async getConfig() { - const overrides = await this.repository.load(); - const config: DeepPartial = {}; - for (const { key, value } of overrides) { - // set via dot notation - _.set(config, key, value); - } - - return _.defaultsDeep(config, defaults) as SystemConfig; + public getConfig(force = false): Promise { + const configFilePath = process.env.IMMICH_CONFIG_FILE; + return configFilePath ? this.loadFromFile(configFilePath, force) : this.loadFromDatabase(); } public async updateConfig(config: SystemConfig): Promise { + if (await this.hasFeature(FeatureFlag.CONFIG_FILE)) { + throw new BadRequestException('Cannot update configuration while IMMICH_CONFIG_FILE is in use'); + } + try { for (const validator of this.validators) { await validator(config); @@ -144,8 +217,45 @@ export class SystemConfigCore { } public async refreshConfig() { - const newConfig = await this.getConfig(); + const newConfig = await this.getConfig(true); this.config$.next(newConfig); } + + private async loadFromDatabase() { + const config: DeepPartial = {}; + const overrides = await this.repository.load(); + for (const { key, value } of overrides) { + // set via dot notation + _.set(config, key, value); + } + + return _.defaultsDeep(config, defaults) as SystemConfig; + } + + private async loadFromFile(filepath: string, force = false) { + if (force || !this.configCache) { + try { + const overrides = JSON.parse((await this.repository.readFile(filepath)).toString()); + const config = plainToClass(SystemConfigDto, _.defaultsDeep(overrides, defaults)); + + const errors = await validate(config, { + whitelist: true, + forbidNonWhitelisted: true, + forbidUnknownValues: true, + }); + if (errors.length > 0) { + this.logger.error('Validation error', errors); + throw new Error(`Invalid value(s) in file: ${errors}`); + } + + this.configCache = config; + } catch (error: Error | any) { + this.logger.error(`Unable to load configuration file: ${filepath} due to ${error}`, error?.stack); + throw new Error('Invalid configuration file'); + } + } + + return this.configCache; + } } diff --git a/server/src/domain/system-config/system-config.repository.ts b/server/src/domain/system-config/system-config.repository.ts index 6b04669ccc151d8086c0f48ab651d646252280ba..f999792071866e50d6eab5fb07b24272c7714f42 100644 --- a/server/src/domain/system-config/system-config.repository.ts +++ b/server/src/domain/system-config/system-config.repository.ts @@ -4,6 +4,7 @@ export const ISystemConfigRepository = 'ISystemConfigRepository'; export interface ISystemConfigRepository { load(): Promise; + readFile(filename: string): Promise; saveAll(items: SystemConfigEntity[]): Promise; deleteKeys(keys: string[]): Promise; } diff --git a/server/src/domain/system-config/system-config.service.spec.ts b/server/src/domain/system-config/system-config.service.spec.ts index bb510c05b3bc2eeb71eb2ea1928a2003893d9f3a..fd450a2963e4cff10f732934b4f1ca209a8968bb 100644 --- a/server/src/domain/system-config/system-config.service.spec.ts +++ b/server/src/domain/system-config/system-config.service.spec.ts @@ -46,6 +46,13 @@ const updatedConfig = Object.freeze({ accel: TranscodeHWAccel.DISABLED, tonemap: ToneMapping.HABLE, }, + machineLearning: { + enabled: true, + url: 'http://immich-machine-learning:3003', + facialRecognitionEnabled: true, + tagImageEnabled: true, + clipEncodeEnabled: true, + }, oauth: { autoLaunch: true, autoRegister: true, @@ -77,6 +84,7 @@ describe(SystemConfigService.name, () => { let jobMock: jest.Mocked; beforeEach(async () => { + delete process.env.IMMICH_CONFIG_FILE; configMock = newSystemConfigRepositoryMock(); jobMock = newJobRepositoryMock(); sut = new SystemConfigService(configMock, jobMock); @@ -119,6 +127,43 @@ describe(SystemConfigService.name, () => { await expect(sut.getConfig()).resolves.toEqual(updatedConfig); }); + + it('should load the config from a file', async () => { + process.env.IMMICH_CONFIG_FILE = 'immich-config.json'; + const partialConfig = { ffmpeg: { crf: 30 }, oauth: { autoLaunch: true } }; + configMock.readFile.mockResolvedValue(Buffer.from(JSON.stringify(partialConfig))); + + await expect(sut.getConfig()).resolves.toEqual(updatedConfig); + + expect(configMock.readFile).toHaveBeenCalledWith('immich-config.json'); + }); + + it('should accept an empty configuration file', async () => { + process.env.IMMICH_CONFIG_FILE = 'immich-config.json'; + configMock.readFile.mockResolvedValue(Buffer.from(JSON.stringify({}))); + + await expect(sut.getConfig()).resolves.toEqual(defaults); + + expect(configMock.readFile).toHaveBeenCalledWith('immich-config.json'); + }); + + const tests = [ + { should: 'validate numbers', config: { ffmpeg: { crf: 'not-a-number' } } }, + { should: 'validate booleans', config: { oauth: { enabled: 'invalid' } } }, + { should: 'validate enums', config: { ffmpeg: { transcode: 'unknown' } } }, + { should: 'validate top level unknown options', config: { unknownOption: true } }, + { should: 'validate nested unknown options', config: { ffmpeg: { unknownOption: true } } }, + { should: 'validate required oauth fields', config: { oauth: { enabled: true } } }, + ]; + + for (const test of tests) { + it(`should ${test.should}`, async () => { + process.env.IMMICH_CONFIG_FILE = 'immich-config.json'; + configMock.readFile.mockResolvedValue(Buffer.from(JSON.stringify(test.config))); + + await expect(sut.getConfig()).rejects.toBeInstanceOf(Error); + }); + } }); describe('getStorageTemplateOptions', () => { @@ -169,6 +214,13 @@ describe(SystemConfigService.name, () => { expect(validator).toHaveBeenCalledWith(updatedConfig); expect(configMock.saveAll).not.toHaveBeenCalled(); }); + + it('should throw an error if a config file is in use', async () => { + process.env.IMMICH_CONFIG_FILE = 'immich-config.json'; + configMock.readFile.mockResolvedValue(Buffer.from(JSON.stringify({}))); + await expect(sut.updateConfig(defaults)).rejects.toBeInstanceOf(BadRequestException); + expect(configMock.saveAll).not.toHaveBeenCalled(); + }); }); describe('refreshConfig', () => { diff --git a/server/src/immich/api-v1/asset/asset-repository.ts b/server/src/immich/api-v1/asset/asset-repository.ts index ce85bd369b6d3debf88caf2bfb704884bb31f54c..a87a115e976f48ce964069eb9fc56446617a2ec7 100644 --- a/server/src/immich/api-v1/asset/asset-repository.ts +++ b/server/src/immich/api-v1/asset/asset-repository.ts @@ -1,7 +1,7 @@ import { AssetEntity, ExifEntity } from '@app/infra/entities'; import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { IsNull, Not } from 'typeorm'; +import { MoreThan } from 'typeorm'; import { In } from 'typeorm/find-options/operator/In'; import { Repository } from 'typeorm/repository/Repository'; import { AssetSearchDto } from './dto/asset-search.dto'; @@ -127,10 +127,10 @@ export class AssetRepository implements IAssetRepository { return this.assetRepository.find({ where: { ownerId, - resizePath: dto.withoutThumbs ? undefined : Not(IsNull()), isVisible: true, isFavorite: dto.isFavorite, isArchived: dto.isArchived, + updatedAt: dto.updatedAfter ? MoreThan(dto.updatedAfter) : undefined, }, relations: { exifInfo: true, diff --git a/server/src/immich/api-v1/asset/dto/asset-search.dto.ts b/server/src/immich/api-v1/asset/dto/asset-search.dto.ts index a629c915c8f7a70f5f3b991fb63f1d21900ba0b9..52aee7c373d70613b7ce2a51b18510d0ca97d5c6 100644 --- a/server/src/immich/api-v1/asset/dto/asset-search.dto.ts +++ b/server/src/immich/api-v1/asset/dto/asset-search.dto.ts @@ -1,7 +1,7 @@ import { toBoolean } from '@app/domain'; import { ApiProperty } from '@nestjs/swagger'; -import { Transform } from 'class-transformer'; -import { IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsUUID } from 'class-validator'; +import { Transform, Type } from 'class-transformer'; +import { IsBoolean, IsDate, IsNotEmpty, IsNumber, IsOptional, IsUUID } from 'class-validator'; export class AssetSearchDto { @IsOptional() @@ -16,14 +16,6 @@ export class AssetSearchDto { @Transform(toBoolean) isArchived?: boolean; - /** - * Include assets without thumbnails - */ - @IsOptional() - @IsBoolean() - @Transform(toBoolean) - withoutThumbs?: boolean; - @IsOptional() @IsNumber() skip?: number; @@ -32,4 +24,9 @@ export class AssetSearchDto { @IsUUID('4') @ApiProperty({ format: 'uuid' }) userId?: string; + + @IsOptional() + @IsDate() + @Type(() => Date) + updatedAfter?: Date; } diff --git a/server/src/immich/app.module.ts b/server/src/immich/app.module.ts index 1067485ac04107ba9b8b179b432a281eb0b6261b..6b08228bd85b99824d9a709bfd0cb5b32ae45751 100644 --- a/server/src/immich/app.module.ts +++ b/server/src/immich/app.module.ts @@ -16,6 +16,7 @@ import { APIKeyController, AppController, AssetController, + AuditController, AuthController, JobController, OAuthController, @@ -42,6 +43,7 @@ import { AppController, AlbumController, APIKeyController, + AuditController, AuthController, JobController, OAuthController, diff --git a/server/src/immich/app.service.ts b/server/src/immich/app.service.ts index aee680b28d5c1c9f60c7a8b56d0d7685b8a46a3b..9e7b149ab2f8fbe978a93aff80ab6ab4ad98cb4f 100644 --- a/server/src/immich/app.service.ts +++ b/server/src/immich/app.service.ts @@ -1,4 +1,4 @@ -import { JobService, MACHINE_LEARNING_ENABLED, SearchService, StorageService } from '@app/domain'; +import { JobService, SearchService, ServerInfoService, StorageService } from '@app/domain'; import { Injectable, Logger } from '@nestjs/common'; import { Cron, CronExpression } from '@nestjs/schedule'; @@ -10,6 +10,7 @@ export class AppService { private jobService: JobService, private searchService: SearchService, private storageService: StorageService, + private serverService: ServerInfoService, ) {} @Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT) @@ -20,8 +21,6 @@ export class AppService { async init() { this.storageService.init(); await this.searchService.init(); - - this.logger.log(`Machine learning is ${MACHINE_LEARNING_ENABLED ? 'enabled' : 'disabled'}`); - this.logger.log(`Search is ${this.searchService.isEnabled() ? 'enabled' : 'disabled'}`); + this.logger.log(`Feature Flags: ${JSON.stringify(await this.serverService.getFeatures(), null, 2)}`); } } diff --git a/server/src/immich/controllers/asset.controller.ts b/server/src/immich/controllers/asset.controller.ts index b55cbb870d3070d5dd118aeaa51e2a6f5a4d7aac..7a69c34e85629adb7b6d58d891f3ffd36ee0a6bd 100644 --- a/server/src/immich/controllers/asset.controller.ts +++ b/server/src/immich/controllers/asset.controller.ts @@ -1,5 +1,7 @@ import { + AssetBulkUpdateDto, AssetIdsDto, + AssetJobsDto, AssetResponseDto, AssetService, AssetStatsDto, @@ -7,15 +9,15 @@ import { AuthUserDto, DownloadInfoDto, DownloadResponseDto, + MapMarkerDto, MapMarkerResponseDto, MemoryLaneDto, + MemoryLaneResponseDto, TimeBucketAssetDto, TimeBucketDto, TimeBucketResponseDto, } from '@app/domain'; -import { MapMarkerDto } from '@app/domain/asset/dto/map-marker.dto'; -import { MemoryLaneResponseDto } from '@app/domain/asset/response-dto/memory-lane-response.dto'; -import { Body, Controller, Get, HttpCode, HttpStatus, Param, Post, Query, StreamableFile } from '@nestjs/common'; +import { Body, Controller, Get, HttpCode, HttpStatus, Param, Post, Put, Query, StreamableFile } from '@nestjs/common'; import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; import { Authenticated, AuthUser, SharedLinkRoute } from '../app.guard'; import { asStreamableFile, UseValidation } from '../app.utils'; @@ -76,4 +78,16 @@ export class AssetController { getByTimeBucket(@AuthUser() authUser: AuthUserDto, @Query() dto: TimeBucketAssetDto): Promise { return this.service.getByTimeBucket(authUser, dto); } + + @Post('jobs') + @HttpCode(HttpStatus.NO_CONTENT) + runAssetJobs(@AuthUser() authUser: AuthUserDto, @Body() dto: AssetJobsDto): Promise { + return this.service.run(authUser, dto); + } + + @Put() + @HttpCode(HttpStatus.NO_CONTENT) + updateAssets(@AuthUser() authUser: AuthUserDto, @Body() dto: AssetBulkUpdateDto): Promise { + return this.service.updateAll(authUser, dto); + } } diff --git a/server/src/immich/controllers/audit.controller.ts b/server/src/immich/controllers/audit.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..8b28f6e9f3a19c59907a1980bceb89e9237de448 --- /dev/null +++ b/server/src/immich/controllers/audit.controller.ts @@ -0,0 +1,18 @@ +import { AuditDeletesDto, AuditDeletesResponseDto, AuditService, AuthUserDto } from '@app/domain'; +import { Controller, Get, Query } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; +import { Authenticated, AuthUser } from '../app.guard'; +import { UseValidation } from '../app.utils'; + +@ApiTags('Audit') +@Controller('audit') +@Authenticated() +@UseValidation() +export class AuditController { + constructor(private service: AuditService) {} + + @Get('deletes') + getAuditDeletes(@AuthUser() authUser: AuthUserDto, @Query() dto: AuditDeletesDto): Promise { + return this.service.getDeletes(authUser, dto); + } +} diff --git a/server/src/immich/controllers/index.ts b/server/src/immich/controllers/index.ts index e257c5a9ad84947014652c54fb47a1827f6b08a4..b28e82ecbf3d9b3b8a5d39a694e33e040945aab4 100644 --- a/server/src/immich/controllers/index.ts +++ b/server/src/immich/controllers/index.ts @@ -2,6 +2,7 @@ export * from './album.controller'; export * from './api-key.controller'; export * from './app.controller'; export * from './asset.controller'; +export * from './audit.controller'; export * from './auth.controller'; export * from './job.controller'; export * from './oauth.controller'; diff --git a/server/src/immich/controllers/search.controller.ts b/server/src/immich/controllers/search.controller.ts index bbc10d9bbecaf018598db164fdcd6a9644c4fa32..a36d1b30533bbae40fa09c088f3b950fc297487a 100644 --- a/server/src/immich/controllers/search.controller.ts +++ b/server/src/immich/controllers/search.controller.ts @@ -1,11 +1,4 @@ -import { - AuthUserDto, - SearchConfigResponseDto, - SearchDto, - SearchExploreResponseDto, - SearchResponseDto, - SearchService, -} from '@app/domain'; +import { AuthUserDto, SearchDto, SearchExploreResponseDto, SearchResponseDto, SearchService } from '@app/domain'; import { Controller, Get, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { Authenticated, AuthUser } from '../app.guard'; @@ -23,11 +16,6 @@ export class SearchController { return this.service.search(authUser, dto); } - @Get('config') - getSearchConfig(): SearchConfigResponseDto { - return this.service.getConfig(); - } - @Get('explore') getExploreData(@AuthUser() authUser: AuthUserDto): Promise { return this.service.getExploreData(authUser) as Promise; diff --git a/server/src/immich/controllers/server-info.controller.ts b/server/src/immich/controllers/server-info.controller.ts index 59b6351787eef67e9772de2230203df659d1b892..3b69c35321c3b7c8d457bdb881b9d8317450d60d 100644 --- a/server/src/immich/controllers/server-info.controller.ts +++ b/server/src/immich/controllers/server-info.controller.ts @@ -1,10 +1,11 @@ import { + ServerFeaturesDto, ServerInfoResponseDto, ServerInfoService, ServerMediaTypesResponseDto, ServerPingResponse, ServerStatsResponseDto, - ServerVersionReponseDto, + ServerVersionResponseDto, } from '@app/domain'; import { Controller, Get } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; @@ -24,25 +25,31 @@ export class ServerInfoController { } @PublicRoute() - @Get('/ping') + @Get('ping') pingServer(): ServerPingResponse { return this.service.ping(); } @PublicRoute() - @Get('/version') - getServerVersion(): ServerVersionReponseDto { + @Get('version') + getServerVersion(): ServerVersionResponseDto { return this.service.getVersion(); } + @PublicRoute() + @Get('features') + getServerFeatures(): Promise { + return this.service.getFeatures(); + } + @AdminRoute() - @Get('/stats') + @Get('stats') getStats(): Promise { return this.service.getStats(); } @PublicRoute() - @Get('/media-types') + @Get('media-types') getSupportedMediaTypes(): ServerMediaTypesResponseDto { return this.service.getSupportedMediaTypes(); } diff --git a/server/src/infra/database.config.ts b/server/src/infra/database.config.ts index 8df8777051ecec5259576e5bccf65063c44b4509..089fd6878c76d231893b738421897780b05fddb8 100644 --- a/server/src/infra/database.config.ts +++ b/server/src/infra/database.config.ts @@ -17,6 +17,7 @@ export const databaseConfig: PostgresConnectionOptions = { entities: [__dirname + '/entities/*.entity.{js,ts}'], synchronize: false, migrations: [__dirname + '/migrations/*.{js,ts}'], + subscribers: [__dirname + '/subscribers/*.{js,ts}'], migrationsRun: true, connectTimeoutMS: 10000, // 10 seconds ...urlOrParts, diff --git a/server/src/infra/entities/audit.entity.ts b/server/src/infra/entities/audit.entity.ts new file mode 100644 index 0000000000000000000000000000000000000000..be5e14891c8b089e0b2a4e3307aa959f6ae2b3f4 --- /dev/null +++ b/server/src/infra/entities/audit.entity.ts @@ -0,0 +1,34 @@ +import { Column, CreateDateColumn, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'; + +export enum DatabaseAction { + CREATE = 'CREATE', + UPDATE = 'UPDATE', + DELETE = 'DELETE', +} + +export enum EntityType { + ASSET = 'ASSET', + ALBUM = 'ALBUM', +} + +@Entity('audit') +@Index('IDX_ownerId_createdAt', ['ownerId', 'createdAt']) +export class AuditEntity { + @PrimaryGeneratedColumn('increment') + id!: number; + + @Column() + entityType!: EntityType; + + @Column({ type: 'uuid' }) + entityId!: string; + + @Column() + action!: DatabaseAction; + + @Column({ type: 'uuid' }) + ownerId!: string; + + @CreateDateColumn({ type: 'timestamptz' }) + createdAt!: Date; +} diff --git a/server/src/infra/entities/index.ts b/server/src/infra/entities/index.ts index 6864a3f7392f1f1e2122e66d9ce4bfb1cac71d7b..632a8d6b44937ee38836f0c3ad20f27d9c268308 100644 --- a/server/src/infra/entities/index.ts +++ b/server/src/infra/entities/index.ts @@ -2,6 +2,7 @@ import { AlbumEntity } from './album.entity'; import { APIKeyEntity } from './api-key.entity'; import { AssetFaceEntity } from './asset-face.entity'; import { AssetEntity } from './asset.entity'; +import { AuditEntity } from './audit.entity'; import { PartnerEntity } from './partner.entity'; import { PersonEntity } from './person.entity'; import { SharedLinkEntity } from './shared-link.entity'; @@ -15,6 +16,7 @@ export * from './album.entity'; export * from './api-key.entity'; export * from './asset-face.entity'; export * from './asset.entity'; +export * from './audit.entity'; export * from './exif.entity'; export * from './partner.entity'; export * from './person.entity'; @@ -30,6 +32,7 @@ export const databaseEntities = [ APIKeyEntity, AssetEntity, AssetFaceEntity, + AuditEntity, PartnerEntity, PersonEntity, SharedLinkEntity, diff --git a/server/src/infra/entities/person.entity.ts b/server/src/infra/entities/person.entity.ts index b93c4bbf9df2026ba56650b434bdcf3c382aee40..b0da2f63da7a4b60009e9186d03228cb8b5df675 100644 --- a/server/src/infra/entities/person.entity.ts +++ b/server/src/infra/entities/person.entity.ts @@ -30,6 +30,9 @@ export class PersonEntity { @Column({ default: '' }) name!: string; + @Column({ type: 'date', nullable: true }) + birthDate!: Date | null; + @Column({ default: '' }) thumbnailPath!: string; diff --git a/server/src/infra/entities/system-config.entity.ts b/server/src/infra/entities/system-config.entity.ts index ddfad682a7d86a535459b904b200faabbc9882a2..642f40c16c48aa28f9b6cc6abd470e2fa00fe81b 100644 --- a/server/src/infra/entities/system-config.entity.ts +++ b/server/src/infra/entities/system-config.entity.ts @@ -37,6 +37,12 @@ export enum SystemConfigKey { JOB_SEARCH_CONCURRENCY = 'job.search.concurrency', JOB_SIDECAR_CONCURRENCY = 'job.sidecar.concurrency', + MACHINE_LEARNING_ENABLED = 'machineLearning.enabled', + MACHINE_LEARNING_URL = 'machineLearning.url', + MACHINE_LEARNING_FACIAL_RECOGNITION_ENABLED = 'machineLearning.facialRecognitionEnabled', + MACHINE_LEARNING_TAG_IMAGE_ENABLED = 'machineLearning.tagImageEnabled', + MACHINE_LEARNING_CLIP_ENCODE_ENABLED = 'machineLearning.clipEncodeEnabled', + OAUTH_ENABLED = 'oauth.enabled', OAUTH_ISSUER_URL = 'oauth.issuerUrl', OAUTH_CLIENT_ID = 'oauth.clientId', @@ -105,6 +111,13 @@ export interface SystemConfig { tonemap: ToneMapping; }; job: Record; + machineLearning: { + enabled: boolean; + url: string; + clipEncodeEnabled: boolean; + facialRecognitionEnabled: boolean; + tagImageEnabled: boolean; + }; oauth: { enabled: boolean; issuerUrl: string; diff --git a/server/src/infra/infra.module.ts b/server/src/infra/infra.module.ts index 060c64ae35c139bacbf216425faa8eb5a45073a1..98d4387ebefc234c92f7db1f1bc603e9573f583f 100644 --- a/server/src/infra/infra.module.ts +++ b/server/src/infra/infra.module.ts @@ -2,6 +2,7 @@ import { IAccessRepository, IAlbumRepository, IAssetRepository, + IAuditRepository, ICommunicationRepository, ICryptoRepository, IFaceRepository, @@ -35,6 +36,7 @@ import { AlbumRepository, APIKeyRepository, AssetRepository, + AuditRepository, CommunicationRepository, CryptoRepository, FaceRepository, @@ -58,6 +60,7 @@ const providers: Provider[] = [ { provide: IAccessRepository, useClass: AccessRepository }, { provide: IAlbumRepository, useClass: AlbumRepository }, { provide: IAssetRepository, useClass: AssetRepository }, + { provide: IAuditRepository, useClass: AuditRepository }, { provide: ICommunicationRepository, useClass: CommunicationRepository }, { provide: ICryptoRepository, useClass: CryptoRepository }, { provide: IFaceRepository, useClass: FaceRepository }, diff --git a/server/src/infra/migrations/1692112147855-AddPersonBirthDate.ts b/server/src/infra/migrations/1692112147855-AddPersonBirthDate.ts new file mode 100644 index 0000000000000000000000000000000000000000..db2ba35dad46caa642c0fc1a50da710152638493 --- /dev/null +++ b/server/src/infra/migrations/1692112147855-AddPersonBirthDate.ts @@ -0,0 +1,13 @@ +import { MigrationInterface, QueryRunner } from "typeorm" + +export class AddPersonBirthDate1692112147855 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "person" ADD "birthDate" date`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "person" DROP COLUMN "birthDate"`); + } + +} diff --git a/server/src/infra/migrations/1692804658140-AddAuditTable.ts b/server/src/infra/migrations/1692804658140-AddAuditTable.ts new file mode 100644 index 0000000000000000000000000000000000000000..71b8c7b2c63f0daaf250836458940a5b68ea63a2 --- /dev/null +++ b/server/src/infra/migrations/1692804658140-AddAuditTable.ts @@ -0,0 +1,16 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AddAuditTable1692804658140 implements MigrationInterface { + name = 'AddAuditTable1692804658140' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE "audit" ("id" SERIAL NOT NULL, "entityType" character varying NOT NULL, "entityId" uuid NOT NULL, "action" character varying NOT NULL, "ownerId" uuid NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), CONSTRAINT "PK_1d3d120ddaf7bc9b1ed68ed463a" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_ownerId_createdAt" ON "audit" ("ownerId", "createdAt") `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP INDEX "public"."IDX_ownerId_createdAt"`); + await queryRunner.query(`DROP TABLE "audit"`); + } + +} diff --git a/server/src/infra/repositories/asset.repository.ts b/server/src/infra/repositories/asset.repository.ts index 74f7f7497e3190565f88be964577b66a3fe781de..2d4a0e91caa41df9c94f79501bb0d0ede9f76f53 100644 --- a/server/src/infra/repositories/asset.repository.ts +++ b/server/src/infra/repositories/asset.repository.ts @@ -129,6 +129,10 @@ export class AssetRepository implements IAssetRepository { }); } + async updateAll(ids: string[], options: Partial): Promise { + await this.repository.update({ id: In(ids) }, options); + } + async save(asset: Partial): Promise { const { id } = await this.repository.save(asset); return this.repository.findOneOrFail({ diff --git a/server/src/infra/repositories/audit.repository.ts b/server/src/infra/repositories/audit.repository.ts new file mode 100644 index 0000000000000000000000000000000000000000..b19d385777732b8c7a32de10ef9101a9b894e2ad --- /dev/null +++ b/server/src/infra/repositories/audit.repository.ts @@ -0,0 +1,26 @@ +import { AuditSearch, IAuditRepository } from '@app/domain'; +import { InjectRepository } from '@nestjs/typeorm'; +import { LessThan, MoreThan, Repository } from 'typeorm'; +import { AuditEntity } from '../entities'; + +export class AuditRepository implements IAuditRepository { + constructor(@InjectRepository(AuditEntity) private repository: Repository) {} + + getAfter(since: Date, options: AuditSearch): Promise { + return this.repository + .createQueryBuilder('audit') + .where({ + createdAt: MoreThan(since), + action: options.action, + entityType: options.entityType, + ownerId: options.ownerId, + }) + .distinctOn(['audit.entityId', 'audit.entityType']) + .orderBy('audit.entityId, audit.entityType, audit.createdAt', 'DESC') + .getMany(); + } + + async removeBefore(before: Date): Promise { + await this.repository.delete({ createdAt: LessThan(before) }); + } +} diff --git a/server/src/infra/repositories/index.ts b/server/src/infra/repositories/index.ts index 5c7261b2d44678a1f44cfc7b0e4ad22a9c34a418..c52c350fbc5e8ff369359338ef4ed3da51a7f701 100644 --- a/server/src/infra/repositories/index.ts +++ b/server/src/infra/repositories/index.ts @@ -2,6 +2,7 @@ export * from './access.repository'; export * from './album.repository'; export * from './api-key.repository'; export * from './asset.repository'; +export * from './audit.repository'; export * from './communication.repository'; export * from './crypto.repository'; export * from './face.repository'; diff --git a/server/src/infra/repositories/machine-learning.repository.ts b/server/src/infra/repositories/machine-learning.repository.ts index 40398445a09ec5f19886f50c53533385e1ef662b..3d3e22449abf396cd7fc86e0d1c6bd0427491a30 100644 --- a/server/src/infra/repositories/machine-learning.repository.ts +++ b/server/src/infra/repositories/machine-learning.repository.ts @@ -1,9 +1,9 @@ -import { DetectFaceResult, IMachineLearningRepository, MachineLearningInput, MACHINE_LEARNING_URL } from '@app/domain'; +import { DetectFaceResult, IMachineLearningRepository, MachineLearningInput } from '@app/domain'; import { Injectable } from '@nestjs/common'; import axios from 'axios'; import { createReadStream } from 'fs'; -const client = axios.create({ baseURL: MACHINE_LEARNING_URL }); +const client = axios.create(); @Injectable() export class MachineLearningRepository implements IMachineLearningRepository { @@ -11,19 +11,19 @@ export class MachineLearningRepository implements IMachineLearningRepository { return client.post(endpoint, createReadStream(input.imagePath)).then((res) => res.data); } - classifyImage(input: MachineLearningInput): Promise { - return this.post(input, '/image-classifier/tag-image'); + classifyImage(url: string, input: MachineLearningInput): Promise { + return this.post(input, `${url}/image-classifier/tag-image`); } - detectFaces(input: MachineLearningInput): Promise { - return this.post(input, '/facial-recognition/detect-faces'); + detectFaces(url: string, input: MachineLearningInput): Promise { + return this.post(input, `${url}/facial-recognition/detect-faces`); } - encodeImage(input: MachineLearningInput): Promise { - return this.post(input, '/sentence-transformer/encode-image'); + encodeImage(url: string, input: MachineLearningInput): Promise { + return this.post(input, `${url}/sentence-transformer/encode-image`); } - encodeText(input: string): Promise { - return client.post('/sentence-transformer/encode-text', { text: input }).then((res) => res.data); + encodeText(url: string, input: string): Promise { + return client.post(`${url}/sentence-transformer/encode-text`, { text: input }).then((res) => res.data); } } diff --git a/server/src/infra/repositories/shared-link.repository.ts b/server/src/infra/repositories/shared-link.repository.ts index a295a42fb4b2bdc7c589247b968f84020aef7f40..127efee43945336586ed7a3ec462c4c2c73e485f 100644 --- a/server/src/infra/repositories/shared-link.repository.ts +++ b/server/src/infra/repositories/shared-link.repository.ts @@ -62,15 +62,8 @@ export class SharedLinkRepository implements ISharedLinkRepository { key, }, relations: { - assets: true, - album: { - assets: true, - }, user: true, }, - order: { - createdAt: 'DESC', - }, }); } diff --git a/server/src/infra/repositories/system-config.repository.ts b/server/src/infra/repositories/system-config.repository.ts index 0ce7c07a565195a2ac47232183c583c4024346c5..cfe0eab3d52faef8c320156965f465036b22a5aa 100644 --- a/server/src/infra/repositories/system-config.repository.ts +++ b/server/src/infra/repositories/system-config.repository.ts @@ -1,5 +1,6 @@ import { ISystemConfigRepository } from '@app/domain'; import { InjectRepository } from '@nestjs/typeorm'; +import { readFile } from 'fs/promises'; import { In, Repository } from 'typeorm'; import { SystemConfigEntity } from '../entities'; @@ -13,6 +14,8 @@ export class SystemConfigRepository implements ISystemConfigRepository { return this.repository.find(); } + readFile = readFile; + saveAll(items: SystemConfigEntity[]): Promise { return this.repository.save(items); } diff --git a/server/src/infra/subscribers/audit.subscriber.ts b/server/src/infra/subscribers/audit.subscriber.ts new file mode 100644 index 0000000000000000000000000000000000000000..c0e83130776f53fa6721d1210a2a0b89e16021d5 --- /dev/null +++ b/server/src/infra/subscribers/audit.subscriber.ts @@ -0,0 +1,38 @@ +import { EntitySubscriberInterface, EventSubscriber, RemoveEvent } from 'typeorm'; +import { AlbumEntity, AssetEntity, AuditEntity, DatabaseAction, EntityType } from '../entities'; + +@EventSubscriber() +export class AuditSubscriber implements EntitySubscriberInterface { + async afterRemove(event: RemoveEvent): Promise { + await this.onEvent(DatabaseAction.DELETE, event); + } + + private async onEvent(action: DatabaseAction, event: RemoveEvent): Promise { + const audit = this.getAudit(event.metadata.name, { ...event.entity, id: event.entityId }); + if (audit && audit.entityId && audit.ownerId) { + await event.manager.getRepository(AuditEntity).save({ ...audit, action }); + } + } + + private getAudit(entityName: string, entity: any): Partial | null { + switch (entityName) { + case AssetEntity.name: + const asset = entity as AssetEntity; + return { + entityType: EntityType.ASSET, + entityId: asset.id, + ownerId: asset.ownerId, + }; + + case AlbumEntity.name: + const album = entity as AlbumEntity; + return { + entityType: EntityType.ALBUM, + entityId: album.id, + ownerId: album.ownerId, + }; + } + + return null; + } +} diff --git a/server/src/microservices/app.service.ts b/server/src/microservices/app.service.ts index 1204a6ebdd5536573aba7277e695c3edbf40fdc0..08d9ca7d2c559b234d092b737b47064f0338fe62 100644 --- a/server/src/microservices/app.service.ts +++ b/server/src/microservices/app.service.ts @@ -1,4 +1,5 @@ import { + AuditService, FacialRecognitionService, IDeleteFilesJob, JobName, @@ -35,11 +36,13 @@ export class AppService { private storageService: StorageService, private systemConfigService: SystemConfigService, private userService: UserService, + private auditService: AuditService, ) {} async init() { await this.jobService.registerHandlers({ [JobName.DELETE_FILES]: (data: IDeleteFilesJob) => this.storageService.handleDeleteFiles(data), + [JobName.CLEAN_OLD_AUDIT_LOGS]: () => this.auditService.handleCleanup(), [JobName.USER_DELETE_CHECK]: () => this.userService.handleUserDeleteCheck(), [JobName.USER_DELETION]: (data) => this.userService.handleUserDelete(data), [JobName.QUEUE_OBJECT_TAGGING]: (data) => this.smartInfoService.handleQueueObjectTagging(data), @@ -87,5 +90,6 @@ export class AppService { }); await this.metadataProcessor.init(); + await this.searchService.init(); } } diff --git a/server/src/microservices/processors/metadata-extraction.processor.ts b/server/src/microservices/processors/metadata-extraction.processor.ts index 7c58f7102e78de99ba16b17c695396314b0fcf61..5d421ebfd274b3289b62fba78bc441cee2c1a17f 100644 --- a/server/src/microservices/processors/metadata-extraction.processor.ts +++ b/server/src/microservices/processors/metadata-extraction.processor.ts @@ -408,7 +408,11 @@ export class MetadataExtractionProcessor { } await this.exifRepository.upsert(newExif, { conflictPaths: ['assetId'] }); - await this.assetRepository.save({ id: asset.id, fileCreatedAt: fileCreatedAt || undefined }); + await this.assetRepository.save({ + id: asset.id, + fileCreatedAt: fileCreatedAt || undefined, + updatedAt: new Date(), + }); return true; } diff --git a/server/start-microservices.sh b/server/start-microservices.sh index f81e6381aa97738b3eba95192dd8f06f38b87f3b..efdafaac020d81a5593bdd4657baa243817ff3f2 100755 --- a/server/start-microservices.sh +++ b/server/start-microservices.sh @@ -1,2 +1,2 @@ -#!/bin/sh +#!/usr/bin/env sh ./start.sh microservices diff --git a/server/start-server.sh b/server/start-server.sh index c3262af83ca789d5565212f1ae961c8eae044271..acbbb82b99ca45c3c3026dcd19b7c6e0f1518f1d 100755 --- a/server/start-server.sh +++ b/server/start-server.sh @@ -1,2 +1,2 @@ -#!/bin/sh +#!/usr/bin/env sh ./start.sh immich diff --git a/server/start.sh b/server/start.sh index 253dfc56db4d87241b7b0e8c311a3ddb10bb7478..5ec1a2643283b973a7e3bee8a5f1f8b72c9b98b9 100755 --- a/server/start.sh +++ b/server/start.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/usr/bin/env sh export LD_PRELOAD=/usr/lib/$(arch)-linux-gnu/libmimalloc.so.2 @@ -32,4 +32,4 @@ if [ "$REDIS_PASSWORD_FILE" ]; then unset REDIS_PASSWORD_FILE fi -exec node dist/main $1 +exec node dist/main $@ diff --git a/server/test/e2e/person.e2e-spec.ts b/server/test/e2e/person.e2e-spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..6395e78b0f3c36d811e6a6d1710fe96f6f10a541 --- /dev/null +++ b/server/test/e2e/person.e2e-spec.ts @@ -0,0 +1,81 @@ +import { IPersonRepository, LoginResponseDto } from '@app/domain'; +import { AppModule, PersonController } from '@app/immich'; +import { INestApplication } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import request from 'supertest'; +import { errorStub, uuidStub } from '../fixtures'; +import { api, db } from '../test-utils'; + +describe(`${PersonController.name}`, () => { + let app: INestApplication; + let server: any; + let loginResponse: LoginResponseDto; + let accessToken: string; + + beforeAll(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + + app = await moduleFixture.createNestApplication().init(); + server = app.getHttpServer(); + }); + + beforeEach(async () => { + await db.reset(); + await api.adminSignUp(server); + loginResponse = await api.adminLogin(server); + accessToken = loginResponse.accessToken; + }); + + afterAll(async () => { + await db.disconnect(); + await app.close(); + }); + + describe('PUT /person/:id', () => { + it('should require authentication', async () => { + const { status, body } = await request(server).put(`/person/${uuidStub.notFound}`); + expect(status).toBe(401); + expect(body).toEqual(errorStub.unauthorized); + }); + + it('should not accept invalid dates', async () => { + for (const birthDate of [false, 'false', '123567', 123456]) { + const { status, body } = await request(server) + .put(`/person/${uuidStub.notFound}`) + .set('Authorization', `Bearer ${accessToken}`) + .send({ birthDate }); + expect(status).toBe(400); + expect(body).toEqual(errorStub.badRequest); + } + }); + it('should update a date of birth', async () => { + const personRepository = app.get(IPersonRepository); + const person = await personRepository.create({ ownerId: loginResponse.userId }); + const { status, body } = await request(server) + .put(`/person/${person.id}`) + .set('Authorization', `Bearer ${accessToken}`) + .send({ birthDate: '1990-01-01T05:00:00.000Z' }); + expect(status).toBe(200); + expect(body).toMatchObject({ birthDate: '1990-01-01' }); + }); + + it('should clear a date of birth', async () => { + const personRepository = app.get(IPersonRepository); + const person = await personRepository.create({ + birthDate: new Date('1990-01-01'), + ownerId: loginResponse.userId, + }); + + expect(person.birthDate).toBeDefined(); + + const { status, body } = await request(server) + .put(`/person/${person.id}`) + .set('Authorization', `Bearer ${accessToken}`) + .send({ birthDate: null }); + expect(status).toBe(200); + expect(body).toMatchObject({ birthDate: null }); + }); + }); +}); diff --git a/server/test/fixtures/audit.stub.ts b/server/test/fixtures/audit.stub.ts new file mode 100644 index 0000000000000000000000000000000000000000..c915ed8214736b37e99478edcb7e111815809143 --- /dev/null +++ b/server/test/fixtures/audit.stub.ts @@ -0,0 +1,29 @@ +import { AuditEntity, DatabaseAction, EntityType } from '@app/infra/entities'; +import { authStub } from './auth.stub'; + +export const auditStub = { + create: Object.freeze({ + id: 1, + entityId: 'asset-created', + action: DatabaseAction.CREATE, + entityType: EntityType.ASSET, + ownerId: authStub.admin.id, + createdAt: new Date(), + }), + update: Object.freeze({ + id: 2, + entityId: 'asset-updated', + action: DatabaseAction.UPDATE, + entityType: EntityType.ASSET, + ownerId: authStub.admin.id, + createdAt: new Date(), + }), + delete: Object.freeze({ + id: 3, + entityId: 'asset-deleted', + action: DatabaseAction.DELETE, + entityType: EntityType.ASSET, + ownerId: authStub.admin.id, + createdAt: new Date(), + }), +}; diff --git a/server/test/fixtures/index.ts b/server/test/fixtures/index.ts index c0e8aed3ce8be96efaa8934fcc9e53e64f4b007f..624cc0758e082b1a11297434c5e4d3baf7f60f6e 100644 --- a/server/test/fixtures/index.ts +++ b/server/test/fixtures/index.ts @@ -1,6 +1,7 @@ export * from './album.stub'; export * from './api-key.stub'; export * from './asset.stub'; +export * from './audit.stub'; export * from './auth.stub'; export * from './device.stub'; export * from './error.stub'; diff --git a/server/test/fixtures/person.stub.ts b/server/test/fixtures/person.stub.ts index f2b512b88f423518bc57090e0cf870475773223e..2d419425d052a85d8c4558906c37f47525915680 100644 --- a/server/test/fixtures/person.stub.ts +++ b/server/test/fixtures/person.stub.ts @@ -9,6 +9,7 @@ export const personStub = { ownerId: userStub.admin.id, owner: userStub.admin, name: '', + birthDate: null, thumbnailPath: '/path/to/thumbnail.jpg', faces: [], isHidden: false, @@ -20,6 +21,7 @@ export const personStub = { ownerId: userStub.admin.id, owner: userStub.admin, name: '', + birthDate: null, thumbnailPath: '/path/to/thumbnail.jpg', faces: [], isHidden: true, @@ -31,6 +33,31 @@ export const personStub = { ownerId: userStub.admin.id, owner: userStub.admin, name: 'Person 1', + birthDate: null, + thumbnailPath: '/path/to/thumbnail.jpg', + faces: [], + isHidden: false, + }), + noBirthDate: Object.freeze({ + id: 'person-1', + createdAt: new Date('2021-01-01'), + updatedAt: new Date('2021-01-01'), + ownerId: userStub.admin.id, + owner: userStub.admin, + name: 'Person 1', + birthDate: null, + thumbnailPath: '/path/to/thumbnail.jpg', + faces: [], + isHidden: false, + }), + withBirthDate: Object.freeze({ + id: 'person-1', + createdAt: new Date('2021-01-01'), + updatedAt: new Date('2021-01-01'), + ownerId: userStub.admin.id, + owner: userStub.admin, + name: 'Person 1', + birthDate: new Date('1976-06-30'), thumbnailPath: '/path/to/thumbnail.jpg', faces: [], isHidden: false, @@ -42,6 +69,7 @@ export const personStub = { ownerId: userStub.admin.id, owner: userStub.admin, name: '', + birthDate: null, thumbnailPath: '', faces: [], isHidden: false, @@ -53,6 +81,7 @@ export const personStub = { ownerId: userStub.admin.id, owner: userStub.admin, name: '', + birthDate: null, thumbnailPath: '/new/path/to/thumbnail.jpg', faces: [], isHidden: false, @@ -64,6 +93,7 @@ export const personStub = { ownerId: userStub.admin.id, owner: userStub.admin, name: 'Person 1', + birthDate: null, thumbnailPath: '/path/to/thumbnail', faces: [], isHidden: false, @@ -75,6 +105,7 @@ export const personStub = { ownerId: userStub.admin.id, owner: userStub.admin, name: 'Person 2', + birthDate: null, thumbnailPath: '/path/to/thumbnail', faces: [], isHidden: false, diff --git a/server/test/repositories/access.repository.mock.ts b/server/test/repositories/access.repository.mock.ts index 1c8d09c5074e9d80934aad270803e28b4eb7401e..ad2f68ca50f5fd235cb7c7f22fde4aa588becd03 100644 --- a/server/test/repositories/access.repository.mock.ts +++ b/server/test/repositories/access.repository.mock.ts @@ -1,10 +1,10 @@ import { IAccessRepository } from '@app/domain'; -export type IAccessRepositoryMock = { +export interface IAccessRepositoryMock { asset: jest.Mocked; album: jest.Mocked; library: jest.Mocked; -}; +} export const newAccessRepositoryMock = (): IAccessRepositoryMock => { return { diff --git a/server/test/repositories/asset.repository.mock.ts b/server/test/repositories/asset.repository.mock.ts index 7f1eac831945007ff538e099e452512129db9237..ecd5d5c105a1a751757c5ed6a548e7dd9596d85e 100644 --- a/server/test/repositories/asset.repository.mock.ts +++ b/server/test/repositories/asset.repository.mock.ts @@ -11,6 +11,7 @@ export const newAssetRepositoryMock = (): jest.Mocked => { getFirstAssetForAlbumId: jest.fn(), getLastUpdatedAssetForAlbumId: jest.fn(), getAll: jest.fn().mockResolvedValue({ items: [], hasNextPage: false }), + updateAll: jest.fn(), deleteAll: jest.fn(), save: jest.fn(), findLivePhotoMatch: jest.fn(), diff --git a/server/test/repositories/audit.repository.mock.ts b/server/test/repositories/audit.repository.mock.ts new file mode 100644 index 0000000000000000000000000000000000000000..bd1a4b815ae9a6e80c0a08d4bf213a78ff1c3c20 --- /dev/null +++ b/server/test/repositories/audit.repository.mock.ts @@ -0,0 +1,8 @@ +import { IAuditRepository } from '@app/domain'; + +export const newAuditRepositoryMock = (): jest.Mocked => { + return { + getAfter: jest.fn(), + removeBefore: jest.fn(), + }; +}; diff --git a/server/test/repositories/index.ts b/server/test/repositories/index.ts index aa62a5f01a9c79c4f81d18600b3f80abc0237b14..2b2c190262cf3804f3639f6f8a9673f469ef4d7d 100644 --- a/server/test/repositories/index.ts +++ b/server/test/repositories/index.ts @@ -2,6 +2,7 @@ export * from './access.repository.mock'; export * from './album.repository.mock'; export * from './api-key.repository.mock'; export * from './asset.repository.mock'; +export * from './audit.repository.mock'; export * from './communication.repository.mock'; export * from './crypto.repository.mock'; export * from './face.repository.mock'; diff --git a/server/test/repositories/system-config.repository.mock.ts b/server/test/repositories/system-config.repository.mock.ts index 258ded0a76d8988b870c11cd646bc6cd218734aa..254f3bad233dd2dbf8d5fc76023ebac0f9a620f8 100644 --- a/server/test/repositories/system-config.repository.mock.ts +++ b/server/test/repositories/system-config.repository.mock.ts @@ -3,6 +3,7 @@ import { ISystemConfigRepository } from '@app/domain'; export const newSystemConfigRepositoryMock = (): jest.Mocked => { return { load: jest.fn().mockResolvedValue([]), + readFile: jest.fn(), saveAll: jest.fn().mockResolvedValue([]), deleteKeys: jest.fn(), }; diff --git a/web/src/api/api.ts b/web/src/api/api.ts index 22f998972143ead594f22d6a0a78400a77aae476..866b78ba3a0ea69939cd148c28677434011b3db8 100644 --- a/web/src/api/api.ts +++ b/web/src/api/api.ts @@ -3,6 +3,7 @@ import { APIKeyApi, AssetApi, AssetApiFp, + AssetJobName, AuthenticationApi, Configuration, ConfigurationParameters, @@ -38,6 +39,11 @@ export class ImmichApi { public userApi: UserApi; private config: Configuration; + private key?: string; + + get isSharedLink() { + return !!this.key; + } constructor(params: ConfigurationParameters) { this.config = new Configuration(params); @@ -72,6 +78,14 @@ export class ImmichApi { return (this.config.basePath || BASE_PATH) + toPathString(url); } + public setKey(key: string) { + this.key = key; + } + + public getKey(): string | undefined { + return this.key; + } + public setAccessToken(accessToken: string) { this.config.accessToken = accessToken; } @@ -84,14 +98,14 @@ export class ImmichApi { this.config.basePath = baseUrl; } - public getAssetFileUrl(...[assetId, isThumb, isWeb, key]: ApiParams) { + public getAssetFileUrl(...[assetId, isThumb, isWeb]: ApiParams) { const path = `/asset/file/${assetId}`; - return this.createUrl(path, { isThumb, isWeb, key }); + return this.createUrl(path, { isThumb, isWeb, key: this.getKey() }); } - public getAssetThumbnailUrl(...[assetId, format, key]: ApiParams) { + public getAssetThumbnailUrl(...[assetId, format]: ApiParams) { const path = `/asset/thumbnail/${assetId}`; - return this.createUrl(path, { format, key }); + return this.createUrl(path, { format, key: this.getKey() }); } public getProfileImageUrl(...[userId]: ApiParams) { @@ -120,6 +134,26 @@ export class ImmichApi { return names[jobName]; } + + public getAssetJobName(job: AssetJobName) { + const names: Record = { + [AssetJobName.RefreshMetadata]: 'Refresh metadata', + [AssetJobName.RegenerateThumbnail]: 'Refresh thumbnails', + [AssetJobName.TranscodeVideo]: 'Refresh encoded videos', + }; + + return names[job]; + } + + public getAssetJobMessage(job: AssetJobName) { + const messages: Record = { + [AssetJobName.RefreshMetadata]: 'Refreshing metadata', + [AssetJobName.RegenerateThumbnail]: `Regenerating thumbnails`, + [AssetJobName.TranscodeVideo]: `Refreshing encoded video`, + }; + + return messages[job]; + } } export const api = new ImmichApi({ basePath: '/api' }); diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index 8ade1d5fde43cf95617cb02eafed9d9218bdcdcf..36189d39f4840047673377a2c7c9991a6d44761d 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.73.0 + * The version of the OpenAPI document: 1.75.2 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). @@ -344,6 +344,31 @@ export interface AllJobStatusResponseDto { */ 'videoConversion': JobStatusDto; } +/** + * + * @export + * @interface AssetBulkUpdateDto + */ +export interface AssetBulkUpdateDto { + /** + * + * @type {Array} + * @memberof AssetBulkUpdateDto + */ + 'ids': Array; + /** + * + * @type {boolean} + * @memberof AssetBulkUpdateDto + */ + 'isArchived'?: boolean; + /** + * + * @type {boolean} + * @memberof AssetBulkUpdateDto + */ + 'isFavorite'?: boolean; +} /** * * @export @@ -500,6 +525,42 @@ export const AssetIdsResponseDtoErrorEnum = { export type AssetIdsResponseDtoErrorEnum = typeof AssetIdsResponseDtoErrorEnum[keyof typeof AssetIdsResponseDtoErrorEnum]; +/** + * + * @export + * @enum {string} + */ + +export const AssetJobName = { + RegenerateThumbnail: 'regenerate-thumbnail', + RefreshMetadata: 'refresh-metadata', + TranscodeVideo: 'transcode-video' +} as const; + +export type AssetJobName = typeof AssetJobName[keyof typeof AssetJobName]; + + +/** + * + * @export + * @interface AssetJobsDto + */ +export interface AssetJobsDto { + /** + * + * @type {Array} + * @memberof AssetJobsDto + */ + 'assetIds': Array; + /** + * + * @type {AssetJobName} + * @memberof AssetJobsDto + */ + 'name': AssetJobName; +} + + /** * * @export @@ -691,6 +752,25 @@ export const AudioCodec = { export type AudioCodec = typeof AudioCodec[keyof typeof AudioCodec]; +/** + * + * @export + * @interface AuditDeletesResponseDto + */ +export interface AuditDeletesResponseDto { + /** + * + * @type {Array} + * @memberof AuditDeletesResponseDto + */ + 'ids': Array; + /** + * + * @type {boolean} + * @memberof AuditDeletesResponseDto + */ + 'needsFullSync': boolean; +} /** * * @export @@ -1182,6 +1262,20 @@ export interface DownloadResponseDto { */ 'totalSize': number; } +/** + * + * @export + * @enum {string} + */ + +export const EntityType = { + Asset: 'ASSET', + Album: 'ALBUM' +} as const; + +export type EntityType = typeof EntityType[keyof typeof EntityType]; + + /** * * @export @@ -1779,6 +1873,12 @@ export interface PeopleUpdateDto { * @interface PeopleUpdateItem */ export interface PeopleUpdateItem { + /** + * Person date of birth. + * @type {string} + * @memberof PeopleUpdateItem + */ + 'birthDate'?: string | null; /** * Asset is used to get the feature face thumbnail. * @type {string} @@ -1810,6 +1910,12 @@ export interface PeopleUpdateItem { * @interface PersonResponseDto */ export interface PersonResponseDto { + /** + * + * @type {string} + * @memberof PersonResponseDto + */ + 'birthDate': string | null; /** * * @type {string} @@ -1841,6 +1947,12 @@ export interface PersonResponseDto { * @interface PersonUpdateDto */ export interface PersonUpdateDto { + /** + * Person date of birth. + * @type {string} + * @memberof PersonUpdateDto + */ + 'birthDate'?: string | null; /** * Asset is used to get the feature face thumbnail. * @type {string} @@ -1954,19 +2066,6 @@ export interface SearchAssetResponseDto { */ 'total': number; } -/** - * - * @export - * @interface SearchConfigResponseDto - */ -export interface SearchConfigResponseDto { - /** - * - * @type {boolean} - * @memberof SearchConfigResponseDto - */ - 'enabled': boolean; -} /** * * @export @@ -2062,6 +2161,67 @@ export interface SearchResponseDto { */ 'assets': SearchAssetResponseDto; } +/** + * + * @export + * @interface ServerFeaturesDto + */ +export interface ServerFeaturesDto { + /** + * + * @type {boolean} + * @memberof ServerFeaturesDto + */ + 'clipEncode': boolean; + /** + * + * @type {boolean} + * @memberof ServerFeaturesDto + */ + 'configFile': boolean; + /** + * + * @type {boolean} + * @memberof ServerFeaturesDto + */ + 'facialRecognition': boolean; + /** + * + * @type {boolean} + * @memberof ServerFeaturesDto + */ + 'oauth': boolean; + /** + * + * @type {boolean} + * @memberof ServerFeaturesDto + */ + 'oauthAutoLaunch': boolean; + /** + * + * @type {boolean} + * @memberof ServerFeaturesDto + */ + 'passwordLogin': boolean; + /** + * + * @type {boolean} + * @memberof ServerFeaturesDto + */ + 'search': boolean; + /** + * + * @type {boolean} + * @memberof ServerFeaturesDto + */ + 'sidecar': boolean; + /** + * + * @type {boolean} + * @memberof ServerFeaturesDto + */ + 'tagImage': boolean; +} /** * * @export @@ -2183,25 +2343,25 @@ export interface ServerStatsResponseDto { /** * * @export - * @interface ServerVersionReponseDto + * @interface ServerVersionResponseDto */ -export interface ServerVersionReponseDto { +export interface ServerVersionResponseDto { /** * * @type {number} - * @memberof ServerVersionReponseDto + * @memberof ServerVersionResponseDto */ 'major': number; /** * * @type {number} - * @memberof ServerVersionReponseDto + * @memberof ServerVersionResponseDto */ 'minor': number; /** * * @type {number} - * @memberof ServerVersionReponseDto + * @memberof ServerVersionResponseDto */ 'patch': number; } @@ -2462,6 +2622,12 @@ export interface SystemConfigDto { * @memberof SystemConfigDto */ 'job': SystemConfigJobDto; + /** + * + * @type {SystemConfigMachineLearningDto} + * @memberof SystemConfigDto + */ + 'machineLearning': SystemConfigMachineLearningDto; /** * * @type {SystemConfigOAuthDto} @@ -2629,6 +2795,43 @@ export interface SystemConfigJobDto { */ 'videoConversion': JobSettingsDto; } +/** + * + * @export + * @interface SystemConfigMachineLearningDto + */ +export interface SystemConfigMachineLearningDto { + /** + * + * @type {boolean} + * @memberof SystemConfigMachineLearningDto + */ + 'clipEncodeEnabled': boolean; + /** + * + * @type {boolean} + * @memberof SystemConfigMachineLearningDto + */ + 'enabled': boolean; + /** + * + * @type {boolean} + * @memberof SystemConfigMachineLearningDto + */ + 'facialRecognitionEnabled': boolean; + /** + * + * @type {boolean} + * @memberof SystemConfigMachineLearningDto + */ + 'tagImageEnabled': boolean; + /** + * + * @type {string} + * @memberof SystemConfigMachineLearningDto + */ + 'url': string; +} /** * * @export @@ -5002,13 +5205,13 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration * @param {string} [userId] * @param {boolean} [isFavorite] * @param {boolean} [isArchived] - * @param {boolean} [withoutThumbs] Include assets without thumbnails * @param {number} [skip] + * @param {string} [updatedAfter] * @param {string} [ifNoneMatch] ETag of data already cached on the client * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getAllAssets: async (userId?: string, isFavorite?: boolean, isArchived?: boolean, withoutThumbs?: boolean, skip?: number, ifNoneMatch?: string, options: AxiosRequestConfig = {}): Promise => { + getAllAssets: async (userId?: string, isFavorite?: boolean, isArchived?: boolean, skip?: number, updatedAfter?: string, ifNoneMatch?: string, options: AxiosRequestConfig = {}): Promise => { const localVarPath = `/asset`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -5042,14 +5245,16 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration localVarQueryParameter['isArchived'] = isArchived; } - if (withoutThumbs !== undefined) { - localVarQueryParameter['withoutThumbs'] = withoutThumbs; - } - if (skip !== undefined) { localVarQueryParameter['skip'] = skip; } + if (updatedAfter !== undefined) { + localVarQueryParameter['updatedAfter'] = (updatedAfter as any instanceof Date) ? + (updatedAfter as any).toISOString() : + updatedAfter; + } + if (ifNoneMatch != null) { localVarHeaderParameter['if-none-match'] = String(ifNoneMatch); } @@ -5722,6 +5927,50 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration options: localVarRequestOptions, }; }, + /** + * + * @param {AssetJobsDto} assetJobsDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + runAssetJobs: async (assetJobsDto: AssetJobsDto, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'assetJobsDto' is not null or undefined + assertParamExists('runAssetJobs', 'assetJobsDto', assetJobsDto) + const localVarPath = `/asset/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: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication cookie required + + // authentication api_key required + await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration) + + // 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(assetJobsDto, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * * @param {SearchAssetDto} searchAssetDto @@ -5871,6 +6120,50 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration options: localVarRequestOptions, }; }, + /** + * + * @param {AssetBulkUpdateDto} assetBulkUpdateDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + updateAssets: async (assetBulkUpdateDto: AssetBulkUpdateDto, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'assetBulkUpdateDto' is not null or undefined + assertParamExists('updateAssets', 'assetBulkUpdateDto', assetBulkUpdateDto) + const localVarPath = `/asset`; + // 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 cookie required + + // authentication api_key required + await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration) + + // 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(assetBulkUpdateDto, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * * @param {File} assetData @@ -6068,14 +6361,14 @@ export const AssetApiFp = function(configuration?: Configuration) { * @param {string} [userId] * @param {boolean} [isFavorite] * @param {boolean} [isArchived] - * @param {boolean} [withoutThumbs] Include assets without thumbnails * @param {number} [skip] + * @param {string} [updatedAfter] * @param {string} [ifNoneMatch] ETag of data already cached on the client * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getAllAssets(userId?: string, isFavorite?: boolean, isArchived?: boolean, withoutThumbs?: boolean, skip?: number, ifNoneMatch?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getAllAssets(userId, isFavorite, isArchived, withoutThumbs, skip, ifNoneMatch, options); + async getAllAssets(userId?: string, isFavorite?: boolean, isArchived?: boolean, skip?: number, updatedAfter?: string, ifNoneMatch?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getAllAssets(userId, isFavorite, isArchived, skip, updatedAfter, ifNoneMatch, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -6225,6 +6518,16 @@ export const AssetApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.importFile(importAssetDto, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @param {AssetJobsDto} assetJobsDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async runAssetJobs(assetJobsDto: AssetJobsDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.runAssetJobs(assetJobsDto, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @param {SearchAssetDto} searchAssetDto @@ -6261,10 +6564,20 @@ export const AssetApiFp = function(configuration?: Configuration) { }, /** * - * @param {File} assetData - * @param {string} deviceAssetId - * @param {string} deviceId - * @param {string} fileCreatedAt + * @param {AssetBulkUpdateDto} assetBulkUpdateDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async updateAssets(assetBulkUpdateDto: AssetBulkUpdateDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.updateAssets(assetBulkUpdateDto, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @param {File} assetData + * @param {string} deviceAssetId + * @param {string} deviceId + * @param {string} fileCreatedAt * @param {string} fileModifiedAt * @param {boolean} isFavorite * @param {string} [key] @@ -6352,7 +6665,7 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath * @throws {RequiredError} */ getAllAssets(requestParameters: AssetApiGetAllAssetsRequest = {}, options?: AxiosRequestConfig): AxiosPromise> { - return localVarFp.getAllAssets(requestParameters.userId, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.withoutThumbs, requestParameters.skip, requestParameters.ifNoneMatch, options).then((request) => request(axios, basePath)); + return localVarFp.getAllAssets(requestParameters.userId, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.skip, requestParameters.updatedAfter, requestParameters.ifNoneMatch, options).then((request) => request(axios, basePath)); }, /** * Get a single asset\'s information @@ -6468,6 +6781,15 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath importFile(requestParameters: AssetApiImportFileRequest, options?: AxiosRequestConfig): AxiosPromise { return localVarFp.importFile(requestParameters.importAssetDto, options).then((request) => request(axios, basePath)); }, + /** + * + * @param {AssetApiRunAssetJobsRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + runAssetJobs(requestParameters: AssetApiRunAssetJobsRequest, options?: AxiosRequestConfig): AxiosPromise { + return localVarFp.runAssetJobs(requestParameters.assetJobsDto, options).then((request) => request(axios, basePath)); + }, /** * * @param {AssetApiSearchAssetRequest} requestParameters Request parameters. @@ -6495,6 +6817,15 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath updateAsset(requestParameters: AssetApiUpdateAssetRequest, options?: AxiosRequestConfig): AxiosPromise { return localVarFp.updateAsset(requestParameters.id, requestParameters.updateAssetDto, options).then((request) => request(axios, basePath)); }, + /** + * + * @param {AssetApiUpdateAssetsRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + updateAssets(requestParameters: AssetApiUpdateAssetsRequest, options?: AxiosRequestConfig): AxiosPromise { + return localVarFp.updateAssets(requestParameters.assetBulkUpdateDto, options).then((request) => request(axios, basePath)); + }, /** * * @param {AssetApiUploadFileRequest} requestParameters Request parameters. @@ -6640,18 +6971,18 @@ export interface AssetApiGetAllAssetsRequest { readonly isArchived?: boolean /** - * Include assets without thumbnails - * @type {boolean} + * + * @type {number} * @memberof AssetApiGetAllAssets */ - readonly withoutThumbs?: boolean + readonly skip?: number /** * - * @type {number} + * @type {string} * @memberof AssetApiGetAllAssets */ - readonly skip?: number + readonly updatedAfter?: string /** * ETag of data already cached on the client @@ -6941,6 +7272,20 @@ export interface AssetApiImportFileRequest { readonly importAssetDto: ImportAssetDto } +/** + * Request parameters for runAssetJobs operation in AssetApi. + * @export + * @interface AssetApiRunAssetJobsRequest + */ +export interface AssetApiRunAssetJobsRequest { + /** + * + * @type {AssetJobsDto} + * @memberof AssetApiRunAssetJobs + */ + readonly assetJobsDto: AssetJobsDto +} + /** * Request parameters for searchAsset operation in AssetApi. * @export @@ -7011,6 +7356,20 @@ export interface AssetApiUpdateAssetRequest { readonly updateAssetDto: UpdateAssetDto } +/** + * Request parameters for updateAssets operation in AssetApi. + * @export + * @interface AssetApiUpdateAssetsRequest + */ +export interface AssetApiUpdateAssetsRequest { + /** + * + * @type {AssetBulkUpdateDto} + * @memberof AssetApiUpdateAssets + */ + readonly assetBulkUpdateDto: AssetBulkUpdateDto +} + /** * Request parameters for uploadFile operation in AssetApi. * @export @@ -7190,7 +7549,7 @@ export class AssetApi extends BaseAPI { * @memberof AssetApi */ public getAllAssets(requestParameters: AssetApiGetAllAssetsRequest = {}, options?: AxiosRequestConfig) { - return AssetApiFp(this.configuration).getAllAssets(requestParameters.userId, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.withoutThumbs, requestParameters.skip, requestParameters.ifNoneMatch, options).then((request) => request(this.axios, this.basePath)); + return AssetApiFp(this.configuration).getAllAssets(requestParameters.userId, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.skip, requestParameters.updatedAfter, requestParameters.ifNoneMatch, options).then((request) => request(this.axios, this.basePath)); } /** @@ -7333,6 +7692,17 @@ export class AssetApi extends BaseAPI { return AssetApiFp(this.configuration).importFile(requestParameters.importAssetDto, options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @param {AssetApiRunAssetJobsRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof AssetApi + */ + public runAssetJobs(requestParameters: AssetApiRunAssetJobsRequest, options?: AxiosRequestConfig) { + return AssetApiFp(this.configuration).runAssetJobs(requestParameters.assetJobsDto, options).then((request) => request(this.axios, this.basePath)); + } + /** * * @param {AssetApiSearchAssetRequest} requestParameters Request parameters. @@ -7366,6 +7736,17 @@ export class AssetApi extends BaseAPI { return AssetApiFp(this.configuration).updateAsset(requestParameters.id, requestParameters.updateAssetDto, options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @param {AssetApiUpdateAssetsRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof AssetApi + */ + public updateAssets(requestParameters: AssetApiUpdateAssetsRequest, options?: AxiosRequestConfig) { + return AssetApiFp(this.configuration).updateAssets(requestParameters.assetBulkUpdateDto, options).then((request) => request(this.axios, this.basePath)); + } + /** * * @param {AssetApiUploadFileRequest} requestParameters Request parameters. @@ -7379,6 +7760,163 @@ export class AssetApi extends BaseAPI { } +/** + * AuditApi - axios parameter creator + * @export + */ +export const AuditApiAxiosParamCreator = function (configuration?: Configuration) { + return { + /** + * + * @param {EntityType} entityType + * @param {string} after + * @param {string} [userId] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getAuditDeletes: async (entityType: EntityType, after: string, userId?: string, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'entityType' is not null or undefined + assertParamExists('getAuditDeletes', 'entityType', entityType) + // verify required parameter 'after' is not null or undefined + assertParamExists('getAuditDeletes', 'after', after) + const localVarPath = `/audit/deletes`; + // 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 cookie required + + // authentication api_key required + await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration) + + // authentication bearer required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + if (entityType !== undefined) { + localVarQueryParameter['entityType'] = entityType; + } + + if (userId !== undefined) { + localVarQueryParameter['userId'] = userId; + } + + if (after !== undefined) { + localVarQueryParameter['after'] = (after as any instanceof Date) ? + (after as any).toISOString() : + after; + } + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + } +}; + +/** + * AuditApi - functional programming interface + * @export + */ +export const AuditApiFp = function(configuration?: Configuration) { + const localVarAxiosParamCreator = AuditApiAxiosParamCreator(configuration) + return { + /** + * + * @param {EntityType} entityType + * @param {string} after + * @param {string} [userId] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getAuditDeletes(entityType: EntityType, after: string, userId?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getAuditDeletes(entityType, after, userId, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + } +}; + +/** + * AuditApi - factory interface + * @export + */ +export const AuditApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { + const localVarFp = AuditApiFp(configuration) + return { + /** + * + * @param {AuditApiGetAuditDeletesRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getAuditDeletes(requestParameters: AuditApiGetAuditDeletesRequest, options?: AxiosRequestConfig): AxiosPromise { + return localVarFp.getAuditDeletes(requestParameters.entityType, requestParameters.after, requestParameters.userId, options).then((request) => request(axios, basePath)); + }, + }; +}; + +/** + * Request parameters for getAuditDeletes operation in AuditApi. + * @export + * @interface AuditApiGetAuditDeletesRequest + */ +export interface AuditApiGetAuditDeletesRequest { + /** + * + * @type {EntityType} + * @memberof AuditApiGetAuditDeletes + */ + readonly entityType: EntityType + + /** + * + * @type {string} + * @memberof AuditApiGetAuditDeletes + */ + readonly after: string + + /** + * + * @type {string} + * @memberof AuditApiGetAuditDeletes + */ + readonly userId?: string +} + +/** + * AuditApi - object-oriented interface + * @export + * @class AuditApi + * @extends {BaseAPI} + */ +export class AuditApi extends BaseAPI { + /** + * + * @param {AuditApiGetAuditDeletesRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof AuditApi + */ + public getAuditDeletes(requestParameters: AuditApiGetAuditDeletesRequest, options?: AxiosRequestConfig) { + return AuditApiFp(this.configuration).getAuditDeletes(requestParameters.entityType, requestParameters.after, requestParameters.userId, options).then((request) => request(this.axios, this.basePath)); + } +} + + /** * AuthenticationApi - axios parameter creator * @export @@ -9622,44 +10160,6 @@ export const SearchApiAxiosParamCreator = function (configuration?: Configuratio - setSearchParams(localVarUrlObj, localVarQueryParameter); - let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; - localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; - - return { - url: toPathString(localVarUrlObj), - options: localVarRequestOptions, - }; - }, - /** - * - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - getSearchConfig: async (options: AxiosRequestConfig = {}): Promise => { - const localVarPath = `/search/config`; - // 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 cookie required - - // authentication api_key required - await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration) - - // 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}; @@ -9806,15 +10306,6 @@ export const SearchApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.getExploreData(options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, - /** - * - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - async getSearchConfig(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getSearchConfig(options); - return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); - }, /** * * @param {string} [q] @@ -9858,14 +10349,6 @@ export const SearchApiFactory = function (configuration?: Configuration, basePat getExploreData(options?: AxiosRequestConfig): AxiosPromise> { return localVarFp.getExploreData(options).then((request) => request(axios, basePath)); }, - /** - * - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - getSearchConfig(options?: AxiosRequestConfig): AxiosPromise { - return localVarFp.getSearchConfig(options).then((request) => request(axios, basePath)); - }, /** * * @param {SearchApiSearchRequest} requestParameters Request parameters. @@ -10014,16 +10497,6 @@ export class SearchApi extends BaseAPI { return SearchApiFp(this.configuration).getExploreData(options).then((request) => request(this.axios, this.basePath)); } - /** - * - * @param {*} [options] Override http request option. - * @throws {RequiredError} - * @memberof SearchApi - */ - public getSearchConfig(options?: AxiosRequestConfig) { - return SearchApiFp(this.configuration).getSearchConfig(options).then((request) => request(this.axios, this.basePath)); - } - /** * * @param {SearchApiSearchRequest} requestParameters Request parameters. @@ -10043,6 +10516,35 @@ export class SearchApi extends BaseAPI { */ export const ServerInfoApiAxiosParamCreator = function (configuration?: Configuration) { return { + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getServerFeatures: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/server-info/features`; + // 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; + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * * @param {*} [options] Override http request option. @@ -10216,6 +10718,15 @@ export const ServerInfoApiAxiosParamCreator = function (configuration?: Configur export const ServerInfoApiFp = function(configuration?: Configuration) { const localVarAxiosParamCreator = ServerInfoApiAxiosParamCreator(configuration) return { + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getServerFeatures(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getServerFeatures(options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @param {*} [options] Override http request option. @@ -10230,7 +10741,7 @@ export const ServerInfoApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getServerVersion(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + async getServerVersion(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { const localVarAxiosArgs = await localVarAxiosParamCreator.getServerVersion(options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, @@ -10271,6 +10782,14 @@ export const ServerInfoApiFp = function(configuration?: Configuration) { export const ServerInfoApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { const localVarFp = ServerInfoApiFp(configuration) return { + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getServerFeatures(options?: AxiosRequestConfig): AxiosPromise { + return localVarFp.getServerFeatures(options).then((request) => request(axios, basePath)); + }, /** * * @param {*} [options] Override http request option. @@ -10284,7 +10803,7 @@ export const ServerInfoApiFactory = function (configuration?: Configuration, bas * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getServerVersion(options?: AxiosRequestConfig): AxiosPromise { + getServerVersion(options?: AxiosRequestConfig): AxiosPromise { return localVarFp.getServerVersion(options).then((request) => request(axios, basePath)); }, /** @@ -10321,6 +10840,16 @@ export const ServerInfoApiFactory = function (configuration?: Configuration, bas * @extends {BaseAPI} */ export class ServerInfoApi extends BaseAPI { + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof ServerInfoApi + */ + public getServerFeatures(options?: AxiosRequestConfig) { + return ServerInfoApiFp(this.configuration).getServerFeatures(options).then((request) => request(this.axios, this.basePath)); + } + /** * * @param {*} [options] Override http request option. diff --git a/web/src/api/open-api/base.ts b/web/src/api/open-api/base.ts index 7109cba71e3ada8951c688443ec7b9e7ddcf0a65..d1eaca8a975828eb55ebcbeecccf74027d1dfd10 100644 --- a/web/src/api/open-api/base.ts +++ b/web/src/api/open-api/base.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.73.0 + * The version of the OpenAPI document: 1.75.2 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/web/src/api/open-api/common.ts b/web/src/api/open-api/common.ts index 66890914ad3d9123755c40e6fe0b0d89342b8548..91139577adc8e686591f367625b6e4314617e84f 100644 --- a/web/src/api/open-api/common.ts +++ b/web/src/api/open-api/common.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.73.0 + * The version of the OpenAPI document: 1.75.2 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/web/src/api/open-api/configuration.ts b/web/src/api/open-api/configuration.ts index 21fd71538b1036460f6114bbb8b219c6c272110e..da61d422161fc257078b3312792f3f32d24df05a 100644 --- a/web/src/api/open-api/configuration.ts +++ b/web/src/api/open-api/configuration.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.73.0 + * The version of the OpenAPI document: 1.75.2 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/web/src/api/open-api/index.ts b/web/src/api/open-api/index.ts index 937e9a24aded17608c54b3196e3443b2fb0712a9..75b153fbcf186dbcf4728581769bee372f9b261a 100644 --- a/web/src/api/open-api/index.ts +++ b/web/src/api/open-api/index.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.73.0 + * The version of the OpenAPI document: 1.75.2 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/web/src/api/utils.ts b/web/src/api/utils.ts index 75f3b47863c3e3a988554d57b893ed6d98777eef..26b6550666dda3cb684bf04d44b456f9ccd7e6a5 100644 --- a/web/src/api/utils.ts +++ b/web/src/api/utils.ts @@ -1,9 +1,23 @@ import type { AxiosError, AxiosPromise } from 'axios'; +import { + notificationController, + NotificationType, +} from '../lib/components/shared-components/notification/notification'; +import { handleError } from '../lib/utils/handle-error'; import { api } from './api'; import type { UserResponseDto } from './open-api'; export type ApiError = AxiosError<{ message: string }>; +export const copyToClipboard = async (secret: string) => { + try { + await navigator.clipboard.writeText(secret); + notificationController.show({ message: 'Copied to clipboard!', type: NotificationType.Info }); + } catch (error) { + handleError(error, 'Cannot copy to clipboard, make sure you are accessing the page through https'); + } +}; + export const oauth = { isCallback: (location: Location) => { const search = location.search; diff --git a/web/src/app.css b/web/src/app.css index 6cf41d300f125c35d43c61ce58a0a3f8639a98aa..eb55ec4c86e991918393872e4a0d798c2e3fbbee 100644 --- a/web/src/app.css +++ b/web/src/app.css @@ -56,6 +56,14 @@ input:focus-visible { outline: none !important; } +.text-white-shadow { + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8); +} + +.icon-white-drop-shadow { + filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.8)); +} + @layer utilities { .immich-form-input { @apply rounded-xl bg-slate-200 p-4 text-sm focus:border-immich-primary disabled:cursor-not-allowed disabled:bg-gray-400 disabled:text-gray-200 dark:bg-gray-600 dark:text-immich-dark-fg dark:disabled:bg-gray-800; diff --git a/web/src/lib/components/admin-page/jobs/job-tile-button.svelte b/web/src/lib/components/admin-page/jobs/job-tile-button.svelte index b794c387c81433c6523cb30e39810942deec548b..709ed6092479e739185cf7735313f7e49e00853a 100644 --- a/web/src/lib/components/admin-page/jobs/job-tile-button.svelte +++ b/web/src/lib/components/admin-page/jobs/job-tile-button.svelte @@ -4,17 +4,23 @@ - {#each jobDetailsArray as [jobName, { title, subtitle, allText, missingText, allowForceCommand, icon, component, handleCommand: handleCommandOverride }]} + {#each jobList as [jobName, { title, subtitle, disabled, allText, missingText, allowForceCommand, icon, component, handleCommand: handleCommandOverride }]} {@const { jobCounts, queueStatus } = jobs[jobName]} dispatch('save', ffmpegConfig)} on:reset-to-default={resetToDefault} showResetToDefault={!isEqual(ffmpegConfig, ffmpegDefault)} + {disabled} /> diff --git a/web/src/lib/components/admin-page/settings/job-settings/job-settings.svelte b/web/src/lib/components/admin-page/settings/job-settings/job-settings.svelte index 282450193b086de7132bfcacde668e0a9f922c3e..a44eaaccb56ed38c7faf68007686c9c9f789d26c 100644 --- a/web/src/lib/components/admin-page/settings/job-settings/job-settings.svelte +++ b/web/src/lib/components/admin-page/settings/job-settings/job-settings.svelte @@ -17,6 +17,7 @@ export let jobConfig: SystemConfigJobDto; // this is the config that is being edited export let jobDefault: SystemConfigJobDto; export let savedConfig: SystemConfigJobDto; + export let disabled = false; const ignoredJobs = [JobName.BackgroundTask, JobName.Search] as JobName[]; const jobNames = Object.values(JobName).filter((jobName) => !ignoredJobs.includes(jobName as JobName)); @@ -47,6 +48,7 @@

+ import { + notificationController, + NotificationType, + } from '$lib/components/shared-components/notification/notification'; + import { handleError } from '$lib/utils/handle-error'; + import { api, SystemConfigDto } from '@api'; + import { isEqual } from 'lodash-es'; + import { fade } from 'svelte/transition'; + import SettingButtonsRow from '../setting-buttons-row.svelte'; + import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte'; + import SettingSwitch from '../setting-switch.svelte'; + + export let disabled = false; + + let config: SystemConfigDto; + let defaultConfig: SystemConfigDto; + + async function refreshConfig() { + [config, defaultConfig] = await Promise.all([ + api.systemConfigApi.getConfig().then((res) => res.data), + api.systemConfigApi.getDefaults().then((res) => res.data), + ]); + } + + async function reset() { + const { data: resetConfig } = await api.systemConfigApi.getConfig(); + config = resetConfig; + notificationController.show({ message: 'Reset to the last saved settings', type: NotificationType.Info }); + } + + async function saveSetting() { + try { + const { data: current } = await api.systemConfigApi.getConfig(); + await api.systemConfigApi.updateConfig({ + systemConfigDto: { ...current, machineLearning: config.machineLearning }, + }); + await refreshConfig(); + notificationController.show({ message: 'Settings saved', type: NotificationType.Info }); + } catch (error) { + handleError(error, 'Unable to save settings'); + } + } + + async function resetToDefault() { + await refreshConfig(); + const { data: defaults } = await api.systemConfigApi.getDefaults(); + config = defaults; + + notificationController.show({ message: 'Reset settings to defaults', type: NotificationType.Info }); + } + + +
+ {#await refreshConfig() then} +
+
+ + +
+ + + + + + + + + + + +
+ {/await} +
diff --git a/web/src/lib/components/admin-page/settings/oauth/oauth-settings.svelte b/web/src/lib/components/admin-page/settings/oauth/oauth-settings.svelte index a6a94f142b84eff0ad9fcd95760bd100377178e6..119ac1a3b476ff54112e66c9291ec2ef462bc45a 100644 --- a/web/src/lib/components/admin-page/settings/oauth/oauth-settings.svelte +++ b/web/src/lib/components/admin-page/settings/oauth/oauth-settings.svelte @@ -20,6 +20,7 @@ export let oauthConfig: SystemConfigOAuthDto; export let oauthDefault: SystemConfigOAuthDto; export let savedConfig: SystemConfigOAuthDto; + export let disabled = false; const handleToggleOverride = () => { // click runs before bind @@ -119,7 +120,7 @@ label="CLIENT SECRET" bind:value={oauthConfig.clientSecret} required={true} - disabled={!oauthConfig.enabled} + disabled={disabled || !oauthConfig.enabled} isEdited={!(oauthConfig.clientSecret == savedConfig.clientSecret)} /> @@ -128,7 +129,7 @@ label="SCOPE" bind:value={oauthConfig.scope} required={true} - disabled={!oauthConfig.enabled} + disabled={disabled || !oauthConfig.enabled} isEdited={!(oauthConfig.scope == savedConfig.scope)} /> @@ -138,7 +139,7 @@ desc="Automatically set the user's storage label to the value of this claim." bind:value={oauthConfig.storageLabelClaim} required={true} - disabled={!oauthConfig.storageLabelClaim} + disabled={disabled || !oauthConfig.storageLabelClaim} isEdited={!(oauthConfig.storageLabelClaim == savedConfig.storageLabelClaim)} /> @@ -147,7 +148,7 @@ label="BUTTON TEXT" bind:value={oauthConfig.buttonText} required={false} - disabled={!oauthConfig.enabled} + disabled={disabled || !oauthConfig.enabled} isEdited={!(oauthConfig.buttonText == savedConfig.buttonText)} /> @@ -161,14 +162,14 @@ handleToggleOverride()} bind:checked={oauthConfig.mobileOverrideEnabled} /> @@ -179,7 +180,7 @@ label="MOBILE REDIRECT URI" bind:value={oauthConfig.mobileRedirectUri} required={true} - disabled={!oauthConfig.enabled} + disabled={disabled || !oauthConfig.enabled} isEdited={!(oauthConfig.mobileRedirectUri == savedConfig.mobileRedirectUri)} /> {/if} @@ -189,6 +190,7 @@ on:save={saveSetting} on:reset-to-default={resetToDefault} showResetToDefault={!isEqual(oauthConfig, oauthDefault)} + {disabled} />
diff --git a/web/src/lib/components/admin-page/settings/password-login/password-login-settings.svelte b/web/src/lib/components/admin-page/settings/password-login/password-login-settings.svelte index 37cda9ec63faaccd934c2f946d1518b77dd9b88d..1deddbeaf6ad6f13bb5328e147b292f2ae592214 100644 --- a/web/src/lib/components/admin-page/settings/password-login/password-login-settings.svelte +++ b/web/src/lib/components/admin-page/settings/password-login/password-login-settings.svelte @@ -19,6 +19,7 @@ export let passwordLoginConfig: SystemConfigPasswordLoginDto; // this is the config that is being edited export let passwordLoginDefault: SystemConfigPasswordLoginDto; export let savedConfig: SystemConfigPasswordLoginDto; + export let disabled = false; let isConfirmOpen = false; let handleConfirm: (value: boolean) => void; @@ -77,6 +78,7 @@
diff --git a/web/src/lib/components/admin-page/settings/setting-buttons-row.svelte b/web/src/lib/components/admin-page/settings/setting-buttons-row.svelte index 679327e6d6414b2f82d8fa16232ebc36a6330b11..3931d41eb24074c7f7f14cedbc5e69c2e87f3aee 100644 --- a/web/src/lib/components/admin-page/settings/setting-buttons-row.svelte +++ b/web/src/lib/components/admin-page/settings/setting-buttons-row.svelte @@ -5,6 +5,7 @@ const dispatch = createEventDispatcher(); export let showResetToDefault = true; + export let disabled = false;
@@ -20,7 +21,7 @@
- - + +
diff --git a/web/src/lib/components/admin-page/settings/setting-select.svelte b/web/src/lib/components/admin-page/settings/setting-select.svelte index 4acc1c2adeee0898f3c830617bd8c5b482753054..cafa053932f06e70907ba1fc368e3202bf9d5039 100644 --- a/web/src/lib/components/admin-page/settings/setting-select.svelte +++ b/web/src/lib/components/admin-page/settings/setting-select.svelte @@ -9,6 +9,7 @@ export let name = ''; export let isEdited = false; export let number = false; + export let disabled = false; const handleChange = (e: Event) => { value = (e.target as HTMLInputElement).value; @@ -40,6 +41,7 @@