308 lines
7.2 KiB
Dart
308 lines
7.2 KiB
Dart
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<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<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) 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<int> 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<void> 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;
|
|
}
|