import 'dart:async'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; import 'package:nc_photos/app_localizations.dart'; import 'package:nc_photos/di_container.dart'; import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/entity/file_descriptor.dart'; import 'package:nc_photos/exception.dart'; import 'package:nc_photos/exception_util.dart' as exception_util; import 'package:nc_photos/k.dart' as k; import 'package:nc_photos/mobile/android/download.dart'; import 'package:nc_photos/mobile/notification.dart'; import 'package:nc_photos/mobile/platform.dart' if (dart.library.html) 'package:nc_photos/web/platform.dart' as platform; import 'package:nc_photos/snack_bar_manager.dart'; import 'package:nc_photos/use_case/download_file.dart'; import 'package:nc_photos/use_case/inflate_file_descriptor.dart'; import 'package:nc_photos_plugin/nc_photos_plugin.dart'; import 'package:np_codegen/np_codegen.dart'; import 'package:np_platform_util/np_platform_util.dart'; part 'download_handler.g.dart'; class DownloadHandler { DownloadHandler(this._c) : assert(require(_c)), assert(InflateFileDescriptor.require(_c)); static bool require(DiContainer c) => true; Future downloadFiles( Account account, List fds, { String? parentDir, }) async { final files = await InflateFileDescriptor(_c)(account, fds); final _DownloadHandlerBase handler; if (getRawPlatform() == NpPlatform.android) { handler = _DownlaodHandlerAndroid(); } else { handler = _DownloadHandlerWeb(); } return handler.downloadFiles( account, files, parentDir: parentDir, ); } final DiContainer _c; } abstract class _DownloadHandlerBase { Future downloadFiles( Account account, List files, { String? parentDir, }); } @npLog class _DownlaodHandlerAndroid extends _DownloadHandlerBase { @override downloadFiles( Account account, List files, { String? parentDir, }) async { _log.info("[downloadFiles] Downloading ${files.length} file"); final nm = platform.NotificationManager(); final notif = AndroidDownloadProgressNotification( 0, files.length, currentItemTitle: files.firstOrNull?.filename, ); final id = await nm.notify(notif); final successes = <({File file, dynamic result})>[]; StreamSubscription? subscription; try { bool isCancel = false; subscription = DownloadEvent.downloadCancelStream().listen((data) { if (data.notificationId == id) { isCancel = true; } }); int count = 0; for (final f in files) { if (isCancel == true) { _log.info("[downloadFiles] User canceled remaining files"); break; } await nm.notify(notif.copyWith( progress: count++, currentItemTitle: f.filename, notificationId: id, )); StreamSubscription? itemSubscription; try { final download = DownloadFile().build( account, f, parentDir: parentDir, shouldNotify: false, ); itemSubscription = DownloadEvent.downloadCancelStream().listen((data) { if (data.notificationId == id) { _log.info("[downloadFiles] Cancel requested"); download.cancel(); } }); final result = await download(); successes.add((file: f, result: result)); } on PermissionException catch (_) { _log.warning("[downloadFiles] Permission not granted"); SnackBarManager().showSnackBar(SnackBar( content: Text(L10n.global().errorNoStoragePermission), duration: k.snackBarDurationNormal, )); break; } on JobCanceledException catch (_) { _log.info("[downloadFiles] User canceled"); break; } catch (e, stackTrace) { _log.shout( "[downloadFiles] Failed while DownloadFile", e, stackTrace); SnackBarManager().showSnackBar(SnackBar( content: Text("${L10n.global().downloadFailureNotification}: " "${exception_util.toUserString(e)}"), duration: k.snackBarDurationNormal, )); } finally { unawaited(itemSubscription?.cancel()); } } } finally { unawaited(subscription?.cancel()); if (successes.isNotEmpty) { await _onDownloadSuccessful(successes.map((e) => e.file).toList(), successes.map((e) => e.result).toList(), id); } else { await nm.dismiss(id); } } } Future _onDownloadSuccessful( List files, List results, int? notificationId) async { final nm = platform.NotificationManager(); await nm.notify(AndroidDownloadSuccessfulNotification( results.cast(), files.map((e) => e.contentType).toList(), notificationId: notificationId, )); } } @npLog class _DownloadHandlerWeb extends _DownloadHandlerBase { @override downloadFiles( Account account, List files, { String? parentDir, }) async { _log.info("[downloadFiles] Downloading ${files.length} file"); SnackBarManager().showSnackBar( SnackBar( content: Text(L10n.global().downloadProcessingNotification), duration: k.snackBarDurationShort, ), canBeReplaced: true, ); int successCount = 0; for (final f in files) { try { await DownloadFile()( account, f, parentDir: parentDir, ); ++successCount; } on PermissionException catch (_) { _log.warning("[downloadFiles] Permission not granted"); SnackBarManager().showSnackBar(SnackBar( content: Text(L10n.global().errorNoStoragePermission), duration: k.snackBarDurationNormal, )); break; } on JobCanceledException catch (_) { _log.info("[downloadFiles] User canceled"); break; } catch (e, stackTrace) { _log.shout("[downloadFiles] Failed while DownloadFile", e, stackTrace); SnackBarManager().showSnackBar(SnackBar( content: Text("${L10n.global().downloadFailureNotification}: " "${exception_util.toUserString(e)}"), duration: k.snackBarDurationNormal, )); } } if (successCount > 0) { SnackBarManager().showSnackBar(SnackBar( content: Text(L10n.global().downloadSuccessNotification), duration: k.snackBarDurationShort, )); } } }