VoidShell/utils.ts

166 lines
4.4 KiB
TypeScript

import Gio from "gi://Gio";
import GLib from "gi://GLib";
import { readFileAsync } from "astal/file";
import GObject from "astal/gobject";
export async function readFromStreamRaw(
stream: Gio.InputStream,
bytes: number,
): Promise<Uint8Array> {
return new Promise<Uint8Array>((resolve, reject) => {
stream.read_bytes_async(
bytes,
GLib.PRIORITY_DEFAULT,
null,
(stream, result) => {
try {
const data = stream!.read_bytes_finish(result);
const buffer = data.get_data();
if (buffer === null) {
reject(new Error("Failed to read from stream"));
return;
}
resolve(buffer);
} catch (e) {
reject(e);
}
},
);
});
}
export async function readFromStream(
stream: Gio.InputStream,
bytes: number,
): Promise<Uint8Array> {
const chunkCount = Math.ceil(bytes / 4096);
const buffer = await Array.from({ length: chunkCount }, (_, i) => i).reduce(
async (acc, i) => {
const buffer = await acc;
const chunkSize = Math.min(4096, bytes - i * 4096);
const chunk = await readFromStreamRaw(stream, chunkSize);
buffer.set(chunk, i * 4096);
return buffer;
},
Promise.resolve(new Uint8Array(bytes)),
);
return buffer;
}
export async function writeToStream(
stream: Gio.OutputStream,
data: ArrayBuffer | Uint8Array,
): Promise<void> {
if (data instanceof ArrayBuffer) {
data = new Uint8Array(data);
}
return new Promise<void>((resolve, reject) => {
stream.write_all_async(
data as Uint8Array,
GLib.PRIORITY_DEFAULT,
null,
(stream, result) => {
try {
stream?.write_all_finish(result) ??
reject(new Error("Failed to write to stream"));
resolve();
} catch (e) {
reject(e);
}
},
);
});
}
export async function dereferenceSymbolicLink(
filename: string,
): Promise<string> {
return new Promise<string>((resolve, reject) => {
console.log(`Dereferencing symbolic link for ${filename}`);
const file = Gio.File.new_for_path(filename);
file.query_info_async(
"standard::is-symlink,standard::symlink-target",
Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS,
GLib.PRIORITY_DEFAULT,
null,
(file, result) => {
try {
const info = file!.query_info_finish(result);
if (info.get_is_symlink()) {
console.log("File is a symlink");
const target = info.get_symlink_target() as string;
console.log(target);
if (target.startsWith("./") || target.startsWith("../")) {
resolve(file!.resolve_relative_path(target).get_path()!);
} else if (target.startsWith("/")) {
resolve(target);
} else {
resolve(file!.get_parent()!.get_child(target).get_path()!);
}
resolve(file!.resolve_relative_path(target).get_path()!);
} else {
resolve(filename);
}
} catch (e) {
console.error(e);
reject(e);
}
},
);
});
}
const dbusXml = new Map<string, string>();
export async function getDbusXml(ifaceName: string) {
if (dbusXml.has(ifaceName)) {
return dbusXml.get(ifaceName) as string;
}
const contents = await readFileAsync(`dbus/${ifaceName}.xml`);
dbusXml.set(ifaceName, contents);
return contents;
}
declare global {
// Add new "rotate" method to Array
interface Array<T> {
/**
* Rotates the array by `n` positions.
*
* @param n The number of positions to rotate the array by.
* @returns The rotated array.
*/
rotate(n: number): T[];
/**
* Asynchronous version of the map() function.
*/
amap<U>(
callbackfn: (value: T, index: number, array: T[]) => Promise<U>,
): Promise<U[]>;
}
}
Array.prototype.amap = function <T>(
this: unknown[],
callbackfn: (value: unknown, index: number, array: unknown[]) => Promise<T>,
): Promise<T[]> {
return Promise.all(this.map(callbackfn));
};
Array.prototype.rotate = function <T>(this: T[], n: number): T[] {
const array = this;
const length = array.length;
if (length === 0) {
return [];
}
n = ((n % length) + length) % length;
return array.slice(n).concat(array.slice(0, n));
};
export async function delay(ms: number) {
return new Promise<void>((resolve) => {
setTimeout(resolve, ms);
});
}