tsacdop-podcast-app-android/lib/state/download_state.dart

388 lines
14 KiB
Dart
Raw Normal View History

2020-10-28 13:10:43 +01:00
import 'dart:async';
import 'dart:developer' as developer;
import 'dart:io';
import 'dart:isolate';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
import '../local_storage/key_value_storage.dart';
import '../local_storage/sqflite_localpodcast.dart';
import '../type/episode_task.dart';
import '../type/episodebrief.dart';
void downloadCallback(String id, DownloadTaskStatus status, int progress) {
developer.log('Homepage callback task in $id status ($status) $progress');
2022-04-30 17:16:19 +02:00
final send = IsolateNameServer.lookupPortByName('downloader_send_port')!;
2020-10-28 13:10:43 +01:00
send.send([id, status, progress]);
}
void autoDownloadCallback(String id, DownloadTaskStatus status, int progress) {
developer
.log('Autodownload callback task in $id status ($status) $progress');
2022-04-30 17:16:19 +02:00
final send = IsolateNameServer.lookupPortByName('auto_downloader_send_port')!;
2020-10-28 13:10:43 +01:00
send.send([id, status, progress]);
}
//For background auto downlaod
class AutoDownloader {
2021-01-02 18:33:45 +01:00
final DBHelper _dbHelper = DBHelper();
2020-10-28 13:10:43 +01:00
final List<EpisodeTask> _episodeTasks = [];
final Completer _completer = Completer();
AutoDownloader() {
FlutterDownloader.registerCallback(autoDownloadCallback);
}
bindBackgroundIsolate() {
var _port = ReceivePort();
var isSuccess = IsolateNameServer.registerPortWithName(
_port.sendPort, 'auto_downloader_send_port');
if (!isSuccess) {
IsolateNameServer.removePortNameMapping('auto_downloader_send_port');
bindBackgroundIsolate();
return;
}
_port.listen((dynamic data) {
2022-04-30 17:16:19 +02:00
String? id = data[0];
DownloadTaskStatus? status = data[1];
int? progress = data[2];
2020-10-28 13:10:43 +01:00
for (var episodeTask in _episodeTasks) {
if (episodeTask.taskId == id) {
2021-01-02 18:33:45 +01:00
episodeTask.status = status;
episodeTask.progress = progress;
2020-10-28 13:10:43 +01:00
if (status == DownloadTaskStatus.complete) {
_saveMediaId(episodeTask);
} else if (status == DownloadTaskStatus.failed) {
_episodeTasks.removeWhere((element) =>
2022-04-30 17:16:19 +02:00
element.episode!.enclosureUrl ==
episodeTask.episode!.enclosureUrl);
2020-10-28 13:10:43 +01:00
if (_episodeTasks.length == 0) _unbindBackgroundIsolate();
}
}
}
});
}
void _unbindBackgroundIsolate() {
IsolateNameServer.removePortNameMapping('auto_downloader_send_port');
2022-05-15 18:18:19 +02:00
_completer.complete();
2020-10-28 13:10:43 +01:00
}
Future<Directory> _getDownloadDirectory() async {
final storage = KeyValueStorage(downloadPositionKey);
2022-05-15 18:18:19 +02:00
final index = await storage.getInt();
final externalDirs = await getExternalStorageDirectories();
return externalDirs![index];
2020-10-28 13:10:43 +01:00
}
Future _saveMediaId(EpisodeTask episodeTask) async {
2022-04-30 17:16:19 +02:00
final completeTask = await (FlutterDownloader.loadTasksWithRawQuery(
2022-05-15 18:18:19 +02:00
query: "SELECT * FROM task WHERE task_id = '${episodeTask.taskId}'")
as FutureOr<List<DownloadTask>>);
2020-10-28 13:10:43 +01:00
var filePath =
2022-04-30 17:16:19 +02:00
'file://${path.join(completeTask.first.savedDir, Uri.encodeComponent(completeTask.first.filename!))}';
2020-10-28 13:10:43 +01:00
var fileStat = await File(
path.join(completeTask.first.savedDir, completeTask.first.filename))
.stat();
2022-04-30 17:16:19 +02:00
await _dbHelper.saveMediaId(episodeTask.episode!.enclosureUrl, filePath,
2020-10-28 13:10:43 +01:00
episodeTask.taskId, fileStat.size);
_episodeTasks.removeWhere((element) =>
2022-04-30 17:16:19 +02:00
element.episode!.enclosureUrl == episodeTask.episode!.enclosureUrl);
2020-10-28 13:10:43 +01:00
if (_episodeTasks.length == 0) _unbindBackgroundIsolate();
}
Future startTask(List<EpisodeBrief> episodes,
{bool showNotification = false}) async {
for (var episode in episodes) {
final dir = await _getDownloadDirectory();
var localPath = path.join(dir.path, episode.feedTitle);
final saveDir = Directory(localPath);
var hasExisted = await saveDir.exists();
if (!hasExisted) {
saveDir.create();
}
var now = DateTime.now();
var datePlus = now.year.toString() +
now.month.toString() +
now.day.toString() +
now.second.toString();
var fileName =
2022-04-30 17:16:19 +02:00
'${episode.title!.replaceAll('/', '')}$datePlus.${episode.enclosureUrl.split('/').last.split('.').last}';
2020-10-28 13:10:43 +01:00
if (fileName.length > 100) {
fileName = fileName.substring(fileName.length - 100);
}
var taskId = await FlutterDownloader.enqueue(
fileName: fileName,
url: episode.enclosureUrl,
savedDir: localPath,
showNotification: showNotification,
openFileFromNotification: false,
);
_episodeTasks.add(EpisodeTask(episode, taskId));
var dbHelper = DBHelper();
await dbHelper.saveDownloaded(episode.enclosureUrl, taskId);
}
await _completer.future;
return;
}
}
//For download episode inside app
class DownloadState extends ChangeNotifier {
2021-01-02 18:33:45 +01:00
final DBHelper _dbHelper = DBHelper();
2020-10-28 13:10:43 +01:00
List<EpisodeTask> _episodeTasks = [];
List<EpisodeTask> get episodeTasks => _episodeTasks;
DownloadState() {
_autoDelete();
_bindBackgroundIsolate();
FlutterDownloader.registerCallback(downloadCallback);
}
@override
void addListener(VoidCallback listener) async {
_loadTasks();
super.addListener(listener);
}
Future<void> _loadTasks() async {
_episodeTasks = [];
var dbHelper = DBHelper();
2022-05-15 18:18:19 +02:00
var tasks = await FlutterDownloader.loadTasks();
if (tasks != null && tasks.isNotEmpty) {
2020-10-28 13:10:43 +01:00
for (var task in tasks) {
var episode = await dbHelper.getRssItemWithUrl(task.url);
if (episode == null) {
await FlutterDownloader.remove(
taskId: task.taskId, shouldDeleteContent: true);
} else {
if (task.status == DownloadTaskStatus.complete) {
var exist =
await File(path.join(task.savedDir, task.filename)).exists();
if (!exist) {
await FlutterDownloader.remove(
taskId: task.taskId, shouldDeleteContent: true);
await dbHelper.delDownloaded(episode.enclosureUrl);
} else {
if (episode.enclosureUrl == episode.mediaId) {
var filePath =
2022-04-30 17:16:19 +02:00
'file://${path.join(task.savedDir, Uri.encodeComponent(task.filename!))}';
2020-10-28 13:10:43 +01:00
var fileStat =
await File(path.join(task.savedDir, task.filename)).stat();
await dbHelper.saveMediaId(
episode.enclosureUrl, filePath, task.taskId, fileStat.size);
}
_episodeTasks.add(EpisodeTask(episode, task.taskId,
progress: task.progress, status: task.status));
}
} else {
_episodeTasks.add(EpisodeTask(episode, task.taskId,
progress: task.progress, status: task.status));
}
}
}
}
notifyListeners();
}
Future<Directory> _getDownloadDirectory() async {
final storage = KeyValueStorage(downloadPositionKey);
2022-04-30 17:16:19 +02:00
final index = await (storage.getInt() as FutureOr<int>);
2022-05-15 18:18:19 +02:00
final externalDirs =
await (getExternalStorageDirectories() as FutureOr<List<Directory>>);
2020-10-28 13:10:43 +01:00
return externalDirs[index];
}
void _bindBackgroundIsolate() {
var _port = ReceivePort();
var isSuccess = IsolateNameServer.registerPortWithName(
_port.sendPort, 'downloader_send_port');
if (!isSuccess) {
_unbindBackgroundIsolate();
_bindBackgroundIsolate();
return;
}
_port.listen((dynamic data) {
2022-04-30 17:16:19 +02:00
String? id = data[0];
DownloadTaskStatus? status = data[1];
int? progress = data[2];
2020-10-28 13:10:43 +01:00
for (var episodeTask in _episodeTasks) {
if (episodeTask.taskId == id) {
2021-01-02 18:33:45 +01:00
episodeTask.status = status;
episodeTask.progress = progress;
2020-10-28 13:10:43 +01:00
if (status == DownloadTaskStatus.complete) {
_saveMediaId(episodeTask).then((value) {
notifyListeners();
});
} else {
notifyListeners();
}
}
}
});
}
Future _saveMediaId(EpisodeTask episodeTask) async {
2021-01-02 18:33:45 +01:00
episodeTask.status = DownloadTaskStatus.complete;
2022-04-30 17:16:19 +02:00
final completeTask = await (FlutterDownloader.loadTasksWithRawQuery(
2022-05-15 18:18:19 +02:00
query: "SELECT * FROM task WHERE task_id = '${episodeTask.taskId}'")
as FutureOr<List<DownloadTask>>);
2020-10-28 13:10:43 +01:00
var filePath =
2022-04-30 17:16:19 +02:00
'file://${path.join(completeTask.first.savedDir, Uri.encodeComponent(completeTask.first.filename!))}';
2020-10-28 13:10:43 +01:00
var fileStat = await File(
path.join(completeTask.first.savedDir, completeTask.first.filename))
.stat();
2022-04-30 17:16:19 +02:00
_dbHelper.saveMediaId(episodeTask.episode!.enclosureUrl, filePath,
2020-10-28 13:10:43 +01:00
episodeTask.taskId, fileStat.size);
var episode =
2022-04-30 17:16:19 +02:00
await _dbHelper.getRssItemWithUrl(episodeTask.episode!.enclosureUrl);
2020-10-28 13:10:43 +01:00
_removeTask(episodeTask.episode);
_episodeTasks.add(EpisodeTask(episode, episodeTask.taskId,
progress: 100, status: DownloadTaskStatus.complete));
}
void _unbindBackgroundIsolate() {
IsolateNameServer.removePortNameMapping('downloader_send_port');
}
2022-04-30 17:16:19 +02:00
EpisodeTask episodeToTask(EpisodeBrief? episode) {
2022-05-15 18:18:19 +02:00
return _episodeTasks.firstWhere(
(task) => task.episode!.enclosureUrl == episode!.enclosureUrl,
orElse: () {
2020-10-28 13:10:43 +01:00
return EpisodeTask(
episode,
'',
);
});
}
@override
void dispose() {
_unbindBackgroundIsolate();
super.dispose();
}
Future startTask(EpisodeBrief episode, {bool showNotification = true}) async {
2021-01-02 18:33:45 +01:00
var isDownloaded = await _dbHelper.isDownloaded(episode.enclosureUrl);
2020-10-28 13:10:43 +01:00
if (!isDownloaded) {
final dir = await _getDownloadDirectory();
var localPath =
path.join(dir.path, episode.feedTitle?.replaceAll('/', ''));
final saveDir = Directory(localPath);
var hasExisted = await saveDir.exists();
if (!hasExisted) {
await saveDir.create();
}
var now = DateTime.now();
var datePlus = now.year.toString() +
now.month.toString() +
now.day.toString() +
now.second.toString();
var fileName =
2022-04-30 17:16:19 +02:00
'${episode.title!.replaceAll('/', '')}$datePlus.${episode.enclosureUrl.split('/').last.split('.').last}';
2020-10-28 13:10:43 +01:00
if (fileName.length > 100) {
fileName = fileName.substring(fileName.length - 100);
}
var taskId = await FlutterDownloader.enqueue(
fileName: fileName,
url: episode.enclosureUrl,
savedDir: localPath,
showNotification: showNotification,
openFileFromNotification: false,
);
_episodeTasks.add(EpisodeTask(episode, taskId));
2021-01-02 18:33:45 +01:00
await _dbHelper.saveDownloaded(episode.enclosureUrl, taskId);
2020-10-28 13:10:43 +01:00
notifyListeners();
}
}
2022-04-30 17:16:19 +02:00
Future pauseTask(EpisodeBrief? episode) async {
2020-10-28 13:10:43 +01:00
var task = episodeToTask(episode);
2022-04-30 17:16:19 +02:00
if (task.progress! > 0) {
await FlutterDownloader.pause(taskId: task.taskId!);
2020-10-28 13:10:43 +01:00
}
notifyListeners();
}
Future resumeTask(EpisodeBrief episode) async {
var task = episodeToTask(episode);
2022-04-30 17:16:19 +02:00
var newTaskId = await FlutterDownloader.resume(taskId: task.taskId!);
await FlutterDownloader.remove(taskId: task.taskId!);
2020-10-28 13:10:43 +01:00
var index = _episodeTasks.indexOf(task);
_episodeTasks[index] = task.copyWith(taskId: newTaskId);
notifyListeners();
2021-01-02 18:33:45 +01:00
await _dbHelper.saveDownloaded(episode.enclosureUrl, newTaskId);
2020-10-28 13:10:43 +01:00
}
Future retryTask(EpisodeBrief episode) async {
var task = episodeToTask(episode);
2022-04-30 17:16:19 +02:00
var newTaskId = await FlutterDownloader.retry(taskId: task.taskId!);
await FlutterDownloader.remove(taskId: task.taskId!);
2020-10-28 13:10:43 +01:00
var index = _episodeTasks.indexOf(task);
_episodeTasks[index] = task.copyWith(taskId: newTaskId);
notifyListeners();
2021-01-02 18:33:45 +01:00
await _dbHelper.saveDownloaded(episode.enclosureUrl, newTaskId);
2020-10-28 13:10:43 +01:00
}
Future removeTask(EpisodeBrief episode) async {
var task = episodeToTask(episode);
await FlutterDownloader.remove(
2022-04-30 17:16:19 +02:00
taskId: task.taskId!, shouldDeleteContent: false);
2020-10-28 13:10:43 +01:00
}
Future<void> delTask(EpisodeBrief episode) async {
var task = episodeToTask(episode);
await FlutterDownloader.remove(
2022-04-30 17:16:19 +02:00
taskId: task.taskId!, shouldDeleteContent: true);
2021-01-02 18:33:45 +01:00
await _dbHelper.delDownloaded(episode.enclosureUrl);
2020-10-28 13:10:43 +01:00
for (var episodeTask in _episodeTasks) {
if (episodeTask.taskId == task.taskId) {
2021-01-02 18:33:45 +01:00
episodeTask.status = DownloadTaskStatus.undefined;
2020-10-28 13:10:43 +01:00
}
notifyListeners();
}
_removeTask(episode);
}
2022-04-30 17:16:19 +02:00
void _removeTask(EpisodeBrief? episode) {
2020-10-28 13:10:43 +01:00
_episodeTasks.removeWhere((element) => element.episode == episode);
notifyListeners();
}
Future<void> _autoDelete() async {
developer.log('Start auto delete outdated episodes');
final autoDeleteStorage = KeyValueStorage(autoDeleteKey);
final deletePlayedStorage = KeyValueStorage(deleteAfterPlayedKey);
final autoDelete = await autoDeleteStorage.getInt();
final deletePlayed = await deletePlayedStorage.getBool(defaultValue: false);
if (autoDelete == 0) {
await autoDeleteStorage.saveInt(30);
2022-05-15 18:18:19 +02:00
} else if (autoDelete > 0) {
2020-10-28 13:10:43 +01:00
var deadline = DateTime.now()
.subtract(Duration(days: autoDelete))
.millisecondsSinceEpoch;
2021-01-02 18:33:45 +01:00
var episodes = await _dbHelper.getOutdatedEpisode(deadline,
2020-10-28 13:10:43 +01:00
deletePlayed: deletePlayed);
if (episodes.isNotEmpty) {
for (var episode in episodes) {
await delTask(episode);
}
}
2022-05-15 18:18:19 +02:00
final tasks = await FlutterDownloader.loadTasksWithRawQuery(
2020-10-28 13:10:43 +01:00
query:
2022-05-15 18:18:19 +02:00
'SELECT * FROM task WHERE time_created < $deadline AND status = 3');
for (var task in tasks ?? []) {
2020-10-28 13:10:43 +01:00
FlutterDownloader.remove(
taskId: task.taskId, shouldDeleteContent: true);
}
}
}
}