diff --git a/CHANGELOG.md b/CHANGELOG.md index 41798b7..61812d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,12 @@ ### Removed +## [0.2.0] - 2023-06-18 + +### Added + +- Show the size of all folders in workspace + ## [0.1.2] - 2023-06-14 Note: need to install it manually from the github if on windows. diff --git a/lib/class/folder_property.dart b/lib/class/folder_property.dart new file mode 100644 index 0000000..e36e245 --- /dev/null +++ b/lib/class/folder_property.dart @@ -0,0 +1,22 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'folder_property.g.dart'; + +@JsonSerializable() +class FolderProperty { + int? size; + String path; + int? nbChildren; + List folderProperties; + + FolderProperty( + {this.size, + required this.path, + this.nbChildren, + required this.folderProperties}); + + factory FolderProperty.fromJson(Map json) => + _$FolderPropertyFromJson(json); + + Map toJson() => _$FolderPropertyToJson(this); +} diff --git a/lib/class/folder_property.g.dart b/lib/class/folder_property.g.dart new file mode 100644 index 0000000..fbf3162 --- /dev/null +++ b/lib/class/folder_property.g.dart @@ -0,0 +1,25 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'folder_property.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +FolderProperty _$FolderPropertyFromJson(Map json) => + FolderProperty( + size: json['size'] as int?, + path: json['path'] as String, + nbChildren: json['nbChildren'] as int?, + folderProperties: (json['folderProperties'] as List) + .map((e) => FolderProperty.fromJson(e as Map)) + .toList(), + ); + +Map _$FolderPropertyToJson(FolderProperty instance) => + { + 'size': instance.size, + 'path': instance.path, + 'nbChildren': instance.nbChildren, + 'folderProperties': instance.folderProperties, + }; diff --git a/lib/class/workspace.dart b/lib/class/workspace.dart index 83bb64e..f694bd2 100644 --- a/lib/class/workspace.dart +++ b/lib/class/workspace.dart @@ -1,3 +1,4 @@ +import 'package:deepfacelab_client/class/folder_property.dart'; import 'package:deepfacelab_client/class/locale_storage_question.dart'; import 'package:json_annotation/json_annotation.dart'; @@ -8,9 +9,13 @@ class Workspace { String name; String path; List? localeStorageQuestions; + FolderProperty? folderProperty; Workspace( - {required this.name, required this.path, this.localeStorageQuestions}); + {required this.name, + required this.path, + this.localeStorageQuestions, + this.folderProperty}); factory Workspace.fromJson(Map json) => _$WorkspaceFromJson(json); diff --git a/lib/class/workspace.g.dart b/lib/class/workspace.g.dart index a2892a8..0b5c365 100644 --- a/lib/class/workspace.g.dart +++ b/lib/class/workspace.g.dart @@ -13,10 +13,15 @@ Workspace _$WorkspaceFromJson(Map json) => Workspace( ?.map( (e) => LocaleStorageQuestion.fromJson(e as Map)) .toList(), + folderProperty: json['folderProperty'] == null + ? null + : FolderProperty.fromJson( + json['folderProperty'] as Map), ); Map _$WorkspaceToJson(Workspace instance) => { 'name': instance.name, 'path': instance.path, 'localeStorageQuestions': instance.localeStorageQuestions, + 'folderProperty': instance.folderProperty, }; diff --git a/lib/main.dart b/lib/main.dart index b2d69c1..c5e4d6f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -13,6 +13,7 @@ import 'package:deepfacelab_client/screens/window_command_screen.dart'; import 'package:deepfacelab_client/screens/workspace_screen.dart'; import 'package:deepfacelab_client/service/locale_storage_service.dart'; import 'package:deepfacelab_client/widget/installation/has_requirements_widget.dart'; +import 'package:file_sizes/file_sizes.dart'; import 'package:filesystem_picker/filesystem_picker.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; @@ -112,7 +113,8 @@ class Root extends HookWidget { destination: NavigationRailDestination( icon: const Icon(Icons.movie), selectedIcon: const Icon(Icons.movie), - label: Text(workspace.name), + label: Text( + '${workspace.name}\n${FileSize.getSize(workspace.folderProperty?.size ?? 0)}'), ), widget: WorkspaceScreen(initWorkspace: workspace)), ); diff --git a/lib/screens/workspace_screen.dart b/lib/screens/workspace_screen.dart index 7beb656..d9d54fe 100644 --- a/lib/screens/workspace_screen.dart +++ b/lib/screens/workspace_screen.dart @@ -68,7 +68,7 @@ class WorkspaceScreen extends HookWidget { if (initWorkspace != null) ...[ const Divider(), FileManagerWidget( - rootPath: initWorkspace!.path, + workspace: initWorkspace, controller: mainController.value, updateFileMissing: updateFileMissingController, ), diff --git a/lib/service/file_manager_service.dart b/lib/service/file_manager_service.dart new file mode 100644 index 0000000..2735ae8 --- /dev/null +++ b/lib/service/file_manager_service.dart @@ -0,0 +1,86 @@ +import 'dart:io'; + +import 'package:collection/collection.dart'; +import 'package:deepfacelab_client/class/folder_property.dart'; +import 'package:deepfacelab_client/service/workspace_service.dart'; + +import '../class/workspace.dart'; + +class FileManagerService { + Future _updateFolderProperty( + {required FolderProperty folderProperty, + required List fileSystemEntities}) async { + folderProperty.nbChildren = fileSystemEntities.length; + var size = 0; + List foldersFound = []; + for (var fileSystemEntity in fileSystemEntities) { + if (fileSystemEntity is File) { + size += await fileSystemEntity.length(); + } + if (fileSystemEntity is Directory) { + var thisFileSystemEntities = + await Directory(fileSystemEntity.path).list().toList(); + foldersFound.add(fileSystemEntity.path); + var thisFolderProperty = folderProperty.folderProperties + .firstWhereOrNull( + (FolderProperty f) => f.path == fileSystemEntity.path); + if (thisFolderProperty == null) { + thisFolderProperty = + FolderProperty(path: fileSystemEntity.path, folderProperties: []); + folderProperty.folderProperties.add(thisFolderProperty); + } + await _updateFolderProperty( + folderProperty: thisFolderProperty, + fileSystemEntities: thisFileSystemEntities); + size += thisFolderProperty.size!; + } + } + folderProperty.folderProperties = folderProperty.folderProperties + .where((f) => foldersFound.contains(f.path)) + .toList(); + folderProperty.size = size; + return folderProperty; + } + + Future updateFolderProperty( + {required String path, + required Workspace workspace, + List? fileSystemEntities, + bool force = false}) async { + // region get thisFolderProperty + workspace.folderProperty ??= + FolderProperty(path: workspace.path, folderProperties: []); + FolderProperty? thisFolderProperty = workspace.folderProperty; + var pathArray = path + .replaceAll(workspace.path, '') + .split(Platform.pathSeparator) + .where((element) => element != '') + .toList(); + var i = 0; + while (thisFolderProperty != null && thisFolderProperty.path != path) { + thisFolderProperty = thisFolderProperty.folderProperties.firstWhereOrNull( + (f) => f.path.endsWith(Platform.pathSeparator + pathArray[i])); + i++; + } + // endregion + if (thisFolderProperty == null) { + return await updateFolderProperty( + path: workspace.path, workspace: workspace, force: true); + } + fileSystemEntities ??= await Directory(path).list().toList(); + if (!force && thisFolderProperty.nbChildren == fileSystemEntities.length) { + return thisFolderProperty; + } + if (thisFolderProperty.path != workspace.path) { + return await updateFolderProperty( + path: workspace.path, workspace: workspace, force: true); + } + thisFolderProperty = await _updateFolderProperty( + folderProperty: thisFolderProperty, + fileSystemEntities: fileSystemEntities); + workspace.folderProperty = thisFolderProperty; + WorkspaceService().createUpdateWorkspace( + oldWorkspace: workspace, newWorkspace: workspace); + return thisFolderProperty; + } +} diff --git a/lib/service/workspace_service.dart b/lib/service/workspace_service.dart index abc7059..927ac8b 100644 --- a/lib/service/workspace_service.dart +++ b/lib/service/workspace_service.dart @@ -46,8 +46,7 @@ class WorkspaceService { if (index != null) { storage?.workspaces![index] = newWorkspace; } - storage?.workspaces = - storage.workspaces?.map((w) => Workspace.fromJson(w.toJson())).toList(); + storage?.workspaces = [...?storage.workspaces]; store.dispatch({'storage': storage}); } diff --git a/lib/widget/common/file_manager_widget.dart b/lib/widget/common/file_manager_widget.dart index a6a6abf..673aafa 100644 --- a/lib/widget/common/file_manager_widget.dart +++ b/lib/widget/common/file_manager_widget.dart @@ -1,11 +1,14 @@ import 'dart:io'; +import 'package:deepfacelab_client/class/folder_property.dart'; import 'package:deepfacelab_client/class/workspace.dart'; import 'package:deepfacelab_client/screens/workspace_screen.dart'; +import 'package:deepfacelab_client/service/file_manager_service.dart'; import 'package:deepfacelab_client/service/platform_service.dart'; import 'package:deepfacelab_client/service/workspace_service.dart'; import 'package:deepfacelab_client/widget/common/context_menu_region.dart'; import 'package:desktop_drop/desktop_drop.dart'; +import 'package:file_sizes/file_sizes.dart'; import 'package:filesystem_picker/filesystem_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -53,7 +56,7 @@ class _FileManagerHeaderWidget extends HookWidget { final bool focusSearchField; final ValueNotifier search; final ValueNotifier pathNotifier; - final void Function() refresh; + final void Function({bool force}) refresh; const _FileManagerHeaderWidget({ Key? key, @@ -146,7 +149,7 @@ class _FileManagerHeaderWidget extends HookWidget { icon: const Icon(Icons.refresh), splashRadius: 20, tooltip: 'Refresh (f5)', - onPressed: refresh, + onPressed: () => refresh(force: true), ), ], ); @@ -281,14 +284,76 @@ ${missingDirectories.value.map((folderPath) => folderPath.replaceFirst(workspace } } +class _FileManagerCardWidget extends HookWidget { + final _FileSystemEntity fileSystemEntity; + final String folderPath; + final FolderProperty? folderProperty; + + const _FileManagerCardWidget( + {Key? key, + required this.fileSystemEntity, + required this.folderProperty, + required this.folderPath}) + : super(key: key); + + @override + Widget build(BuildContext context) { + var size = useState(""); + + updateSize() { + if (folderProperty == null) { + size.value = ""; + return; + } + for (var childFolderProperty in folderProperty!.folderProperties) { + if (childFolderProperty.path + .endsWith(Platform.pathSeparator + fileSystemEntity.filename)) { + size.value = FileSize.getSize(childFolderProperty.size); + return; + } + } + } + + useEffect(() { + updateSize(); + return null; + }, [folderProperty]); + + return Card( + color: fileSystemEntity.selected != null + ? Theme.of(context).colorScheme.primary + : null, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + fileSystemEntity.required + ? const Icon(Icons.add, size: 50) + : fileSystemEntity.directory + ? const Icon(Icons.folder, size: 50) + : fileSystemEntity.video + ? const Icon(Icons.video_file, size: 50) + : fileSystemEntity.image + ? Image.file( + File( + "$folderPath${Platform.pathSeparator}${fileSystemEntity.filename}"), + height: 70) + : const Icon(Icons.file_open, size: 50), + Text(fileSystemEntity.filename, maxLines: 1), + if (size.value != '') Text(size.value, maxLines: 1), + ], + ), + ); + } +} + class FileManagerWidget extends HookWidget { - final String rootPath; + final Workspace? workspace; final UpdateChildController controller; final Null Function() updateFileMissing; const FileManagerWidget( {Key? key, - required this.rootPath, + required this.workspace, required this.controller, required this.updateFileMissing}) : super(key: key); @@ -297,7 +362,8 @@ class FileManagerWidget extends HookWidget { Widget build(BuildContext context) { String homeDirectory = PlatformService.getHomeDirectory(); - var folderPath = useState(rootPath); + var folderPath = useState(workspace!.path); + var folderProperty = useState(workspace?.folderProperty); var fileSystemEntities = useState?>(null); var nbSelectedItems = useState(0); var myFocusNode = useState(FocusNode()); @@ -308,18 +374,27 @@ class FileManagerWidget extends HookWidget { var search = useState(""); final formRenameKey = GlobalKey(); - loadFilesFolders() async { + loadFilesFolders({bool force = false}) async { fileSystemEntities.value = null; - var thisFiles = Directory(folderPath.value).list(); - if (search.value != "") { - thisFiles = thisFiles.where( - (thisFile) => p.basename(thisFile.path).contains(search.value)); + List thisFiles; + try { + thisFiles = await Directory(folderPath.value).list().toList(); + } catch (e) { + return; } - List<_FileSystemEntity> newFileSystemEntities = - await thisFiles.map((fileSystemEntity) { - // https://stackoverflow.com/questions/75915594/pathinfo-method-equivalent-for-dart-language#answer-75915804 + List<_FileSystemEntity> newFileSystemEntities = []; + folderProperty.value = await FileManagerService().updateFolderProperty( + workspace: workspace!, + path: folderPath.value, + fileSystemEntities: thisFiles, + force: force); + for (var fileSystemEntity in thisFiles) { + if (search.value != "" && + !p.basename(fileSystemEntity.path).contains(search.value)) { + continue; + } String filename = p.basename(fileSystemEntity.path); - return _FileSystemEntity( + newFileSystemEntities.add(_FileSystemEntity( filename: filename, directory: fileSystemEntity is Directory, image: filename.endsWith('.png') || @@ -360,8 +435,8 @@ class FileManagerWidget extends HookWidget { filename.endsWith('.vob') || filename.endsWith('.webm') || filename.endsWith('.wmv') || - filename.endsWith('.yuv')); - }).toList(); + filename.endsWith('.yuv'))); + } newFileSystemEntities.sort((a, b) { if (a.directory == true && b.directory == true) { return a.filename.compareTo(b.filename); @@ -374,7 +449,7 @@ class FileManagerWidget extends HookWidget { } return a.filename.compareTo(b.filename); }); - if (folderPath.value == rootPath && search.value == "") { + if (folderPath.value == workspace!.path && search.value == "") { for (var video in ["data_src", "data_dst"]) { if (newFileSystemEntities.indexWhere( (element) => element.filename.contains("$video.")) == @@ -755,9 +830,9 @@ class FileManagerWidget extends HookWidget { controller.updateFromParent = updateFromParent; useEffect(() { - folderPath.value = rootPath; + folderPath.value = workspace!.path; return null; - }, [rootPath]); + }, [workspace!.path]); useEffect(() { loadFilesFolders(); @@ -807,7 +882,7 @@ class FileManagerWidget extends HookWidget { pathNotifier: folderPath, folderPath: folderPath.value, focusSearchField: focusSearchField.value, - rootPath: rootPath, + rootPath: workspace!.path, search: search, refresh: loadFilesFolders), Expanded( @@ -817,8 +892,8 @@ class FileManagerWidget extends HookWidget { control: true): selectAll, const SingleActivator(LogicalKeyboardKey.f2): rename, const SingleActivator(LogicalKeyboardKey.delete): delete, - const SingleActivator(LogicalKeyboardKey.f5): - loadFilesFolders, + const SingleActivator(LogicalKeyboardKey.f5): () => + loadFilesFolders(force: true), const SingleActivator(LogicalKeyboardKey.keyC, control: true): copyClipboard, const SingleActivator(LogicalKeyboardKey.keyV, @@ -878,7 +953,8 @@ class FileManagerWidget extends HookWidget { ), ], if (nbSelectedItems.value == 2 && - folderPath.value == rootPath && + folderPath.value == + workspace!.path && ((fileSystemEntities .value![index] .filename @@ -908,50 +984,12 @@ class FileManagerWidget extends HookWidget { onTap: () => onTapCard(index, keysPressed: RawKeyboard .instance.keysPressed), - child: Card( - color: fileSystemEntities - .value![index].selected != - null - ? Theme.of(context) - .colorScheme - .primary - : null, - child: Column( - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - fileSystemEntities - .value![index].required - ? const Icon(Icons.add, - size: 50) - : fileSystemEntities - .value![index] - .directory - ? const Icon(Icons.folder, - size: 50) - : fileSystemEntities - .value![index] - .video - ? const Icon( - Icons.video_file, - size: 50) - : fileSystemEntities - .value![index] - .image - ? Image.file( - File("${folderPath.value}${Platform.pathSeparator}${fileSystemEntities.value![index].filename}"), - height: 70) - : const Icon( - Icons - .file_open, - size: 50), - Text( - fileSystemEntities - .value![index].filename, - maxLines: 1), - ], - ), - ), + child: _FileManagerCardWidget( + fileSystemEntity: fileSystemEntities + .value![index], + folderProperty: + folderProperty.value, + folderPath: folderPath.value), ), ); if (fileSystemEntities diff --git a/pubspec.lock b/pubspec.lock index 4c22465..eb57250 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -225,6 +225,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.4" + file_sizes: + dependency: "direct main" + description: + name: file_sizes + sha256: d24964a4b194b6116d490005428d07cb3e83834ad1f7ec6a1012dedc2f6d2a19 + url: "https://pub.dev" + source: hosted + version: "1.0.6" filesystem_picker: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 8918031..9351133 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 0.1.2 +version: 0.2.0 environment: sdk: '>=2.19.4 <3.0.0' @@ -50,6 +50,7 @@ dependencies: desktop_multi_window: ^0.2.0 desktop_drop: ^0.4.1 http: ^0.13.6 + file_sizes: ^1.0.6 dev_dependencies: flutter_test: