Remove db to track download tasks

This commit is contained in:
Neeraj Gupta
2025-05-23 16:31:28 +05:30
parent e3b72fc929
commit f7bb9d5974
2 changed files with 31 additions and 89 deletions

View File

@@ -1,64 +0,0 @@
import "package:path/path.dart";
import "package:photos/module/download/task.dart";
import "package:sqflite/sqflite.dart";
class DatabaseHelper {
static final _instance = DatabaseHelper._internal();
static Database? _database;
factory DatabaseHelper() => _instance;
DatabaseHelper._internal();
Future<Database> get database async {
_database ??= await _initDatabase();
return _database!;
}
Future<Database> _initDatabase() async {
final path = join(await getDatabasesPath(), 'download_manager.db');
return await openDatabase(
path,
version: 1,
onCreate: (db, version) async {
await db.execute('''
CREATE TABLE downloads (
id INTEGER PRIMARY KEY,
filename TEXT NOT NULL,
totalBytes INTEGER NOT NULL,
bytesDownloaded INTEGER DEFAULT 0,
status TEXT NOT NULL,
error TEXT,
filePath TEXT
)
''');
},
);
}
Future<void> save(DownloadTask task) async {
final db = await database;
await db.insert(
'downloads',
task.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
Future<DownloadTask?> get(int id) async {
final db = await database;
final maps = await db.query('downloads', where: 'id = ?', whereArgs: [id]);
return maps.isNotEmpty ? DownloadTask.fromMap(maps.first) : null;
}
Future<List<DownloadTask>> getAll() async {
final db = await database;
final maps = await db.query('downloads');
return maps.map(DownloadTask.fromMap).toList();
}
Future<void> delete(int id) async {
final db = await database;
await db.delete('downloads', where: 'id = ?', whereArgs: [id]);
}
}

View File

@@ -5,23 +5,25 @@ import "package:dio/dio.dart";
import "package:logging/logging.dart";
import "package:photos/core/configuration.dart";
import 'package:photos/module/download/db.dart';
import "package:photos/module/download/file_url.dart";
import "package:photos/module/download/task.dart";
class DownloadManager {
final _logger = Logger('DownloadManager');
static const int downloadChunkSize = 40 * 1024 * 1024;
final _db = DatabaseHelper();
final Dio _dio;
final _logger = Logger('DownloadManager');
DownloadManager(this._dio);
// In-memory storage for download tasks
final Map<int, DownloadTask> _tasks = {};
// Active downloads with their completers and streams
final Map<int, Completer<DownloadResult>> _completers = {};
final Map<int, StreamController<DownloadTask>> _streams = {};
final Map<int, CancelToken> _cancelTokens = {};
DownloadManager(this._dio);
/// Subscribe to download progress updates for a specific file ID
Stream<DownloadTask> watchDownload(int fileId) {
_streams[fileId] ??= StreamController<DownloadTask>.broadcast();
@@ -43,13 +45,17 @@ class DownloadManager {
_completers[fileId] = completer;
// Get or create task
final task = await _db.get(fileId) ??
final existingTask = _tasks[fileId];
final task = existingTask ??
DownloadTask(
id: fileId,
filename: filename,
totalBytes: totalBytes,
);
// Store task in memory
_tasks[fileId] = task;
// Don't restart if already completed
if (task.isCompleted) {
// ensure that the file exists
@@ -64,7 +70,7 @@ class DownloadManager {
error: 'File not found',
filePath: null,
);
await _updateTask(updatedTask);
_updateTask(updatedTask);
} else {
_logger.info(
'Download already completed for ${task.filename} (${task.bytesDownloaded}/${task.totalBytes} bytes)',
@@ -85,9 +91,9 @@ class DownloadManager {
token.cancel('paused');
}
final task = await _db.get(fileId);
final task = _tasks[fileId];
if (task != null && task.isActive) {
await _updateTask(task.copyWith(status: DownloadStatus.paused));
_updateTask(task.copyWith(status: DownloadStatus.paused));
}
}
@@ -95,21 +101,21 @@ class DownloadManager {
Future<void> cancel(int fileId) async {
await pause(fileId);
final task = await _db.get(fileId);
final task = _tasks[fileId];
if (task != null) {
await _deleteFiles(task);
await _updateTask(task.copyWith(status: DownloadStatus.cancelled));
await _db.delete(fileId);
_updateTask(task.copyWith(status: DownloadStatus.cancelled));
_tasks.remove(fileId);
}
_cleanup(fileId);
}
/// Get current download status
Future<DownloadTask?> getDownload(int fileId) => _db.get(fileId);
Future<DownloadTask?> getDownload(int fileId) async => _tasks[fileId];
/// Get all downloads
Future<List<DownloadTask>> getAllDownloads() => _db.getAll();
Future<List<DownloadTask>> getAllDownloads() async => _tasks.values.toList();
Future<void> _startDownload(
DownloadTask task,
@@ -117,7 +123,7 @@ class DownloadManager {
) async {
try {
task = task.copyWith(status: DownloadStatus.downloading);
await _updateTask(task);
_updateTask(task);
final cancelToken = CancelToken();
_cancelTokens[task.id] = cancelToken;
@@ -137,7 +143,7 @@ class DownloadManager {
totalChunks,
),
);
await _updateTask(task);
_updateTask(task);
// Download missing chunks
// log existing progress and download bytes
@@ -159,7 +165,7 @@ class DownloadManager {
filePath: finalPath,
bytesDownloaded: task.totalBytes,
);
await _updateTask(task);
_updateTask(task);
completer.complete(DownloadResult(task, true));
}
} catch (e) {
@@ -169,7 +175,7 @@ class DownloadManager {
}
task = task.copyWith(status: DownloadStatus.error, error: e.toString());
await _updateTask(task);
_updateTask(task);
completer.complete(DownloadResult(task, false));
} finally {
_cleanup(task.id);
@@ -177,7 +183,7 @@ class DownloadManager {
}
String _getChunkPath(String basePath, int part) {
return '$basePath.${part}_part}';
return '$basePath.${part}_part';
}
Future<List<bool>> _validateExistingChunks(
@@ -197,7 +203,7 @@ class DownloadManager {
final actualSize = await chunkFile.length();
if (actualSize == expectedSize) {
_logger.info('exiging chunk ${i + 1} is valid');
_logger.info('existing chunk ${i + 1} is valid');
existingChunks[i] = true;
} else {
_logger.warning(
@@ -262,13 +268,12 @@ class DownloadManager {
// Update progress after chunk completion
final chunkFileSize = await File(chunkPath).length();
task = task.copyWith(bytesDownloaded: task.bytesDownloaded + chunkFileSize);
await _updateTask(task);
_updateTask(task);
}
Future<String> _combineChunks(String basePath, int totalChunks) async {
final finalFile = File(basePath);
final sink = finalFile.openWrite();
try {
for (int i = 1; i <= totalChunks; i++) {
final chunkFile = File(_getChunkPath(basePath, i));
@@ -279,14 +284,13 @@ class DownloadManager {
} finally {
await sink.close();
}
return finalFile.path;
}
Future<void> _deleteFiles(DownloadTask task) async {
try {
final directory = Configuration.instance.getTempDirectory();
final basePath = '$directory${task.id}_${task.filename}';
final basePath = '$directory${task.id}.encrypted';
// Delete final file
final finalFile = File(basePath);
if (await finalFile.exists()) await finalFile.delete();
@@ -302,8 +306,8 @@ class DownloadManager {
}
}
Future<void> _updateTask(DownloadTask task) async {
await _db.save(task);
void _updateTask(DownloadTask task) {
_tasks[task.id] = task;
_notifyProgress(task);
}
@@ -342,5 +346,7 @@ class DownloadManager {
await stream.close();
}
_streams.clear();
_tasks.clear();
}
}