mirror of
https://github.com/ente-io/ente.git
synced 2025-08-14 10:16:10 +00:00
feat(backup): introduce backup status screen
This commit is contained in:
committed by
Neeraj Gupta
parent
2044d3eb6b
commit
864b5514be
10
mobile/lib/events/backup_updated_event.dart
Normal file
10
mobile/lib/events/backup_updated_event.dart
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import "dart:collection";
|
||||||
|
|
||||||
|
import "package:photos/events/event.dart";
|
||||||
|
import "package:photos/models/backup/backup_item.dart";
|
||||||
|
|
||||||
|
class BackupUpdatedEvent extends Event {
|
||||||
|
final LinkedHashMap<String, BackupItem> items;
|
||||||
|
|
||||||
|
BackupUpdatedEvent(this.items);
|
||||||
|
}
|
55
mobile/lib/models/backup/backup_item.dart
Normal file
55
mobile/lib/models/backup/backup_item.dart
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import "dart:async";
|
||||||
|
|
||||||
|
import "package:photos/models/backup/backup_item_status.dart";
|
||||||
|
import "package:photos/models/file/file.dart";
|
||||||
|
|
||||||
|
class BackupItem {
|
||||||
|
final BackupItemStatus status;
|
||||||
|
final EnteFile file;
|
||||||
|
final int collectionID;
|
||||||
|
final Completer<EnteFile> completer;
|
||||||
|
|
||||||
|
BackupItem({
|
||||||
|
required this.status,
|
||||||
|
required this.file,
|
||||||
|
required this.collectionID,
|
||||||
|
required this.completer,
|
||||||
|
});
|
||||||
|
|
||||||
|
BackupItem copyWith({
|
||||||
|
BackupItemStatus? status,
|
||||||
|
EnteFile? file,
|
||||||
|
int? collectionID,
|
||||||
|
Completer<EnteFile>? completer,
|
||||||
|
}) {
|
||||||
|
return BackupItem(
|
||||||
|
status: status ?? this.status,
|
||||||
|
file: file ?? this.file,
|
||||||
|
collectionID: collectionID ?? this.collectionID,
|
||||||
|
completer: completer ?? this.completer,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'BackupItem(status: $status, file: $file, collectionID: $collectionID)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(covariant BackupItem other) {
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
|
||||||
|
return other.status == status &&
|
||||||
|
other.file == file &&
|
||||||
|
other.collectionID == collectionID &&
|
||||||
|
other.completer == completer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
return status.hashCode ^
|
||||||
|
file.hashCode ^
|
||||||
|
collectionID.hashCode ^
|
||||||
|
completer.hashCode;
|
||||||
|
}
|
||||||
|
}
|
7
mobile/lib/models/backup/backup_item_status.dart
Normal file
7
mobile/lib/models/backup/backup_item_status.dart
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
enum BackupItemStatus {
|
||||||
|
inBackground,
|
||||||
|
inQueue,
|
||||||
|
uploading,
|
||||||
|
completed,
|
||||||
|
retry,
|
||||||
|
}
|
171
mobile/lib/ui/settings/backup/backup_item_card.dart
Normal file
171
mobile/lib/ui/settings/backup/backup_item_card.dart
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
import "dart:typed_data";
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import "package:photos/models/backup/backup_item.dart";
|
||||||
|
import "package:photos/models/backup/backup_item_status.dart";
|
||||||
|
import 'package:photos/theme/ente_theme.dart';
|
||||||
|
import "package:photos/utils/file_uploader.dart";
|
||||||
|
import "package:photos/utils/thumbnail_util.dart";
|
||||||
|
|
||||||
|
class BackupItemCard extends StatefulWidget {
|
||||||
|
const BackupItemCard({
|
||||||
|
super.key,
|
||||||
|
required this.item,
|
||||||
|
});
|
||||||
|
|
||||||
|
final BackupItem item;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<BackupItemCard> createState() => _BackupItemCardState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BackupItemCardState extends State<BackupItemCard> {
|
||||||
|
Uint8List? thumbnail;
|
||||||
|
String? folderName;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_getThumbnail();
|
||||||
|
_getFolderName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_getThumbnail() async {
|
||||||
|
thumbnail = await getThumbnail(widget.item.file);
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
_getFolderName() async {
|
||||||
|
folderName = widget.item.file.deviceFolder ?? '';
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final colorScheme = getEnteColorScheme(context);
|
||||||
|
return Container(
|
||||||
|
height: 60,
|
||||||
|
margin: const EdgeInsets.symmetric(vertical: 10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
border: Border.all(
|
||||||
|
color: Theme.of(context).brightness == Brightness.light
|
||||||
|
? const Color(0xFF000000).withOpacity(0.08)
|
||||||
|
: const Color(0xFFFFFFFF).withOpacity(0.08),
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 60,
|
||||||
|
height: 60,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
child: thumbnail != null
|
||||||
|
? Image.memory(
|
||||||
|
thumbnail!,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
)
|
||||||
|
: const SizedBox(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
widget.item.file.displayName,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
height: 20 / 16,
|
||||||
|
color: Theme.of(context).brightness == Brightness.light
|
||||||
|
? const Color(0xFF000000)
|
||||||
|
: const Color(0xFFFFFFFF),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
folderName ?? "",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
height: 17 / 14,
|
||||||
|
color: Theme.of(context).brightness == Brightness.light
|
||||||
|
? const Color.fromRGBO(0, 0, 0, 0.7)
|
||||||
|
: const Color.fromRGBO(255, 255, 255, 0.7),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
SizedBox(
|
||||||
|
height: 48,
|
||||||
|
width: 48,
|
||||||
|
child: Center(
|
||||||
|
child: switch (widget.item.status) {
|
||||||
|
BackupItemStatus.uploading => SizedBox(
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2.0,
|
||||||
|
color: colorScheme.primary700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
BackupItemStatus.completed => const SizedBox(
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
child: Icon(
|
||||||
|
Icons.check,
|
||||||
|
color: Color(0xFF00B33C),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
BackupItemStatus.inQueue => SizedBox(
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
child: Icon(
|
||||||
|
Icons.history,
|
||||||
|
color: Theme.of(context).brightness == Brightness.light
|
||||||
|
? const Color.fromRGBO(0, 0, 0, .6)
|
||||||
|
: const Color.fromRGBO(255, 255, 255, .6),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
BackupItemStatus.retry => IconButton(
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.sync,
|
||||||
|
color: Color(0xFFFDB816),
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
await FileUploader.instance.forceUpload(
|
||||||
|
widget.item.file,
|
||||||
|
widget.item.collectionID,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
BackupItemStatus.inBackground => SizedBox(
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
child: Icon(
|
||||||
|
Icons.lock_reset,
|
||||||
|
color: Theme.of(context).brightness == Brightness.light
|
||||||
|
? const Color.fromRGBO(0, 0, 0, .6)
|
||||||
|
: const Color.fromRGBO(255, 255, 255, .6),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -6,6 +6,7 @@ import 'package:photos/ui/components/expandable_menu_item_widget.dart';
|
|||||||
import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart';
|
import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart';
|
||||||
import 'package:photos/ui/settings/backup/backup_folder_selection_page.dart';
|
import 'package:photos/ui/settings/backup/backup_folder_selection_page.dart';
|
||||||
import 'package:photos/ui/settings/backup/backup_settings_screen.dart';
|
import 'package:photos/ui/settings/backup/backup_settings_screen.dart';
|
||||||
|
import "package:photos/ui/settings/backup/backup_status_screen.dart";
|
||||||
import "package:photos/ui/settings/backup/free_space_options.dart";
|
import "package:photos/ui/settings/backup/free_space_options.dart";
|
||||||
import 'package:photos/ui/settings/common_settings.dart';
|
import 'package:photos/ui/settings/common_settings.dart';
|
||||||
import 'package:photos/utils/navigation_util.dart';
|
import 'package:photos/utils/navigation_util.dart';
|
||||||
@@ -47,6 +48,21 @@ class BackupSectionWidgetState extends State<BackupSectionWidget> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
sectionOptionSpacing,
|
sectionOptionSpacing,
|
||||||
|
MenuItemWidget(
|
||||||
|
captionedTextWidget: CaptionedTextWidget(
|
||||||
|
title: S.of(context).backupStatus,
|
||||||
|
),
|
||||||
|
pressedColor: getEnteColorScheme(context).fillFaint,
|
||||||
|
trailingIcon: Icons.chevron_right_outlined,
|
||||||
|
trailingIconIsMuted: true,
|
||||||
|
onTap: () async {
|
||||||
|
await routeToPage(
|
||||||
|
context,
|
||||||
|
const BackupStatusScreen(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
sectionOptionSpacing,
|
||||||
MenuItemWidget(
|
MenuItemWidget(
|
||||||
captionedTextWidget: CaptionedTextWidget(
|
captionedTextWidget: CaptionedTextWidget(
|
||||||
title: S.of(context).backupSettings,
|
title: S.of(context).backupSettings,
|
||||||
|
110
mobile/lib/ui/settings/backup/backup_status_screen.dart
Normal file
110
mobile/lib/ui/settings/backup/backup_status_screen.dart
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||||
|
import "dart:collection";
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import "package:photos/core/event_bus.dart";
|
||||||
|
import "package:photos/events/backup_updated_event.dart";
|
||||||
|
import "package:photos/generated/l10n.dart";
|
||||||
|
import "package:photos/models/backup/backup_item.dart";
|
||||||
|
import 'package:photos/ui/components/title_bar_title_widget.dart';
|
||||||
|
import 'package:photos/ui/components/title_bar_widget.dart';
|
||||||
|
import "package:photos/ui/settings/backup/backup_item_card.dart";
|
||||||
|
import "package:photos/utils/file_uploader.dart";
|
||||||
|
|
||||||
|
class BackupStatusScreen extends StatefulWidget {
|
||||||
|
const BackupStatusScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<BackupStatusScreen> createState() => _BackupStatusScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BackupStatusScreenState extends State<BackupStatusScreen> {
|
||||||
|
LinkedHashMap<String, BackupItem> items = FileUploader.instance.allBackups;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
checkBackupUpdatedEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
void checkBackupUpdatedEvent() {
|
||||||
|
Bus.instance.on<BackupUpdatedEvent>().listen((event) {
|
||||||
|
items = event.items;
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final List<BackupItem> items = this.items.values.toList();
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
body: CustomScrollView(
|
||||||
|
primary: false,
|
||||||
|
slivers: <Widget>[
|
||||||
|
TitleBarWidget(
|
||||||
|
flexibleSpaceTitle: TitleBarTitleWidget(
|
||||||
|
title: S.of(context).backupStatus,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
items.isEmpty
|
||||||
|
? SliverFillRemaining(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 60,
|
||||||
|
vertical: 12,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.cloud_upload_outlined,
|
||||||
|
color:
|
||||||
|
Theme.of(context).brightness == Brightness.light
|
||||||
|
? const Color.fromRGBO(0, 0, 0, 0.6)
|
||||||
|
: const Color.fromRGBO(255, 255, 255, 0.6),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
S.of(context).backupStatusDescription,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
height: 20 / 16,
|
||||||
|
color:
|
||||||
|
Theme.of(context).brightness == Brightness.light
|
||||||
|
? const Color(0xFF000000).withOpacity(0.7)
|
||||||
|
: const Color(0xFFFFFFFF).withOpacity(0.7),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 48),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: SliverList(
|
||||||
|
delegate: SliverChildBuilderDelegate(
|
||||||
|
(delegateBuildContext, index) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 20,
|
||||||
|
horizontal: 16,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
for (final item in items)
|
||||||
|
BackupItemCard(item: item),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
childCount: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -16,11 +16,14 @@ import 'package:photos/core/event_bus.dart';
|
|||||||
import 'package:photos/core/network/network.dart';
|
import 'package:photos/core/network/network.dart';
|
||||||
import 'package:photos/db/files_db.dart';
|
import 'package:photos/db/files_db.dart';
|
||||||
import 'package:photos/db/upload_locks_db.dart';
|
import 'package:photos/db/upload_locks_db.dart';
|
||||||
|
import "package:photos/events/backup_updated_event.dart";
|
||||||
import "package:photos/events/file_uploaded_event.dart";
|
import "package:photos/events/file_uploaded_event.dart";
|
||||||
import 'package:photos/events/files_updated_event.dart';
|
import 'package:photos/events/files_updated_event.dart';
|
||||||
import 'package:photos/events/local_photos_updated_event.dart';
|
import 'package:photos/events/local_photos_updated_event.dart';
|
||||||
import 'package:photos/events/subscription_purchased_event.dart';
|
import 'package:photos/events/subscription_purchased_event.dart';
|
||||||
import 'package:photos/main.dart';
|
import 'package:photos/main.dart';
|
||||||
|
import "package:photos/models/backup/backup_item.dart";
|
||||||
|
import "package:photos/models/backup/backup_item_status.dart";
|
||||||
import 'package:photos/models/encryption_result.dart';
|
import 'package:photos/models/encryption_result.dart';
|
||||||
import 'package:photos/models/file/file.dart';
|
import 'package:photos/models/file/file.dart';
|
||||||
import 'package:photos/models/file/file_type.dart';
|
import 'package:photos/models/file/file_type.dart';
|
||||||
@@ -59,11 +62,15 @@ class FileUploader {
|
|||||||
final _enteDio = NetworkClient.instance.enteDio;
|
final _enteDio = NetworkClient.instance.enteDio;
|
||||||
final LinkedHashMap<String, FileUploadItem> _queue =
|
final LinkedHashMap<String, FileUploadItem> _queue =
|
||||||
LinkedHashMap<String, FileUploadItem>();
|
LinkedHashMap<String, FileUploadItem>();
|
||||||
|
final LinkedHashMap<String, BackupItem> _allBackups =
|
||||||
|
LinkedHashMap<String, BackupItem>();
|
||||||
final _uploadLocks = UploadLocksDB.instance;
|
final _uploadLocks = UploadLocksDB.instance;
|
||||||
final kSafeBufferForLockExpiry = const Duration(days: 1).inMicroseconds;
|
final kSafeBufferForLockExpiry = const Duration(days: 1).inMicroseconds;
|
||||||
final kBGTaskDeathTimeout = const Duration(seconds: 5).inMicroseconds;
|
final kBGTaskDeathTimeout = const Duration(seconds: 5).inMicroseconds;
|
||||||
final _uploadURLs = Queue<UploadURL>();
|
final _uploadURLs = Queue<UploadURL>();
|
||||||
|
|
||||||
|
LinkedHashMap<String, BackupItem> get allBackups => _allBackups;
|
||||||
|
|
||||||
// Maintains the count of files in the current upload session.
|
// Maintains the count of files in the current upload session.
|
||||||
// Upload session is the period between the first entry into the _queue and last entry out of the _queue
|
// Upload session is the period between the first entry into the _queue and last entry out of the _queue
|
||||||
int _totalCountInUploadSession = 0;
|
int _totalCountInUploadSession = 0;
|
||||||
@@ -160,6 +167,13 @@ class FileUploader {
|
|||||||
if (!_queue.containsKey(localID)) {
|
if (!_queue.containsKey(localID)) {
|
||||||
final completer = Completer<EnteFile>();
|
final completer = Completer<EnteFile>();
|
||||||
_queue[localID] = FileUploadItem(file, collectionID, completer);
|
_queue[localID] = FileUploadItem(file, collectionID, completer);
|
||||||
|
_allBackups[localID] = BackupItem(
|
||||||
|
status: BackupItemStatus.inQueue,
|
||||||
|
file: file,
|
||||||
|
collectionID: collectionID,
|
||||||
|
completer: completer,
|
||||||
|
);
|
||||||
|
Bus.instance.fire(BackupUpdatedEvent(_allBackups));
|
||||||
_pollQueue();
|
_pollQueue();
|
||||||
return completer.future;
|
return completer.future;
|
||||||
}
|
}
|
||||||
@@ -203,6 +217,10 @@ class FileUploader {
|
|||||||
});
|
});
|
||||||
for (final id in uploadsToBeRemoved) {
|
for (final id in uploadsToBeRemoved) {
|
||||||
_queue.remove(id)?.completer.completeError(reason);
|
_queue.remove(id)?.completer.completeError(reason);
|
||||||
|
_allBackups[id] = _allBackups[id]!.copyWith(
|
||||||
|
status: BackupItemStatus.retry,
|
||||||
|
);
|
||||||
|
Bus.instance.fire(BackupUpdatedEvent(_allBackups));
|
||||||
}
|
}
|
||||||
_totalCountInUploadSession = 0;
|
_totalCountInUploadSession = 0;
|
||||||
}
|
}
|
||||||
@@ -225,6 +243,9 @@ class FileUploader {
|
|||||||
});
|
});
|
||||||
for (final id in uploadsToBeRemoved) {
|
for (final id in uploadsToBeRemoved) {
|
||||||
_queue.remove(id)?.completer.completeError(reason);
|
_queue.remove(id)?.completer.completeError(reason);
|
||||||
|
_allBackups[id] =
|
||||||
|
_allBackups[id]!.copyWith(status: BackupItemStatus.retry);
|
||||||
|
Bus.instance.fire(BackupUpdatedEvent(_allBackups));
|
||||||
}
|
}
|
||||||
_logger.info(
|
_logger.info(
|
||||||
'number of enteries removed from queue ${uploadsToBeRemoved.length}',
|
'number of enteries removed from queue ${uploadsToBeRemoved.length}',
|
||||||
@@ -291,13 +312,21 @@ class FileUploader {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
_queue.remove(localID)!.completer.complete(uploadedFile);
|
_queue.remove(localID)!.completer.complete(uploadedFile);
|
||||||
|
_allBackups[localID] =
|
||||||
|
_allBackups[localID]!.copyWith(status: BackupItemStatus.completed);
|
||||||
return uploadedFile;
|
return uploadedFile;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e is LockAlreadyAcquiredError) {
|
if (e is LockAlreadyAcquiredError) {
|
||||||
_queue[localID]!.status = UploadStatus.inBackground;
|
_queue[localID]!.status = UploadStatus.inBackground;
|
||||||
|
_allBackups[localID] = _allBackups[localID]!
|
||||||
|
.copyWith(status: BackupItemStatus.inBackground);
|
||||||
|
Bus.instance.fire(BackupUpdatedEvent(_allBackups));
|
||||||
return _queue[localID]!.completer.future;
|
return _queue[localID]!.completer.future;
|
||||||
} else {
|
} else {
|
||||||
_queue.remove(localID)!.completer.completeError(e);
|
_queue.remove(localID)!.completer.completeError(e);
|
||||||
|
_allBackups[localID] =
|
||||||
|
_allBackups[localID]!.copyWith(status: BackupItemStatus.retry);
|
||||||
|
Bus.instance.fire(BackupUpdatedEvent(_allBackups));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@@ -406,7 +435,20 @@ class FileUploader {
|
|||||||
|
|
||||||
Future<EnteFile> forceUpload(EnteFile file, int collectionID) async {
|
Future<EnteFile> forceUpload(EnteFile file, int collectionID) async {
|
||||||
_hasInitiatedForceUpload = true;
|
_hasInitiatedForceUpload = true;
|
||||||
return _tryToUpload(file, collectionID, true);
|
try {
|
||||||
|
final result = await _tryToUpload(file, collectionID, true);
|
||||||
|
_allBackups[file.localID!] = _allBackups[file.localID]!.copyWith(
|
||||||
|
status: BackupItemStatus.completed,
|
||||||
|
);
|
||||||
|
Bus.instance.fire(BackupUpdatedEvent(_allBackups));
|
||||||
|
return result;
|
||||||
|
} catch (_) {
|
||||||
|
_allBackups[file.localID!] = _allBackups[file.localID]!.copyWith(
|
||||||
|
status: BackupItemStatus.retry,
|
||||||
|
);
|
||||||
|
Bus.instance.fire(BackupUpdatedEvent(_allBackups));
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<EnteFile> _tryToUpload(
|
Future<EnteFile> _tryToUpload(
|
||||||
@@ -426,6 +468,14 @@ class FileUploader {
|
|||||||
return fileOnDisk;
|
return fileOnDisk;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_allBackups[file.localID!] != null &&
|
||||||
|
_allBackups[file.localID]!.status != BackupItemStatus.uploading) {
|
||||||
|
_allBackups[file.localID!] = _allBackups[file.localID]!.copyWith(
|
||||||
|
status: BackupItemStatus.uploading,
|
||||||
|
);
|
||||||
|
Bus.instance.fire(BackupUpdatedEvent(_allBackups));
|
||||||
|
}
|
||||||
if ((file.localID ?? '') == '') {
|
if ((file.localID ?? '') == '') {
|
||||||
_logger.severe('Trying to upload file with missing localID');
|
_logger.severe('Trying to upload file with missing localID');
|
||||||
return file;
|
return file;
|
||||||
@@ -442,7 +492,7 @@ class FileUploader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final String lockKey = file.localID!;
|
final String lockKey = file.localID!;
|
||||||
bool _isMultipartUpload = false;
|
bool isMultipartUpload = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await _uploadLocks.acquireLock(
|
await _uploadLocks.acquireLock(
|
||||||
@@ -589,7 +639,7 @@ class FileUploader {
|
|||||||
final fileUploadURL = await _getUploadURL();
|
final fileUploadURL = await _getUploadURL();
|
||||||
fileObjectKey = await _putFile(fileUploadURL, encryptedFile);
|
fileObjectKey = await _putFile(fileUploadURL, encryptedFile);
|
||||||
} else {
|
} else {
|
||||||
_isMultipartUpload = true;
|
isMultipartUpload = true;
|
||||||
_logger.finest(
|
_logger.finest(
|
||||||
"Init multipartUpload $multipartEntryExists, isUpdate $isUpdatedFile",
|
"Init multipartUpload $multipartEntryExists, isUpdate $isUpdatedFile",
|
||||||
);
|
);
|
||||||
@@ -757,7 +807,7 @@ class FileUploader {
|
|||||||
encryptedFilePath,
|
encryptedFilePath,
|
||||||
encryptedThumbnailPath,
|
encryptedThumbnailPath,
|
||||||
lockKey: lockKey,
|
lockKey: lockKey,
|
||||||
isMultiPartUpload: _isMultipartUpload,
|
isMultiPartUpload: isMultipartUpload,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1280,10 +1330,15 @@ class FileUploader {
|
|||||||
if (dbFile?.uploadedFileID != null) {
|
if (dbFile?.uploadedFileID != null) {
|
||||||
_logger.info("Background upload success detected");
|
_logger.info("Background upload success detected");
|
||||||
completer?.complete(dbFile);
|
completer?.complete(dbFile);
|
||||||
|
_allBackups[upload.key] = _allBackups[upload.key]!
|
||||||
|
.copyWith(status: BackupItemStatus.completed);
|
||||||
} else {
|
} else {
|
||||||
_logger.info("Background upload failure detected");
|
_logger.info("Background upload failure detected");
|
||||||
completer?.completeError(SilentlyCancelUploadsError());
|
completer?.completeError(SilentlyCancelUploadsError());
|
||||||
|
_allBackups[upload.key] =
|
||||||
|
_allBackups[upload.key]!.copyWith(status: BackupItemStatus.retry);
|
||||||
}
|
}
|
||||||
|
Bus.instance.fire(BackupUpdatedEvent(_allBackups));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Future.delayed(kBlockedUploadsPollFrequency, () async {
|
Future.delayed(kBlockedUploadsPollFrequency, () async {
|
||||||
|
Reference in New Issue
Block a user