diff --git a/mobile/lib/module/download/manager.dart b/mobile/lib/module/download/manager.dart index c93dfde066..47cacc7d69 100644 --- a/mobile/lib/module/download/manager.dart +++ b/mobile/lib/module/download/manager.dart @@ -22,6 +22,7 @@ class DownloadManager { final Map> _completers = {}; final Map> _streams = {}; final Map _cancelTokens = {}; + final Map _downloadStartTimes = {}; DownloadManager(this._dio); @@ -44,6 +45,7 @@ class DownloadManager { String filename, int totalBytes, ) async { + _downloadStartTimes[fileId] = DateTime.now().microsecondsSinceEpoch; // If already downloading, return existing future if (_completers.containsKey(fileId)) { return _completers[fileId]!.future; @@ -97,6 +99,15 @@ class DownloadManager { /// Pause download Future pause(int fileId) async { + // ignore cancel if download started less than 1 second ago, + // this is to avoid cancellination due to different type of video players, where dispose is called + // little later after other video player operations + final startTime = _downloadStartTimes[fileId]; + if (startTime == null || + DateTime.now().microsecondsSinceEpoch - startTime < 1e6) { + _logger.info('Download paused too soon, ignoring pause request'); + return; + } final token = _cancelTokens[fileId]; if (token != null && !token.isCancelled) { token.cancel('paused'); @@ -151,6 +162,31 @@ class DownloadManager { final directory = Configuration.instance.getTempDirectory(); final basePath = '$directory${task.id}.encrypted'; + // check if base file already exists and is of correct size + final baseFile = File(basePath); + if (await baseFile.exists()) { + final existingSize = await baseFile.length(); + if (existingSize == task.totalBytes) { + _logger.info( + 'Download already exists for ${task.filename} (${existingSize}/${task.totalBytes} bytes)', + ); + task = task.copyWith( + status: DownloadStatus.completed, + filePath: basePath, + bytesDownloaded: existingSize, + ); + _updateTask(task); + completer.complete(DownloadResult(task, true)); + return; + } else { + _logger.warning( + 'Existing file size mismatch for ${task.filename}: ' + 'expected ${task.totalBytes}, but got $existingSize', + ); + await baseFile.delete(); // Remove corrupted file + } + } + // Check existing chunks and calculate progress final totalChunks = (task.totalBytes / downloadChunkSize).ceil(); final existingChunks = @@ -169,8 +205,13 @@ class DownloadManager { 'Resuming download for ${task.filename} (${task.bytesDownloaded}/${task.totalBytes} bytes)', ); for (int i = 0; i < totalChunks; i++) { - if (existingChunks[i] || cancelToken.isCancelled) continue; - _logger.info('Downloading chunk ${i + 1} of $totalChunks'); + if (existingChunks[i]) { + continue; + } + if (cancelToken.isCancelled) { + _logger.info('Download cancelled for ${task.filename}'); + break; + } await _downloadChunk(task, basePath, i, totalChunks, cancelToken); existingChunks[i] = true; } @@ -235,7 +276,7 @@ class DownloadManager { 'but got $actualSize bytes', ); existingChunks[i] = false; - await chunkFile.delete(); // Remove corrupted chunk + // await chunkFile.delete(); // Remove corrupted chunk } } @@ -270,7 +311,7 @@ class DownloadManager { final endByte = chunkIndex == totalChunks - 1 ? task.totalBytes - 1 : (startByte + downloadChunkSize) - 1; - + _logger.info('Downloading chunk ${chunkIndex + 1}/$totalChunks'); await _dio.download( FileUrl.getUrl(task.id, FileUrlType.directDownload), chunkPath,