Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d1a5ba7a66 | |||
| d3646b2eab |
@@ -920,7 +920,6 @@
|
|||||||
"cant_get_number_of_comments": "Can't get number of comments",
|
"cant_get_number_of_comments": "Can't get number of comments",
|
||||||
"cant_search_people": "Can't search people",
|
"cant_search_people": "Can't search people",
|
||||||
"cant_search_places": "Can't search places",
|
"cant_search_places": "Can't search places",
|
||||||
"clipboard_unsupported_mime_type": "The system clipboard does not support copying this type of content: {mimeType}",
|
|
||||||
"error_adding_assets_to_album": "Error adding assets to album",
|
"error_adding_assets_to_album": "Error adding assets to album",
|
||||||
"error_adding_users_to_album": "Error adding users to album",
|
"error_adding_users_to_album": "Error adding users to album",
|
||||||
"error_deleting_shared_user": "Error deleting shared user",
|
"error_deleting_shared_user": "Error deleting shared user",
|
||||||
|
|||||||
@@ -89,7 +89,8 @@ data class PlatformAsset (
|
|||||||
val height: Long? = null,
|
val height: Long? = null,
|
||||||
val durationInSeconds: Long,
|
val durationInSeconds: Long,
|
||||||
val orientation: Long,
|
val orientation: Long,
|
||||||
val isFavorite: Boolean
|
val isFavorite: Boolean,
|
||||||
|
val adjustmentTimestamp: Long? = null
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
companion object {
|
companion object {
|
||||||
@@ -104,7 +105,8 @@ data class PlatformAsset (
|
|||||||
val durationInSeconds = pigeonVar_list[7] as Long
|
val durationInSeconds = pigeonVar_list[7] as Long
|
||||||
val orientation = pigeonVar_list[8] as Long
|
val orientation = pigeonVar_list[8] as Long
|
||||||
val isFavorite = pigeonVar_list[9] as Boolean
|
val isFavorite = pigeonVar_list[9] as Boolean
|
||||||
return PlatformAsset(id, name, type, createdAt, updatedAt, width, height, durationInSeconds, orientation, isFavorite)
|
val adjustmentTimestamp = pigeonVar_list[10] as Long?
|
||||||
|
return PlatformAsset(id, name, type, createdAt, updatedAt, width, height, durationInSeconds, orientation, isFavorite, adjustmentTimestamp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun toList(): List<Any?> {
|
fun toList(): List<Any?> {
|
||||||
@@ -119,6 +121,7 @@ data class PlatformAsset (
|
|||||||
durationInSeconds,
|
durationInSeconds,
|
||||||
orientation,
|
orientation,
|
||||||
isFavorite,
|
isFavorite,
|
||||||
|
adjustmentTimestamp,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
|
|||||||
@@ -138,6 +138,7 @@ open class NativeSyncApiImplBase(context: Context) {
|
|||||||
duration,
|
duration,
|
||||||
orientation.toLong(),
|
orientation.toLong(),
|
||||||
isFavorite,
|
isFavorite,
|
||||||
|
adjustmentTimestamp = null
|
||||||
)
|
)
|
||||||
yield(AssetResult.ValidAsset(asset, bucketId))
|
yield(AssetResult.ValidAsset(asset, bucketId))
|
||||||
}
|
}
|
||||||
|
|||||||
+1
File diff suppressed because one or more lines are too long
@@ -6,9 +6,6 @@ PODS:
|
|||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- connectivity_plus (0.0.1):
|
- connectivity_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- cupertino_http (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- FlutterMacOS
|
|
||||||
- device_info_plus (0.0.1):
|
- device_info_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- DKImagePickerController/Core (4.3.9):
|
- DKImagePickerController/Core (4.3.9):
|
||||||
@@ -80,8 +77,6 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
- network_info_plus (0.0.1):
|
- network_info_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- objective_c (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- package_info_plus (0.4.5):
|
- package_info_plus (0.4.5):
|
||||||
- Flutter
|
- Flutter
|
||||||
- path_provider_foundation (0.0.1):
|
- path_provider_foundation (0.0.1):
|
||||||
@@ -141,7 +136,6 @@ DEPENDENCIES:
|
|||||||
- background_downloader (from `.symlinks/plugins/background_downloader/ios`)
|
- background_downloader (from `.symlinks/plugins/background_downloader/ios`)
|
||||||
- bonsoir_darwin (from `.symlinks/plugins/bonsoir_darwin/darwin`)
|
- bonsoir_darwin (from `.symlinks/plugins/bonsoir_darwin/darwin`)
|
||||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||||
- cupertino_http (from `.symlinks/plugins/cupertino_http/darwin`)
|
|
||||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||||
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
@@ -160,7 +154,6 @@ DEPENDENCIES:
|
|||||||
- maplibre_gl (from `.symlinks/plugins/maplibre_gl/ios`)
|
- maplibre_gl (from `.symlinks/plugins/maplibre_gl/ios`)
|
||||||
- native_video_player (from `.symlinks/plugins/native_video_player/ios`)
|
- native_video_player (from `.symlinks/plugins/native_video_player/ios`)
|
||||||
- network_info_plus (from `.symlinks/plugins/network_info_plus/ios`)
|
- network_info_plus (from `.symlinks/plugins/network_info_plus/ios`)
|
||||||
- objective_c (from `.symlinks/plugins/objective_c/ios`)
|
|
||||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||||
@@ -191,8 +184,6 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/bonsoir_darwin/darwin"
|
:path: ".symlinks/plugins/bonsoir_darwin/darwin"
|
||||||
connectivity_plus:
|
connectivity_plus:
|
||||||
:path: ".symlinks/plugins/connectivity_plus/ios"
|
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||||
cupertino_http:
|
|
||||||
:path: ".symlinks/plugins/cupertino_http/darwin"
|
|
||||||
device_info_plus:
|
device_info_plus:
|
||||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||||
file_picker:
|
file_picker:
|
||||||
@@ -229,8 +220,6 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/native_video_player/ios"
|
:path: ".symlinks/plugins/native_video_player/ios"
|
||||||
network_info_plus:
|
network_info_plus:
|
||||||
:path: ".symlinks/plugins/network_info_plus/ios"
|
:path: ".symlinks/plugins/network_info_plus/ios"
|
||||||
objective_c:
|
|
||||||
:path: ".symlinks/plugins/objective_c/ios"
|
|
||||||
package_info_plus:
|
package_info_plus:
|
||||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
@@ -260,7 +249,6 @@ SPEC CHECKSUMS:
|
|||||||
background_downloader: 50e91d979067b82081aba359d7d916b3ba5fadad
|
background_downloader: 50e91d979067b82081aba359d7d916b3ba5fadad
|
||||||
bonsoir_darwin: 29c7ccf356646118844721f36e1de4b61f6cbd0e
|
bonsoir_darwin: 29c7ccf356646118844721f36e1de4b61f6cbd0e
|
||||||
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
|
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
|
||||||
cupertino_http: 94ac07f5ff090b8effa6c5e2c47871d48ab7c86c
|
|
||||||
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
|
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
|
||||||
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
|
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
|
||||||
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
||||||
@@ -282,7 +270,6 @@ SPEC CHECKSUMS:
|
|||||||
maplibre_gl: 3c924e44725147b03dda33430ad216005b40555f
|
maplibre_gl: 3c924e44725147b03dda33430ad216005b40555f
|
||||||
native_video_player: b65c58951ede2f93d103a25366bdebca95081265
|
native_video_player: b65c58951ede2f93d103a25366bdebca95081265
|
||||||
network_info_plus: cf61925ab5205dce05a4f0895989afdb6aade5fc
|
network_info_plus: cf61925ab5205dce05a4f0895989afdb6aade5fc
|
||||||
objective_c: 89e720c30d716b036faf9c9684022048eee1eee2
|
|
||||||
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
||||||
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
||||||
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ struct PlatformAsset: Hashable {
|
|||||||
var durationInSeconds: Int64
|
var durationInSeconds: Int64
|
||||||
var orientation: Int64
|
var orientation: Int64
|
||||||
var isFavorite: Bool
|
var isFavorite: Bool
|
||||||
|
var adjustmentTimestamp: Int64? = nil
|
||||||
|
|
||||||
|
|
||||||
// swift-format-ignore: AlwaysUseLowerCamelCase
|
// swift-format-ignore: AlwaysUseLowerCamelCase
|
||||||
@@ -154,6 +155,7 @@ struct PlatformAsset: Hashable {
|
|||||||
let durationInSeconds = pigeonVar_list[7] as! Int64
|
let durationInSeconds = pigeonVar_list[7] as! Int64
|
||||||
let orientation = pigeonVar_list[8] as! Int64
|
let orientation = pigeonVar_list[8] as! Int64
|
||||||
let isFavorite = pigeonVar_list[9] as! Bool
|
let isFavorite = pigeonVar_list[9] as! Bool
|
||||||
|
let adjustmentTimestamp: Int64? = nilOrValue(pigeonVar_list[10])
|
||||||
|
|
||||||
return PlatformAsset(
|
return PlatformAsset(
|
||||||
id: id,
|
id: id,
|
||||||
@@ -165,7 +167,8 @@ struct PlatformAsset: Hashable {
|
|||||||
height: height,
|
height: height,
|
||||||
durationInSeconds: durationInSeconds,
|
durationInSeconds: durationInSeconds,
|
||||||
orientation: orientation,
|
orientation: orientation,
|
||||||
isFavorite: isFavorite
|
isFavorite: isFavorite,
|
||||||
|
adjustmentTimestamp: adjustmentTimestamp
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
func toList() -> [Any?] {
|
func toList() -> [Any?] {
|
||||||
@@ -180,6 +183,7 @@ struct PlatformAsset: Hashable {
|
|||||||
durationInSeconds,
|
durationInSeconds,
|
||||||
orientation,
|
orientation,
|
||||||
isFavorite,
|
isFavorite,
|
||||||
|
adjustmentTimestamp,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
static func == (lhs: PlatformAsset, rhs: PlatformAsset) -> Bool {
|
static func == (lhs: PlatformAsset, rhs: PlatformAsset) -> Bool {
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ extension PHAsset {
|
|||||||
height: Int64(pixelHeight),
|
height: Int64(pixelHeight),
|
||||||
durationInSeconds: Int64(duration),
|
durationInSeconds: Int64(duration),
|
||||||
orientation: 0,
|
orientation: 0,
|
||||||
isFavorite: isFavorite
|
isFavorite: isFavorite,
|
||||||
|
adjustmentTimestamp: adjustmentTimestamp
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,6 +24,10 @@ extension PHAsset {
|
|||||||
var filename: String? {
|
var filename: String? {
|
||||||
return value(forKey: "filename") as? String
|
return value(forKey: "filename") as? String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var adjustmentTimestamp: Int64 {
|
||||||
|
return (value(forKey: "adjustmentTimestamp") as? Date?).map( {Int64($0?.timeIntervalSince1970 ?? 0)} ) ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
// This method is expected to be slow as it goes through the asset resources to fetch the originalFilename
|
// This method is expected to be slow as it goes through the asset resources to fetch the originalFilename
|
||||||
var originalFilename: String? {
|
var originalFilename: String? {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ class LocalAsset extends BaseAsset {
|
|||||||
final String id;
|
final String id;
|
||||||
final String? remoteId;
|
final String? remoteId;
|
||||||
final int orientation;
|
final int orientation;
|
||||||
|
final int? adjustmentTimestamp;
|
||||||
|
|
||||||
const LocalAsset({
|
const LocalAsset({
|
||||||
required this.id,
|
required this.id,
|
||||||
@@ -19,6 +20,7 @@ class LocalAsset extends BaseAsset {
|
|||||||
super.isFavorite = false,
|
super.isFavorite = false,
|
||||||
super.livePhotoVideoId,
|
super.livePhotoVideoId,
|
||||||
this.orientation = 0,
|
this.orientation = 0,
|
||||||
|
this.adjustmentTimestamp,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -41,6 +43,7 @@ class LocalAsset extends BaseAsset {
|
|||||||
remoteId: ${remoteId ?? "<NA>"}
|
remoteId: ${remoteId ?? "<NA>"}
|
||||||
isFavorite: $isFavorite,
|
isFavorite: $isFavorite,
|
||||||
orientation: $orientation,
|
orientation: $orientation,
|
||||||
|
adjustmentTimestamp: ${adjustmentTimestamp ?? "<NA>"}
|
||||||
}''';
|
}''';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,11 +52,15 @@ class LocalAsset extends BaseAsset {
|
|||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
if (other is! LocalAsset) return false;
|
if (other is! LocalAsset) return false;
|
||||||
if (identical(this, other)) return true;
|
if (identical(this, other)) return true;
|
||||||
return super == other && id == other.id && orientation == other.orientation;
|
return super == other &&
|
||||||
|
id == other.id &&
|
||||||
|
orientation == other.orientation &&
|
||||||
|
adjustmentTimestamp == other.adjustmentTimestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => super.hashCode ^ id.hashCode ^ remoteId.hashCode ^ orientation.hashCode;
|
int get hashCode =>
|
||||||
|
super.hashCode ^ id.hashCode ^ remoteId.hashCode ^ orientation.hashCode ^ adjustmentTimestamp.hashCode;
|
||||||
|
|
||||||
LocalAsset copyWith({
|
LocalAsset copyWith({
|
||||||
String? id,
|
String? id,
|
||||||
@@ -68,6 +75,7 @@ class LocalAsset extends BaseAsset {
|
|||||||
int? durationInSeconds,
|
int? durationInSeconds,
|
||||||
bool? isFavorite,
|
bool? isFavorite,
|
||||||
int? orientation,
|
int? orientation,
|
||||||
|
int? adjustmentTimestamp,
|
||||||
}) {
|
}) {
|
||||||
return LocalAsset(
|
return LocalAsset(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
@@ -82,6 +90,7 @@ class LocalAsset extends BaseAsset {
|
|||||||
durationInSeconds: durationInSeconds ?? this.durationInSeconds,
|
durationInSeconds: durationInSeconds ?? this.durationInSeconds,
|
||||||
isFavorite: isFavorite ?? this.isFavorite,
|
isFavorite: isFavorite ?? this.isFavorite,
|
||||||
orientation: orientation ?? this.orientation,
|
orientation: orientation ?? this.orientation,
|
||||||
|
adjustmentTimestamp: adjustmentTimestamp ?? this.adjustmentTimestamp,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'dart:io';
|
|||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:background_downloader/background_downloader.dart';
|
import 'package:background_downloader/background_downloader.dart';
|
||||||
|
import 'package:cancellation_token_http/http.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/constants.dart';
|
import 'package:immich_mobile/constants/constants.dart';
|
||||||
@@ -62,7 +63,7 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
|||||||
final Drift _drift;
|
final Drift _drift;
|
||||||
final DriftLogger _driftLogger;
|
final DriftLogger _driftLogger;
|
||||||
final BackgroundWorkerBgHostApi _backgroundHostApi;
|
final BackgroundWorkerBgHostApi _backgroundHostApi;
|
||||||
final Completer _cancellationToken = Completer();
|
final CancellationToken _cancellationToken = CancellationToken();
|
||||||
final Logger _logger = Logger('BackgroundWorkerBgService');
|
final Logger _logger = Logger('BackgroundWorkerBgService');
|
||||||
|
|
||||||
bool _isCleanedUp = false;
|
bool _isCleanedUp = false;
|
||||||
@@ -187,7 +188,7 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
|||||||
_isCleanedUp = true;
|
_isCleanedUp = true;
|
||||||
_ref.dispose();
|
_ref.dispose();
|
||||||
|
|
||||||
_cancellationToken.complete();
|
_cancellationToken.cancel();
|
||||||
_logger.info("Cleaning up background worker");
|
_logger.info("Cleaning up background worker");
|
||||||
final cleanupFutures = [
|
final cleanupFutures = [
|
||||||
workerManager.dispose().catchError((_) async {
|
workerManager.dispose().catchError((_) async {
|
||||||
|
|||||||
@@ -303,6 +303,7 @@ extension on Iterable<PlatformAsset> {
|
|||||||
durationInSeconds: e.durationInSeconds,
|
durationInSeconds: e.durationInSeconds,
|
||||||
orientation: e.orientation,
|
orientation: e.orientation,
|
||||||
isFavorite: e.isFavorite,
|
isFavorite: e.isFavorite,
|
||||||
|
adjustmentTimestamp: e.adjustmentTimestamp,
|
||||||
),
|
),
|
||||||
).toList();
|
).toList();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ class LocalAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin {
|
|||||||
|
|
||||||
IntColumn get orientation => integer().withDefault(const Constant(0))();
|
IntColumn get orientation => integer().withDefault(const Constant(0))();
|
||||||
|
|
||||||
|
IntColumn get adjustmentTimestamp => integer().nullable()();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {id};
|
Set<Column> get primaryKey => {id};
|
||||||
}
|
}
|
||||||
@@ -34,5 +36,6 @@ extension LocalAssetEntityDataDomainExtension on LocalAssetEntityData {
|
|||||||
width: width,
|
width: width,
|
||||||
remoteId: null,
|
remoteId: null,
|
||||||
orientation: orientation,
|
orientation: orientation,
|
||||||
|
adjustmentTimestamp: adjustmentTimestamp,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ typedef $$LocalAssetEntityTableCreateCompanionBuilder =
|
|||||||
i0.Value<String?> checksum,
|
i0.Value<String?> checksum,
|
||||||
i0.Value<bool> isFavorite,
|
i0.Value<bool> isFavorite,
|
||||||
i0.Value<int> orientation,
|
i0.Value<int> orientation,
|
||||||
|
i0.Value<int?> adjustmentTimestamp,
|
||||||
});
|
});
|
||||||
typedef $$LocalAssetEntityTableUpdateCompanionBuilder =
|
typedef $$LocalAssetEntityTableUpdateCompanionBuilder =
|
||||||
i1.LocalAssetEntityCompanion Function({
|
i1.LocalAssetEntityCompanion Function({
|
||||||
@@ -35,6 +36,7 @@ typedef $$LocalAssetEntityTableUpdateCompanionBuilder =
|
|||||||
i0.Value<String?> checksum,
|
i0.Value<String?> checksum,
|
||||||
i0.Value<bool> isFavorite,
|
i0.Value<bool> isFavorite,
|
||||||
i0.Value<int> orientation,
|
i0.Value<int> orientation,
|
||||||
|
i0.Value<int?> adjustmentTimestamp,
|
||||||
});
|
});
|
||||||
|
|
||||||
class $$LocalAssetEntityTableFilterComposer
|
class $$LocalAssetEntityTableFilterComposer
|
||||||
@@ -101,6 +103,11 @@ class $$LocalAssetEntityTableFilterComposer
|
|||||||
column: $table.orientation,
|
column: $table.orientation,
|
||||||
builder: (column) => i0.ColumnFilters(column),
|
builder: (column) => i0.ColumnFilters(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
i0.ColumnFilters<int> get adjustmentTimestamp => $composableBuilder(
|
||||||
|
column: $table.adjustmentTimestamp,
|
||||||
|
builder: (column) => i0.ColumnFilters(column),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$LocalAssetEntityTableOrderingComposer
|
class $$LocalAssetEntityTableOrderingComposer
|
||||||
@@ -166,6 +173,11 @@ class $$LocalAssetEntityTableOrderingComposer
|
|||||||
column: $table.orientation,
|
column: $table.orientation,
|
||||||
builder: (column) => i0.ColumnOrderings(column),
|
builder: (column) => i0.ColumnOrderings(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
i0.ColumnOrderings<int> get adjustmentTimestamp => $composableBuilder(
|
||||||
|
column: $table.adjustmentTimestamp,
|
||||||
|
builder: (column) => i0.ColumnOrderings(column),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$LocalAssetEntityTableAnnotationComposer
|
class $$LocalAssetEntityTableAnnotationComposer
|
||||||
@@ -215,6 +227,11 @@ class $$LocalAssetEntityTableAnnotationComposer
|
|||||||
column: $table.orientation,
|
column: $table.orientation,
|
||||||
builder: (column) => column,
|
builder: (column) => column,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
i0.GeneratedColumn<int> get adjustmentTimestamp => $composableBuilder(
|
||||||
|
column: $table.adjustmentTimestamp,
|
||||||
|
builder: (column) => column,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$LocalAssetEntityTableTableManager
|
class $$LocalAssetEntityTableTableManager
|
||||||
@@ -268,6 +285,7 @@ class $$LocalAssetEntityTableTableManager
|
|||||||
i0.Value<String?> checksum = const i0.Value.absent(),
|
i0.Value<String?> checksum = const i0.Value.absent(),
|
||||||
i0.Value<bool> isFavorite = const i0.Value.absent(),
|
i0.Value<bool> isFavorite = const i0.Value.absent(),
|
||||||
i0.Value<int> orientation = const i0.Value.absent(),
|
i0.Value<int> orientation = const i0.Value.absent(),
|
||||||
|
i0.Value<int?> adjustmentTimestamp = const i0.Value.absent(),
|
||||||
}) => i1.LocalAssetEntityCompanion(
|
}) => i1.LocalAssetEntityCompanion(
|
||||||
name: name,
|
name: name,
|
||||||
type: type,
|
type: type,
|
||||||
@@ -280,6 +298,7 @@ class $$LocalAssetEntityTableTableManager
|
|||||||
checksum: checksum,
|
checksum: checksum,
|
||||||
isFavorite: isFavorite,
|
isFavorite: isFavorite,
|
||||||
orientation: orientation,
|
orientation: orientation,
|
||||||
|
adjustmentTimestamp: adjustmentTimestamp,
|
||||||
),
|
),
|
||||||
createCompanionCallback:
|
createCompanionCallback:
|
||||||
({
|
({
|
||||||
@@ -294,6 +313,7 @@ class $$LocalAssetEntityTableTableManager
|
|||||||
i0.Value<String?> checksum = const i0.Value.absent(),
|
i0.Value<String?> checksum = const i0.Value.absent(),
|
||||||
i0.Value<bool> isFavorite = const i0.Value.absent(),
|
i0.Value<bool> isFavorite = const i0.Value.absent(),
|
||||||
i0.Value<int> orientation = const i0.Value.absent(),
|
i0.Value<int> orientation = const i0.Value.absent(),
|
||||||
|
i0.Value<int?> adjustmentTimestamp = const i0.Value.absent(),
|
||||||
}) => i1.LocalAssetEntityCompanion.insert(
|
}) => i1.LocalAssetEntityCompanion.insert(
|
||||||
name: name,
|
name: name,
|
||||||
type: type,
|
type: type,
|
||||||
@@ -306,6 +326,7 @@ class $$LocalAssetEntityTableTableManager
|
|||||||
checksum: checksum,
|
checksum: checksum,
|
||||||
isFavorite: isFavorite,
|
isFavorite: isFavorite,
|
||||||
orientation: orientation,
|
orientation: orientation,
|
||||||
|
adjustmentTimestamp: adjustmentTimestamp,
|
||||||
),
|
),
|
||||||
withReferenceMapper: (p0) => p0
|
withReferenceMapper: (p0) => p0
|
||||||
.map((e) => (e.readTable(table), i0.BaseReferences(db, table, e)))
|
.map((e) => (e.readTable(table), i0.BaseReferences(db, table, e)))
|
||||||
@@ -473,6 +494,17 @@ class $LocalAssetEntityTable extends i3.LocalAssetEntity
|
|||||||
requiredDuringInsert: false,
|
requiredDuringInsert: false,
|
||||||
defaultValue: const i4.Constant(0),
|
defaultValue: const i4.Constant(0),
|
||||||
);
|
);
|
||||||
|
static const i0.VerificationMeta _adjustmentTimestampMeta =
|
||||||
|
const i0.VerificationMeta('adjustmentTimestamp');
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<int> adjustmentTimestamp =
|
||||||
|
i0.GeneratedColumn<int>(
|
||||||
|
'adjustment_timestamp',
|
||||||
|
aliasedName,
|
||||||
|
true,
|
||||||
|
type: i0.DriftSqlType.int,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
);
|
||||||
@override
|
@override
|
||||||
List<i0.GeneratedColumn> get $columns => [
|
List<i0.GeneratedColumn> get $columns => [
|
||||||
name,
|
name,
|
||||||
@@ -486,6 +518,7 @@ class $LocalAssetEntityTable extends i3.LocalAssetEntity
|
|||||||
checksum,
|
checksum,
|
||||||
isFavorite,
|
isFavorite,
|
||||||
orientation,
|
orientation,
|
||||||
|
adjustmentTimestamp,
|
||||||
];
|
];
|
||||||
@override
|
@override
|
||||||
String get aliasedName => _alias ?? actualTableName;
|
String get aliasedName => _alias ?? actualTableName;
|
||||||
@@ -566,6 +599,15 @@ class $LocalAssetEntityTable extends i3.LocalAssetEntity
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (data.containsKey('adjustment_timestamp')) {
|
||||||
|
context.handle(
|
||||||
|
_adjustmentTimestampMeta,
|
||||||
|
adjustmentTimestamp.isAcceptableOrUnknown(
|
||||||
|
data['adjustment_timestamp']!,
|
||||||
|
_adjustmentTimestampMeta,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -624,6 +666,10 @@ class $LocalAssetEntityTable extends i3.LocalAssetEntity
|
|||||||
i0.DriftSqlType.int,
|
i0.DriftSqlType.int,
|
||||||
data['${effectivePrefix}orientation'],
|
data['${effectivePrefix}orientation'],
|
||||||
)!,
|
)!,
|
||||||
|
adjustmentTimestamp: attachedDatabase.typeMapping.read(
|
||||||
|
i0.DriftSqlType.int,
|
||||||
|
data['${effectivePrefix}adjustment_timestamp'],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -653,6 +699,7 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
final String? checksum;
|
final String? checksum;
|
||||||
final bool isFavorite;
|
final bool isFavorite;
|
||||||
final int orientation;
|
final int orientation;
|
||||||
|
final int? adjustmentTimestamp;
|
||||||
const LocalAssetEntityData({
|
const LocalAssetEntityData({
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.type,
|
required this.type,
|
||||||
@@ -665,6 +712,7 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
this.checksum,
|
this.checksum,
|
||||||
required this.isFavorite,
|
required this.isFavorite,
|
||||||
required this.orientation,
|
required this.orientation,
|
||||||
|
this.adjustmentTimestamp,
|
||||||
});
|
});
|
||||||
@override
|
@override
|
||||||
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||||
@@ -692,6 +740,9 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
}
|
}
|
||||||
map['is_favorite'] = i0.Variable<bool>(isFavorite);
|
map['is_favorite'] = i0.Variable<bool>(isFavorite);
|
||||||
map['orientation'] = i0.Variable<int>(orientation);
|
map['orientation'] = i0.Variable<int>(orientation);
|
||||||
|
if (!nullToAbsent || adjustmentTimestamp != null) {
|
||||||
|
map['adjustment_timestamp'] = i0.Variable<int>(adjustmentTimestamp);
|
||||||
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -714,6 +765,9 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
checksum: serializer.fromJson<String?>(json['checksum']),
|
checksum: serializer.fromJson<String?>(json['checksum']),
|
||||||
isFavorite: serializer.fromJson<bool>(json['isFavorite']),
|
isFavorite: serializer.fromJson<bool>(json['isFavorite']),
|
||||||
orientation: serializer.fromJson<int>(json['orientation']),
|
orientation: serializer.fromJson<int>(json['orientation']),
|
||||||
|
adjustmentTimestamp: serializer.fromJson<int?>(
|
||||||
|
json['adjustmentTimestamp'],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@override
|
@override
|
||||||
@@ -733,6 +787,7 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
'checksum': serializer.toJson<String?>(checksum),
|
'checksum': serializer.toJson<String?>(checksum),
|
||||||
'isFavorite': serializer.toJson<bool>(isFavorite),
|
'isFavorite': serializer.toJson<bool>(isFavorite),
|
||||||
'orientation': serializer.toJson<int>(orientation),
|
'orientation': serializer.toJson<int>(orientation),
|
||||||
|
'adjustmentTimestamp': serializer.toJson<int?>(adjustmentTimestamp),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -748,6 +803,7 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
i0.Value<String?> checksum = const i0.Value.absent(),
|
i0.Value<String?> checksum = const i0.Value.absent(),
|
||||||
bool? isFavorite,
|
bool? isFavorite,
|
||||||
int? orientation,
|
int? orientation,
|
||||||
|
i0.Value<int?> adjustmentTimestamp = const i0.Value.absent(),
|
||||||
}) => i1.LocalAssetEntityData(
|
}) => i1.LocalAssetEntityData(
|
||||||
name: name ?? this.name,
|
name: name ?? this.name,
|
||||||
type: type ?? this.type,
|
type: type ?? this.type,
|
||||||
@@ -762,6 +818,9 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
checksum: checksum.present ? checksum.value : this.checksum,
|
checksum: checksum.present ? checksum.value : this.checksum,
|
||||||
isFavorite: isFavorite ?? this.isFavorite,
|
isFavorite: isFavorite ?? this.isFavorite,
|
||||||
orientation: orientation ?? this.orientation,
|
orientation: orientation ?? this.orientation,
|
||||||
|
adjustmentTimestamp: adjustmentTimestamp.present
|
||||||
|
? adjustmentTimestamp.value
|
||||||
|
: this.adjustmentTimestamp,
|
||||||
);
|
);
|
||||||
LocalAssetEntityData copyWithCompanion(i1.LocalAssetEntityCompanion data) {
|
LocalAssetEntityData copyWithCompanion(i1.LocalAssetEntityCompanion data) {
|
||||||
return LocalAssetEntityData(
|
return LocalAssetEntityData(
|
||||||
@@ -782,6 +841,9 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
orientation: data.orientation.present
|
orientation: data.orientation.present
|
||||||
? data.orientation.value
|
? data.orientation.value
|
||||||
: this.orientation,
|
: this.orientation,
|
||||||
|
adjustmentTimestamp: data.adjustmentTimestamp.present
|
||||||
|
? data.adjustmentTimestamp.value
|
||||||
|
: this.adjustmentTimestamp,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -798,7 +860,8 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
..write('id: $id, ')
|
..write('id: $id, ')
|
||||||
..write('checksum: $checksum, ')
|
..write('checksum: $checksum, ')
|
||||||
..write('isFavorite: $isFavorite, ')
|
..write('isFavorite: $isFavorite, ')
|
||||||
..write('orientation: $orientation')
|
..write('orientation: $orientation, ')
|
||||||
|
..write('adjustmentTimestamp: $adjustmentTimestamp')
|
||||||
..write(')'))
|
..write(')'))
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
@@ -816,6 +879,7 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
checksum,
|
checksum,
|
||||||
isFavorite,
|
isFavorite,
|
||||||
orientation,
|
orientation,
|
||||||
|
adjustmentTimestamp,
|
||||||
);
|
);
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
@@ -831,7 +895,8 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
other.id == this.id &&
|
other.id == this.id &&
|
||||||
other.checksum == this.checksum &&
|
other.checksum == this.checksum &&
|
||||||
other.isFavorite == this.isFavorite &&
|
other.isFavorite == this.isFavorite &&
|
||||||
other.orientation == this.orientation);
|
other.orientation == this.orientation &&
|
||||||
|
other.adjustmentTimestamp == this.adjustmentTimestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
class LocalAssetEntityCompanion
|
class LocalAssetEntityCompanion
|
||||||
@@ -847,6 +912,7 @@ class LocalAssetEntityCompanion
|
|||||||
final i0.Value<String?> checksum;
|
final i0.Value<String?> checksum;
|
||||||
final i0.Value<bool> isFavorite;
|
final i0.Value<bool> isFavorite;
|
||||||
final i0.Value<int> orientation;
|
final i0.Value<int> orientation;
|
||||||
|
final i0.Value<int?> adjustmentTimestamp;
|
||||||
const LocalAssetEntityCompanion({
|
const LocalAssetEntityCompanion({
|
||||||
this.name = const i0.Value.absent(),
|
this.name = const i0.Value.absent(),
|
||||||
this.type = const i0.Value.absent(),
|
this.type = const i0.Value.absent(),
|
||||||
@@ -859,6 +925,7 @@ class LocalAssetEntityCompanion
|
|||||||
this.checksum = const i0.Value.absent(),
|
this.checksum = const i0.Value.absent(),
|
||||||
this.isFavorite = const i0.Value.absent(),
|
this.isFavorite = const i0.Value.absent(),
|
||||||
this.orientation = const i0.Value.absent(),
|
this.orientation = const i0.Value.absent(),
|
||||||
|
this.adjustmentTimestamp = const i0.Value.absent(),
|
||||||
});
|
});
|
||||||
LocalAssetEntityCompanion.insert({
|
LocalAssetEntityCompanion.insert({
|
||||||
required String name,
|
required String name,
|
||||||
@@ -872,6 +939,7 @@ class LocalAssetEntityCompanion
|
|||||||
this.checksum = const i0.Value.absent(),
|
this.checksum = const i0.Value.absent(),
|
||||||
this.isFavorite = const i0.Value.absent(),
|
this.isFavorite = const i0.Value.absent(),
|
||||||
this.orientation = const i0.Value.absent(),
|
this.orientation = const i0.Value.absent(),
|
||||||
|
this.adjustmentTimestamp = const i0.Value.absent(),
|
||||||
}) : name = i0.Value(name),
|
}) : name = i0.Value(name),
|
||||||
type = i0.Value(type),
|
type = i0.Value(type),
|
||||||
id = i0.Value(id);
|
id = i0.Value(id);
|
||||||
@@ -887,6 +955,7 @@ class LocalAssetEntityCompanion
|
|||||||
i0.Expression<String>? checksum,
|
i0.Expression<String>? checksum,
|
||||||
i0.Expression<bool>? isFavorite,
|
i0.Expression<bool>? isFavorite,
|
||||||
i0.Expression<int>? orientation,
|
i0.Expression<int>? orientation,
|
||||||
|
i0.Expression<int>? adjustmentTimestamp,
|
||||||
}) {
|
}) {
|
||||||
return i0.RawValuesInsertable({
|
return i0.RawValuesInsertable({
|
||||||
if (name != null) 'name': name,
|
if (name != null) 'name': name,
|
||||||
@@ -900,6 +969,8 @@ class LocalAssetEntityCompanion
|
|||||||
if (checksum != null) 'checksum': checksum,
|
if (checksum != null) 'checksum': checksum,
|
||||||
if (isFavorite != null) 'is_favorite': isFavorite,
|
if (isFavorite != null) 'is_favorite': isFavorite,
|
||||||
if (orientation != null) 'orientation': orientation,
|
if (orientation != null) 'orientation': orientation,
|
||||||
|
if (adjustmentTimestamp != null)
|
||||||
|
'adjustment_timestamp': adjustmentTimestamp,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -915,6 +986,7 @@ class LocalAssetEntityCompanion
|
|||||||
i0.Value<String?>? checksum,
|
i0.Value<String?>? checksum,
|
||||||
i0.Value<bool>? isFavorite,
|
i0.Value<bool>? isFavorite,
|
||||||
i0.Value<int>? orientation,
|
i0.Value<int>? orientation,
|
||||||
|
i0.Value<int?>? adjustmentTimestamp,
|
||||||
}) {
|
}) {
|
||||||
return i1.LocalAssetEntityCompanion(
|
return i1.LocalAssetEntityCompanion(
|
||||||
name: name ?? this.name,
|
name: name ?? this.name,
|
||||||
@@ -928,6 +1000,7 @@ class LocalAssetEntityCompanion
|
|||||||
checksum: checksum ?? this.checksum,
|
checksum: checksum ?? this.checksum,
|
||||||
isFavorite: isFavorite ?? this.isFavorite,
|
isFavorite: isFavorite ?? this.isFavorite,
|
||||||
orientation: orientation ?? this.orientation,
|
orientation: orientation ?? this.orientation,
|
||||||
|
adjustmentTimestamp: adjustmentTimestamp ?? this.adjustmentTimestamp,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -969,6 +1042,9 @@ class LocalAssetEntityCompanion
|
|||||||
if (orientation.present) {
|
if (orientation.present) {
|
||||||
map['orientation'] = i0.Variable<int>(orientation.value);
|
map['orientation'] = i0.Variable<int>(orientation.value);
|
||||||
}
|
}
|
||||||
|
if (adjustmentTimestamp.present) {
|
||||||
|
map['adjustment_timestamp'] = i0.Variable<int>(adjustmentTimestamp.value);
|
||||||
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -985,7 +1061,8 @@ class LocalAssetEntityCompanion
|
|||||||
..write('id: $id, ')
|
..write('id: $id, ')
|
||||||
..write('checksum: $checksum, ')
|
..write('checksum: $checksum, ')
|
||||||
..write('isFavorite: $isFavorite, ')
|
..write('isFavorite: $isFavorite, ')
|
||||||
..write('orientation: $orientation')
|
..write('orientation: $orientation, ')
|
||||||
|
..write('adjustmentTimestamp: $adjustmentTimestamp')
|
||||||
..write(')'))
|
..write(')'))
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:ffi';
|
import 'dart:ffi';
|
||||||
|
import 'dart:io';
|
||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
import 'package:cronet_http/cronet_http.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:ffi/ffi.dart';
|
import 'package:ffi/ffi.dart';
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/timeline/constants.dart';
|
import 'package:immich_mobile/providers/image/cache/remote_image_cache_manager.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
part 'local_image_request.dart';
|
part 'local_image_request.dart';
|
||||||
part 'thumbhash_image_request.dart';
|
part 'thumbhash_image_request.dart';
|
||||||
|
|||||||
@@ -1,18 +1,14 @@
|
|||||||
part of 'image_request.dart';
|
part of 'image_request.dart';
|
||||||
|
|
||||||
class RemoteImageRequest extends ImageRequest {
|
class RemoteImageRequest extends ImageRequest {
|
||||||
static final _client = const NetworkRepository().getHttpClient(
|
static final log = Logger('RemoteImageRequest');
|
||||||
'thumbnails',
|
static final client = HttpClient()..maxConnectionsPerHost = 16;
|
||||||
diskCapacity: kThumbnailDiskCacheSize,
|
final RemoteCacheManager? cacheManager;
|
||||||
memoryCapacity: 0,
|
|
||||||
maxConnections: 16,
|
|
||||||
cacheMode: CacheMode.disk,
|
|
||||||
);
|
|
||||||
final String uri;
|
final String uri;
|
||||||
final Map<String, String> headers;
|
final Map<String, String> headers;
|
||||||
final abortTrigger = Completer<void>();
|
HttpClientRequest? _request;
|
||||||
|
|
||||||
RemoteImageRequest({required this.uri, required this.headers});
|
RemoteImageRequest({required this.uri, required this.headers, this.cacheManager});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<ImageInfo?> load(ImageDecoderCallback decode, {double scale = 1.0}) async {
|
Future<ImageInfo?> load(ImageDecoderCallback decode, {double scale = 1.0}) async {
|
||||||
@@ -20,8 +16,15 @@ class RemoteImageRequest extends ImageRequest {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: the cache manager makes everything sequential with its DB calls and its operations cannot be cancelled,
|
||||||
|
// so it ends up being a bottleneck. We only prefer fetching from it when it can skip the DB call.
|
||||||
|
final cachedFileImage = await _loadCachedFile(uri, decode, scale, inMemoryOnly: true);
|
||||||
|
if (cachedFileImage != null) {
|
||||||
|
return cachedFileImage;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final buffer = await _downloadImage();
|
final buffer = await _downloadImage(uri);
|
||||||
if (buffer == null) {
|
if (buffer == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -32,41 +35,57 @@ class RemoteImageRequest extends ImageRequest {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final cachedFileImage = await _loadCachedFile(uri, decode, scale, inMemoryOnly: false);
|
||||||
|
if (cachedFileImage != null) {
|
||||||
|
return cachedFileImage;
|
||||||
|
}
|
||||||
|
|
||||||
rethrow;
|
rethrow;
|
||||||
|
} finally {
|
||||||
|
_request = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ImmutableBuffer?> _downloadImage() async {
|
Future<ImmutableBuffer?> _downloadImage(String url) async {
|
||||||
if (_isCancelled) {
|
if (_isCancelled) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final req = http.AbortableRequest('GET', Uri.parse(uri), abortTrigger: abortTrigger.future);
|
final request = _request = await client.getUrl(Uri.parse(url));
|
||||||
req.headers.addAll(headers);
|
if (_isCancelled) {
|
||||||
final res = await _client.send(req);
|
request.abort();
|
||||||
|
return _request = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final entry in headers.entries) {
|
||||||
|
request.headers.set(entry.key, entry.value);
|
||||||
|
}
|
||||||
|
final response = await request.close();
|
||||||
if (_isCancelled) {
|
if (_isCancelled) {
|
||||||
_onCancelled();
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res.statusCode != 200) {
|
final cacheManager = this.cacheManager;
|
||||||
throw Exception('Failed to download $uri: ${res.statusCode}');
|
final streamController = StreamController<List<int>>(sync: true);
|
||||||
}
|
final Stream<List<int>> stream;
|
||||||
|
cacheManager?.putStreamedFile(url, streamController.stream);
|
||||||
final stream = res.stream.map((chunk) {
|
stream = response.map((chunk) {
|
||||||
if (_isCancelled) {
|
if (_isCancelled) {
|
||||||
throw StateError('Cancelled request');
|
throw StateError('Cancelled request');
|
||||||
}
|
}
|
||||||
|
if (cacheManager != null) {
|
||||||
|
streamController.add(chunk);
|
||||||
|
}
|
||||||
return chunk;
|
return chunk;
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final Uint8List bytes = await _downloadBytes(stream, res.contentLength ?? -1);
|
final Uint8List bytes = await _downloadBytes(stream, response.contentLength);
|
||||||
if (_isCancelled) {
|
streamController.close();
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return await ImmutableBuffer.fromUint8List(bytes);
|
return await ImmutableBuffer.fromUint8List(bytes);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
streamController.addError(e);
|
||||||
|
streamController.close();
|
||||||
if (_isCancelled) {
|
if (_isCancelled) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -103,6 +122,40 @@ class RemoteImageRequest extends ImageRequest {
|
|||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<ImageInfo?> _loadCachedFile(
|
||||||
|
String url,
|
||||||
|
ImageDecoderCallback decode,
|
||||||
|
double scale, {
|
||||||
|
required bool inMemoryOnly,
|
||||||
|
}) async {
|
||||||
|
final cacheManager = this.cacheManager;
|
||||||
|
if (_isCancelled || cacheManager == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final file = await (inMemoryOnly ? cacheManager.getFileFromMemory(url) : cacheManager.getFileFromCache(url));
|
||||||
|
if (_isCancelled || file == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final buffer = await ImmutableBuffer.fromFilePath(file.file.path);
|
||||||
|
return await _decodeBuffer(buffer, decode, scale);
|
||||||
|
} catch (e) {
|
||||||
|
log.severe('Failed to decode cached image', e);
|
||||||
|
_evictFile(url);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _evictFile(String url) async {
|
||||||
|
try {
|
||||||
|
await cacheManager?.removeFile(url);
|
||||||
|
} catch (e) {
|
||||||
|
log.severe('Failed to remove cached image', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<ImageInfo?> _decodeBuffer(ImmutableBuffer buffer, ImageDecoderCallback decode, scale) async {
|
Future<ImageInfo?> _decodeBuffer(ImmutableBuffer buffer, ImageDecoderCallback decode, scale) async {
|
||||||
if (_isCancelled) {
|
if (_isCancelled) {
|
||||||
buffer.dispose();
|
buffer.dispose();
|
||||||
@@ -120,6 +173,7 @@ class RemoteImageRequest extends ImageRequest {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void _onCancelled() {
|
void _onCancelled() {
|
||||||
abortTrigger.complete();
|
_request?.abort();
|
||||||
|
_request = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ class Drift extends $Drift implements IDatabaseRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 11;
|
int get schemaVersion => 12;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MigrationStrategy get migration => MigrationStrategy(
|
MigrationStrategy get migration => MigrationStrategy(
|
||||||
@@ -159,6 +159,9 @@ class Drift extends $Drift implements IDatabaseRepository {
|
|||||||
from10To11: (m, v11) async {
|
from10To11: (m, v11) async {
|
||||||
await m.addColumn(v11.localAlbumAssetEntity, v11.localAlbumAssetEntity.marker_);
|
await m.addColumn(v11.localAlbumAssetEntity, v11.localAlbumAssetEntity.marker_);
|
||||||
},
|
},
|
||||||
|
from11To12: (m, v12) async {
|
||||||
|
await m.addColumn(v12.localAssetEntity, v12.localAssetEntity.adjustmentTimestamp);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -4659,6 +4659,420 @@ class Shape22 extends i0.VersionedTable {
|
|||||||
columnsByName['marker']! as i1.GeneratedColumn<bool>;
|
columnsByName['marker']! as i1.GeneratedColumn<bool>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class Schema12 extends i0.VersionedSchema {
|
||||||
|
Schema12({required super.database}) : super(version: 12);
|
||||||
|
@override
|
||||||
|
late final List<i1.DatabaseSchemaEntity> entities = [
|
||||||
|
userEntity,
|
||||||
|
remoteAssetEntity,
|
||||||
|
stackEntity,
|
||||||
|
localAssetEntity,
|
||||||
|
remoteAlbumEntity,
|
||||||
|
localAlbumEntity,
|
||||||
|
localAlbumAssetEntity,
|
||||||
|
idxLocalAssetChecksum,
|
||||||
|
idxRemoteAssetOwnerChecksum,
|
||||||
|
uQRemoteAssetsOwnerChecksum,
|
||||||
|
uQRemoteAssetsOwnerLibraryChecksum,
|
||||||
|
idxRemoteAssetChecksum,
|
||||||
|
authUserEntity,
|
||||||
|
userMetadataEntity,
|
||||||
|
partnerEntity,
|
||||||
|
remoteExifEntity,
|
||||||
|
remoteAlbumAssetEntity,
|
||||||
|
remoteAlbumUserEntity,
|
||||||
|
memoryEntity,
|
||||||
|
memoryAssetEntity,
|
||||||
|
personEntity,
|
||||||
|
assetFaceEntity,
|
||||||
|
storeEntity,
|
||||||
|
idxLatLng,
|
||||||
|
];
|
||||||
|
late final Shape20 userEntity = Shape20(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'user_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_1,
|
||||||
|
_column_3,
|
||||||
|
_column_84,
|
||||||
|
_column_85,
|
||||||
|
_column_91,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape17 remoteAssetEntity = Shape17(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_1,
|
||||||
|
_column_8,
|
||||||
|
_column_9,
|
||||||
|
_column_5,
|
||||||
|
_column_10,
|
||||||
|
_column_11,
|
||||||
|
_column_12,
|
||||||
|
_column_0,
|
||||||
|
_column_13,
|
||||||
|
_column_14,
|
||||||
|
_column_15,
|
||||||
|
_column_16,
|
||||||
|
_column_17,
|
||||||
|
_column_18,
|
||||||
|
_column_19,
|
||||||
|
_column_20,
|
||||||
|
_column_21,
|
||||||
|
_column_86,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape3 stackEntity = Shape3(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'stack_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [_column_0, _column_9, _column_5, _column_15, _column_75],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape23 localAssetEntity = Shape23(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'local_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_1,
|
||||||
|
_column_8,
|
||||||
|
_column_9,
|
||||||
|
_column_5,
|
||||||
|
_column_10,
|
||||||
|
_column_11,
|
||||||
|
_column_12,
|
||||||
|
_column_0,
|
||||||
|
_column_22,
|
||||||
|
_column_14,
|
||||||
|
_column_23,
|
||||||
|
_column_95,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape9 remoteAlbumEntity = Shape9(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_album_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_1,
|
||||||
|
_column_56,
|
||||||
|
_column_9,
|
||||||
|
_column_5,
|
||||||
|
_column_15,
|
||||||
|
_column_57,
|
||||||
|
_column_58,
|
||||||
|
_column_59,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape19 localAlbumEntity = Shape19(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'local_album_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_1,
|
||||||
|
_column_5,
|
||||||
|
_column_31,
|
||||||
|
_column_32,
|
||||||
|
_column_90,
|
||||||
|
_column_33,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape22 localAlbumAssetEntity = Shape22(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'local_album_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(asset_id, album_id)'],
|
||||||
|
columns: [_column_34, _column_35, _column_33],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
final i1.Index idxLocalAssetChecksum = i1.Index(
|
||||||
|
'idx_local_asset_checksum',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)',
|
||||||
|
);
|
||||||
|
final i1.Index idxRemoteAssetOwnerChecksum = i1.Index(
|
||||||
|
'idx_remote_asset_owner_checksum',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)',
|
||||||
|
);
|
||||||
|
final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index(
|
||||||
|
'UQ_remote_assets_owner_checksum',
|
||||||
|
'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)',
|
||||||
|
);
|
||||||
|
final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index(
|
||||||
|
'UQ_remote_assets_owner_library_checksum',
|
||||||
|
'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)',
|
||||||
|
);
|
||||||
|
final i1.Index idxRemoteAssetChecksum = i1.Index(
|
||||||
|
'idx_remote_asset_checksum',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)',
|
||||||
|
);
|
||||||
|
late final Shape21 authUserEntity = Shape21(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'auth_user_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_1,
|
||||||
|
_column_3,
|
||||||
|
_column_2,
|
||||||
|
_column_84,
|
||||||
|
_column_85,
|
||||||
|
_column_92,
|
||||||
|
_column_93,
|
||||||
|
_column_7,
|
||||||
|
_column_94,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape4 userMetadataEntity = Shape4(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'user_metadata_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(user_id, "key")'],
|
||||||
|
columns: [_column_25, _column_26, _column_27],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape5 partnerEntity = Shape5(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'partner_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'],
|
||||||
|
columns: [_column_28, _column_29, _column_30],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape8 remoteExifEntity = Shape8(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_exif_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(asset_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_36,
|
||||||
|
_column_37,
|
||||||
|
_column_38,
|
||||||
|
_column_39,
|
||||||
|
_column_40,
|
||||||
|
_column_41,
|
||||||
|
_column_11,
|
||||||
|
_column_10,
|
||||||
|
_column_42,
|
||||||
|
_column_43,
|
||||||
|
_column_44,
|
||||||
|
_column_45,
|
||||||
|
_column_46,
|
||||||
|
_column_47,
|
||||||
|
_column_48,
|
||||||
|
_column_49,
|
||||||
|
_column_50,
|
||||||
|
_column_51,
|
||||||
|
_column_52,
|
||||||
|
_column_53,
|
||||||
|
_column_54,
|
||||||
|
_column_55,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape7 remoteAlbumAssetEntity = Shape7(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_album_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(asset_id, album_id)'],
|
||||||
|
columns: [_column_36, _column_60],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape10 remoteAlbumUserEntity = Shape10(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_album_user_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(album_id, user_id)'],
|
||||||
|
columns: [_column_60, _column_25, _column_61],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape11 memoryEntity = Shape11(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'memory_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_9,
|
||||||
|
_column_5,
|
||||||
|
_column_18,
|
||||||
|
_column_15,
|
||||||
|
_column_8,
|
||||||
|
_column_62,
|
||||||
|
_column_63,
|
||||||
|
_column_64,
|
||||||
|
_column_65,
|
||||||
|
_column_66,
|
||||||
|
_column_67,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape12 memoryAssetEntity = Shape12(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'memory_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'],
|
||||||
|
columns: [_column_36, _column_68],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape14 personEntity = Shape14(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'person_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_9,
|
||||||
|
_column_5,
|
||||||
|
_column_15,
|
||||||
|
_column_1,
|
||||||
|
_column_69,
|
||||||
|
_column_71,
|
||||||
|
_column_72,
|
||||||
|
_column_73,
|
||||||
|
_column_74,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape15 assetFaceEntity = Shape15(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'asset_face_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_36,
|
||||||
|
_column_76,
|
||||||
|
_column_77,
|
||||||
|
_column_78,
|
||||||
|
_column_79,
|
||||||
|
_column_80,
|
||||||
|
_column_81,
|
||||||
|
_column_82,
|
||||||
|
_column_83,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape18 storeEntity = Shape18(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'store_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [_column_87, _column_88, _column_89],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
final i1.Index idxLatLng = i1.Index(
|
||||||
|
'idx_lat_lng',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Shape23 extends i0.VersionedTable {
|
||||||
|
Shape23({required super.source, required super.alias}) : super.aliased();
|
||||||
|
i1.GeneratedColumn<String> get name =>
|
||||||
|
columnsByName['name']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<int> get type =>
|
||||||
|
columnsByName['type']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<DateTime> get createdAt =>
|
||||||
|
columnsByName['created_at']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
i1.GeneratedColumn<DateTime> get updatedAt =>
|
||||||
|
columnsByName['updated_at']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
i1.GeneratedColumn<int> get width =>
|
||||||
|
columnsByName['width']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<int> get height =>
|
||||||
|
columnsByName['height']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<int> get durationInSeconds =>
|
||||||
|
columnsByName['duration_in_seconds']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<String> get id =>
|
||||||
|
columnsByName['id']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get checksum =>
|
||||||
|
columnsByName['checksum']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<bool> get isFavorite =>
|
||||||
|
columnsByName['is_favorite']! as i1.GeneratedColumn<bool>;
|
||||||
|
i1.GeneratedColumn<int> get orientation =>
|
||||||
|
columnsByName['orientation']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<int> get adjustmentTimestamp =>
|
||||||
|
columnsByName['adjustment_timestamp']! as i1.GeneratedColumn<int>;
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.GeneratedColumn<int> _column_95(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<int>(
|
||||||
|
'adjustment_timestamp',
|
||||||
|
aliasedName,
|
||||||
|
true,
|
||||||
|
type: i1.DriftSqlType.int,
|
||||||
|
);
|
||||||
i0.MigrationStepWithVersion migrationSteps({
|
i0.MigrationStepWithVersion migrationSteps({
|
||||||
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
||||||
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
||||||
@@ -4670,6 +5084,7 @@ i0.MigrationStepWithVersion migrationSteps({
|
|||||||
required Future<void> Function(i1.Migrator m, Schema9 schema) from8To9,
|
required Future<void> Function(i1.Migrator m, Schema9 schema) from8To9,
|
||||||
required Future<void> Function(i1.Migrator m, Schema10 schema) from9To10,
|
required Future<void> Function(i1.Migrator m, Schema10 schema) from9To10,
|
||||||
required Future<void> Function(i1.Migrator m, Schema11 schema) from10To11,
|
required Future<void> Function(i1.Migrator m, Schema11 schema) from10To11,
|
||||||
|
required Future<void> Function(i1.Migrator m, Schema12 schema) from11To12,
|
||||||
}) {
|
}) {
|
||||||
return (currentVersion, database) async {
|
return (currentVersion, database) async {
|
||||||
switch (currentVersion) {
|
switch (currentVersion) {
|
||||||
@@ -4723,6 +5138,11 @@ i0.MigrationStepWithVersion migrationSteps({
|
|||||||
final migrator = i1.Migrator(database, schema);
|
final migrator = i1.Migrator(database, schema);
|
||||||
await from10To11(migrator, schema);
|
await from10To11(migrator, schema);
|
||||||
return 11;
|
return 11;
|
||||||
|
case 11:
|
||||||
|
final schema = Schema12(database: database);
|
||||||
|
final migrator = i1.Migrator(database, schema);
|
||||||
|
await from11To12(migrator, schema);
|
||||||
|
return 12;
|
||||||
default:
|
default:
|
||||||
throw ArgumentError.value('Unknown migration from $currentVersion');
|
throw ArgumentError.value('Unknown migration from $currentVersion');
|
||||||
}
|
}
|
||||||
@@ -4740,6 +5160,7 @@ i1.OnUpgrade stepByStep({
|
|||||||
required Future<void> Function(i1.Migrator m, Schema9 schema) from8To9,
|
required Future<void> Function(i1.Migrator m, Schema9 schema) from8To9,
|
||||||
required Future<void> Function(i1.Migrator m, Schema10 schema) from9To10,
|
required Future<void> Function(i1.Migrator m, Schema10 schema) from9To10,
|
||||||
required Future<void> Function(i1.Migrator m, Schema11 schema) from10To11,
|
required Future<void> Function(i1.Migrator m, Schema11 schema) from10To11,
|
||||||
|
required Future<void> Function(i1.Migrator m, Schema12 schema) from11To12,
|
||||||
}) => i0.VersionedSchema.stepByStepHelper(
|
}) => i0.VersionedSchema.stepByStepHelper(
|
||||||
step: migrationSteps(
|
step: migrationSteps(
|
||||||
from1To2: from1To2,
|
from1To2: from1To2,
|
||||||
@@ -4752,5 +5173,6 @@ i1.OnUpgrade stepByStep({
|
|||||||
from8To9: from8To9,
|
from8To9: from8To9,
|
||||||
from9To10: from9To10,
|
from9To10: from9To10,
|
||||||
from10To11: from10To11,
|
from10To11: from10To11,
|
||||||
|
from11To12: from11To12,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -263,6 +263,7 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
|
|||||||
orientation: Value(asset.orientation),
|
orientation: Value(asset.orientation),
|
||||||
checksum: const Value(null),
|
checksum: const Value(null),
|
||||||
isFavorite: Value(asset.isFavorite),
|
isFavorite: Value(asset.isFavorite),
|
||||||
|
adjustmentTimestamp: Value(asset.adjustmentTimestamp),
|
||||||
);
|
);
|
||||||
batch.insert<$LocalAssetEntityTable, LocalAssetEntityData>(
|
batch.insert<$LocalAssetEntityTable, LocalAssetEntityData>(
|
||||||
_db.localAssetEntity,
|
_db.localAssetEntity,
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:cronet_http/cronet_http.dart';
|
|
||||||
import 'package:cupertino_http/cupertino_http.dart';
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
import 'package:immich_mobile/utils/user_agent.dart';
|
|
||||||
import 'package:path_provider/path_provider.dart';
|
|
||||||
|
|
||||||
class NetworkRepository {
|
|
||||||
static late Directory _cachePath;
|
|
||||||
static late String _userAgent;
|
|
||||||
static final _clients = <String, http.Client>{};
|
|
||||||
|
|
||||||
static Future<void> init() {
|
|
||||||
return (
|
|
||||||
getTemporaryDirectory().then((cachePath) => _cachePath = cachePath),
|
|
||||||
getUserAgentString().then((userAgent) => _userAgent = userAgent),
|
|
||||||
).wait;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void reset() {
|
|
||||||
Future.microtask(init);
|
|
||||||
for (final client in _clients.values) {
|
|
||||||
client.close();
|
|
||||||
}
|
|
||||||
_clients.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
const NetworkRepository();
|
|
||||||
|
|
||||||
/// Note: when disk caching is enabled, only one client may use a given directory at a time.
|
|
||||||
/// Different isolates or engines must use different directories.
|
|
||||||
http.Client getHttpClient(
|
|
||||||
String directoryName, {
|
|
||||||
CacheMode cacheMode = CacheMode.memory,
|
|
||||||
int diskCapacity = 0,
|
|
||||||
int maxConnections = 6,
|
|
||||||
int memoryCapacity = 10 << 20,
|
|
||||||
}) {
|
|
||||||
final cachedClient = _clients[directoryName];
|
|
||||||
if (cachedClient != null) {
|
|
||||||
return cachedClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
final directory = Directory('${_cachePath.path}/$directoryName');
|
|
||||||
directory.createSync(recursive: true);
|
|
||||||
if (Platform.isAndroid) {
|
|
||||||
final engine = CronetEngine.build(
|
|
||||||
cacheMode: cacheMode,
|
|
||||||
cacheMaxSize: diskCapacity,
|
|
||||||
storagePath: directory.path,
|
|
||||||
userAgent: _userAgent,
|
|
||||||
);
|
|
||||||
return _clients[directoryName] = CronetClient.fromCronetEngine(engine, closeEngine: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
final config = URLSessionConfiguration.defaultSessionConfiguration()
|
|
||||||
..httpMaximumConnectionsPerHost = maxConnections
|
|
||||||
..cache = URLCache.withCapacity(
|
|
||||||
diskCapacity: diskCapacity,
|
|
||||||
memoryCapacity: memoryCapacity,
|
|
||||||
directory: directory.uri,
|
|
||||||
)
|
|
||||||
..httpAdditionalHeaders = {'User-Agent': _userAgent};
|
|
||||||
return _clients[directoryName] = CupertinoClient.fromSessionConfiguration(config);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,13 +6,11 @@ import 'package:immich_mobile/constants/constants.dart';
|
|||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/sync_event.model.dart';
|
import 'package:immich_mobile/domain/models/sync_event.model.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
|
||||||
import 'package:immich_mobile/services/api.service.dart';
|
import 'package:immich_mobile/services/api.service.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
class SyncApiRepository {
|
class SyncApiRepository {
|
||||||
static final _client = const NetworkRepository().getHttpClient('api');
|
|
||||||
final Logger _logger = Logger('SyncApiRepository');
|
final Logger _logger = Logger('SyncApiRepository');
|
||||||
final ApiService _api;
|
final ApiService _api;
|
||||||
SyncApiRepository(this._api);
|
SyncApiRepository(this._api);
|
||||||
@@ -28,7 +26,7 @@ class SyncApiRepository {
|
|||||||
http.Client? httpClient,
|
http.Client? httpClient,
|
||||||
}) async {
|
}) async {
|
||||||
final stopwatch = Stopwatch()..start();
|
final stopwatch = Stopwatch()..start();
|
||||||
final client = httpClient ?? _client;
|
final client = httpClient ?? http.Client();
|
||||||
final endpoint = "${_api.apiClient.basePath}/sync/stream";
|
final endpoint = "${_api.apiClient.basePath}/sync/stream";
|
||||||
|
|
||||||
final headers = {'Content-Type': 'application/json', 'Accept': 'application/jsonlines+json'};
|
final headers = {'Content-Type': 'application/json', 'Accept': 'application/jsonlines+json'};
|
||||||
@@ -114,6 +112,8 @@ class SyncApiRepository {
|
|||||||
} catch (error, stack) {
|
} catch (error, stack) {
|
||||||
_logger.severe("Error processing stream", error, stack);
|
_logger.severe("Error processing stream", error, stack);
|
||||||
return Future.error(error, stack);
|
return Future.error(error, stack);
|
||||||
|
} finally {
|
||||||
|
client.close();
|
||||||
}
|
}
|
||||||
stopwatch.stop();
|
stopwatch.stop();
|
||||||
_logger.info("Remote Sync completed in ${stopwatch.elapsed.inMilliseconds}ms");
|
_logger.info("Remote Sync completed in ${stopwatch.elapsed.inMilliseconds}ms");
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import 'package:immich_mobile/constants/locales.dart';
|
|||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/generated/codegen_loader.g.dart';
|
import 'package:immich_mobile/generated/codegen_loader.g.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
|
||||||
import 'package:immich_mobile/providers/app_life_cycle.provider.dart';
|
import 'package:immich_mobile/providers/app_life_cycle.provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/share_intent_upload.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/share_intent_upload.provider.dart';
|
||||||
import 'package:immich_mobile/providers/db.provider.dart';
|
import 'package:immich_mobile/providers/db.provider.dart';
|
||||||
@@ -223,14 +222,6 @@ class ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserve
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void reassemble() {
|
|
||||||
if (kDebugMode) {
|
|
||||||
NetworkRepository.reset();
|
|
||||||
}
|
|
||||||
super.reassemble();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final router = ref.watch(appRouterProvider);
|
final router = ref.watch(appRouterProvider);
|
||||||
|
|||||||
+17
-1
@@ -41,6 +41,7 @@ class PlatformAsset {
|
|||||||
required this.durationInSeconds,
|
required this.durationInSeconds,
|
||||||
required this.orientation,
|
required this.orientation,
|
||||||
required this.isFavorite,
|
required this.isFavorite,
|
||||||
|
this.adjustmentTimestamp,
|
||||||
});
|
});
|
||||||
|
|
||||||
String id;
|
String id;
|
||||||
@@ -63,8 +64,22 @@ class PlatformAsset {
|
|||||||
|
|
||||||
bool isFavorite;
|
bool isFavorite;
|
||||||
|
|
||||||
|
int? adjustmentTimestamp;
|
||||||
|
|
||||||
List<Object?> _toList() {
|
List<Object?> _toList() {
|
||||||
return <Object?>[id, name, type, createdAt, updatedAt, width, height, durationInSeconds, orientation, isFavorite];
|
return <Object?>[
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
type,
|
||||||
|
createdAt,
|
||||||
|
updatedAt,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
durationInSeconds,
|
||||||
|
orientation,
|
||||||
|
isFavorite,
|
||||||
|
adjustmentTimestamp,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
Object encode() {
|
Object encode() {
|
||||||
@@ -84,6 +99,7 @@ class PlatformAsset {
|
|||||||
durationInSeconds: result[7]! as int,
|
durationInSeconds: result[7]! as int,
|
||||||
orientation: result[8]! as int,
|
orientation: result[8]! as int,
|
||||||
isFavorite: result[9]! as bool,
|
isFavorite: result[9]! as bool,
|
||||||
|
adjustmentTimestamp: result[10] as int?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -129,6 +129,7 @@ class _AssetPropertiesSectionState extends ConsumerState<_AssetPropertiesSection
|
|||||||
properties.insert(4, _PropertyItem(label: 'Orientation', value: asset.orientation.toString()));
|
properties.insert(4, _PropertyItem(label: 'Orientation', value: asset.orientation.toString()));
|
||||||
final albums = await ref.read(assetServiceProvider).getSourceAlbums(asset.id);
|
final albums = await ref.read(assetServiceProvider).getSourceAlbums(asset.id);
|
||||||
properties.add(_PropertyItem(label: 'Album', value: albums.map((a) => a.name).join(', ')));
|
properties.add(_PropertyItem(label: 'Album', value: albums.map((a) => a.name).join(', ')));
|
||||||
|
properties.add(_PropertyItem(label: 'Adjustment Timestamp', value: asset.adjustmentTimestamp?.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _addRemoteAssetProperties(RemoteAsset asset) async {
|
Future<void> _addRemoteAssetProperties(RemoteAsset asset) async {
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ import 'package:immich_mobile/domain/services/setting.service.dart';
|
|||||||
import 'package:immich_mobile/infrastructure/loaders/image_request.dart';
|
import 'package:immich_mobile/infrastructure/loaders/image_request.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
|
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/images/one_frame_multi_image_stream_completer.dart';
|
import 'package:immich_mobile/presentation/widgets/images/one_frame_multi_image_stream_completer.dart';
|
||||||
|
import 'package:immich_mobile/providers/image/cache/remote_image_cache_manager.dart';
|
||||||
import 'package:immich_mobile/services/api.service.dart';
|
import 'package:immich_mobile/services/api.service.dart';
|
||||||
import 'package:immich_mobile/utils/image_url_builder.dart';
|
import 'package:immich_mobile/utils/image_url_builder.dart';
|
||||||
|
|
||||||
class RemoteThumbProvider extends CancellableImageProvider<RemoteThumbProvider>
|
class RemoteThumbProvider extends CancellableImageProvider<RemoteThumbProvider>
|
||||||
with CancellableImageProviderMixin<RemoteThumbProvider> {
|
with CancellableImageProviderMixin<RemoteThumbProvider> {
|
||||||
|
static final cacheManager = RemoteThumbnailCacheManager();
|
||||||
final String assetId;
|
final String assetId;
|
||||||
|
|
||||||
RemoteThumbProvider({required this.assetId});
|
RemoteThumbProvider({required this.assetId});
|
||||||
@@ -37,6 +39,7 @@ class RemoteThumbProvider extends CancellableImageProvider<RemoteThumbProvider>
|
|||||||
final request = this.request = RemoteImageRequest(
|
final request = this.request = RemoteImageRequest(
|
||||||
uri: getThumbnailUrlForRemoteId(key.assetId),
|
uri: getThumbnailUrlForRemoteId(key.assetId),
|
||||||
headers: ApiService.getRequestHeaders(),
|
headers: ApiService.getRequestHeaders(),
|
||||||
|
cacheManager: cacheManager,
|
||||||
);
|
);
|
||||||
return loadRequest(request, decode);
|
return loadRequest(request, decode);
|
||||||
}
|
}
|
||||||
@@ -57,6 +60,7 @@ class RemoteThumbProvider extends CancellableImageProvider<RemoteThumbProvider>
|
|||||||
|
|
||||||
class RemoteFullImageProvider extends CancellableImageProvider<RemoteFullImageProvider>
|
class RemoteFullImageProvider extends CancellableImageProvider<RemoteFullImageProvider>
|
||||||
with CancellableImageProviderMixin<RemoteFullImageProvider> {
|
with CancellableImageProviderMixin<RemoteFullImageProvider> {
|
||||||
|
static final cacheManager = RemoteThumbnailCacheManager();
|
||||||
final String assetId;
|
final String assetId;
|
||||||
|
|
||||||
RemoteFullImageProvider({required this.assetId});
|
RemoteFullImageProvider({required this.assetId});
|
||||||
@@ -88,7 +92,11 @@ class RemoteFullImageProvider extends CancellableImageProvider<RemoteFullImagePr
|
|||||||
}
|
}
|
||||||
|
|
||||||
final headers = ApiService.getRequestHeaders();
|
final headers = ApiService.getRequestHeaders();
|
||||||
final request = this.request = RemoteImageRequest(uri: getPreviewUrlForRemoteId(key.assetId), headers: headers);
|
final request = this.request = RemoteImageRequest(
|
||||||
|
uri: getPreviewUrlForRemoteId(key.assetId),
|
||||||
|
headers: headers,
|
||||||
|
cacheManager: cacheManager,
|
||||||
|
);
|
||||||
yield* loadRequest(request, decode);
|
yield* loadRequest(request, decode);
|
||||||
|
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
|
|||||||
@@ -2,11 +2,9 @@ import 'dart:ui';
|
|||||||
|
|
||||||
const double kTimelineHeaderExtent = 80.0;
|
const double kTimelineHeaderExtent = 80.0;
|
||||||
const Size kTimelineFixedTileExtent = Size.square(256);
|
const Size kTimelineFixedTileExtent = Size.square(256);
|
||||||
|
const Size kThumbnailResolution = Size.square(320); // TODO: make the resolution vary based on actual tile size
|
||||||
const double kTimelineSpacing = 2.0;
|
const double kTimelineSpacing = 2.0;
|
||||||
const int kTimelineColumnCount = 3;
|
const int kTimelineColumnCount = 3;
|
||||||
|
|
||||||
const Duration kTimelineScrubberFadeInDuration = Duration(milliseconds: 300);
|
const Duration kTimelineScrubberFadeInDuration = Duration(milliseconds: 300);
|
||||||
const Duration kTimelineScrubberFadeOutDuration = Duration(milliseconds: 800);
|
const Duration kTimelineScrubberFadeOutDuration = Duration(milliseconds: 800);
|
||||||
|
|
||||||
const Size kThumbnailResolution = Size.square(320); // TODO: make the resolution vary based on actual tile size
|
|
||||||
const kThumbnailDiskCacheSize = 1024 << 20; // 1GiB
|
|
||||||
|
|||||||
@@ -1,25 +1,148 @@
|
|||||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
|
// ignore: implementation_imports
|
||||||
|
import 'package:flutter_cache_manager/src/cache_store.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
class RemoteImageCacheManager extends CacheManager {
|
abstract class RemoteCacheManager extends CacheManager {
|
||||||
|
static final _log = Logger('RemoteCacheManager');
|
||||||
|
|
||||||
|
RemoteCacheManager.custom(super.config, CacheStore store)
|
||||||
|
// Unfortunately, CacheStore is not a public API
|
||||||
|
// ignore: invalid_use_of_visible_for_testing_member
|
||||||
|
: super.custom(cacheStore: store);
|
||||||
|
|
||||||
|
Future<void> putStreamedFile(
|
||||||
|
String url,
|
||||||
|
Stream<List<int>> source, {
|
||||||
|
String? key,
|
||||||
|
String? eTag,
|
||||||
|
Duration maxAge = const Duration(days: 30),
|
||||||
|
String fileExtension = 'file',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Unlike `putFileStream`, this method handles request cancellation,
|
||||||
|
// does not make a (slow) DB call checking if the file is already cached,
|
||||||
|
// does not synchronously check if a file exists,
|
||||||
|
// and deletes the file on cancellation without making these checks again.
|
||||||
|
Future<void> putStreamedFileToStore(
|
||||||
|
CacheStore store,
|
||||||
|
String url,
|
||||||
|
Stream<List<int>> source, {
|
||||||
|
String? key,
|
||||||
|
String? eTag,
|
||||||
|
Duration maxAge = const Duration(days: 30),
|
||||||
|
String fileExtension = 'file',
|
||||||
|
}) async {
|
||||||
|
final path = '${const Uuid().v1()}.$fileExtension';
|
||||||
|
final file = await store.fileSystem.createFile(path);
|
||||||
|
final sink = file.openWrite();
|
||||||
|
try {
|
||||||
|
await source.listen(sink.add, cancelOnError: true).asFuture();
|
||||||
|
} catch (e) {
|
||||||
|
try {
|
||||||
|
await sink.close();
|
||||||
|
await file.delete();
|
||||||
|
} catch (e) {
|
||||||
|
_log.severe('Failed to delete incomplete cache file: $e');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sink.flush();
|
||||||
|
await sink.close();
|
||||||
|
} catch (e) {
|
||||||
|
try {
|
||||||
|
await file.delete();
|
||||||
|
} catch (e) {
|
||||||
|
_log.severe('Failed to delete incomplete cache file: $e');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final cacheObject = CacheObject(
|
||||||
|
url,
|
||||||
|
key: key,
|
||||||
|
relativePath: path,
|
||||||
|
validTill: DateTime.now().add(maxAge),
|
||||||
|
eTag: eTag,
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
await store.putFile(cacheObject);
|
||||||
|
} catch (e) {
|
||||||
|
try {
|
||||||
|
await file.delete();
|
||||||
|
} catch (e) {
|
||||||
|
_log.severe('Failed to delete untracked cache file: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RemoteImageCacheManager extends RemoteCacheManager {
|
||||||
static const key = 'remoteImageCacheKey';
|
static const key = 'remoteImageCacheKey';
|
||||||
static final RemoteImageCacheManager _instance = RemoteImageCacheManager._();
|
static final RemoteImageCacheManager _instance = RemoteImageCacheManager._();
|
||||||
static final _config = Config(key, maxNrOfCacheObjects: 500, stalePeriod: const Duration(days: 30));
|
static final _config = Config(key, maxNrOfCacheObjects: 500, stalePeriod: const Duration(days: 30));
|
||||||
|
static final _store = CacheStore(_config);
|
||||||
|
|
||||||
factory RemoteImageCacheManager() {
|
factory RemoteImageCacheManager() {
|
||||||
return _instance;
|
return _instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
RemoteImageCacheManager._() : super(_config);
|
RemoteImageCacheManager._() : super.custom(_config, _store);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> putStreamedFile(
|
||||||
|
String url,
|
||||||
|
Stream<List<int>> source, {
|
||||||
|
String? key,
|
||||||
|
String? eTag,
|
||||||
|
Duration maxAge = const Duration(days: 30),
|
||||||
|
String fileExtension = 'file',
|
||||||
|
}) {
|
||||||
|
return putStreamedFileToStore(
|
||||||
|
_store,
|
||||||
|
url,
|
||||||
|
source,
|
||||||
|
key: key,
|
||||||
|
eTag: eTag,
|
||||||
|
maxAge: maxAge,
|
||||||
|
fileExtension: fileExtension,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RemoteThumbnailCacheManager extends CacheManager {
|
/// The cache manager for full size images [ImmichRemoteImageProvider]
|
||||||
|
class RemoteThumbnailCacheManager extends RemoteCacheManager {
|
||||||
static const key = 'remoteThumbnailCacheKey';
|
static const key = 'remoteThumbnailCacheKey';
|
||||||
static final RemoteThumbnailCacheManager _instance = RemoteThumbnailCacheManager._();
|
static final RemoteThumbnailCacheManager _instance = RemoteThumbnailCacheManager._();
|
||||||
static final _config = Config(key, maxNrOfCacheObjects: 5000, stalePeriod: const Duration(days: 30));
|
static final _config = Config(key, maxNrOfCacheObjects: 5000, stalePeriod: const Duration(days: 30));
|
||||||
|
static final _store = CacheStore(_config);
|
||||||
|
|
||||||
factory RemoteThumbnailCacheManager() {
|
factory RemoteThumbnailCacheManager() {
|
||||||
return _instance;
|
return _instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
RemoteThumbnailCacheManager._() : super(_config);
|
RemoteThumbnailCacheManager._() : super.custom(_config, _store);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> putStreamedFile(
|
||||||
|
String url,
|
||||||
|
Stream<List<int>> source, {
|
||||||
|
String? key,
|
||||||
|
String? eTag,
|
||||||
|
Duration maxAge = const Duration(days: 30),
|
||||||
|
String fileExtension = 'file',
|
||||||
|
}) {
|
||||||
|
return putStreamedFileToStore(
|
||||||
|
_store,
|
||||||
|
url,
|
||||||
|
source,
|
||||||
|
key: key,
|
||||||
|
eTag: eTag,
|
||||||
|
maxAge: maxAge,
|
||||||
|
fileExtension: fileExtension,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:background_downloader/background_downloader.dart';
|
import 'package:background_downloader/background_downloader.dart';
|
||||||
|
import 'package:cancellation_token_http/http.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:http/http.dart';
|
|
||||||
import 'package:immich_mobile/constants/constants.dart';
|
import 'package:immich_mobile/constants/constants.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:immich_mobile/utils/debug_print.dart';
|
import 'package:immich_mobile/utils/debug_print.dart';
|
||||||
|
|
||||||
@@ -22,8 +20,6 @@ class UploadTaskWithFile {
|
|||||||
final uploadRepositoryProvider = Provider((ref) => UploadRepository());
|
final uploadRepositoryProvider = Provider((ref) => UploadRepository());
|
||||||
|
|
||||||
class UploadRepository {
|
class UploadRepository {
|
||||||
static final _client = const NetworkRepository().getHttpClient('upload');
|
|
||||||
|
|
||||||
void Function(TaskStatusUpdate)? onUploadStatus;
|
void Function(TaskStatusUpdate)? onUploadStatus;
|
||||||
void Function(TaskProgressUpdate)? onTaskProgress;
|
void Function(TaskProgressUpdate)? onTaskProgress;
|
||||||
|
|
||||||
@@ -96,12 +92,13 @@ class UploadRepository {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> backupWithDartClient(Iterable<UploadTaskWithFile> tasks, Completer cancelToken) async {
|
Future<void> backupWithDartClient(Iterable<UploadTaskWithFile> tasks, CancellationToken cancelToken) async {
|
||||||
|
final httpClient = Client();
|
||||||
final String savedEndpoint = Store.get(StoreKey.serverEndpoint);
|
final String savedEndpoint = Store.get(StoreKey.serverEndpoint);
|
||||||
|
|
||||||
Logger logger = Logger('UploadRepository');
|
Logger logger = Logger('UploadRepository');
|
||||||
for (final candidate in tasks) {
|
for (final candidate in tasks) {
|
||||||
if (cancelToken.isCompleted) {
|
if (cancelToken.isCancelled) {
|
||||||
logger.warning("Backup was cancelled by the user");
|
logger.warning("Backup was cancelled by the user");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -115,17 +112,13 @@ class UploadRepository {
|
|||||||
filename: candidate.task.filename,
|
filename: candidate.task.filename,
|
||||||
);
|
);
|
||||||
|
|
||||||
final baseRequest = AbortableMultipartRequest(
|
final baseRequest = MultipartRequest('POST', Uri.parse('$savedEndpoint/assets'));
|
||||||
'POST',
|
|
||||||
Uri.parse('$savedEndpoint/assets'),
|
|
||||||
abortTrigger: cancelToken.future,
|
|
||||||
)..headers['Accept'] = 'application/json';
|
|
||||||
|
|
||||||
baseRequest.headers.addAll(candidate.task.headers);
|
baseRequest.headers.addAll(candidate.task.headers);
|
||||||
baseRequest.fields.addAll(candidate.task.fields);
|
baseRequest.fields.addAll(candidate.task.fields);
|
||||||
baseRequest.files.add(assetRawUploadData);
|
baseRequest.files.add(assetRawUploadData);
|
||||||
|
|
||||||
final response = await _client.send(baseRequest);
|
final response = await httpClient.send(baseRequest, cancellationToken: cancelToken);
|
||||||
|
|
||||||
final responseBody = jsonDecode(await response.stream.bytesToString());
|
final responseBody = jsonDecode(await response.stream.bytesToString());
|
||||||
|
|
||||||
@@ -138,7 +131,7 @@ class UploadRepository {
|
|||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
} on RequestAbortedException {
|
} on CancelledException {
|
||||||
logger.warning("Backup was cancelled by the user");
|
logger.warning("Backup was cancelled by the user");
|
||||||
break;
|
break;
|
||||||
} catch (error, stackTrace) {
|
} catch (error, stackTrace) {
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import 'dart:convert';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
|
import 'package:http/http.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
|
||||||
import 'package:immich_mobile/utils/url_helper.dart';
|
import 'package:immich_mobile/utils/url_helper.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
@@ -13,7 +13,6 @@ import 'package:immich_mobile/utils/user_agent.dart';
|
|||||||
import 'package:immich_mobile/utils/debug_print.dart';
|
import 'package:immich_mobile/utils/debug_print.dart';
|
||||||
|
|
||||||
class ApiService implements Authentication {
|
class ApiService implements Authentication {
|
||||||
static final _client = const NetworkRepository().getHttpClient('api');
|
|
||||||
late ApiClient _apiClient;
|
late ApiClient _apiClient;
|
||||||
|
|
||||||
late UsersApi usersApi;
|
late UsersApi usersApi;
|
||||||
@@ -51,7 +50,6 @@ class ApiService implements Authentication {
|
|||||||
|
|
||||||
setEndpoint(String endpoint) {
|
setEndpoint(String endpoint) {
|
||||||
_apiClient = ApiClient(basePath: endpoint, authentication: this);
|
_apiClient = ApiClient(basePath: endpoint, authentication: this);
|
||||||
_apiClient.client = _client;
|
|
||||||
_setUserAgentHeader();
|
_setUserAgentHeader();
|
||||||
if (_accessToken != null) {
|
if (_accessToken != null) {
|
||||||
setAccessToken(_accessToken!);
|
setAccessToken(_accessToken!);
|
||||||
@@ -136,11 +134,13 @@ class ApiService implements Authentication {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<String> _getWellKnownEndpoint(String baseUrl) async {
|
Future<String> _getWellKnownEndpoint(String baseUrl) async {
|
||||||
|
final Client client = Client();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var headers = {"Accept": "application/json"};
|
var headers = {"Accept": "application/json"};
|
||||||
headers.addAll(getRequestHeaders());
|
headers.addAll(getRequestHeaders());
|
||||||
|
|
||||||
final res = await _client
|
final res = await client
|
||||||
.get(Uri.parse("$baseUrl/.well-known/immich"), headers: headers)
|
.get(Uri.parse("$baseUrl/.well-known/immich"), headers: headers)
|
||||||
.timeout(const Duration(seconds: 5));
|
.timeout(const Duration(seconds: 5));
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'dart:convert';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:background_downloader/background_downloader.dart';
|
import 'package:background_downloader/background_downloader.dart';
|
||||||
|
import 'package:cancellation_token_http/http.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/constants.dart';
|
import 'package:immich_mobile/constants/constants.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
@@ -157,7 +158,7 @@ class UploadService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> startBackupWithHttpClient(String userId, bool hasWifi, Completer token) async {
|
Future<void> startBackupWithHttpClient(String userId, bool hasWifi, CancellationToken token) async {
|
||||||
await _storageRepository.clearCache();
|
await _storageRepository.clearCache();
|
||||||
|
|
||||||
shouldAbortQueuingTasks = false;
|
shouldAbortQueuingTasks = false;
|
||||||
@@ -169,7 +170,7 @@ class UploadService {
|
|||||||
|
|
||||||
const batchSize = 100;
|
const batchSize = 100;
|
||||||
for (int i = 0; i < candidates.length; i += batchSize) {
|
for (int i = 0; i < candidates.length; i += batchSize) {
|
||||||
if (shouldAbortQueuingTasks || token.isCompleted) {
|
if (shouldAbortQueuingTasks || token.isCancelled) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
|||||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/log.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/log.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
@@ -107,7 +106,5 @@ abstract final class Bootstrap {
|
|||||||
storeRepository: storeRepo,
|
storeRepository: storeRepo,
|
||||||
shouldBuffer: shouldBufferLogs,
|
shouldBuffer: shouldBufferLogs,
|
||||||
);
|
);
|
||||||
|
|
||||||
await NetworkRepository.init();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ class PlatformAsset {
|
|||||||
final int durationInSeconds;
|
final int durationInSeconds;
|
||||||
final int orientation;
|
final int orientation;
|
||||||
final bool isFavorite;
|
final bool isFavorite;
|
||||||
|
final int? adjustmentTimestamp;
|
||||||
|
|
||||||
const PlatformAsset({
|
const PlatformAsset({
|
||||||
required this.id,
|
required this.id,
|
||||||
@@ -36,6 +37,7 @@ class PlatformAsset {
|
|||||||
this.durationInSeconds = 0,
|
this.durationInSeconds = 0,
|
||||||
this.orientation = 0,
|
this.orientation = 0,
|
||||||
this.isFavorite = false,
|
this.isFavorite = false,
|
||||||
|
this.adjustmentTimestamp,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-42
@@ -337,14 +337,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.2"
|
version: "3.1.2"
|
||||||
cronet_http:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: cronet_http
|
|
||||||
sha256: "1b99ad5ae81aa9d2f12900e5f17d3681f3828629bb7f7fe7ad88076a34209840"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.5.0"
|
|
||||||
crop_image:
|
crop_image:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -377,14 +369,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.2"
|
version: "1.0.2"
|
||||||
cupertino_http:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: cupertino_http
|
|
||||||
sha256: "72187f715837290a63479a5b0ae709f4fedad0ed6bd0441c275eceaa02d5abae"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.3.0"
|
|
||||||
custom_lint:
|
custom_lint:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@@ -915,10 +899,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: http
|
name: http
|
||||||
sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007
|
sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.0"
|
version: "1.3.0"
|
||||||
http_multi_server:
|
http_multi_server:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -935,14 +919,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.2"
|
version: "4.1.2"
|
||||||
http_profile:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: http_profile
|
|
||||||
sha256: "7e679e355b09aaee2ab5010915c932cce3f2d1c11c3b2dc177891687014ffa78"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.1.0"
|
|
||||||
image:
|
image:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1068,14 +1044,6 @@ packages:
|
|||||||
url: "https://github.com/immich-app/isar"
|
url: "https://github.com/immich-app/isar"
|
||||||
source: git
|
source: git
|
||||||
version: "3.1.8"
|
version: "3.1.8"
|
||||||
jni:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: jni
|
|
||||||
sha256: d2c361082d554d4593c3012e26f6b188f902acd291330f13d6427641a92b3da1
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.14.2"
|
|
||||||
js:
|
js:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1269,14 +1237,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.0"
|
version: "0.5.0"
|
||||||
objective_c:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: objective_c
|
|
||||||
sha256: "9f034ba1eeca53ddb339bc8f4813cb07336a849cd735559b60cdc068ecce2dc7"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "7.1.0"
|
|
||||||
octo_image:
|
octo_image:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -89,8 +89,6 @@ dependencies:
|
|||||||
# DB
|
# DB
|
||||||
drift: ^2.23.1
|
drift: ^2.23.1
|
||||||
drift_flutter: ^0.2.4
|
drift_flutter: ^0.2.4
|
||||||
cronet_http: ^1.5.0
|
|
||||||
cupertino_http: ^2.3.0
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
+4
-1
@@ -14,6 +14,7 @@ import 'schema_v8.dart' as v8;
|
|||||||
import 'schema_v9.dart' as v9;
|
import 'schema_v9.dart' as v9;
|
||||||
import 'schema_v10.dart' as v10;
|
import 'schema_v10.dart' as v10;
|
||||||
import 'schema_v11.dart' as v11;
|
import 'schema_v11.dart' as v11;
|
||||||
|
import 'schema_v12.dart' as v12;
|
||||||
|
|
||||||
class GeneratedHelper implements SchemaInstantiationHelper {
|
class GeneratedHelper implements SchemaInstantiationHelper {
|
||||||
@override
|
@override
|
||||||
@@ -41,10 +42,12 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
|||||||
return v10.DatabaseAtV10(db);
|
return v10.DatabaseAtV10(db);
|
||||||
case 11:
|
case 11:
|
||||||
return v11.DatabaseAtV11(db);
|
return v11.DatabaseAtV11(db);
|
||||||
|
case 12:
|
||||||
|
return v12.DatabaseAtV12(db);
|
||||||
default:
|
default:
|
||||||
throw MissingSchemaException(version, versions);
|
throw MissingSchemaException(version, versions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
|
static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
|
||||||
}
|
}
|
||||||
|
|||||||
+7241
File diff suppressed because it is too large
Load Diff
@@ -118,6 +118,7 @@ void main() {
|
|||||||
expect(onDataCallCount, 1);
|
expect(onDataCallCount, 1);
|
||||||
expect(abortWasCalledInCallback, isTrue);
|
expect(abortWasCalledInCallback, isTrue);
|
||||||
expect(receivedEventsBatch1.length, testBatchSize);
|
expect(receivedEventsBatch1.length, testBatchSize);
|
||||||
|
verify(() => mockHttpClient.close()).called(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('streamChanges does not process remaining lines in finally block if aborted', () async {
|
test('streamChanges does not process remaining lines in finally block if aborted', () async {
|
||||||
@@ -158,6 +159,7 @@ void main() {
|
|||||||
|
|
||||||
expect(onDataCallCount, 1);
|
expect(onDataCallCount, 1);
|
||||||
expect(abortWasCalledInCallback, isTrue);
|
expect(abortWasCalledInCallback, isTrue);
|
||||||
|
verify(() => mockHttpClient.close()).called(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('streamChanges processes remaining lines in finally block if not aborted', () async {
|
test('streamChanges processes remaining lines in finally block if not aborted', () async {
|
||||||
@@ -202,6 +204,7 @@ void main() {
|
|||||||
expect(onDataCallCount, 2);
|
expect(onDataCallCount, 2);
|
||||||
expect(receivedEventsBatch1.length, testBatchSize);
|
expect(receivedEventsBatch1.length, testBatchSize);
|
||||||
expect(receivedEventsBatch2.length, 1);
|
expect(receivedEventsBatch2.length, 1);
|
||||||
|
verify(() => mockHttpClient.close()).called(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('streamChanges handles stream error gracefully', () async {
|
test('streamChanges handles stream error gracefully', () async {
|
||||||
@@ -226,6 +229,7 @@ void main() {
|
|||||||
await expectLater(streamChangesFuture, throwsA(streamError));
|
await expectLater(streamChangesFuture, throwsA(streamError));
|
||||||
|
|
||||||
expect(onDataCallCount, 0);
|
expect(onDataCallCount, 0);
|
||||||
|
verify(() => mockHttpClient.close()).called(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('streamChanges throws ApiException on non-200 status code', () async {
|
test('streamChanges throws ApiException on non-200 status code', () async {
|
||||||
@@ -253,5 +257,6 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(onDataCallCount, 0);
|
expect(onDataCallCount, 0);
|
||||||
|
verify(() => mockHttpClient.close()).called(1);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,8 +34,7 @@ type SendFile = Parameters<Response['sendFile']>;
|
|||||||
type SendFileOptions = SendFile[1];
|
type SendFileOptions = SendFile[1];
|
||||||
|
|
||||||
const cacheControlHeaders: Record<CacheControl, string | null> = {
|
const cacheControlHeaders: Record<CacheControl, string | null> = {
|
||||||
[CacheControl.PrivateWithCache]:
|
[CacheControl.PrivateWithCache]: 'private, max-age=86400, no-transform',
|
||||||
'private, max-age=86400, no-transform, stale-while-revalidate=2592000, stale-if-error=2592000',
|
|
||||||
[CacheControl.PrivateWithoutCache]: 'private, no-cache, no-transform',
|
[CacheControl.PrivateWithoutCache]: 'private, no-cache, no-transform',
|
||||||
[CacheControl.None]: null, // falsy value to prevent adding Cache-Control header
|
[CacheControl.None]: null, // falsy value to prevent adding Cache-Control header
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -97,15 +97,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await copyImageToClipboard($photoViewerImgElement ?? assetFileUrl);
|
await copyImageToClipboard($photoViewerImgElement ?? assetFileUrl);
|
||||||
if (result.success) {
|
notificationController.show({
|
||||||
notificationController.show({ type: NotificationType.Info, message: $t('copied_image_to_clipboard') });
|
type: NotificationType.Info,
|
||||||
} else {
|
message: $t('copied_image_to_clipboard'),
|
||||||
notificationController.show({
|
timeout: 3000,
|
||||||
type: NotificationType.Error,
|
});
|
||||||
message: $t('errors.clipboard_unsupported_mime_type', { values: { mimeType: result.mimeType } }),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, $t('copy_error'));
|
handleError(error, $t('copy_error'));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -625,21 +625,7 @@ const urlToBlob = async (imageSource: string) => {
|
|||||||
return await response.blob();
|
return await response.blob();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const copyImageToClipboard = async (
|
export const copyImageToClipboard = async (source: HTMLImageElement | string) => {
|
||||||
source: HTMLImageElement | string,
|
const blob = source instanceof HTMLImageElement ? await imgToBlob(source) : await urlToBlob(source);
|
||||||
): Promise<{ success: true } | { success: false; mimeType: string }> => {
|
|
||||||
if (source instanceof HTMLImageElement) {
|
|
||||||
// do not await, so the Safari clipboard write happens in the context of the user gesture
|
|
||||||
await navigator.clipboard.write([new ClipboardItem({ ['image/png']: imgToBlob(source) })]);
|
|
||||||
return { success: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we had a way to get the mime type synchronously, we could do the same thing here
|
|
||||||
const blob = await urlToBlob(source);
|
|
||||||
if (!ClipboardItem.supports(blob.type)) {
|
|
||||||
return { success: false, mimeType: blob.type };
|
|
||||||
}
|
|
||||||
|
|
||||||
await navigator.clipboard.write([new ClipboardItem({ [blob.type]: blob })]);
|
await navigator.clipboard.write([new ClipboardItem({ [blob.type]: blob })]);
|
||||||
return { success: true };
|
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user