1
0
mirror of https://github.com/immich-app/immich.git synced 2025-08-08 23:07:06 +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:
Fynn Petersen-Frey
2022-11-26 17:16:02 +01:00
committed by GitHub
parent efa7b3ba54
commit 47f5e4134e
18 changed files with 322 additions and 201 deletions

View File

@ -26,10 +26,10 @@ class AssetResponseDto {
required this.mimeType,
required this.duration,
required this.webpPath,
required this.encodedVideoPath,
this.encodedVideoPath,
this.exifInfo,
this.smartInfo,
required this.livePhotoVideoId,
this.livePhotoVideoId,
});
AssetTypeEnum type;
@ -79,74 +79,71 @@ class AssetResponseDto {
String? livePhotoVideoId;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is AssetResponseDto &&
other.type == type &&
other.id == id &&
other.deviceAssetId == deviceAssetId &&
other.ownerId == ownerId &&
other.deviceId == deviceId &&
other.originalPath == originalPath &&
other.resizePath == resizePath &&
other.createdAt == createdAt &&
other.modifiedAt == modifiedAt &&
other.isFavorite == isFavorite &&
other.mimeType == mimeType &&
other.duration == duration &&
other.webpPath == webpPath &&
other.encodedVideoPath == encodedVideoPath &&
other.exifInfo == exifInfo &&
other.smartInfo == smartInfo &&
other.livePhotoVideoId == livePhotoVideoId;
bool operator ==(Object other) => identical(this, other) || other is AssetResponseDto &&
other.type == type &&
other.id == id &&
other.deviceAssetId == deviceAssetId &&
other.ownerId == ownerId &&
other.deviceId == deviceId &&
other.originalPath == originalPath &&
other.resizePath == resizePath &&
other.createdAt == createdAt &&
other.modifiedAt == modifiedAt &&
other.isFavorite == isFavorite &&
other.mimeType == mimeType &&
other.duration == duration &&
other.webpPath == webpPath &&
other.encodedVideoPath == encodedVideoPath &&
other.exifInfo == exifInfo &&
other.smartInfo == smartInfo &&
other.livePhotoVideoId == livePhotoVideoId;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(type.hashCode) +
(id.hashCode) +
(deviceAssetId.hashCode) +
(ownerId.hashCode) +
(deviceId.hashCode) +
(originalPath.hashCode) +
(resizePath == null ? 0 : resizePath!.hashCode) +
(createdAt.hashCode) +
(modifiedAt.hashCode) +
(isFavorite.hashCode) +
(mimeType == null ? 0 : mimeType!.hashCode) +
(duration.hashCode) +
(webpPath == null ? 0 : webpPath!.hashCode) +
(encodedVideoPath == null ? 0 : encodedVideoPath!.hashCode) +
(exifInfo == null ? 0 : exifInfo!.hashCode) +
(smartInfo == null ? 0 : smartInfo!.hashCode) +
(livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode);
// ignore: unnecessary_parenthesis
(type.hashCode) +
(id.hashCode) +
(deviceAssetId.hashCode) +
(ownerId.hashCode) +
(deviceId.hashCode) +
(originalPath.hashCode) +
(resizePath == null ? 0 : resizePath!.hashCode) +
(createdAt.hashCode) +
(modifiedAt.hashCode) +
(isFavorite.hashCode) +
(mimeType == null ? 0 : mimeType!.hashCode) +
(duration.hashCode) +
(webpPath == null ? 0 : webpPath!.hashCode) +
(encodedVideoPath == null ? 0 : encodedVideoPath!.hashCode) +
(exifInfo == null ? 0 : exifInfo!.hashCode) +
(smartInfo == null ? 0 : smartInfo!.hashCode) +
(livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode);
@override
String toString() =>
'AssetResponseDto[type=$type, id=$id, deviceAssetId=$deviceAssetId, ownerId=$ownerId, deviceId=$deviceId, originalPath=$originalPath, resizePath=$resizePath, createdAt=$createdAt, modifiedAt=$modifiedAt, isFavorite=$isFavorite, mimeType=$mimeType, duration=$duration, webpPath=$webpPath, encodedVideoPath=$encodedVideoPath, exifInfo=$exifInfo, smartInfo=$smartInfo, livePhotoVideoId=$livePhotoVideoId]';
String toString() => 'AssetResponseDto[type=$type, id=$id, deviceAssetId=$deviceAssetId, ownerId=$ownerId, deviceId=$deviceId, originalPath=$originalPath, resizePath=$resizePath, createdAt=$createdAt, modifiedAt=$modifiedAt, isFavorite=$isFavorite, mimeType=$mimeType, duration=$duration, webpPath=$webpPath, encodedVideoPath=$encodedVideoPath, exifInfo=$exifInfo, smartInfo=$smartInfo, livePhotoVideoId=$livePhotoVideoId]';
Map<String, dynamic> toJson() {
final _json = <String, dynamic>{};
_json[r'type'] = type;
_json[r'id'] = id;
_json[r'deviceAssetId'] = deviceAssetId;
_json[r'ownerId'] = ownerId;
_json[r'deviceId'] = deviceId;
_json[r'originalPath'] = originalPath;
_json[r'type'] = type;
_json[r'id'] = id;
_json[r'deviceAssetId'] = deviceAssetId;
_json[r'ownerId'] = ownerId;
_json[r'deviceId'] = deviceId;
_json[r'originalPath'] = originalPath;
if (resizePath != null) {
_json[r'resizePath'] = resizePath;
} else {
_json[r'resizePath'] = null;
}
_json[r'createdAt'] = createdAt;
_json[r'modifiedAt'] = modifiedAt;
_json[r'isFavorite'] = isFavorite;
_json[r'createdAt'] = createdAt;
_json[r'modifiedAt'] = modifiedAt;
_json[r'isFavorite'] = isFavorite;
if (mimeType != null) {
_json[r'mimeType'] = mimeType;
} else {
_json[r'mimeType'] = null;
}
_json[r'duration'] = duration;
_json[r'duration'] = duration;
if (webpPath != null) {
_json[r'webpPath'] = webpPath;
} else {
@ -185,13 +182,13 @@ class AssetResponseDto {
// Ensure that the map contains the required keys.
// Note 1: the values aren't checked for validity beyond being non-null.
// Note 2: this code is stripped in release mode!
// assert(() {
// requiredKeys.forEach((key) {
// assert(json.containsKey(key), 'Required key "AssetResponseDto[$key]" is missing from JSON.');
// assert(json[key] != null, 'Required key "AssetResponseDto[$key]" has a null value in JSON.');
// });
// return true;
// }());
assert(() {
requiredKeys.forEach((key) {
assert(json.containsKey(key), 'Required key "AssetResponseDto[$key]" is missing from JSON.');
assert(json[key] != null, 'Required key "AssetResponseDto[$key]" has a null value in JSON.');
});
return true;
}());
return AssetResponseDto(
type: AssetTypeEnum.fromJson(json[r'type'])!,
@ -216,10 +213,7 @@ class AssetResponseDto {
return null;
}
static List<AssetResponseDto>? listFromJson(
dynamic json, {
bool growable = false,
}) {
static List<AssetResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
final result = <AssetResponseDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
@ -247,18 +241,12 @@ class AssetResponseDto {
}
// maps a json object with a list of AssetResponseDto-objects as value to a dart map
static Map<String, List<AssetResponseDto>> mapListFromJson(
dynamic json, {
bool growable = false,
}) {
static Map<String, List<AssetResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<AssetResponseDto>>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = AssetResponseDto.listFromJson(
entry.value,
growable: growable,
);
final value = AssetResponseDto.listFromJson(entry.value, growable: growable,);
if (value != null) {
map[entry.key] = value;
}
@ -282,7 +270,6 @@ class AssetResponseDto {
'mimeType',
'duration',
'webpPath',
'encodedVideoPath',
'livePhotoVideoId',
};
}

View File

@ -21,7 +21,7 @@ class UserResponseDto {
required this.profileImagePath,
required this.shouldChangePassword,
required this.isAdmin,
required this.deletedAt,
this.deletedAt,
});
String id;
@ -40,49 +40,52 @@ class UserResponseDto {
bool isAdmin;
///
/// 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.
///
DateTime? deletedAt;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is UserResponseDto &&
other.id == id &&
other.email == email &&
other.firstName == firstName &&
other.lastName == lastName &&
other.createdAt == createdAt &&
other.profileImagePath == profileImagePath &&
other.shouldChangePassword == shouldChangePassword &&
other.isAdmin == isAdmin &&
other.deletedAt == deletedAt;
bool operator ==(Object other) => identical(this, other) || other is UserResponseDto &&
other.id == id &&
other.email == email &&
other.firstName == firstName &&
other.lastName == lastName &&
other.createdAt == createdAt &&
other.profileImagePath == profileImagePath &&
other.shouldChangePassword == shouldChangePassword &&
other.isAdmin == isAdmin &&
other.deletedAt == deletedAt;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(id.hashCode) +
(email.hashCode) +
(firstName.hashCode) +
(lastName.hashCode) +
(createdAt.hashCode) +
(profileImagePath.hashCode) +
(shouldChangePassword.hashCode) +
(isAdmin.hashCode) +
(deletedAt == null ? 0 : deletedAt!.hashCode);
// ignore: unnecessary_parenthesis
(id.hashCode) +
(email.hashCode) +
(firstName.hashCode) +
(lastName.hashCode) +
(createdAt.hashCode) +
(profileImagePath.hashCode) +
(shouldChangePassword.hashCode) +
(isAdmin.hashCode) +
(deletedAt == null ? 0 : deletedAt!.hashCode);
@override
String toString() =>
'UserResponseDto[id=$id, email=$email, firstName=$firstName, lastName=$lastName, createdAt=$createdAt, profileImagePath=$profileImagePath, shouldChangePassword=$shouldChangePassword, isAdmin=$isAdmin, deletedAt=$deletedAt]';
String toString() => 'UserResponseDto[id=$id, email=$email, firstName=$firstName, lastName=$lastName, createdAt=$createdAt, profileImagePath=$profileImagePath, shouldChangePassword=$shouldChangePassword, isAdmin=$isAdmin, deletedAt=$deletedAt]';
Map<String, dynamic> toJson() {
final _json = <String, dynamic>{};
_json[r'id'] = id;
_json[r'email'] = email;
_json[r'firstName'] = firstName;
_json[r'lastName'] = lastName;
_json[r'createdAt'] = createdAt;
_json[r'profileImagePath'] = profileImagePath;
_json[r'shouldChangePassword'] = shouldChangePassword;
_json[r'isAdmin'] = isAdmin;
_json[r'id'] = id;
_json[r'email'] = email;
_json[r'firstName'] = firstName;
_json[r'lastName'] = lastName;
_json[r'createdAt'] = createdAt;
_json[r'profileImagePath'] = profileImagePath;
_json[r'shouldChangePassword'] = shouldChangePassword;
_json[r'isAdmin'] = isAdmin;
if (deletedAt != null) {
_json[r'deletedAt'] = deletedAt!.toUtc().toIso8601String();
} else {
@ -101,13 +104,13 @@ class UserResponseDto {
// Ensure that the map contains the required keys.
// Note 1: the values aren't checked for validity beyond being non-null.
// Note 2: this code is stripped in release mode!
// assert(() {
// requiredKeys.forEach((key) {
// assert(json.containsKey(key), 'Required key "UserResponseDto[$key]" is missing from JSON.');
// assert(json[key] != null, 'Required key "UserResponseDto[$key]" has a null value in JSON.');
// });
// return true;
// }());
assert(() {
requiredKeys.forEach((key) {
assert(json.containsKey(key), 'Required key "UserResponseDto[$key]" is missing from JSON.');
assert(json[key] != null, 'Required key "UserResponseDto[$key]" has a null value in JSON.');
});
return true;
}());
return UserResponseDto(
id: mapValueOfType<String>(json, r'id')!,
@ -116,8 +119,7 @@ class UserResponseDto {
lastName: mapValueOfType<String>(json, r'lastName')!,
createdAt: mapValueOfType<String>(json, r'createdAt')!,
profileImagePath: mapValueOfType<String>(json, r'profileImagePath')!,
shouldChangePassword:
mapValueOfType<bool>(json, r'shouldChangePassword')!,
shouldChangePassword: mapValueOfType<bool>(json, r'shouldChangePassword')!,
isAdmin: mapValueOfType<bool>(json, r'isAdmin')!,
deletedAt: mapDateTime(json, r'deletedAt', ''),
);
@ -125,10 +127,7 @@ class UserResponseDto {
return null;
}
static List<UserResponseDto>? listFromJson(
dynamic json, {
bool growable = false,
}) {
static List<UserResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
final result = <UserResponseDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
@ -156,18 +155,12 @@ class UserResponseDto {
}
// maps a json object with a list of UserResponseDto-objects as value to a dart map
static Map<String, List<UserResponseDto>> mapListFromJson(
dynamic json, {
bool growable = false,
}) {
static Map<String, List<UserResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<UserResponseDto>>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = UserResponseDto.listFromJson(
entry.value,
growable: growable,
);
final value = UserResponseDto.listFromJson(entry.value, growable: growable,);
if (value != null) {
map[entry.key] = value;
}
@ -186,6 +179,6 @@ class UserResponseDto {
'profileImagePath',
'shouldChangePassword',
'isAdmin',
'deletedAt',
};
}