import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; import 'package:libac_dart/consts.dart'; 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 generateHTMLReport( {bool ascending = false, String VERSION = ""}) async { String header = ''' MKFSReport

File System Report


Generated by mkfsreport v${VERSION}
Bundled LibAC Version: ${Constants.VERSION}
    MKFSREPORT and LibAC are provided free of charge with no implied warranties. The software is provided as-is.
    
    This program was created by Tara Piccari, as a part of the Aria's Creations common code library as a helper utility.
    Aria's Creations is a alias for online-only works. Aria is an anagram derived from my name. piccARI, tarA. You place my last name first, take the last 3, skip the first 3 in my first name and take the remaining character(s) and you have my alias.
    
    This utility analyzed the specified folder path to generate a detailed report of directory and file sizes as seen below.
    

'''; header += ''' '''; for (var entry in (await getOrderedList(ascending: ascending))) { header += ''' '''; } header += '''
Type Path Size
${entry.isFile ? "FILE" : "DIR"} ${entry.path} ${entry.toString()}
'''; header += ''' '''; return header; } 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) prnt("> Checking size of file $path\r"); /*final fileBytes = await File(path).readAsBytes(); int size = fileBytes.lengthInBytes;*/ final size = await File(path).length(); if (verbose) prnt(">> Size of file $path is $size bytes\r"); if (cacheSize) { FileInformationCache FIC = FileInformationCache.obtain(); FIC.addFile(path, FileInfo(path: path, size: size, isFile: true)); } return size; } Future getDirectorySize(String path, {bool recursive = false, bool cacheSize = false, bool verbose = false}) async { int totalSize = 0; if (verbose) prnt("> Check dir size of $path\r"); final entityList = await Directory(path) .list(recursive: recursive, 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) prnt(">> Size of dir $path is $totalSize bytes\r"); if (cacheSize) { FileInformationCache FIC = FileInformationCache.obtain(); FileInfo FI = FileInfo(path: path, size: totalSize, isFile: false); FIC.addFile(path, FI); } return totalSize; } void prnt(String text) { // 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 if (char == 0x5C && i + 1 < runes.length) { // '\\' character in UTF-16 final nextChar = runes[i + 1]; if (nextChar == 0x72) { // 'r' character in UTF-16 // Handle '\r' escape sequence stdout.write('\r'); // Move the cursor back to the beginning of the line stdout.write( ' ' * stdout.terminalColumns); // Erase the entire line with spaces stdout.write( '\r'); // Move the cursor back to the start of the line again after clearing i++; // Skip the 'r' character continue; } } // Write the character as-is stdout.write(String.fromCharCode(char)); } }