nc-photos/app/lib/mobile/download.dart

160 lines
4 KiB
Dart
Raw Normal View History

import 'dart:async';
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:logging/logging.dart';
import 'package:nc_photos/exception.dart';
2021-10-02 11:12:54 +02:00
import 'package:nc_photos/platform/download.dart' as itf;
2022-05-03 12:44:48 +02:00
import 'package:nc_photos_plugin/nc_photos_plugin.dart';
2022-12-16 16:01:04 +01:00
import 'package:np_codegen/np_codegen.dart';
2023-08-27 12:58:05 +02:00
import 'package:np_platform_util/np_platform_util.dart';
import 'package:path_provider/path_provider.dart';
import 'package:uuid/uuid.dart';
2022-12-16 16:01:04 +01:00
part 'download.g.dart';
2021-10-02 11:12:54 +02:00
class DownloadBuilder extends itf.DownloadBuilder {
@override
2021-10-02 11:12:54 +02:00
build({
required String url,
Map<String, String>? headers,
String? mimeType,
required String filename,
2021-09-29 16:34:56 +02:00
String? parentDir,
bool? shouldNotify,
}) {
2023-08-27 12:58:05 +02:00
if (getRawPlatform() == NpPlatform.android) {
2021-10-02 11:12:54 +02:00
return _AndroidDownload(
url: url,
headers: headers,
mimeType: mimeType,
filename: filename,
2021-09-29 16:34:56 +02:00
parentDir: parentDir,
shouldNotify: shouldNotify,
);
} else {
throw UnimplementedError();
}
}
2021-10-02 11:12:54 +02:00
}
2022-12-16 16:01:04 +01:00
@npLog
2021-10-02 11:12:54 +02:00
class _AndroidDownload extends itf.Download {
_AndroidDownload({
required this.url,
this.headers,
this.mimeType,
required this.filename,
this.parentDir,
this.shouldNotify,
});
@override
call() async {
if (_isInitialDownload) {
await _cleanUp();
_isInitialDownload = false;
2021-09-29 16:34:56 +02:00
}
final file = await _createTempFile();
try {
// download file to a temp dir
final fileWrite = file.openWrite();
try {
final uri = Uri.parse(url);
final req = http.Request("GET", uri)..headers.addAll(headers ?? {});
final response = await http.Client().send(req);
bool isEnd = false;
Object? error;
final subscription = response.stream.listen(
fileWrite.add,
onDone: () {
isEnd = true;
},
onError: (e, stackTrace) {
_log.severe("Failed while request", e, stackTrace);
isEnd = true;
error = e;
},
cancelOnError: true,
);
// wait until download finished
while (!isEnd) {
if (shouldInterrupt) {
await subscription.cancel();
break;
}
await Future.delayed(const Duration(seconds: 1));
}
if (error != null) {
throw error!;
}
} finally {
2022-07-28 18:59:26 +02:00
await fileWrite.flush();
await fileWrite.close();
}
if (shouldInterrupt) {
throw JobCanceledException();
}
// copy the file to the actual dir
2022-05-03 12:44:48 +02:00
return await MediaStore.copyFileToDownload(
file.path,
filename: filename,
subDir: parentDir,
);
} finally {
2022-07-28 18:59:26 +02:00
await file.delete();
}
}
@override
cancel() {
shouldInterrupt = true;
return true;
}
Future<Directory> _openDownloadDir() async {
final tempDir = await getTemporaryDirectory();
final downloadDir = Directory("${tempDir.path}/downloads");
if (!await downloadDir.exists()) {
return downloadDir.create();
} else {
return downloadDir;
}
}
Future<File> _createTempFile() async {
final downloadDir = await _openDownloadDir();
while (true) {
final fileName = const Uuid().v4();
final file = File("${downloadDir.path}/$fileName");
if (await file.exists()) {
continue;
}
return file;
}
}
/// Clean up remaining cache files from previous runs
///
/// Normally the files will be deleted automatically
Future<void> _cleanUp() async {
final downloadDir = await _openDownloadDir();
await for (final f in downloadDir.list(followLinks: false)) {
_log.warning("[_cleanUp] Deleting file: ${f.path}");
await f.delete();
2021-10-02 11:12:54 +02:00
}
}
final String url;
final Map<String, String>? headers;
final String? mimeType;
final String filename;
final String? parentDir;
final bool? shouldNotify;
bool shouldInterrupt = false;
static bool _isInitialDownload = true;
}