import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; class PathHelper { String pth = ""; PathHelper({required this.pth}); static String combine(String path1, String path2) { return path1 + Platform.pathSeparator + path2; } static PathHelper builder(String startPath) { return PathHelper(pth: startPath); } bool exists() { File fi = File(build()); Directory dir = Directory(build()); return fi.existsSync() || dir.existsSync(); } PathHelper clone() { return PathHelper.builder(build()); } PathHelper resolve(String path2) { pth += Platform.pathSeparator + path2; return this; } PathHelper conditionalResolve(bool flag, String path) { if (flag) pth += Platform.pathSeparator + path; return this; } bool deleteDirectory({bool recursive = false}) { Directory dir = new Directory(build()); try { dir.deleteSync(recursive: recursive); return true; } catch (E) { return false; } } bool deleteFile() { File file = new File(build()); try { file.deleteSync(recursive: true); return true; } catch (E) { return false; } } PathHelper removeDir() { deleteDirectory(recursive: true); return this; } PathHelper removeFile() { deleteFile(); return this; } PathHelper mkdir() { Directory dir = new Directory(build()); dir.createSync(recursive: true); return this; } String build() { return pth; } } Stream> tail(final File file) async* { final randomAccess = await file.open(mode: FileMode.read); var pos = await randomAccess.position(); var len = await randomAccess.length(); // Increase/decrease buffer size as needed. var buf = Uint8List(8192); Stream _read() async* { while (pos < len) { try { final bytesRead = await randomAccess.readInto(buf); pos += bytesRead; yield buf.sublist(0, bytesRead); } catch (E) {} } } // Step 1: read whole file yield* _read(); // Step 2: wait for modify events and read more bytes from file await for (final event in file.watch(events: FileSystemEvent.modify)) { if ((event as FileSystemModifyEvent).contentChanged) { try { len = await (randomAccess.length()); yield* _read(); } catch (E) {} } } } void tailAndPrint(File file) { try { tail(file) .transform(utf8.decoder) .transform(LineSplitter()) .forEach((line) { // Do something with the line that has been read, e.g. print it out... print(line); }); } catch (E) {} } class FileInformationCache { static FileInformationCache? _inst; FileInformationCache._(); Map _registry = {}; factory FileInformationCache.obtain() { if (_inst == null) _inst = FileInformationCache._(); return _inst!; } /// Flushes the cache and clears it static void flush() { FileInformationCache.obtain()._registry.clear(); } /// Adds the specified file's information to the cache void addFile(String path, FileInfo info) { _registry[path] = info; } Future> getOrderedList({bool ascending = false}) async { List infoList = []; for (var entry in _registry.entries) { infoList.add(entry.value); } if (ascending) infoList.sort((A, B) => A.size.compareTo(B.size)); else infoList.sort((A, B) => B.size.compareTo(A.size)); return infoList; } } class FileInfo { String path; int size; bool isFile; FileInfo({required this.path, required this.size, required this.isFile}); double kb() { return (size / 1024); } double mb() { return (kb() / 1024); } double gb() { return (mb() / 1024); } double tb() { return (gb() / 1024); } @override String toString() { if (tb() > 0.5) return "${tb()} TB"; else if (gb() > 0.5) return "${gb()} GB"; else if (mb() > 0.5) return "${mb()} MB"; else if (kb() > 0.5) return "${kb()} KB"; else return "$size bytes"; } /// This automatically picks TB, GB, MB, KB, or bytes depending on which returns larger than 0.5 /// /// This provides no way to know what it returns, if you need to know, use the toString method, or the individual functions to get various size types double getSize() { if (tb() > 0.5) return tb(); else if (gb() > 0.5) return gb(); else if (mb() > 0.5) return mb(); else if (kb() > 0.5) return kb(); else return size.toDouble(); } } Future getFileSize(String path, {bool cacheSize = false, bool verbose = false}) async { if (verbose) await prnt("${trunc("\r> Checking size of file $path")}"); /*final fileBytes = await File(path).readAsBytes(); int size = fileBytes.lengthInBytes;*/ try { final size = await File(path).length(); if (verbose) await prnt("${trunc("\r>> Size of file $path")} is $size bytes"); if (cacheSize) { FileInformationCache FIC = FileInformationCache.obtain(); FIC.addFile(path, FileInfo(path: path, size: size, isFile: true)); } return size; } catch (E) { return 0; } } Future getDirectorySize(String path, {bool recursive = false, bool cacheSize = false, bool verbose = false}) async { try { int totalSize = 0; if (verbose) await prnt("\r${trunc("> Check dir size of $path")}"); final entityList = await Directory(path) .list(recursive: false, followLinks: true) .toList(); await Future.forEach(entityList, (entity) async { if (entity is File) { totalSize += await getFileSize(entity.path, cacheSize: cacheSize, verbose: verbose); } else if (entity is Directory) { totalSize += await getDirectorySize(entity.path, recursive: true, cacheSize: cacheSize, verbose: verbose); } }); if (verbose) await prnt("${trunc("\r>> Size of dir $path")} is $totalSize bytes"); if (cacheSize) { FileInformationCache FIC = FileInformationCache.obtain(); FileInfo FI = FileInfo(path: path, size: totalSize, isFile: false); FIC.addFile(path, FI); } return totalSize; } catch (E) { return 0; } } Future prnt(String text) async { // Convert the string to a list of runes to handle escape sequences properly. final runes = text.runes.toList(); for (var i = 0; i < runes.length; i++) { final char = runes[i]; // Check for escape sequences final charStr = String.fromCharCode(char); if (charStr == '\r') { // 'r' character in UTF-16 // Handle '\r' escape sequence stdout.write('\r'); // Move the cursor back to the beginning of the line stdout.write(' '.padLeft( stdout.terminalColumns - 1)); // Erase the entire line with spaces stdout.write( '\r'); // Move the cursor back to the start of the line again after clearing continue; } // Write the character as-is stdout.write(String.fromCharCode(char)); } await stdout.flush(); } String trunc(String input) { if (input.length > stdout.terminalColumns - 32) { return "${input.substring(0, (stdout.terminalColumns / 2).round())}..."; } else return input; }