mirror of
https://codeberg.org/shroff/phylum.git
synced 2026-02-20 20:48:59 -06:00
Download Files
This commit is contained in:
@@ -3,6 +3,7 @@ import 'package:flutter_state_notifier/flutter_state_notifier.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:offtheline/offtheline.dart';
|
||||
import 'package:phylum/app_shortcuts.dart';
|
||||
import 'package:phylum/integrations/download_manager.dart';
|
||||
import 'package:phylum/libphylum/phylum_account.dart';
|
||||
import 'package:phylum/ui/app/app_layout.dart';
|
||||
import 'package:phylum/ui/app/nav_forward.dart';
|
||||
@@ -28,6 +29,7 @@ class _PhylumAppState extends State<PhylumApp> {
|
||||
Provider.value(value: widget.account),
|
||||
StateNotifierProvider<ApiActionQueue, ApiActionQueueState>.value(key: ValueKey(widget.account.id), value: widget.account.actionQueue),
|
||||
StateNotifierProvider<NavForwardManager, NavForwardState>.value(key: ValueKey(widget.account.id), value: historyManager),
|
||||
StateNotifierProvider<DownloadManager, DownloadManagerState>(create: (context) => DownloadManager(widget.account))
|
||||
],
|
||||
child: MaterialApp.router(
|
||||
key: ValueKey(widget.account),
|
||||
|
||||
38
client/lib/integrations/directories.dart
Normal file
38
client/lib/integrations/directories.dart
Normal file
@@ -0,0 +1,38 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
class PhylumDirectories {
|
||||
late final Directory? tmp;
|
||||
late final Directory? downloads;
|
||||
|
||||
static final PhylumDirectories instance = PhylumDirectories._();
|
||||
|
||||
PhylumDirectories._();
|
||||
|
||||
Future<void> initialize() async {
|
||||
if (kIsWeb) return;
|
||||
|
||||
final tmpDirectory = await getTemporaryDirectory();
|
||||
tmpDirectory.create(recursive: true);
|
||||
if (!tmpDirectory.existsSync()) {
|
||||
throw 'Unable to create temporary directory';
|
||||
}
|
||||
tmp = tmpDirectory;
|
||||
|
||||
// final downloadsDirectory = switch() {
|
||||
|
||||
// }
|
||||
final downloadsDirectory = await (Platform.isLinux || Platform.isWindows || Platform.isMacOS || Platform.isFuchsia
|
||||
? getDownloadsDirectory()
|
||||
: Platform.isAndroid
|
||||
? getExternalStorageDirectory()
|
||||
: getApplicationDocumentsDirectory());
|
||||
downloadsDirectory!.createSync(recursive: true);
|
||||
if (!downloadsDirectory.existsSync()) {
|
||||
throw 'Unable to create temporary directory';
|
||||
}
|
||||
downloads = downloadsDirectory;
|
||||
}
|
||||
}
|
||||
130
client/lib/integrations/download_manager.dart
Normal file
130
client/lib/integrations/download_manager.dart
Normal file
@@ -0,0 +1,130 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:phylum/integrations/directories.dart';
|
||||
import 'package:phylum/libphylum/db/db.dart';
|
||||
import 'package:phylum/libphylum/phylum_account.dart';
|
||||
import 'package:phylum/libphylum/requests/resource_contents_request.dart';
|
||||
import 'package:state_notifier/state_notifier.dart';
|
||||
|
||||
class DownloadManagerState {
|
||||
final List<DownloadTask> tasks;
|
||||
const DownloadManagerState([this.tasks = const []]);
|
||||
}
|
||||
|
||||
class DownloadManager extends StateNotifier<DownloadManagerState> {
|
||||
final PhylumAccount account;
|
||||
|
||||
DownloadManager(this.account) : super(const DownloadManagerState());
|
||||
|
||||
void downloadResource(Resource r) async {
|
||||
final output = _createDownloadFile(PhylumDirectories.instance.downloads!, r.name);
|
||||
if (output == null) {
|
||||
return;
|
||||
}
|
||||
final task = DownloadTask(resourceId: r.id, resourceName: r.name, savePath: output.path, contentLength: r.size);
|
||||
state = DownloadManagerState([...state.tasks, task]);
|
||||
IOSink? sink;
|
||||
sink = output.openWrite();
|
||||
final response = await account.api.sendRequestRaw(ResourceContentsRequest(r.id));
|
||||
if (response == null) return;
|
||||
final length = response.contentLength ?? r.size;
|
||||
int received = 0;
|
||||
response.stream
|
||||
.map((s) {
|
||||
received += s.length;
|
||||
task._status.value = DownloadStatusRunning(received, length);
|
||||
return s;
|
||||
})
|
||||
.pipe(sink)
|
||||
.whenComplete(() => task._status.value = const DownloadStatusFinished())
|
||||
.onError((err, _) {
|
||||
task._status.value = DownloadStatusError(err.toString());
|
||||
return null;
|
||||
});
|
||||
|
||||
l() {
|
||||
final status = task._status.value;
|
||||
debugPrint(status.toString());
|
||||
if (status is DownloadStatusError || status is DownloadStatusFinished) {
|
||||
task._status.removeListener(l);
|
||||
}
|
||||
}
|
||||
|
||||
task._status.addListener(l);
|
||||
}
|
||||
|
||||
File? _createDownloadFile(Directory dir, String name) {
|
||||
final file = File(p.join(dir.path, name));
|
||||
if (!file.existsSync()) {
|
||||
file.createSync();
|
||||
return file;
|
||||
}
|
||||
|
||||
final basename = p.basename(name);
|
||||
final ext = p.extension(name);
|
||||
for (int i = 1; i < 1000; i++) {
|
||||
final file = File(p.join(dir.path, '$basename ($i).$ext'));
|
||||
if (!file.existsSync()) {
|
||||
file.createSync();
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class DownloadTask {
|
||||
final String resourceId;
|
||||
final String resourceName;
|
||||
final String savePath;
|
||||
final int contentLength;
|
||||
final ValueNotifier<DownloadStatus> _status = ValueNotifier(const DownloadStatusEnqueued());
|
||||
ValueListenable<DownloadStatus> get status => _status;
|
||||
|
||||
DownloadTask({
|
||||
required this.resourceId,
|
||||
required this.resourceName,
|
||||
required this.savePath,
|
||||
required this.contentLength,
|
||||
});
|
||||
}
|
||||
|
||||
sealed class DownloadStatus {
|
||||
const DownloadStatus();
|
||||
}
|
||||
|
||||
class DownloadStatusEnqueued extends DownloadStatus {
|
||||
const DownloadStatusEnqueued();
|
||||
|
||||
@override
|
||||
String toString() => "Download Enqueued";
|
||||
}
|
||||
|
||||
class DownloadStatusRunning extends DownloadStatus {
|
||||
final int received;
|
||||
final int total;
|
||||
|
||||
const DownloadStatusRunning(this.received, this.total);
|
||||
|
||||
@override
|
||||
String toString() => "Download Running: $received / $total (${received / total * 100} %)";
|
||||
}
|
||||
|
||||
class DownloadStatusFinished extends DownloadStatus {
|
||||
const DownloadStatusFinished();
|
||||
|
||||
@override
|
||||
String toString() => "Download Finished";
|
||||
}
|
||||
|
||||
class DownloadStatusError extends DownloadStatus {
|
||||
final String error;
|
||||
|
||||
const DownloadStatusError(this.error);
|
||||
|
||||
@override
|
||||
String toString() => "Download Error: $error";
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import 'package:offtheline/offtheline.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:phylum/app.dart';
|
||||
import 'package:phylum/integrations/directories.dart';
|
||||
import 'package:phylum/libphylum/actions/deserializers.dart';
|
||||
import 'package:phylum/libphylum/db/db.dart';
|
||||
import 'package:phylum/libphylum/phylum_account.dart';
|
||||
@@ -21,6 +22,7 @@ const storageDir = String.fromEnvironment("STORAGE_DIR");
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
PhylumDirectories.instance.initialize();
|
||||
OTL.logger = Logger(level: Level.info);
|
||||
GoRouter.optionURLReflectsImperativeAPIs = true;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:phylum/integrations/download_manager.dart';
|
||||
import 'package:phylum/libphylum/db/db.dart';
|
||||
import 'package:phylum/libphylum/phylum_account.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@@ -33,7 +34,7 @@ class _ResourcePreviewState extends State<ResourcePreview> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final resource = this.resource;
|
||||
final r = resource;
|
||||
return Theme(
|
||||
data: ThemeData.dark(),
|
||||
child: Scaffold(
|
||||
@@ -42,21 +43,27 @@ class _ResourcePreviewState extends State<ResourcePreview> {
|
||||
leading: CloseButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
actions: [IconButton(onPressed: () {}, icon: const Icon(Icons.download))],
|
||||
title: (resource != null) ? Text(resource.name) : null,
|
||||
actions: [IconButton(onPressed: (r == null || r.dir) ? null : () => downloadResource(r), icon: const Icon(Icons.download))],
|
||||
title: (r != null) ? Text(r.name) : null,
|
||||
backgroundColor: Colors.transparent,
|
||||
),
|
||||
body: Center(
|
||||
child: loading
|
||||
? const CircularProgressIndicator()
|
||||
: resource == null
|
||||
: r == null
|
||||
? const Text('Error loading details', style: TextStyle(fontSize: 18))
|
||||
: buildPreview(resource)),
|
||||
: buildPreview(r)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildPreview(Resource r) {
|
||||
if (r.dir) {
|
||||
return const Text(
|
||||
'Cannot preview directory',
|
||||
style: TextStyle(fontSize: 18),
|
||||
);
|
||||
}
|
||||
if (r.size > maxPreviewSize) {
|
||||
return const Text(
|
||||
'File too large to preview',
|
||||
@@ -79,4 +86,8 @@ class _ResourcePreviewState extends State<ResourcePreview> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void downloadResource(Resource r) async {
|
||||
context.read<DownloadManager>().downloadResource(r);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -576,12 +576,18 @@ packages:
|
||||
offtheline:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: "7e4457c8e7f86662cd6d8424383423ceec7c06bb"
|
||||
resolved-ref: "7e4457c8e7f86662cd6d8424383423ceec7c06bb"
|
||||
url: "https://codeberg.org/shroff/offtheline.git"
|
||||
source: git
|
||||
path: "../../offtheline"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.13.0"
|
||||
open_file:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: open_file
|
||||
sha256: a5a32d44acb7c899987d0999e1e3cbb0a0f1adebbf41ac813ec6d2d8faa0af20
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.2"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -19,9 +19,8 @@ dependencies:
|
||||
logger:
|
||||
mime:
|
||||
offtheline:
|
||||
git:
|
||||
url: https://codeberg.org/shroff/offtheline.git
|
||||
ref: 7e4457c8e7f86662cd6d8424383423ceec7c06bb
|
||||
path: ../../offtheline
|
||||
open_file:
|
||||
path:
|
||||
path_provider:
|
||||
provider:
|
||||
|
||||
Reference in New Issue
Block a user