diff --git a/client/lib/main.dart b/client/lib/main.dart index a8368d5b..f3bccf01 100644 --- a/client/lib/main.dart +++ b/client/lib/main.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_state_notifier/flutter_state_notifier.dart'; import 'package:hive/hive.dart'; +import 'package:logger/logger.dart'; import 'package:offtheline/offtheline.dart'; import 'package:path/path.dart' as path; import 'package:path_provider/path_provider.dart'; @@ -21,6 +22,8 @@ void main() async { ? Directory(path.join((await getApplicationSupportDirectory()).path, storageDir.isNotEmpty ? storageDir : 'data')) : await getApplicationDocumentsDirectory(); + OTL.logger = Logger(); + await appDir.create(); Hive.init(appDir.path); Hive.registerAdapter(ApiActionTypeAdapter(actionDeserializers)); diff --git a/client/lib/ui/folder/folder_contents_view.dart b/client/lib/ui/folder/folder_contents_view.dart index 05dc1101..134e47f5 100644 --- a/client/lib/ui/folder/folder_contents_view.dart +++ b/client/lib/ui/folder/folder_contents_view.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:math'; import 'package:flutter/material.dart'; @@ -9,8 +10,10 @@ import 'package:phylum/libphylum/phylum_account.dart'; import 'package:phylum/ui/folder/folder_navigator_stack.dart'; import 'package:phylum/ui/folder/folder_selection_manager.dart'; import 'package:phylum/ui/folder/resource_details_row.dart'; +import 'package:phylum/util/upload_utils.dart'; import 'package:phylum/util/dialogs.dart'; import 'package:provider/provider.dart'; +import 'package:super_clipboard/super_clipboard.dart'; class FolderContentsView extends StatefulWidget { final List resources; @@ -111,6 +114,28 @@ class _FolderContentsViewState extends State { deleteSelected(); return null; }), + PasteFromClipboardIntent: CallbackAction(onInvoke: (i) async { + final clipboard = SystemClipboard.instance; + if (clipboard == null) { + return; + } + final reader = await clipboard.read(); + final files = reader.items.where((item) => item.canProvide(Formats.fileUri)); + final paths = []; + if (files.isNotEmpty) { + for (final item in reader.items) { + final c = Completer(); + item.getValue(Formats.fileUri, (value) => c.complete(value?.toFilePath()), onError: (value) => c.complete(null)); + final path = await c.future; + if (path != null) { + paths.add(path); + } + } + if (!context.mounted) return; + uploadFilesAndDirs(context, context.read().folderId, paths); + } + return null; + }), }, child: Focus( autofocus: true, diff --git a/client/lib/ui/folder/folder_navigator_scaffold.dart b/client/lib/ui/folder/folder_navigator_scaffold.dart index 70ef892c..ef484777 100644 --- a/client/lib/ui/folder/folder_navigator_scaffold.dart +++ b/client/lib/ui/folder/folder_navigator_scaffold.dart @@ -5,7 +5,7 @@ import 'package:phylum/ui/common/expandable_fab.dart'; import 'package:phylum/ui/folder/folder_heriarchy_view.dart'; import 'package:phylum/ui/folder/nav_list.dart'; import 'package:phylum/ui/folder/folder_view.dart'; -import 'package:phylum/ui/folder/upload_utils.dart'; +import 'package:phylum/util/upload_utils.dart'; import 'package:provider/provider.dart'; import 'folder_navigator_stack.dart'; diff --git a/client/lib/ui/folder/nav_list.dart b/client/lib/ui/folder/nav_list.dart index d539a9b2..421c44da 100644 --- a/client/lib/ui/folder/nav_list.dart +++ b/client/lib/ui/folder/nav_list.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:offtheline/offtheline.dart'; -import 'package:phylum/ui/folder/upload_utils.dart'; +import 'package:phylum/util/upload_utils.dart'; import 'package:phylum/util/dialogs.dart'; import 'package:provider/provider.dart'; diff --git a/client/lib/ui/folder/upload_utils.dart b/client/lib/util/upload_utils.dart similarity index 78% rename from client/lib/ui/folder/upload_utils.dart rename to client/lib/util/upload_utils.dart index defd38f7..be8a4044 100644 --- a/client/lib/ui/folder/upload_utils.dart +++ b/client/lib/util/upload_utils.dart @@ -22,27 +22,31 @@ void createDirectory(BuildContext context, String folderId) async { } void pickAndUploadFiles(BuildContext context, String folderId) async { - final account = context.read(); final files = await openFiles(); - for (final file in files) { - final action = await ResourceUploadAction.fromPath(parent: folderId, resourceName: file.name, path: file.path); - await account.addAction(action); - } + if (!context.mounted) return; + uploadFilesAndDirs(context, folderId, files.map((f) => f.path)); } void pickAndUploadDirectory(BuildContext context, String folderId) async { - final account = context.read(); final path = await getDirectoryPath(); - if (path == null) return; - if (!context.mounted) return; - showProgressDialog(context, message: 'Counting Files'); - final tree = await DirTree.stat(path); + if (path == null || !context.mounted) return; + uploadFilesAndDirs(context, folderId, [path]); +} - if (!context.mounted) return; +Future uploadFilesAndDirs(BuildContext context, String folderId, Iterable paths) async { + final account = context.read(); + showProgressDialog(context, message: 'Counting Files'); + DirTree? tree; + for (final path in paths) { + final stat = await DirTree.stat(path); + tree = tree?.mergeWith(stat) ?? stat; + } + + if (tree == null || !context.mounted) return; Navigator.of(context).pop(); final confirm = await showAlertDialog( context, - title: 'Upload Folder?', + title: 'Upload Files?', message: 'Found ${tree.files.length} files and ${tree.dirs.length} directories.\n\nTotal Upload Size: ${tree.totalSize.getHumanString()}', positiveText: 'YES', negativeText: 'NO', @@ -50,12 +54,10 @@ void pickAndUploadDirectory(BuildContext context, String folderId) async { false; if (!confirm || !context.mounted) return; final dirIds = {}; - dirIds[path] = uuid.v4(); - account.addAction(ResourceMkdirAction(id: dirIds[path]!, parent: folderId, resourceName: p.basename(path))); for (final dir in tree.dirs) { dirIds[dir] = uuid.v4(); final parent = p.dirname(dir); - account.addAction(ResourceMkdirAction(id: dirIds[dir]!, parent: dirIds[parent]!, resourceName: p.basename(dir))); + account.addAction(ResourceMkdirAction(id: dirIds[dir]!, parent: dirIds[parent] ?? folderId, resourceName: p.basename(dir))); } for (final file in tree.files) { final parent = p.dirname(file); @@ -79,11 +81,21 @@ class DirTree { required this.dirs, }); + DirTree mergeWith(DirTree other) { + return DirTree( + numFiles: numFiles + other.numFiles, + numDirs: numDirs + other.numDirs, + totalSize: totalSize + other.totalSize, + files: files..addAll(other.files), + dirs: dirs..addAll(other.dirs), + ); + } + static Future stat(String path) { final dir = Directory(path); final completer = Completer(); final files = []; - final dirs = []; + final dirs = [path]; int numFiles = 0; int numDirs = 0; int totalSize = 0; diff --git a/client/linux/flutter/generated_plugin_registrant.cc b/client/linux/flutter/generated_plugin_registrant.cc index 68ae3dae..36be0a91 100644 --- a/client/linux/flutter/generated_plugin_registrant.cc +++ b/client/linux/flutter/generated_plugin_registrant.cc @@ -7,13 +7,21 @@ #include "generated_plugin_registrant.h" #include +#include #include +#include void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); file_selector_plugin_register_with_registrar(file_selector_linux_registrar); + g_autoptr(FlPluginRegistrar) irondash_engine_context_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "IrondashEngineContextPlugin"); + irondash_engine_context_plugin_register_with_registrar(irondash_engine_context_registrar); g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin"); sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar); + g_autoptr(FlPluginRegistrar) super_native_extensions_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "SuperNativeExtensionsPlugin"); + super_native_extensions_plugin_register_with_registrar(super_native_extensions_registrar); } diff --git a/client/linux/flutter/generated_plugins.cmake b/client/linux/flutter/generated_plugins.cmake index ee11cfed..b61222b9 100644 --- a/client/linux/flutter/generated_plugins.cmake +++ b/client/linux/flutter/generated_plugins.cmake @@ -4,7 +4,9 @@ list(APPEND FLUTTER_PLUGIN_LIST file_selector_linux + irondash_engine_context sqlite3_flutter_libs + super_native_extensions ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/client/macos/Flutter/GeneratedPluginRegistrant.swift b/client/macos/Flutter/GeneratedPluginRegistrant.swift index 5f0de90a..9741182f 100644 --- a/client/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/client/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,12 +5,18 @@ import FlutterMacOS import Foundation +import device_info_plus import file_selector_macos +import irondash_engine_context import path_provider_foundation import sqlite3_flutter_libs +import super_native_extensions func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) + IrondashEngineContextPlugin.register(with: registry.registrar(forPlugin: "IrondashEngineContextPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin")) + SuperNativeExtensionsPlugin.register(with: registry.registrar(forPlugin: "SuperNativeExtensionsPlugin")) } diff --git a/client/pubspec.lock b/client/pubspec.lock index 97e6adb7..a5cdd2c8 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -206,6 +206,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.6" + device_info_plus: + dependency: transitive + description: + name: device_info_plus + sha256: a7fd703482b391a87d60b6061d04dfdeab07826b96f9abd8f5ed98068acc0074 + url: "https://pub.dev" + source: hosted + version: "10.1.2" + device_info_plus_platform_interface: + dependency: transitive + description: + name: device_info_plus_platform_interface + sha256: "282d3cf731045a2feb66abfe61bbc40870ae50a3ed10a4d3d217556c35c8c2ba" + url: "https://pub.dev" + source: hosted + version: "7.0.1" drift: dependency: "direct main" description: @@ -429,6 +445,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" + irondash_engine_context: + dependency: transitive + description: + name: irondash_engine_context + sha256: cd7b769db11a2b5243b037c8a9b1ecaef02e1ae27a2d909ffa78c1dad747bb10 + url: "https://pub.dev" + source: hosted + version: "0.5.4" + irondash_message_channel: + dependency: transitive + description: + name: irondash_message_channel + sha256: b4101669776509c76133b8917ab8cfc704d3ad92a8c450b92934dd8884a2f060 + url: "https://pub.dev" + source: hosted + version: "0.7.0" js: dependency: transitive description: @@ -478,7 +510,7 @@ packages: source: hosted version: "4.0.0" logger: - dependency: transitive + dependency: "direct main" description: name: logger sha256: "697d067c60c20999686a0add96cf6aba723b3aa1f83ecf806a8097231529ec32" @@ -612,6 +644,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" + pixel_snap: + dependency: transitive + description: + name: pixel_snap + sha256: "677410ea37b07cd37ecb6d5e6c0d8d7615a7cf3bd92ba406fd1ac57e937d1fb0" + url: "https://pub.dev" + source: hosted + version: "0.1.5" platform: dependency: transitive description: @@ -785,6 +825,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + super_clipboard: + dependency: "direct main" + description: + name: super_clipboard + sha256: "74098001413e075cc53dee72b68c32eaffc10709df41806800393abaa6dac9d5" + url: "https://pub.dev" + source: hosted + version: "0.8.19" + super_native_extensions: + dependency: transitive + description: + name: super_native_extensions + sha256: c24676825c9f3ae844676a843d45ad186f2270539ffe72be4277753e46d14e29 + url: "https://pub.dev" + source: hosted + version: "0.8.19" term_glyph: dependency: transitive description: @@ -881,6 +937,22 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" + win32: + dependency: transitive + description: + name: win32 + sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a" + url: "https://pub.dev" + source: hosted + version: "5.5.4" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "723b7f851e5724c55409bb3d5a32b203b3afe8587eaf5dafb93a5fed8ecda0d6" + url: "https://pub.dev" + source: hosted + version: "1.1.4" xdg_directories: dependency: transitive description: diff --git a/client/pubspec.yaml b/client/pubspec.yaml index 88de694d..d8cc9ce0 100644 --- a/client/pubspec.yaml +++ b/client/pubspec.yaml @@ -1,5 +1,5 @@ name: phylum -description: "Self-hosted file storage" +description: Self-hosted file storage publish_to: 'none' version: 0.0.1+1 @@ -11,19 +11,21 @@ dependencies: sdk: flutter drift: ^2.19.1+1 drift_flutter: ^0.1.0 + file_selector: ^1.0.3 flutter_state_notifier: go_router: hive: http: + logger: offtheline: path: ../../offtheline state_notifier: path: path_provider: provider: + super_clipboard: uri: uuid: - file_selector: ^1.0.3 dev_dependencies: flutter_test: diff --git a/client/windows/flutter/generated_plugin_registrant.cc b/client/windows/flutter/generated_plugin_registrant.cc index 987fb3d8..e491861a 100644 --- a/client/windows/flutter/generated_plugin_registrant.cc +++ b/client/windows/flutter/generated_plugin_registrant.cc @@ -7,11 +7,17 @@ #include "generated_plugin_registrant.h" #include +#include #include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { FileSelectorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("FileSelectorWindows")); + IrondashEngineContextPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("IrondashEngineContextPluginCApi")); Sqlite3FlutterLibsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin")); + SuperNativeExtensionsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("SuperNativeExtensionsPluginCApi")); } diff --git a/client/windows/flutter/generated_plugins.cmake b/client/windows/flutter/generated_plugins.cmake index 30578133..23f691d4 100644 --- a/client/windows/flutter/generated_plugins.cmake +++ b/client/windows/flutter/generated_plugins.cmake @@ -4,7 +4,9 @@ list(APPEND FLUTTER_PLUGIN_LIST file_selector_windows + irondash_engine_context sqlite3_flutter_libs + super_native_extensions ) list(APPEND FLUTTER_FFI_PLUGIN_LIST