diff --git a/client/lib/util/pick_directory_web.dart b/client/lib/util/pick_directory_web.dart index 6f0e7a15..2798aac6 100644 --- a/client/lib/util/pick_directory_web.dart +++ b/client/lib/util/pick_directory_web.dart @@ -1,38 +1,76 @@ +import 'dart:async'; import 'dart:js_interop'; +import 'dart:js_interop_unsafe'; +import 'dart:typed_data'; import 'package:flutter/material.dart'; +import 'package:http/http.dart'; +import 'package:phylum/libphylum/repositories/resource_repository.dart'; +import 'package:phylum/util/upload_utils.dart'; import 'package:web/web.dart' as web; -bool get folderUploadSupported => true; +bool _folderUploadSupported = web.window.has('showDirectoryPicker'); +bool get folderUploadSupported => _folderUploadSupported; -@JS() -external JSPromise showDirectoryPicker(); - -Future pickAndUploadDirectory(BuildContext context, String parent) async { - final dir = await showDirectoryPicker().toDart; - processDirs(dir); +Future pickAndUploadDirectory(BuildContext context, String folderId) async { + final dir = + await (web.window.callMethod('showDirectoryPicker'.toJS) as JSPromise).toDart; + final tree = await enumerateDir(dir, ''); + uploadDirTree(context, folderId, tree); } -Future processDirs(web.FileSystemDirectoryHandle dir) async { - print('Processing ${dir.name}'); - final it = dir.entries().asStream(); - it.forEach((e) async { - final l = e.toDart; +Future enumerateDir(web.FileSystemDirectoryHandle dir, String prefix) async { + if (prefix.isNotEmpty) prefix += '/'; + final files = []; + final dirs = ['$prefix${dir.name}']; + int totalSize = 0; - if (l[1].isA()) { - await processDirs(l[1] as web.FileSystemDirectoryHandle); - } else if (l[1].isA()) { - final file = await (l[1] as web.FileSystemFileHandle).getFile().toDart; - final reader = web.FileReader(); - reader.onload = () { - // final bytes = (reader.result as JSArrayBuffer).toDart; - print('contents of ${file.name}: ${reader.result}'); - }.toJS; + final entries = dir.entries(); + while (true) { + final result = await entries.next().toDart; + if (result.done) break; + final handle = result.value.toDart; - reader.readAsText(file); - // reader.readAsArrayBuffer(file); + if (handle[1].isA()) { + final subtree = await enumerateDir(handle[1] as web.FileSystemDirectoryHandle, '$prefix${dir.name}'); + dirs.addAll(subtree.dirs); + files.addAll(subtree.files); + totalSize += subtree.totalSize; + } else if (handle[1].isA()) { + final f = handle[1] as web.FileSystemFileHandle; + final file = await f.getFile().toDart; + totalSize += file.size; + files.add(UploadFile( + size: file.size, + contentType: getMimeType(file.name), + byteStream: ByteStream(byteStream(file)), + path: '$prefix${dir.name}/${file.name}')); } - }); + } + ; + return DirTree( + totalSize: totalSize, + files: files, + dirs: dirs, + ); +} + +Stream byteStream(web.File file) async* { + // Hack: wait to allocate the array buffer until after the first yield + yield Uint8List(0); + + const int size = 64 * 1024; + final stream = file.stream(); + final reader = stream.getReader(web.ReadableStreamGetReaderOptions(mode: "byob")) as web.ReadableStreamBYOBReader; + final buffer = JSArrayBuffer(size); + final view = JSUint8Array(buffer, 0, size); + + var result = await reader.read(view).toDart; + while (!result.done) { + final buf = result.value.dartify() as Uint8List; + yield buf; + result = await reader.read(buf.toJS).toDart; + } } extension on web.FileSystemDirectoryHandle { @@ -41,14 +79,6 @@ extension on web.FileSystemDirectoryHandle { extension type JsAsyncIterator._(JSObject _) implements JSObject { external JSPromise> next(); - - Stream asStream() async* { - while (true) { - final result = await next().toDart; - if (result.done) break; - yield result.value; - } - } } extension type JsAsyncIteratorState._(JSObject _) implements JSObject { diff --git a/client/lib/util/upload_utils.dart b/client/lib/util/upload_utils.dart index ef7d6d3f..f0c4b448 100644 --- a/client/lib/util/upload_utils.dart +++ b/client/lib/util/upload_utils.dart @@ -109,7 +109,6 @@ Future uploadFiles(BuildContext context, String folderId, Iterable uploadPath(BuildContext context, String folderId, Iterable paths) async { - final account = context.read(); showProgressDialog(context, message: 'Counting Files'); DirTree? tree; for (final path in paths) { @@ -121,6 +120,11 @@ Future uploadPath(BuildContext context, String folderId, Iterable Navigator.of(context).pop(); if (tree == null) return; + return uploadDirTree(context, folderId, tree); +} + +Future uploadDirTree(BuildContext context, String folderId, DirTree tree) async { + final account = context.read(); final confirm = await showAlertDialog( context, title: 'Upload Files?',