diff --git a/.github/workflows/build-mobile.yml b/.github/workflows/build-mobile.yml index 31d87a285..54da83c52 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 826873993..e7cc9cfd2 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 62a606939..85a538464 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 8a3c116a3..df3e03d5a 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 8ade1d5fd..36189d39f 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 @@ -6259,6 +6562,16 @@ export const AssetApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.updateAsset(id, updateAssetDto, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @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 @@ -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. @@ -6639,13 +6970,6 @@ export interface AssetApiGetAllAssetsRequest { */ readonly isArchived?: boolean - /** - * Include assets without thumbnails - * @type {boolean} - * @memberof AssetApiGetAllAssets - */ - readonly withoutThumbs?: boolean - /** * * @type {number} @@ -6653,6 +6977,13 @@ export interface AssetApiGetAllAssetsRequest { */ readonly skip?: number + /** + * + * @type {string} + * @memberof AssetApiGetAllAssets + */ + readonly updatedAfter?: string + /** * ETag of data already cached on the client * @type {string} @@ -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 7109cba71..d1eaca8a9 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 66890914a..91139577a 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 21fd71538..da61d4221 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 937e9a24a..75b153fbc 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 06247e29c..c2fb8fee9 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 c8b69da0e..14c5238d0 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 8e4a6ca61..0374def08 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 c7e3be993..d96763972 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 c0aff99ab..af43f9192 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 000000000..0cf131a02 --- /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 dca8c0211..d09ba531f 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 09a430516..a344b6fd9 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 82fecc52f..131c83a4f 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 4413f28f2..103c8ba65 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 5714c0f4f..8a64d9119 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 59327d575..ddbce0f65 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 e5b5aa759..7d1dd0c71 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 9bdbb08b3..07bc16dcd 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 b9d5f75a0..f9094dc79 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 875671d39..c59abfbb9 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 32ea629df..b0ff1d041 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 9a9ba4219..6c12dd8d7 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 465624004..d8bc8a252 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 bb59ee5c4..cb668f9a1 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" @@ -1760,38 +2082,150 @@ files = [ ] [[package]] -name = "mypy" -version = "1.4.1" -description = "Optional static typing for Python" +name = "multidict" +version = "6.0.4" +description = "multidict implementation" optional = false python-versions = ">=3.7" 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 = "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.5.1" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {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"}, + {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)"] +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 b2fa1b28e..355fb23bd 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 000000000..c3caa0644 --- /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 0870e7648..04c1b862c 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 ccf632140..732a8c753 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 c3b7f1c92..71ff1230d 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 e9be5f654..09741ae07 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 8aeae7043..ddf5b88e7 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 000000000..e2df34e10 Binary files /dev/null and b/mobile/assets/lighthouse.png differ diff --git a/mobile/ios/Podfile.lock b/mobile/ios/Podfile.lock index 4902388c6..75168ce1c 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 8bb1e3192..afba814a6 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 83d495ca8..43b89d762 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 1a4f0cd16..ea244484b 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 7707eadb9..b0bac12e0 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 b447481c6..07dc98139 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 8b342dd45..4f36c4633 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 0960978a4..4488eca23 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 83dae248c..c9237ea27 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 8e63506b8..38c1f681a 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 3392ed518..5ca9bb81f 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 b7a5d3544..8a7e46f8c 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 000000000..eb08b6bda --- /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 31c48c450..cb389059a 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 7ea60e249..afec5f8ae 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 4a4e7a9e5..e1f0d65e7 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 a2e35f715..c22129a50 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 ec7ddb17e..e13637d23 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 eaf991645..fe6cef401 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 b87cbdb06..1f1745d49 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 f5ca80406..2e1c1cd4a 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 6bace49af..fcc1d4440 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!, - ), - ); - } - }, + builder: (ctx) => const Image( + image: AssetImage('assets/location-pin.png'), + ), ), - 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'], - ), - 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 4cc0b6f42..08e9b79af 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 72a1dce61..a56d65595 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 823a7c2bc..0d0501c30 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 0c38a6831..2aacf49be 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 3a335a7b1..6d585e289 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 520c8310b..8535496e1 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 a8fcde8ee..eaace503e 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 9c20c95bb..23bdf11ed 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 80259d06f..9e4118038 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 c689a3141..33584bb35 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 7028591a7..c55383cf3 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 fa68ff03c..62f8763bb 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 6d9b293df..d63b0631e 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 891bde100..c4a6d527e 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 1ad7f3a28..8f50c2883 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 1d1632ef7..24e2e7ce7 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 cff37b0bb..ce038d327 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 5d41a6842..fbc9b4ed7 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 da8a2c654..328d9cbc1 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 441aab5cd..000000000 --- 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 c3226f228..e37491440 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 904c59563..555dd6c0d 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 000000000..63665173d --- /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 000000000..ed2b033fd --- /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 000000000..30343f280 --- /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 000000000..7fd7d6061 --- /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 000000000..ec8dbbb39 --- /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 000000000..db6d1a10e --- /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 000000000..a55202e14 --- /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 000000000..c43cd9d3c --- /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 000000000..f74df4331 --- /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 000000000..d04ff2b85 --- /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 000000000..78998276d --- /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 000000000..379b209d9 --- /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 0dbe5749b..8ef06e0d1 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 cab16d82d..efbbc78a3 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 789f036c4..61c639746 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 18a1da2c3..8a65c25f7 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 000000000..ef394b83b --- /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 e4c606195..b66be410f 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 d16d548bf..bbb7e6834 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 bcdae6185..01483f0bd 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 b94806730..a7edcd90e 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 e54e6b60e..7ad93ea08 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 6e5ddec70..798d9916a 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 06f462ae5..56885aeaf 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 7e92aebed..4aef3beab 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 7b1ea9c91..6623ac166 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 ebbef904e..f67b2b411 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 461168f6e..687a784c0 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 7dc898be3..3cebc4d2a 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 2613b8522..ff0945ead 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 9165e6308..8e0fc1847 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 f37da8a4c..ed0065528 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 b112fc46c..bf923dfab 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 87d77ecd0..773007f73 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 95f6552be..d061b7b76 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 887dcd86f..d8ea664a6 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 000000000..39f27dbd9 --- /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 212787aa6..0963605b4 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 f718c480d..cb5a3c362 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 7cb8213fc..7a01ef304 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 000000000..c15582326 --- /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 000000000..273ee8ba9 --- /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 000000000..4fc812b4a --- /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 3fe68f131..2056237cb 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 0fd3f2be5..bd4458e4a 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 000000000..08de7961b --- /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 bf485ef08..e62417987 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 9167613b2..148eaa7e4 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 5c1c7bb4b..4bec9fa2a 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 000000000..b48268464 --- /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 25020ea75..d9612705a 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 000000000..e1d04388a --- /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 000000000..63a1c97a3 --- /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 000000000..c7c9594f1 --- /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 000000000..a01b3cf5e --- /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 43a1b0225..25152c4e4 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 e43d67a61..c2acbacd1 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 935b4348c..a4df66878 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 74840a73c..5cc36956a 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 000000000..168479b99 --- /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 0666c7d41..62758afbe 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 68dfa972c..b48291f12 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 ebccc31eb..6fc980852 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 000000000..9b2c596e2 --- /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 a38d8784e..d1b51ea8e 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 ba9f1d5a1..9067f8dd0 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 000000000..4eabd17c9 --- /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 8178395e0..9393b5c61 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 fb34e09ce..3b7899cc2 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 eb76c12c5..3e8196920 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 bc1dfd7bc..a9df71cfd 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 000000000..7eb0e31af --- /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 000000000..61334df08 --- /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 000000000..3c88438b9 --- /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 31927662c..a0bc0dd4d 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 000000000..1a45e1b2e --- /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 3a35c8a58..0abb7a474 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 21120f23b..5e65d947a 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 baa985b1c..fc384c842 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 000000000..60827add6 --- /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 a1ec0e68e..353d65049 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 aefc97d1b..da0500ebf 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 000000000..c8d70d92c --- /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 ebb472c4a..ea45fc7d1 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 000000000..cb23751e0 --- /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 2a4975201..dc6313c92 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 000000000..e114d9fb2 --- /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 000000000..68ffede19 --- /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 000000000..45dbccc28 --- /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 000000000..81f023308 --- /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 9c366e4eb..4c91143bd 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 8b9f7bec8..0ba730611 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 b515c2c8e..80c46e44f 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 a6bbacfa2..8365eff07 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 000000000..2cd1387ba --- /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 4fee682a2..8ca3e30ef 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 3095e7a46..add42ccd6 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 946324282..4ea0b98bd 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 000000000..e8003763f --- /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 5881a8e10..03d7f020c 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 + sha256: ee0e0d164516b90ae1f970bdf29f726f1aa730d7cfc449ecc74c495378b705da url: "https://pub.dev" source: hosted - version: "2.1.6" - pedantic: - dependency: transitive - description: - name: pedantic - sha256: "67fc27ed9639506c856c840ccce7594d0bdcd91bc8d53d6e52359449a1d50602" - 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: + version: "1.1.1" + wakelock_plus_platform_interface: dependency: transitive description: - name: wakelock_macos - sha256: "047c6be2f88cb6b76d02553bca5a3a3b95323b15d30867eca53a19a0a319d4cd" + name: wakelock_plus_platform_interface + sha256: "40fabed5da06caff0796dc638e1f07ee395fb18801fbff3255a2372db2d80385" 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: - dependency: transitive - description: - name: wakelock_windows - sha256: "857f77b3fe6ae82dd045455baa626bc4b93cb9bb6c86bf3f27c182167c3a5567" - 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 dc90a5373..1bb5f3738 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 63b5614bd..8f98e88b6 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 f836aadd0..e4d85c7cf 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 664de1a20..5d4acb1ba 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 2146c7cc8..4199cc89a 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 000000000..b63e331eb --- /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 01225eb46..e4a57a211 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 510753fae..50bc049ea 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 bed60e415..2c5bc55ba 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 9f3783805..148954d9a 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 8e60b3b70..46b9e51ef 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 000000000..507afeb3e --- /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 f193eb25e..29af0fe58 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -752,15 +752,6 @@ "type": "boolean" } }, - { - "name": "withoutThumbs", - "required": false, - "in": "query", - "description": "Include assets without thumbnails", - "schema": { - "type": "boolean" - } - }, { "name": "skip", "required": false, @@ -769,6 +760,15 @@ "type": "number" } }, + { + "name": "updatedAfter", + "required": false, + "in": "query", + "schema": { + "format": "date-time", + "type": "string" + } + }, { "name": "if-none-match", "in": "header", @@ -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,38 +3243,6 @@ ] } }, - "/search/config": { - "get": { - "operationId": "getSearchConfig", - "parameters": [], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SearchConfigResponseDto" - } - } - }, - "description": "" - } - }, - "security": [ - { - "bearer": [] - }, - { - "cookie": [] - }, - { - "api_key": [] - } - ], - "tags": [ - "Search" - ] - } - }, "/search/explore": { "get": { "operationId": "getExploreData", @@ -3215,6 +3310,27 @@ ] } }, + "/server-info/features": { + "get": { + "operationId": "getServerFeatures", + "parameters": [], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ServerFeaturesDto" + } + } + }, + "description": "" + } + }, + "tags": [ + "Server Info" + ] + } + }, "/server-info/media-types": { "get": { "operationId": "getSupportedMediaTypes", @@ -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 e6e2de6ab..df1e0b9ab 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 cdfb87d59..4f79e2dc1 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 faf21361e..d1e7a8fe6 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 4c91ce342..9de81a4f0 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 2d4051b0f..8f681d9bd 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 6d2c58528..5ee988bb4 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 000000000..1e4c3faa9 --- /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 8e9440f02..8e780869a 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 226cc77a9..1a018c233 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 875a0d5b7..000000000 --- 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 000000000..32601caf0 --- /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 000000000..2494883e0 --- /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 000000000..774ab1e42 --- /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 000000000..47d98e688 --- /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 000000000..2074b86f3 --- /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 36cec5e1b..aa958d582 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 b06720613..2e076ad21 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 72d530083..a2efd8796 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 537d5e5fe..3f57dc9bf 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 90dd4a646..68886d1f2 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 201e31ff3..c66c4eadc 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 02fa588c9..7062ab86b 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 f605bef4b..a452ad4f9 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 503440a5c..f8a323bbb 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 1c8290891..7f151689f 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 f9557fbae..71fe0bd41 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 e5bca7c83..b75bea23f 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 187ef3358..07a41400b 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 e74cc29b3..f48856bca 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 9f2f37958..000000000 --- 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 4ffec5832..d73c269ca 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 80236b8d4..66dd6ffb0 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 72113665a..74a46a52b 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 47cbd2ff8..000000000 --- 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 e844da689..000000000 --- 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 8b41b4af1..000000000 --- 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 1459ba452..000000000 --- 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 373fa734f..000000000 --- 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 ac3a82907..000000000 --- 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 000000000..b9cdd181f --- /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 ebb0d800f..6c8d19464 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 4a7e7f22b..655b21603 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 3f7b9b2d8..7c431fd5f 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 f6464cb02..7461058e2 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 c1341a04b..2512c4c32 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 000000000..b4063669d --- /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 f34ebf710..c089da3df 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 e5a685a30..da270886b 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 80f650571..5fa55a709 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 6b04669cc..f99979207 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 bb510c05b..fd450a296 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 ce85bd369..a87a115e9 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 a629c915c..52aee7c37 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 1067485ac..6b08228bd 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 aee680b28..9e7b149ab 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 b55cbb870..7a69c34e8 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 000000000..8b28f6e9f --- /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 e257c5a9a..b28e82ecb 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 bbc10d9bb..a36d1b305 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 59b635178..3b69c3532 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 8df877705..089fd6878 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 000000000..be5e14891 --- /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 6864a3f73..632a8d6b4 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 b93c4bbf9..b0da2f63d 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 ddfad682a..642f40c16 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 060c64ae3..98d4387eb 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 000000000..db2ba35da --- /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 000000000..71b8c7b2c --- /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 74f7f7497..2d4a0e91c 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 000000000..b19d38577 --- /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 5c7261b2d..c52c350fb 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 40398445a..3d3e22449 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 a295a42fb..127efee43 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 0ce7c07a5..cfe0eab3d 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 000000000..c0e831307 --- /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 1204a6ebd..08d9ca7d2 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 7c58f7102..5d421ebfd 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 f81e6381a..efdafaac0 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 c3262af83..acbbb82b9 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 253dfc56d..5ec1a2643 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 000000000..6395e78b0 --- /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 000000000..c915ed821 --- /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 c0e8aed3c..624cc0758 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 f2b512b88..2d419425d 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 1c8d09c50..ad2f68ca5 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 7f1eac831..ecd5d5c10 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 000000000..bd1a4b815 --- /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 aa62a5f01..2b2c19026 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 258ded0a7..254f3bad2 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 22f998972..866b78ba3 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 8ade1d5fd..36189d39f 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 @@ -6259,6 +6562,16 @@ export const AssetApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.updateAsset(id, updateAssetDto, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @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 @@ -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. @@ -6639,13 +6970,6 @@ export interface AssetApiGetAllAssetsRequest { */ readonly isArchived?: boolean - /** - * Include assets without thumbnails - * @type {boolean} - * @memberof AssetApiGetAllAssets - */ - readonly withoutThumbs?: boolean - /** * * @type {number} @@ -6653,6 +6977,13 @@ export interface AssetApiGetAllAssetsRequest { */ readonly skip?: number + /** + * + * @type {string} + * @memberof AssetApiGetAllAssets + */ + readonly updatedAfter?: string + /** * ETag of data already cached on the client * @type {string} @@ -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 7109cba71..d1eaca8a9 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 66890914a..91139577a 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 21fd71538..da61d4221 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 937e9a24a..75b153fbc 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 75f3b4786..26b655066 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 6cf41d300..eb55ec4c8 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 b794c387c..709ed6092 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 282450193..a44eaaccb 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 a6a94f142..119ac1a3b 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 37cda9ec6..1deddbeaf 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 679327e6d..3931d41eb 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 4acc1c2ad..cafa05393 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 @@