2020-06-12 19:56:13 +02:00
|
|
|
import 'dart:async';
|
2020-03-31 18:36:20 +02:00
|
|
|
import 'dart:io';
|
|
|
|
import 'dart:isolate';
|
|
|
|
import 'dart:ui';
|
|
|
|
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:flutter/rendering.dart';
|
|
|
|
import 'package:flutter_downloader/flutter_downloader.dart';
|
|
|
|
import 'package:path_provider/path_provider.dart';
|
|
|
|
import 'package:path/path.dart' as path;
|
|
|
|
|
2020-06-12 19:56:13 +02:00
|
|
|
import '../local_storage/key_value_storage.dart';
|
|
|
|
import '../local_storage/sqflite_localpodcast.dart';
|
2020-05-06 18:50:32 +02:00
|
|
|
import '../type/episodebrief.dart';
|
2020-03-31 18:36:20 +02:00
|
|
|
|
|
|
|
class EpisodeTask {
|
|
|
|
final String taskId;
|
|
|
|
int progress;
|
|
|
|
DownloadTaskStatus status;
|
|
|
|
final EpisodeBrief episode;
|
|
|
|
EpisodeTask(
|
|
|
|
this.episode,
|
|
|
|
this.taskId, {
|
|
|
|
this.progress = 0,
|
|
|
|
this.status = DownloadTaskStatus.undefined,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void downloadCallback(String id, DownloadTaskStatus status, int progress) {
|
|
|
|
print('Homepage callback task in $id status ($status) $progress');
|
|
|
|
final SendPort send =
|
|
|
|
IsolateNameServer.lookupPortByName('downloader_send_port');
|
|
|
|
send.send([id, status, progress]);
|
|
|
|
}
|
|
|
|
|
2020-06-12 19:56:13 +02:00
|
|
|
void autoDownloadCallback(String id, DownloadTaskStatus status, int progress) {
|
|
|
|
print('Autodownload callback task in $id status ($status) $progress');
|
|
|
|
final SendPort send =
|
|
|
|
IsolateNameServer.lookupPortByName('auto_downloader_send_port');
|
|
|
|
send.send([id, status, progress]);
|
|
|
|
}
|
|
|
|
|
|
|
|
//For background auto downlaod
|
|
|
|
class AutoDownloader {
|
|
|
|
DBHelper dbHelper = DBHelper();
|
|
|
|
List<EpisodeTask> _episodeTasks = [];
|
|
|
|
Completer _completer = Completer();
|
|
|
|
AutoDownloader() {
|
|
|
|
FlutterDownloader.registerCallback(autoDownloadCallback);
|
|
|
|
}
|
|
|
|
|
|
|
|
bindBackgroundIsolate() {
|
|
|
|
ReceivePort _port = ReceivePort();
|
|
|
|
bool isSuccess = IsolateNameServer.registerPortWithName(
|
|
|
|
_port.sendPort, 'auto_downloader_send_port');
|
|
|
|
if (!isSuccess) {
|
2020-06-16 06:40:51 +02:00
|
|
|
IsolateNameServer.removePortNameMapping('auto_downloader_send_port');
|
|
|
|
bindBackgroundIsolate();
|
2020-06-12 19:56:13 +02:00
|
|
|
return;
|
|
|
|
}
|
2020-06-16 06:40:51 +02:00
|
|
|
print('start listen');
|
2020-06-12 19:56:13 +02:00
|
|
|
_port.listen((dynamic data) {
|
|
|
|
String id = data[0];
|
|
|
|
DownloadTaskStatus status = data[1];
|
|
|
|
int progress = data[2];
|
|
|
|
_episodeTasks.forEach((episodeTask) {
|
|
|
|
if (episodeTask.taskId == id) {
|
|
|
|
episodeTask.status = status;
|
|
|
|
episodeTask.progress = progress;
|
|
|
|
if (status == DownloadTaskStatus.complete) {
|
|
|
|
_saveMediaId(episodeTask);
|
|
|
|
} else if (status == DownloadTaskStatus.failed) {
|
|
|
|
_episodeTasks.removeWhere((element) =>
|
|
|
|
element.episode.enclosureUrl ==
|
|
|
|
episodeTask.episode.enclosureUrl);
|
|
|
|
if (_episodeTasks.length == 0) _unbindBackgroundIsolate();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void _unbindBackgroundIsolate() {
|
|
|
|
IsolateNameServer.removePortNameMapping('auto_downloader_send_port');
|
|
|
|
_completer?.complete();
|
|
|
|
}
|
|
|
|
|
|
|
|
Future _saveMediaId(EpisodeTask episodeTask) async {
|
|
|
|
final completeTask = await FlutterDownloader.loadTasksWithRawQuery(
|
|
|
|
query: "SELECT * FROM task WHERE task_id = '${episodeTask.taskId}'");
|
|
|
|
String filePath = 'file://' +
|
|
|
|
path.join(completeTask.first.savedDir,
|
|
|
|
Uri.encodeComponent(completeTask.first.filename));
|
2020-06-13 15:23:08 +02:00
|
|
|
FileStat fileStat = await File(
|
|
|
|
path.join(completeTask.first.savedDir, completeTask.first.filename))
|
|
|
|
.stat();
|
|
|
|
await dbHelper.saveMediaId(episodeTask.episode.enclosureUrl, filePath,
|
|
|
|
episodeTask.taskId, fileStat.size);
|
2020-06-12 19:56:13 +02:00
|
|
|
_episodeTasks.removeWhere((element) =>
|
|
|
|
element.episode.enclosureUrl == episodeTask.episode.enclosureUrl);
|
|
|
|
if (_episodeTasks.length == 0) _unbindBackgroundIsolate();
|
|
|
|
}
|
|
|
|
|
|
|
|
Future startTask(List<EpisodeBrief> episodes,
|
2020-06-16 06:40:51 +02:00
|
|
|
{bool showNotification = false}) async {
|
2020-06-12 19:56:13 +02:00
|
|
|
episodes.forEach((episode) async {
|
|
|
|
final dir = await getExternalStorageDirectory();
|
|
|
|
String localPath = path.join(dir.path, episode.feedTitle);
|
|
|
|
final saveDir = Directory(localPath);
|
|
|
|
bool hasExisted = await saveDir.exists();
|
|
|
|
if (!hasExisted) {
|
|
|
|
saveDir.create();
|
|
|
|
}
|
|
|
|
DateTime now = DateTime.now();
|
|
|
|
String datePlus = now.year.toString() +
|
|
|
|
now.month.toString() +
|
|
|
|
now.day.toString() +
|
|
|
|
now.second.toString();
|
|
|
|
String fileName = episode.title +
|
|
|
|
datePlus +
|
|
|
|
'.' +
|
|
|
|
episode.enclosureUrl.split('/').last.split('.').last;
|
|
|
|
String 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
|
2020-03-31 18:36:20 +02:00
|
|
|
class DownloadState extends ChangeNotifier {
|
|
|
|
DBHelper dbHelper = DBHelper();
|
|
|
|
List<EpisodeTask> _episodeTasks = [];
|
|
|
|
List<EpisodeTask> get episodeTasks => _episodeTasks;
|
|
|
|
|
2020-06-10 18:36:53 +02:00
|
|
|
DownloadState() {
|
|
|
|
_autoDelete();
|
2020-06-12 19:56:13 +02:00
|
|
|
_bindBackgroundIsolate();
|
|
|
|
FlutterDownloader.registerCallback(downloadCallback);
|
2020-06-10 18:36:53 +02:00
|
|
|
}
|
|
|
|
|
2020-03-31 18:36:20 +02:00
|
|
|
@override
|
|
|
|
void addListener(VoidCallback listener) async {
|
|
|
|
_loadTasks();
|
|
|
|
super.addListener(listener);
|
|
|
|
}
|
|
|
|
|
|
|
|
_loadTasks() async {
|
|
|
|
_episodeTasks = [];
|
|
|
|
DBHelper dbHelper = DBHelper();
|
|
|
|
var tasks = await FlutterDownloader.loadTasks();
|
|
|
|
if (tasks.length != 0)
|
|
|
|
await Future.forEach(tasks, (DownloadTask task) async {
|
|
|
|
EpisodeBrief episode = await dbHelper.getRssItemWithUrl(task.url);
|
2020-06-20 06:52:29 +02:00
|
|
|
if (episode == null)
|
|
|
|
await FlutterDownloader.remove(
|
|
|
|
taskId: task.taskId, shouldDeleteContent: true);
|
|
|
|
else
|
|
|
|
_episodeTasks.add(EpisodeTask(episode, task.taskId,
|
|
|
|
progress: task.progress, status: task.status));
|
2020-03-31 18:36:20 +02:00
|
|
|
});
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
|
|
|
|
void _bindBackgroundIsolate() {
|
|
|
|
ReceivePort _port = ReceivePort();
|
|
|
|
bool isSuccess = IsolateNameServer.registerPortWithName(
|
|
|
|
_port.sendPort, 'downloader_send_port');
|
|
|
|
if (!isSuccess) {
|
|
|
|
_unbindBackgroundIsolate();
|
|
|
|
_bindBackgroundIsolate();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
_port.listen((dynamic data) {
|
|
|
|
String id = data[0];
|
|
|
|
DownloadTaskStatus status = data[1];
|
|
|
|
int progress = data[2];
|
|
|
|
_episodeTasks.forEach((episodeTask) {
|
|
|
|
if (episodeTask.taskId == id) {
|
|
|
|
episodeTask.status = status;
|
|
|
|
episodeTask.progress = progress;
|
|
|
|
if (status == DownloadTaskStatus.complete) {
|
|
|
|
_saveMediaId(episodeTask).then((value) {
|
|
|
|
notifyListeners();
|
|
|
|
});
|
|
|
|
} else
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
Future _saveMediaId(EpisodeTask episodeTask) async {
|
|
|
|
episodeTask.status = DownloadTaskStatus.complete;
|
|
|
|
final completeTask = await FlutterDownloader.loadTasksWithRawQuery(
|
2020-04-11 19:23:12 +02:00
|
|
|
query: "SELECT * FROM task WHERE task_id = '${episodeTask.taskId}'");
|
2020-03-31 18:36:20 +02:00
|
|
|
String filePath = 'file://' +
|
2020-06-10 09:42:40 +02:00
|
|
|
path.join(completeTask.first.savedDir,
|
|
|
|
Uri.encodeComponent(completeTask.first.filename));
|
2020-06-13 15:23:08 +02:00
|
|
|
print(filePath);
|
|
|
|
FileStat fileStat = await File(
|
|
|
|
path.join(completeTask.first.savedDir, completeTask.first.filename))
|
|
|
|
.stat();
|
|
|
|
dbHelper.saveMediaId(episodeTask.episode.enclosureUrl, filePath,
|
|
|
|
episodeTask.taskId, fileStat.size);
|
2020-04-11 19:23:12 +02:00
|
|
|
EpisodeBrief episode =
|
|
|
|
await dbHelper.getRssItemWithUrl(episodeTask.episode.enclosureUrl);
|
|
|
|
_removeTask(episodeTask.episode);
|
|
|
|
_episodeTasks.add(EpisodeTask(episode, episodeTask.taskId,
|
|
|
|
progress: 100, status: DownloadTaskStatus.complete));
|
2020-03-31 18:36:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void _unbindBackgroundIsolate() {
|
|
|
|
IsolateNameServer.removePortNameMapping('downloader_send_port');
|
|
|
|
}
|
|
|
|
|
|
|
|
EpisodeTask episodeToTask(EpisodeBrief episode) {
|
|
|
|
return _episodeTasks
|
|
|
|
.firstWhere((task) => task.episode.enclosureUrl == episode.enclosureUrl,
|
|
|
|
orElse: () {
|
|
|
|
return EpisodeTask(
|
|
|
|
episode,
|
|
|
|
'',
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void dispose() {
|
|
|
|
_unbindBackgroundIsolate();
|
|
|
|
super.dispose();
|
|
|
|
}
|
|
|
|
|
2020-06-10 09:42:40 +02:00
|
|
|
Future startTask(EpisodeBrief episode, {bool showNotification = true}) async {
|
2020-06-07 20:42:27 +02:00
|
|
|
var dbHelper = DBHelper();
|
2020-06-13 15:23:08 +02:00
|
|
|
bool isDownloaded = await dbHelper.isDownloaded(episode.enclosureUrl);
|
|
|
|
if (!isDownloaded) {
|
|
|
|
final dir = await getExternalStorageDirectory();
|
|
|
|
String localPath = path.join(dir.path, episode.feedTitle);
|
|
|
|
final saveDir = Directory(localPath);
|
|
|
|
bool hasExisted = await saveDir.exists();
|
|
|
|
if (!hasExisted) {
|
|
|
|
saveDir.create();
|
|
|
|
}
|
|
|
|
DateTime now = DateTime.now();
|
|
|
|
String datePlus = now.year.toString() +
|
|
|
|
now.month.toString() +
|
|
|
|
now.day.toString() +
|
|
|
|
now.second.toString();
|
|
|
|
String fileName = episode.title +
|
|
|
|
datePlus +
|
|
|
|
'.' +
|
|
|
|
episode.enclosureUrl.split('/').last.split('.').last;
|
|
|
|
String taskId = await FlutterDownloader.enqueue(
|
|
|
|
fileName: fileName,
|
|
|
|
url: episode.enclosureUrl,
|
|
|
|
savedDir: localPath,
|
|
|
|
showNotification: showNotification,
|
|
|
|
openFileFromNotification: false,
|
|
|
|
);
|
|
|
|
_episodeTasks.add(EpisodeTask(episode, taskId));
|
|
|
|
await dbHelper.saveDownloaded(episode.enclosureUrl, taskId);
|
|
|
|
notifyListeners();
|
|
|
|
}
|
2020-03-31 18:36:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
Future pauseTask(EpisodeBrief episode) async {
|
|
|
|
EpisodeTask task = episodeToTask(episode);
|
|
|
|
await FlutterDownloader.pause(taskId: task.taskId);
|
|
|
|
}
|
|
|
|
|
|
|
|
Future resumeTask(EpisodeBrief episode) async {
|
|
|
|
EpisodeTask task = episodeToTask(episode);
|
|
|
|
String newTaskId = await FlutterDownloader.resume(taskId: task.taskId);
|
|
|
|
int index = _episodeTasks.indexOf(task);
|
|
|
|
_removeTask(episode);
|
|
|
|
FlutterDownloader.remove(taskId: task.taskId);
|
|
|
|
var dbHelper = DBHelper();
|
|
|
|
_episodeTasks.insert(index, EpisodeTask(episode, newTaskId));
|
|
|
|
await dbHelper.saveDownloaded(newTaskId, episode.enclosureUrl);
|
|
|
|
}
|
|
|
|
|
|
|
|
Future retryTask(EpisodeBrief episode) async {
|
|
|
|
EpisodeTask task = episodeToTask(episode);
|
|
|
|
String newTaskId = await FlutterDownloader.retry(taskId: task.taskId);
|
|
|
|
await FlutterDownloader.remove(taskId: task.taskId);
|
|
|
|
int index = _episodeTasks.indexOf(task);
|
|
|
|
_removeTask(episode);
|
|
|
|
var dbHelper = DBHelper();
|
|
|
|
_episodeTasks.insert(index, EpisodeTask(episode, newTaskId));
|
|
|
|
await dbHelper.saveDownloaded(newTaskId, episode.enclosureUrl);
|
|
|
|
}
|
|
|
|
|
|
|
|
Future removeTask(EpisodeBrief episode) async {
|
|
|
|
EpisodeTask task = episodeToTask(episode);
|
|
|
|
await FlutterDownloader.remove(
|
|
|
|
taskId: task.taskId, shouldDeleteContent: false);
|
|
|
|
}
|
|
|
|
|
|
|
|
Future delTask(EpisodeBrief episode) async {
|
|
|
|
EpisodeTask task = episodeToTask(episode);
|
|
|
|
await FlutterDownloader.remove(
|
|
|
|
taskId: task.taskId, shouldDeleteContent: true);
|
|
|
|
await dbHelper.delDownloaded(episode.enclosureUrl);
|
|
|
|
_episodeTasks.forEach((episodeTask) {
|
|
|
|
if (episodeTask.taskId == task.taskId)
|
|
|
|
episodeTask.status = DownloadTaskStatus.undefined;
|
|
|
|
notifyListeners();
|
|
|
|
});
|
|
|
|
_removeTask(episode);
|
|
|
|
}
|
|
|
|
|
|
|
|
_removeTask(EpisodeBrief episode) {
|
2020-06-27 03:33:01 +02:00
|
|
|
_episodeTasks.removeWhere((element) => element.episode == episode);
|
2020-03-31 18:36:20 +02:00
|
|
|
}
|
2020-06-10 18:36:53 +02:00
|
|
|
|
|
|
|
_autoDelete() async {
|
|
|
|
print('Start auto delete outdated episodes');
|
|
|
|
KeyValueStorage autoDeleteStorage = KeyValueStorage(autoDeleteKey);
|
|
|
|
int autoDelete = await autoDeleteStorage.getInt();
|
|
|
|
if (autoDelete == 0)
|
|
|
|
await autoDeleteStorage.saveInt(30);
|
|
|
|
else if (autoDelete > 0) {
|
2020-06-27 03:33:01 +02:00
|
|
|
int deadline = DateTime.now()
|
|
|
|
.subtract(Duration(days: autoDelete))
|
|
|
|
.millisecondsSinceEpoch;
|
|
|
|
List<EpisodeBrief> episodes = await dbHelper.getOutdatedEpisode(deadline);
|
2020-06-10 18:36:53 +02:00
|
|
|
if (episodes.length > 0) {
|
2020-06-27 03:33:01 +02:00
|
|
|
await Future.forEach(
|
|
|
|
episodes, (episode) async => await delTask(episode));
|
2020-06-10 18:36:53 +02:00
|
|
|
}
|
2020-06-27 03:33:01 +02:00
|
|
|
final tasks = await FlutterDownloader.loadTasksWithRawQuery(
|
|
|
|
query:
|
|
|
|
'SELECT * FROM task WHERE time_created < $deadline AND status = 3');
|
|
|
|
await Future.forEach(
|
|
|
|
tasks,
|
|
|
|
(task) async => FlutterDownloader.remove(
|
|
|
|
taskId: task.taskId, shouldDeleteContent: true));
|
2020-06-10 18:36:53 +02:00
|
|
|
}
|
|
|
|
}
|
2020-03-31 18:36:20 +02:00
|
|
|
}
|