mirror of
https://github.com/immich-app/immich.git
synced 2024-12-25 10:43:13 +02:00
feat(mobile): use cached asset info if unchanged instead of downloading all assets (#1017)
* feat(mobile): use cached asset info if unchanged instead of downloading all assets This adds an HTTP ETag to the getAllAssets endpoint and client-side support in the app. If locally cache content is identical to the content on the server, the potentially large list of all assets does not need to be downloaded. * use ts import instead of require
This commit is contained in:
parent
efa7b3ba54
commit
47f5e4134e
@ -4,6 +4,7 @@ const String accessTokenKey = "immichBoxAccessTokenKey"; // Key 1
|
|||||||
const String deviceIdKey = 'immichBoxDeviceIdKey'; // Key 2
|
const String deviceIdKey = 'immichBoxDeviceIdKey'; // Key 2
|
||||||
const String isLoggedInKey = 'immichIsLoggedInKey'; // Key 3
|
const String isLoggedInKey = 'immichIsLoggedInKey'; // Key 3
|
||||||
const String serverEndpointKey = 'immichBoxServerEndpoint'; // Key 4
|
const String serverEndpointKey = 'immichBoxServerEndpoint'; // Key 4
|
||||||
|
const String assetEtagKey = 'immichAssetEtagKey'; // Key 5
|
||||||
|
|
||||||
// Login Info
|
// Login Info
|
||||||
const String hiveLoginInfoBox = "immichLoginInfoBox"; // Box
|
const String hiveLoginInfoBox = "immichLoginInfoBox"; // Box
|
||||||
|
@ -10,8 +10,9 @@ import 'package:immich_mobile/modules/backup/services/backup.service.dart';
|
|||||||
import 'package:immich_mobile/shared/models/asset.dart';
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
import 'package:immich_mobile/shared/providers/api.provider.dart';
|
import 'package:immich_mobile/shared/providers/api.provider.dart';
|
||||||
import 'package:immich_mobile/shared/services/api.service.dart';
|
import 'package:immich_mobile/shared/services/api.service.dart';
|
||||||
|
import 'package:immich_mobile/utils/openapi_extensions.dart';
|
||||||
|
import 'package:immich_mobile/utils/tuple.dart';
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
import 'package:photo_manager/photo_manager.dart';
|
|
||||||
|
|
||||||
final assetServiceProvider = Provider(
|
final assetServiceProvider = Provider(
|
||||||
(ref) => AssetService(
|
(ref) => AssetService(
|
||||||
@ -28,39 +29,22 @@ class AssetService {
|
|||||||
|
|
||||||
AssetService(this._apiService, this._backupService, this._backgroundService);
|
AssetService(this._apiService, this._backupService, this._backgroundService);
|
||||||
|
|
||||||
/// Returns all local, remote assets in that order
|
/// Returns `null` if the server state did not change, else list of assets
|
||||||
Future<List<Asset>> getAllAsset({bool urgent = false}) async {
|
Future<List<Asset>?> getRemoteAssets() async {
|
||||||
final List<Asset> assets = [];
|
final Box box = Hive.box(userInfoBox);
|
||||||
try {
|
final Pair<List<AssetResponseDto>, String?>? remote = await _apiService
|
||||||
// not using `await` here to fetch local & remote assets concurrently
|
.assetApi
|
||||||
final Future<List<AssetResponseDto>?> remoteTask =
|
.getAllAssetsWithETag(eTag: box.get(assetEtagKey));
|
||||||
_apiService.assetApi.getAllAssets();
|
if (remote == null) {
|
||||||
final Iterable<AssetEntity> newLocalAssets;
|
return null;
|
||||||
final List<AssetEntity> localAssets = await _getLocalAssets(urgent);
|
|
||||||
final List<AssetResponseDto> remoteAssets = await remoteTask ?? [];
|
|
||||||
if (remoteAssets.isNotEmpty && localAssets.isNotEmpty) {
|
|
||||||
final String deviceId = Hive.box(userInfoBox).get(deviceIdKey);
|
|
||||||
final Set<String> existingIds = remoteAssets
|
|
||||||
.where((e) => e.deviceId == deviceId)
|
|
||||||
.map((e) => e.deviceAssetId)
|
|
||||||
.toSet();
|
|
||||||
newLocalAssets = localAssets.where((e) => !existingIds.contains(e.id));
|
|
||||||
} else {
|
|
||||||
newLocalAssets = localAssets;
|
|
||||||
}
|
|
||||||
|
|
||||||
assets.addAll(newLocalAssets.map((e) => Asset.local(e)));
|
|
||||||
// the order (first all local, then remote assets) is important!
|
|
||||||
assets.addAll(remoteAssets.map((e) => Asset.remote(e)));
|
|
||||||
} catch (e) {
|
|
||||||
debugPrint("Error [getAllAsset] ${e.toString()}");
|
|
||||||
}
|
}
|
||||||
return assets;
|
box.put(assetEtagKey, remote.second);
|
||||||
|
return remote.first.map(Asset.remote).toList(growable: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// if [urgent] is `true`, do not block by waiting on the background service
|
/// if [urgent] is `true`, do not block by waiting on the background service
|
||||||
/// to finish running. Returns an empty list instead after a timeout.
|
/// to finish running. Returns `null` instead after a timeout.
|
||||||
Future<List<AssetEntity>> _getLocalAssets(bool urgent) async {
|
Future<List<Asset>?> getLocalAssets({bool urgent = false}) async {
|
||||||
try {
|
try {
|
||||||
final Future<bool> hasAccess = urgent
|
final Future<bool> hasAccess = urgent
|
||||||
? _backgroundService.hasAccess
|
? _backgroundService.hasAccess
|
||||||
@ -71,15 +55,16 @@ class AssetService {
|
|||||||
}
|
}
|
||||||
final box = await Hive.openBox<HiveBackupAlbums>(hiveBackupInfoBox);
|
final box = await Hive.openBox<HiveBackupAlbums>(hiveBackupInfoBox);
|
||||||
final HiveBackupAlbums? backupAlbumInfo = box.get(backupInfoKey);
|
final HiveBackupAlbums? backupAlbumInfo = box.get(backupInfoKey);
|
||||||
|
if (backupAlbumInfo != null) {
|
||||||
return backupAlbumInfo != null
|
return (await _backupService
|
||||||
? await _backupService
|
.buildUploadCandidates(backupAlbumInfo.deepCopy()))
|
||||||
.buildUploadCandidates(backupAlbumInfo.deepCopy())
|
.map(Asset.local)
|
||||||
: [];
|
.toList(growable: false);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint("Error [_getLocalAssets] ${e.toString()}");
|
debugPrint("Error [_getLocalAssets] ${e.toString()}");
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Asset?> getAssetById(String assetId) async {
|
Future<Asset?> getAssetById(String assetId) async {
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/constants/hive_box.dart';
|
||||||
import 'package:immich_mobile/modules/home/services/asset.service.dart';
|
import 'package:immich_mobile/modules/home/services/asset.service.dart';
|
||||||
import 'package:immich_mobile/modules/home/services/asset_cache.service.dart';
|
import 'package:immich_mobile/modules/home/services/asset_cache.service.dart';
|
||||||
import 'package:immich_mobile/shared/models/asset.dart';
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
@ -33,10 +35,11 @@ class AssetNotifier extends StateNotifier<List<Asset>> {
|
|||||||
final stopwatch = Stopwatch();
|
final stopwatch = Stopwatch();
|
||||||
try {
|
try {
|
||||||
_getAllAssetInProgress = true;
|
_getAllAssetInProgress = true;
|
||||||
|
|
||||||
final bool isCacheValid = await _assetCacheService.isValid();
|
final bool isCacheValid = await _assetCacheService.isValid();
|
||||||
|
stopwatch.start();
|
||||||
|
final localTask = _assetService.getLocalAssets(urgent: !isCacheValid);
|
||||||
|
final remoteTask = _assetService.getRemoteAssets();
|
||||||
if (isCacheValid && state.isEmpty) {
|
if (isCacheValid && state.isEmpty) {
|
||||||
stopwatch.start();
|
|
||||||
state = await _assetCacheService.get();
|
state = await _assetCacheService.get();
|
||||||
debugPrint(
|
debugPrint(
|
||||||
"Reading assets from cache: ${stopwatch.elapsedMilliseconds}ms",
|
"Reading assets from cache: ${stopwatch.elapsedMilliseconds}ms",
|
||||||
@ -44,21 +47,49 @@ class AssetNotifier extends StateNotifier<List<Asset>> {
|
|||||||
stopwatch.reset();
|
stopwatch.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
stopwatch.start();
|
int remoteBegin = state.indexWhere((a) => a.isRemote);
|
||||||
var allAssets = await _assetService.getAllAsset(urgent: !isCacheValid);
|
remoteBegin = remoteBegin == -1 ? state.length : remoteBegin;
|
||||||
debugPrint("Query assets from API: ${stopwatch.elapsedMilliseconds}ms");
|
final List<Asset> currentLocal = state.slice(0, remoteBegin);
|
||||||
|
List<Asset>? newRemote = await remoteTask;
|
||||||
|
List<Asset>? newLocal = await localTask;
|
||||||
|
debugPrint("Load assets: ${stopwatch.elapsedMilliseconds}ms");
|
||||||
stopwatch.reset();
|
stopwatch.reset();
|
||||||
|
if (newRemote == null &&
|
||||||
state = allAssets;
|
(newLocal == null || currentLocal.equals(newLocal))) {
|
||||||
|
debugPrint("state is already up-to-date");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
newRemote ??= state.slice(remoteBegin);
|
||||||
|
newLocal ??= [];
|
||||||
|
state = _combineLocalAndRemoteAssets(local: newLocal, remote: newRemote);
|
||||||
|
debugPrint("Combining assets: ${stopwatch.elapsedMilliseconds}ms");
|
||||||
} finally {
|
} finally {
|
||||||
_getAllAssetInProgress = false;
|
_getAllAssetInProgress = false;
|
||||||
}
|
}
|
||||||
debugPrint("[getAllAsset] setting new asset state");
|
debugPrint("[getAllAsset] setting new asset state");
|
||||||
|
|
||||||
stopwatch.start();
|
stopwatch.reset();
|
||||||
_cacheState();
|
_cacheState();
|
||||||
debugPrint("Store assets in cache: ${stopwatch.elapsedMilliseconds}ms");
|
debugPrint("Store assets in cache: ${stopwatch.elapsedMilliseconds}ms");
|
||||||
stopwatch.reset();
|
}
|
||||||
|
|
||||||
|
List<Asset> _combineLocalAndRemoteAssets({
|
||||||
|
required Iterable<Asset> local,
|
||||||
|
required List<Asset> remote,
|
||||||
|
}) {
|
||||||
|
final List<Asset> assets = [];
|
||||||
|
if (remote.isNotEmpty && local.isNotEmpty) {
|
||||||
|
final String deviceId = Hive.box(userInfoBox).get(deviceIdKey);
|
||||||
|
final Set<String> existingIds = remote
|
||||||
|
.where((e) => e.deviceId == deviceId)
|
||||||
|
.map((e) => e.deviceAssetId)
|
||||||
|
.toSet();
|
||||||
|
local = local.where((e) => !existingIds.contains(e.id));
|
||||||
|
}
|
||||||
|
assets.addAll(local);
|
||||||
|
// the order (first all local, then remote assets) is important!
|
||||||
|
assets.addAll(remote);
|
||||||
|
return assets;
|
||||||
}
|
}
|
||||||
|
|
||||||
clearAllAsset() {
|
clearAllAsset() {
|
||||||
|
53
mobile/lib/utils/openapi_extensions.dart
Normal file
53
mobile/lib/utils/openapi_extensions.dart
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:http/http.dart';
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
|
import 'tuple.dart';
|
||||||
|
|
||||||
|
/// Extension methods to retrieve ETag together with the API call
|
||||||
|
extension WithETag on AssetApi {
|
||||||
|
/// Get all AssetEntity belong to the user
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
///
|
||||||
|
/// * [String] eTag:
|
||||||
|
/// ETag of data already cached on the client
|
||||||
|
Future<Pair<List<AssetResponseDto>, String?>?> getAllAssetsWithETag({
|
||||||
|
String? eTag,
|
||||||
|
}) async {
|
||||||
|
final response = await getAllAssetsWithHttpInfo(
|
||||||
|
ifNoneMatch: eTag,
|
||||||
|
);
|
||||||
|
if (response.statusCode >= HttpStatus.badRequest) {
|
||||||
|
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||||
|
}
|
||||||
|
// When a remote server returns no body with a status of 204, we shall not decode it.
|
||||||
|
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
|
||||||
|
// FormatException when trying to decode an empty string.
|
||||||
|
if (response.body.isNotEmpty &&
|
||||||
|
response.statusCode != HttpStatus.noContent) {
|
||||||
|
final responseBody = await _decodeBodyBytes(response);
|
||||||
|
final etag = response.headers[HttpHeaders.etagHeader];
|
||||||
|
final data = (await apiClient.deserializeAsync(
|
||||||
|
responseBody, 'List<AssetResponseDto>') as List)
|
||||||
|
.cast<AssetResponseDto>()
|
||||||
|
.toList();
|
||||||
|
return Pair(data, etag);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the decoded body as UTF-8 if the given headers indicate an 'application/json'
|
||||||
|
/// content type. Otherwise, returns the decoded body as decoded by dart:http package.
|
||||||
|
Future<String> _decodeBodyBytes(Response response) async {
|
||||||
|
final contentType = response.headers['content-type'];
|
||||||
|
return contentType != null &&
|
||||||
|
contentType.toLowerCase().startsWith('application/json')
|
||||||
|
? response.bodyBytes.isEmpty
|
||||||
|
? ''
|
||||||
|
: utf8.decode(response.bodyBytes)
|
||||||
|
: response.body;
|
||||||
|
}
|
8
mobile/lib/utils/tuple.dart
Normal file
8
mobile/lib/utils/tuple.dart
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/// An immutable pair or 2-tuple
|
||||||
|
/// TODO replace with Record once Dart 2.19 is available
|
||||||
|
class Pair<T1, T2> {
|
||||||
|
final T1 first;
|
||||||
|
final T2 second;
|
||||||
|
|
||||||
|
const Pair(this.first, this.second);
|
||||||
|
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -14,6 +14,7 @@ import {
|
|||||||
Header,
|
Header,
|
||||||
Put,
|
Put,
|
||||||
UploadedFiles,
|
UploadedFiles,
|
||||||
|
Request,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { Authenticated } from '../../decorators/authenticated.decorator';
|
import { Authenticated } from '../../decorators/authenticated.decorator';
|
||||||
import { AssetService } from './asset.service';
|
import { AssetService } from './asset.service';
|
||||||
@ -21,12 +22,12 @@ import { FileFieldsInterceptor } from '@nestjs/platform-express';
|
|||||||
import { assetUploadOption } from '../../config/asset-upload.config';
|
import { assetUploadOption } from '../../config/asset-upload.config';
|
||||||
import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator';
|
import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator';
|
||||||
import { ServeFileDto } from './dto/serve-file.dto';
|
import { ServeFileDto } from './dto/serve-file.dto';
|
||||||
import { Response as Res } from 'express';
|
import { Response as Res, Request as Req } from 'express';
|
||||||
import { BackgroundTaskService } from '../../modules/background-task/background-task.service';
|
import { BackgroundTaskService } from '../../modules/background-task/background-task.service';
|
||||||
import { DeleteAssetDto } from './dto/delete-asset.dto';
|
import { DeleteAssetDto } from './dto/delete-asset.dto';
|
||||||
import { SearchAssetDto } from './dto/search-asset.dto';
|
import { SearchAssetDto } from './dto/search-asset.dto';
|
||||||
import { CheckDuplicateAssetDto } from './dto/check-duplicate-asset.dto';
|
import { CheckDuplicateAssetDto } from './dto/check-duplicate-asset.dto';
|
||||||
import { ApiBearerAuth, ApiBody, ApiConsumes, ApiTags } from '@nestjs/swagger';
|
import { ApiBearerAuth, ApiBody, ApiConsumes, ApiHeader, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||||
import { CuratedObjectsResponseDto } from './response-dto/curated-objects-response.dto';
|
import { CuratedObjectsResponseDto } from './response-dto/curated-objects-response.dto';
|
||||||
import { CuratedLocationsResponseDto } from './response-dto/curated-locations-response.dto';
|
import { CuratedLocationsResponseDto } from './response-dto/curated-locations-response.dto';
|
||||||
import { AssetResponseDto } from './response-dto/asset-response.dto';
|
import { AssetResponseDto } from './response-dto/asset-response.dto';
|
||||||
@ -49,6 +50,7 @@ import {
|
|||||||
IMMICH_ARCHIVE_FILE_COUNT,
|
IMMICH_ARCHIVE_FILE_COUNT,
|
||||||
IMMICH_CONTENT_LENGTH_HINT,
|
IMMICH_CONTENT_LENGTH_HINT,
|
||||||
} from '../../constants/download.constant';
|
} from '../../constants/download.constant';
|
||||||
|
import { etag } from '../../utils/etag';
|
||||||
|
|
||||||
@Authenticated()
|
@Authenticated()
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@ -168,8 +170,28 @@ export class AssetController {
|
|||||||
* Get all AssetEntity belong to the user
|
* Get all AssetEntity belong to the user
|
||||||
*/
|
*/
|
||||||
@Get('/')
|
@Get('/')
|
||||||
async getAllAssets(@GetAuthUser() authUser: AuthUserDto): Promise<AssetResponseDto[]> {
|
@ApiHeader({
|
||||||
return await this.assetService.getAllAssets(authUser);
|
name: 'if-none-match',
|
||||||
|
description: 'ETag of data already cached on the client',
|
||||||
|
required: false,
|
||||||
|
schema: { type: 'string' },
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
headers: { ETag: { required: true, schema: { type: 'string' } } },
|
||||||
|
type: [AssetResponseDto],
|
||||||
|
})
|
||||||
|
async getAllAssets(@GetAuthUser() authUser: AuthUserDto, @Response() response: Res, @Request() request: Req) {
|
||||||
|
const assets = await this.assetService.getAllAssets(authUser);
|
||||||
|
const clientEtag = request.headers['if-none-match'];
|
||||||
|
const json = JSON.stringify(assets);
|
||||||
|
const serverEtag = await etag(json);
|
||||||
|
response.setHeader('ETag', serverEtag);
|
||||||
|
if (clientEtag === serverEtag) {
|
||||||
|
response.status(304).end();
|
||||||
|
} else {
|
||||||
|
response.contentType('application/json').status(200).send(json);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/time-bucket')
|
@Post('/time-bucket')
|
||||||
|
@ -19,10 +19,10 @@ export class AssetResponseDto {
|
|||||||
mimeType!: string | null;
|
mimeType!: string | null;
|
||||||
duration!: string;
|
duration!: string;
|
||||||
webpPath!: string | null;
|
webpPath!: string | null;
|
||||||
encodedVideoPath!: string | null;
|
encodedVideoPath?: string | null;
|
||||||
exifInfo?: ExifResponseDto;
|
exifInfo?: ExifResponseDto;
|
||||||
smartInfo?: SmartInfoResponseDto;
|
smartInfo?: SmartInfoResponseDto;
|
||||||
livePhotoVideoId!: string | null;
|
livePhotoVideoId?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mapAsset(entity: AssetEntity): AssetResponseDto {
|
export function mapAsset(entity: AssetEntity): AssetResponseDto {
|
||||||
|
@ -9,7 +9,7 @@ export class UserResponseDto {
|
|||||||
profileImagePath!: string;
|
profileImagePath!: string;
|
||||||
shouldChangePassword!: boolean;
|
shouldChangePassword!: boolean;
|
||||||
isAdmin!: boolean;
|
isAdmin!: boolean;
|
||||||
deletedAt!: Date | null;
|
deletedAt?: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mapUser(entity: UserEntity): UserResponseDto {
|
export function mapUser(entity: UserEntity): UserResponseDto {
|
||||||
@ -22,6 +22,6 @@ export function mapUser(entity: UserEntity): UserResponseDto {
|
|||||||
profileImagePath: entity.profileImagePath,
|
profileImagePath: entity.profileImagePath,
|
||||||
shouldChangePassword: entity.shouldChangePassword,
|
shouldChangePassword: entity.shouldChangePassword,
|
||||||
isAdmin: entity.isAdmin,
|
isAdmin: entity.isAdmin,
|
||||||
deletedAt: entity.deletedAt || null,
|
deletedAt: entity.deletedAt,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
5
server/apps/immich/src/types/index.d.ts
vendored
Normal file
5
server/apps/immich/src/types/index.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
declare module 'crypto' {
|
||||||
|
namespace webcrypto {
|
||||||
|
const subtle: SubtleCrypto;
|
||||||
|
}
|
||||||
|
}
|
10
server/apps/immich/src/utils/etag.ts
Normal file
10
server/apps/immich/src/utils/etag.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { webcrypto } from 'node:crypto';
|
||||||
|
const { subtle } = webcrypto;
|
||||||
|
|
||||||
|
export async function etag(text: string): Promise<string> {
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
const data = encoder.encode(text);
|
||||||
|
const buffer = await subtle.digest('SHA-1', data);
|
||||||
|
const hash = Buffer.from(buffer).toString('base64').slice(0, 27);
|
||||||
|
return `"${data.length}-${hash}"`;
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
@ -427,7 +427,7 @@ export interface AssetResponseDto {
|
|||||||
* @type {string}
|
* @type {string}
|
||||||
* @memberof AssetResponseDto
|
* @memberof AssetResponseDto
|
||||||
*/
|
*/
|
||||||
'encodedVideoPath': string | null;
|
'encodedVideoPath'?: string | null;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {ExifResponseDto}
|
* @type {ExifResponseDto}
|
||||||
@ -445,7 +445,7 @@ export interface AssetResponseDto {
|
|||||||
* @type {string}
|
* @type {string}
|
||||||
* @memberof AssetResponseDto
|
* @memberof AssetResponseDto
|
||||||
*/
|
*/
|
||||||
'livePhotoVideoId': string | null;
|
'livePhotoVideoId'?: string | null;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -1729,7 +1729,7 @@ export interface UserResponseDto {
|
|||||||
* @type {string}
|
* @type {string}
|
||||||
* @memberof UserResponseDto
|
* @memberof UserResponseDto
|
||||||
*/
|
*/
|
||||||
'deletedAt': string | null;
|
'deletedAt'?: string;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -2788,10 +2788,11 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
|
|||||||
/**
|
/**
|
||||||
* Get all AssetEntity belong to the user
|
* Get all AssetEntity belong to the user
|
||||||
* @summary
|
* @summary
|
||||||
|
* @param {string} [ifNoneMatch] ETag of data already cached on the client
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
getAllAssets: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
getAllAssets: async (ifNoneMatch?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
const localVarPath = `/asset`;
|
const localVarPath = `/asset`;
|
||||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||||
@ -2808,6 +2809,10 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
|
|||||||
// http bearer authentication required
|
// http bearer authentication required
|
||||||
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||||
|
|
||||||
|
if (ifNoneMatch !== undefined && ifNoneMatch !== null) {
|
||||||
|
localVarHeaderParameter['if-none-match'] = String(ifNoneMatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
@ -3388,11 +3393,12 @@ export const AssetApiFp = function(configuration?: Configuration) {
|
|||||||
/**
|
/**
|
||||||
* Get all AssetEntity belong to the user
|
* Get all AssetEntity belong to the user
|
||||||
* @summary
|
* @summary
|
||||||
|
* @param {string} [ifNoneMatch] ETag of data already cached on the client
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
async getAllAssets(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<AssetResponseDto>>> {
|
async getAllAssets(ifNoneMatch?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<AssetResponseDto>>> {
|
||||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getAllAssets(options);
|
const localVarAxiosArgs = await localVarAxiosParamCreator.getAllAssets(ifNoneMatch, options);
|
||||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
@ -3590,11 +3596,12 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
|
|||||||
/**
|
/**
|
||||||
* Get all AssetEntity belong to the user
|
* Get all AssetEntity belong to the user
|
||||||
* @summary
|
* @summary
|
||||||
|
* @param {string} [ifNoneMatch] ETag of data already cached on the client
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
getAllAssets(options?: any): AxiosPromise<Array<AssetResponseDto>> {
|
getAllAssets(ifNoneMatch?: string, options?: any): AxiosPromise<Array<AssetResponseDto>> {
|
||||||
return localVarFp.getAllAssets(options).then((request) => request(axios, basePath));
|
return localVarFp.getAllAssets(ifNoneMatch, options).then((request) => request(axios, basePath));
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Get a single asset\'s information
|
* Get a single asset\'s information
|
||||||
@ -3788,12 +3795,13 @@ export class AssetApi extends BaseAPI {
|
|||||||
/**
|
/**
|
||||||
* Get all AssetEntity belong to the user
|
* Get all AssetEntity belong to the user
|
||||||
* @summary
|
* @summary
|
||||||
|
* @param {string} [ifNoneMatch] ETag of data already cached on the client
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
* @memberof AssetApi
|
* @memberof AssetApi
|
||||||
*/
|
*/
|
||||||
public getAllAssets(options?: AxiosRequestConfig) {
|
public getAllAssets(ifNoneMatch?: string, options?: AxiosRequestConfig) {
|
||||||
return AssetApiFp(this.configuration).getAllAssets(options).then((request) => request(this.axios, this.basePath));
|
return AssetApiFp(this.configuration).getAllAssets(ifNoneMatch, options).then((request) => request(this.axios, this.basePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user