LibAC-dart/lib/utils/IOTools.dart

343 lines
8.5 KiB
Dart

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<List<int>> 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<Uint8List> _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<String, FileInfo> _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<String> generateHTMLReport(
{bool ascending = false, String VERSION = ""}) async {
String header = '''
<html>
<title>MKFSReport</title>
<body style="background-color: black;color: #00D2FA">
<h2>File System Report</h2><br/>
<b>Generated by mkfsreport v${VERSION}</b><br/>
<b>Bundled <a href="https://git.zontreck.com/AriasCreations/LibAC-dart">LibAC</a> Version: ${Constants.VERSION}</b><br/>
<pre style="color: #7a0c17">
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.
</pre><br/>
''';
header += '''
<table>
<tr>
<th>Type</th>
<th>Path</th>
<th>Size</th>
</tr>
''';
for (var entry in (await getOrderedList(ascending: ascending))) {
header += '''
<tr>
<td>${entry.isFile ? "FILE" : "DIR"}</td>
<td>${entry.path}</td>
<td>${entry.toString()}</td>
</tr>
''';
}
header += '''
</table>
''';
header += '''
</body>
</html>
''';
return header;
}
Future<List<FileInfo>> getOrderedList({bool ascending = false}) async {
List<FileInfo> 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<int> 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<int> 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));
}
}