finish basic access control system

This commit is contained in:
2025-10-09 14:09:47 +08:00
parent d3cbc15450
commit 11d138751a
32 changed files with 4687 additions and 345 deletions

View File

@@ -0,0 +1,67 @@
{
"detectRange": 64,
"detectInterval": 1,
"warnInterval": 7,
"adminGroupConfig": {
"groupName": "Admin",
"groupUsers": ["Selcon"],
"isAllowed": true,
"isWarnTarget": true
},
"defaultToastConfig": {
"title": {
"text": "Welcome",
"color": "green"
},
"msg": {
"text": "Hello %groupName% %playerName%",
"color": "green"
},
"prefix": "桃花源",
"brackets": "[]",
"bracketColor": ""
},
"warnToastConfig": {
"title": {
"text": "Attention!!!",
"color": "red"
},
"msg": {
"text": "%playerName% you are not allowed to be here",
"color": "red"
},
"prefix": "Taohuayuan",
"brackets": "[]",
"bracketColor": ""
},
"usersGroups": [
{
"groupName": "user",
"groupUsers": [],
"isAllowed": true,
"isWarnTarget": true
},
{
"groupName": "VIP",
"groupUsers": [],
"isAllowed": true,
"isWarnTarget": false
},
{
"groupName": "enemy",
"groupUsers": [],
"isAllowed": false,
"isWarnTarget": false,
"toastConfig": {
"title": {
"text": "Warn",
"color": "red"
},
"msg": {
"text": "Warn %playerName%",
"color": "red"
}
}
}
]
}

0
src/accesscontrol/cli.ts Normal file
View File

158
src/accesscontrol/config.ts Normal file
View File

@@ -0,0 +1,158 @@
import { CCLog } from "@/lib/ccLog";
import * as dkjson from "@sikongjueluo/dkjson-types";
let log: CCLog | undefined;
interface ToastConfig {
title: MinecraftTextComponent;
msg: MinecraftTextComponent;
prefix?: string;
brackets?: string;
bracketColor?: string;
}
interface UserGroupConfig {
groupName: string;
isAllowed: boolean;
isWarnTarget: boolean;
groupUsers: string[];
toastConfig?: ToastConfig;
}
interface AccessConfig {
detectInterval: number;
warnInterval: number;
detectRange: number;
adminGroupConfig: UserGroupConfig;
defaultToastConfig: ToastConfig;
warnToastConfig: ToastConfig;
usersGroups: UserGroupConfig[];
}
const defaultConfig: AccessConfig = {
detectRange: 64,
detectInterval: 3,
warnInterval: 7,
adminGroupConfig: {
groupName: "Admin",
groupUsers: ["Selcon"],
isAllowed: true,
isWarnTarget: false,
},
usersGroups: [
{
groupName: "user",
groupUsers: [],
isAllowed: true,
isWarnTarget: true,
},
{
groupName: "VIP",
groupUsers: [],
isAllowed: true,
isWarnTarget: false,
},
{
groupName: "enemies",
groupUsers: [],
isAllowed: false,
isWarnTarget: false,
toastConfig: {
title: {
text: "Warn",
color: "red",
},
msg: {
text: "Warn %playerName%",
color: "red",
},
},
},
],
defaultToastConfig: {
title: {
text: "Welcome",
color: "green",
},
msg: {
text: "Hello User %playerName%",
color: "green",
},
prefix: "Taohuayuan",
brackets: "[]",
bracketColor: "",
},
warnToastConfig: {
title: {
text: "Attention!!!",
color: "red",
},
msg: {
text: "%playerName% you are not allowed to be here",
color: "red",
},
prefix: "Taohuayuan",
brackets: "[]",
bracketColor: "",
},
};
function setLog(newLog: CCLog) {
log = newLog;
}
function loadConfig(filepath: string): AccessConfig {
const [fp] = io.open(filepath, "r");
if (fp == undefined) {
print("Failed to open config file " + filepath);
return defaultConfig;
}
const configJson = fp.read("*a");
if (configJson == undefined) {
print("Failed to read config file");
return defaultConfig;
}
const [config, pos, err] = dkjson.decode(configJson);
if (config == undefined) {
log?.warn(
`Config decode failed at ${pos}, use default instead. Error :${err}`,
);
return defaultConfig;
}
// Not use external lib
// const config = textutils.unserialiseJSON(configJson, {
// parse_empty_array: true,
// });
return config as AccessConfig;
}
function saveConfig(config: AccessConfig, filepath: string) {
const configJson = dkjson.encode(config, { indent: true }) as string;
// Not use external lib
// const configJson = textutils.serializeJSON(config, { unicode_strings: true });
if (configJson == undefined) {
print("Failed to save config");
}
const [fp, _err] = io.open(filepath, "w+");
if (fp == undefined) {
print("Failed to open config file " + filepath);
return;
}
fp.write(configJson);
fp.close();
}
export {
ToastConfig,
UserGroupConfig,
AccessConfig,
loadConfig,
saveConfig,
setLog,
};

355
src/accesscontrol/main.ts Normal file
View File

@@ -0,0 +1,355 @@
import { CCLog, DAY } from "@/lib/ccLog";
import {
ToastConfig,
UserGroupConfig,
loadConfig,
saveConfig,
setLog,
} from "./config";
import * as peripheralManager from "../lib/PeripheralManager";
import { ChatBoxEvent, pullEventAs } from "@/lib/event";
import { quotestring } from "@sikongjueluo/dkjson-types";
const DEBUG = false;
const args = [...$vararg];
const log = new CCLog("accesscontrol.log", DAY);
setLog(log);
const configFilepath = `${shell.dir()}/access.config.json`;
const config = loadConfig(configFilepath);
log.info("Load config successfully!");
log.debug(textutils.serialise(config, { allow_repetitions: true }));
const groupNames = config.usersGroups.map((value) => value.groupName);
const warnTargetPlayers = config.adminGroupConfig.groupUsers.concat(
config.usersGroups
.filter((value) => value.isWarnTarget)
.map((value) => value.groupUsers ?? [])
.flat(),
);
const playerDetector = peripheralManager.findByNameRequired("playerDetector");
const chatBox = peripheralManager.findByNameRequired("chatBox");
let inRangePlayers: string[] = [];
let notAllowedPlayers: string[] = [];
function safeParseTextComponent(
component: MinecraftTextComponent,
playerName: string,
groupName?: string,
): string {
if (component.text == undefined) {
component.text = "Wrong text, please contanct with admin";
} else if (component.text.includes("%")) {
component.text = component.text.replace("%playerName%", playerName);
if (groupName != undefined)
component.text = component.text.replace("%groupName%", groupName);
}
return textutils.serialiseJSON(component);
}
function sendToast(
toastConfig: ToastConfig,
player: string,
groupConfig?: UserGroupConfig,
) {
return chatBox.sendFormattedToastToPlayer(
safeParseTextComponent(
toastConfig.msg ?? config.defaultToastConfig.msg,
player,
groupConfig?.groupName,
),
safeParseTextComponent(
toastConfig.title ?? config.defaultToastConfig.title,
player,
groupConfig?.groupName,
),
player,
quotestring(toastConfig.prefix ?? config.defaultToastConfig.prefix!),
toastConfig.brackets ?? config.defaultToastConfig.brackets,
toastConfig.bracketColor ?? config.defaultToastConfig.bracketColor,
undefined,
true,
);
}
function sendWarn(player: string) {
const playerPos = playerDetector.getPlayerPos(player);
const onlinePlayers = playerDetector.getOnlinePlayers();
const warnMsg = `Not Allowed Player ${player} Break in Home at Position ${playerPos?.x}, ${playerPos?.y}, ${playerPos?.z}`;
log.warn(warnMsg);
sendToast(config.warnToastConfig, player);
chatBox.sendFormattedMessageToPlayer(
safeParseTextComponent(config.warnToastConfig.msg, player),
player,
"AccessControl",
"[]",
undefined,
undefined,
true,
);
for (const targetPlayer of warnTargetPlayers) {
if (!onlinePlayers.includes(targetPlayer)) continue;
chatBox.sendFormattedMessageToPlayer(
textutils.serialise({
text: warnMsg,
color: "red",
} as MinecraftTextComponent),
targetPlayer,
"AccessControl",
"[]",
undefined,
undefined,
true,
);
}
}
function sendCommandHelp(targetPlayer: string) {
chatBox.sendMessageToPlayer(
`
Command Usage: @AC /<Command> [args]
Command:
- add <userGroup> <playerName>
add player to group
userGroup: ${groupNames.join(", ")}
- del <userGroup> <playerName>
delete player in the group, except Admin
userGroup: ${groupNames.join(", ")}
- list
list all of the player with its group
- set <options> [params]
config access control settins
options:
- warnInterval <number>
set the interval of warn, which is not allowed
- detectInterval <number>
set the interval of detecting players
- detectRange <number>
set the sphere range of detect
`,
targetPlayer,
"AccessControl",
"[]",
undefined,
undefined,
true,
);
}
function warnLoop() {
while (true) {
for (const player of notAllowedPlayers) {
if (inRangePlayers.includes(player)) {
sendWarn(player);
} else {
notAllowedPlayers = notAllowedPlayers.filter(
(value) => value != player,
);
}
}
os.sleep(config.warnInterval);
}
}
function mainLoop() {
while (true) {
const players = playerDetector.getPlayersInRange(config.detectRange);
if (DEBUG) {
const playersList = "[ " + players.join(",") + " ]";
log.debug(`Detected ${players.length} players: ${playersList}`);
}
for (const player of players) {
if (inRangePlayers.includes(player)) continue;
if (config.adminGroupConfig.groupUsers.includes(player)) {
log.info(`Admin ${player} enter`);
sendToast(
config.adminGroupConfig.toastConfig ?? config.defaultToastConfig,
player,
config.adminGroupConfig,
);
continue;
}
let inUserGroup = false;
for (const userGroupConfig of config.usersGroups) {
if (userGroupConfig.groupUsers == undefined) continue;
if (!userGroupConfig.groupUsers.includes(player)) continue;
if (!userGroupConfig.isAllowed) {
sendWarn(player);
notAllowedPlayers.push(player);
continue;
}
log.info(`${userGroupConfig.groupName} ${player} enter`);
sendToast(
userGroupConfig.toastConfig ?? config.defaultToastConfig,
player,
userGroupConfig,
);
inUserGroup = true;
}
if (inUserGroup) continue;
sendWarn(player);
notAllowedPlayers.push(player);
}
inRangePlayers = players;
os.sleep(config.detectInterval);
}
}
function configLoop() {
while (true) {
const ev = pullEventAs(ChatBoxEvent, "chat");
if (ev == undefined) continue;
if (!config.adminGroupConfig.groupUsers.includes(ev.username)) continue;
if (!ev.message.startsWith("@AC")) continue;
// log.info(`Received "${ev.message}" from admin ${ev.username}`);
const params = ev.message.split(" ");
if (params.length < 2) {
sendCommandHelp(ev.username);
continue;
}
if (params[1] == "/add" && params.length == 4) {
if (params[2] == "admin") {
config.adminGroupConfig.groupUsers.push(params[3]);
chatBox.sendMessageToPlayer(
`Add player ${params[3]} to admin`,
ev.username,
"AccessControl",
);
} else if (groupNames.includes(params[2])) {
const groupConfig = config.usersGroups.find(
(value) => value.groupName == params[2],
)!;
if (groupConfig.groupUsers == undefined)
groupConfig.groupUsers = [params[3]];
else groupConfig.groupUsers.push(params[3]);
chatBox.sendMessageToPlayer(
`Add player ${params[3]} to ${groupConfig.groupName}`,
ev.username,
"AccessControl",
);
} else {
sendCommandHelp(ev.username);
continue;
}
} else if (params[1] == "/del" && params.length == 4) {
if (params[2] == "admin") {
chatBox.sendMessageToPlayer(
`Could't delete admin, please edit config`,
ev.username,
"AccessControl",
);
} else if (groupNames.includes(params[2])) {
const groupConfig = config.usersGroups.find(
(value) => value.groupName == params[2],
)!;
if (groupConfig.groupUsers == undefined) groupConfig.groupUsers = [];
else
groupConfig.groupUsers = groupConfig.groupUsers.filter(
(user) => user != params[3],
);
chatBox.sendMessageToPlayer(
`Delete ${groupConfig.groupName} ${params[3]}`,
ev.username,
"AccessControl",
);
} else {
sendCommandHelp(ev.username);
continue;
}
} else if (params[1] == "/list") {
chatBox.sendMessageToPlayer(
`Admins : [ ${config.adminGroupConfig.groupUsers.join(", ")} ]`,
ev.username,
"AccessControl",
);
for (const groupConfig of config.usersGroups) {
chatBox.sendMessageToPlayer(
`${groupConfig.groupName} : [ ${config.adminGroupConfig.groupUsers.join(", ")} ]`,
ev.username,
"AccessControl",
);
}
} else if (params[1] == "/set" && params.length == 4) {
if (params[2] == "warnInterval") {
config.warnInterval = parseInt(params[3]);
chatBox.sendMessageToPlayer(
`Set warn interval to ${config.warnInterval}`,
ev.username,
"AccessControl",
);
} else if (params[2] == "detectInterval") {
config.detectInterval = parseInt(params[3]);
chatBox.sendMessageToPlayer(
`Set detect interval to ${config.detectInterval}`,
ev.username,
"AccessControl",
);
} else if (params[2] == "detectRange") {
config.detectRange = parseInt(params[3]);
chatBox.sendMessageToPlayer(
`Set detect range to ${config.detectRange}`,
ev.username,
"AccessControl",
);
} else {
sendCommandHelp(ev.username);
continue;
}
} else {
sendCommandHelp(ev.username);
continue;
}
saveConfig(config, configFilepath);
}
}
function main(args: string[]) {
log.debug("Starting access control system, get args: " + args.join(", "));
if (args.length == 1) {
if (args[0] == "start") {
parallel.waitForAll(
() => {
mainLoop();
},
() => {
configLoop();
},
() => {
warnLoop();
},
);
return;
}
}
print(`Usage: accesscontrol start`);
}
try {
main(args);
} catch (error: unknown) {
log.error(textutils.serialise(error as object));
} finally {
log.close();
}

View File

@@ -58,7 +58,7 @@ function main() {
// Get package NBT
packagesContainer.pushItems(turtleLocalName, slot);
const packageInfo = blockReader.getBlockData().Items[1];
const packageInfo = blockReader.getBlockData()!.Items[1];
// log.info(textutils.serialise(packageInfo));
// Get recipe
@@ -96,7 +96,7 @@ function main() {
restCraftCnt -= craftCnt;
// Get output item
craftOutputItem ??= blockReader.getBlockData().Items[1];
craftOutputItem ??= blockReader.getBlockData()!.Items[1];
} while (restCraftCnt > 0);
// Finally output

View File

@@ -1,43 +1,62 @@
type PeripheralType = "inventory" | "modem" | "wiredModem" | "blockReader";
type PeripheralType =
| "inventory"
| "modem"
| "wiredModem"
| "blockReader"
| "chatBox"
| "playerDetector";
type BlockSide = "top" | "bottom" | "left" | "right" | "front" | "back";
// Declare the function signature for findBySide
function findByName(
devType: "inventory",
devName: string,
devName?: string,
): InventoryPeripheral | undefined;
function findByName(
devType: "modem",
devName: string,
devName?: string,
): ModemPeripheral | undefined;
function findByName(
devType: "wiredModem",
devName: string,
devName?: string,
): WiredModemPeripheral | undefined;
function findByName(
devType: "blockReader",
devName: string,
devName?: string,
): BlockReaderPeripheral | undefined;
function findByName(
devType: "chatBox",
devName?: string,
): ChatBoxPeripheral | undefined;
function findByName(
devType: "playerDetector",
devName?: string,
): PlayerDetectorPeripheral | undefined;
function findByName(
devType: PeripheralType,
side: BlockSide,
): IPeripheral | undefined;
function findByName(
devType: PeripheralType,
devName: string,
devName?: string,
): IPeripheral | undefined;
// Implement the function signature for findBySide
// Implement the function signature for findByName
function findByName(
devType: PeripheralType,
devName: string,
devName?: string,
): IPeripheral | undefined {
const dev = peripheral.find(
devType == "wiredModem" ? "modem" : devType,
(name: string, _) => {
return name == devName;
},
)[0];
let dev;
if (devName == undefined) {
dev = peripheral.find(devType)[0];
} else {
dev = peripheral.find(
devType == "wiredModem" ? "modem" : devType,
(name: string, _) => {
return name == devName;
},
)[0];
}
// Seperate Modem and wiredModem
if (
@@ -54,41 +73,54 @@ function findByName(
)
return undefined;
if (dev != undefined && peripheral.getType(dev) != devType) return undefined;
return dev;
}
// Declare the function signature for findBySideRequired
function findByNameRequired(
devType: "inventory",
devName: string,
devName?: string,
): InventoryPeripheral;
function findByNameRequired(devType: "modem", devName: string): ModemPeripheral;
function findByNameRequired(
devType: "modem",
devName?: string,
): ModemPeripheral;
function findByNameRequired(
devType: "wiredModem",
devName: string,
devName?: string,
): WiredModemPeripheral;
function findByNameRequired(
devType: "blockReader",
devName: string,
devName?: string,
): BlockReaderPeripheral;
function findByNameRequired(
devType: "chatBox",
devName?: string,
): ChatBoxPeripheral;
function findByNameRequired(
devType: "playerDetector",
devName?: string,
): PlayerDetectorPeripheral;
function findByNameRequired<T extends IPeripheral>(
devType: PeripheralType,
side: BlockSide,
): T;
function findByNameRequired<T extends IPeripheral>(
devType: PeripheralType,
devName: string,
devName?: string,
): T;
// Implement the function signature for findBySideRequired
function findByNameRequired<T extends IPeripheral>(
devType: PeripheralType,
side: string,
devName?: string,
): T {
const dev = findByName(devType, side);
if (!dev) {
const dev = findByName(devType, devName);
if (dev == undefined) {
throw new Error(
`Required peripheral of type '${devType}' not found on side '${side}'`,
`Required peripheral of type '${devType}' not found with name '${devName}'`,
);
}
return dev as T;

View File

@@ -1,16 +1,35 @@
enum LogLevel {
Info = 0,
Warn = 1,
Error = 2,
Debug = 0,
Info = 1,
Warn = 2,
Error = 3,
}
// Define time interval constants in seconds
export const SECOND = 1;
export const MINUTE = 60 * SECOND;
export const HOUR = 60 * MINUTE;
export const DAY = 24 * HOUR;
export const WEEK = 7 * DAY;
export class CCLog {
private fp: LuaFile | undefined;
constructor(filename?: string) {
private filename?: string;
private interval: number;
private startTime: number;
private currentTimePeriod: string;
constructor(filename?: string, interval: number = DAY) {
term.clear();
term.setCursorPos(1, 1);
this.interval = interval;
this.startTime = os.time(os.date("*t"));
this.currentTimePeriod = this.getTimePeriodString(this.startTime);
if (filename != undefined && filename.length != 0) {
const filepath = shell.dir() + "/" + filename;
this.filename = filename;
const filepath = this.generateFilePath(filename, this.currentTimePeriod);
const [file, error] = io.open(filepath, fs.exists(filepath) ? "a" : "w+");
if (file != undefined) {
this.fp = file;
@@ -20,12 +39,84 @@ export class CCLog {
}
}
/**
* Generates a time period string based on the interval
* For DAY interval: YYYY-MM-DD
* For HOUR interval: YYYY-MM-DD-HH
* For MINUTE interval: YYYY-MM-DD-HH-MM
* For SECOND interval: YYYY-MM-DD-HH-MM-SS
*/
private getTimePeriodString(time: number): string {
// Calculate which time period this timestamp falls into
const periodStart = Math.floor(time / this.interval) * this.interval;
const periodDate = os.date("*t", periodStart);
if (this.interval >= DAY) {
return `${periodDate.year}-${String(periodDate.month).padStart(2, "0")}-${String(periodDate.day).padStart(2, "0")}`;
} else {
return `[${periodDate.year}-${String(periodDate.month).padStart(2, "0")}-${String(periodDate.day).padStart(2, "0")}] - [${String(periodDate.hour).padStart(2, "0")}-${String(periodDate.min).padStart(2, "0")}-${String(periodDate.sec).padStart(2, "0")}]`;
}
}
private generateFilePath(baseFilename: string, timePeriod: string): string {
// Extract file extension if present
const fileNameSubStrings = baseFilename.split(".");
let filenameWithoutExt: string;
let extension = "";
if (fileNameSubStrings.length > 1) {
filenameWithoutExt = fileNameSubStrings[0];
extension = fileNameSubStrings[1];
} else {
filenameWithoutExt = baseFilename;
}
return `${shell.dir()}/${filenameWithoutExt}[${timePeriod}].${extension}`;
}
private checkAndRotateLogFile() {
if (this.filename != undefined && this.filename.length != 0) {
const currentTime = os.time(os.date("*t"));
const currentTimePeriod = this.getTimePeriodString(currentTime);
// If we're in a new time period, rotate the log file
if (currentTimePeriod !== this.currentTimePeriod) {
// Close current file if open
if (this.fp) {
this.fp.close();
this.fp = undefined;
}
// Update the current time period
this.currentTimePeriod = currentTimePeriod;
// Open new log file for the new time period
const filepath = this.generateFilePath(
this.filename,
this.currentTimePeriod,
);
const [file, error] = io.open(
filepath,
fs.exists(filepath) ? "a" : "w+",
);
if (file != undefined) {
this.fp = file;
} else {
throw Error(error);
}
}
}
}
private getFormatMsg(msg: string, level: LogLevel): string {
const date = os.date("*t");
return `[ ${date.year}/${date.month}/${date.day} -- ${date.hour}:${date.min}:${date.sec} ${LogLevel[level]} ] : ${msg}\r\n`;
return `[ ${date.year}/${String(date.month).padStart(2, "0")}/${String(date.day).padStart(2, "0")} ${String(date.hour).padStart(2, "0")}:${String(date.min).padStart(2, "0")}:${String(date.sec).padStart(2, "0")} ${LogLevel[level]} ] : ${msg}`;
}
public writeLine(msg: string, color?: Color) {
// Check if we need to rotate the log file
this.checkAndRotateLogFile();
let originalColor: Color = 0;
if (color != undefined) {
originalColor = term.getTextColor();
@@ -33,17 +124,18 @@ export class CCLog {
}
// Log
term.write(msg);
print(msg);
if (this.fp != undefined) {
this.fp.write(msg);
this.fp.write(msg + "\r\n");
}
if (color != undefined) {
term.setTextColor(originalColor);
}
}
// Next line
term.setCursorPos(1, term.getCursorPos()[1] + 1);
public debug(msg: string) {
this.writeLine(this.getFormatMsg(msg, LogLevel.Debug), colors.gray);
}
public info(msg: string) {

25
src/lib/ccTime.ts Normal file
View File

@@ -0,0 +1,25 @@
class ccDate {
private _timestamp: number;
constructor() {
this._timestamp = os.time(os.date("*t"));
}
public static toDateTable(timestamp: number): LuaDate {
return os.date("*t", timestamp) as LuaDate;
}
public toDateTable(): LuaDate {
return os.date("*t", this._timestamp) as LuaDate;
}
public static toTimestamp(date: LuaDate): number {
return os.time(date);
}
public toTimestamp(): number {
return this._timestamp;
}
}
export { ccDate };

View File

@@ -2,312 +2,461 @@
// delete them from eventInitializers as well.
export interface IEvent {
get_name(): string;
get_args(): any[];
get_name(): string;
get_args(): unknown[];
}
export class CharEvent implements IEvent {
public character: string = "";
public get_name() {return "char";}
public get_args() {return [this.character];}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string") || (args[0] as string) != "char") return null;
let ev = new CharEvent();
ev.character = (args[1] as string);
return ev;
}
public character = "";
public get_name() {
return "char";
}
public get_args() {
return [this.character];
}
public static init(args: unknown[]): IEvent | undefined {
if (!(typeof args[0] === "string") || args[0] != "char") return undefined;
const ev = new CharEvent();
ev.character = args[1] as string;
return ev;
}
}
export class KeyEvent implements IEvent {
public key: Key = 0;
public isHeld: boolean = false;
public isUp: boolean = false;
public get_name() {return this.isUp ? "key_up" : "key";}
public get_args() {return [this.key, (this.isUp ? null : this.isHeld)];}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string") || ((args[0] as string) != "key" && (args[0] as string) != "key_up")) return null;
let ev = new KeyEvent();
ev.key = (args[1] as number);
ev.isUp = (args[0] as string) == "key_up";
ev.isHeld = ev.isUp ? false : (args[2] as boolean);
return ev;
}
public key: Key = 0;
public isHeld = false;
public isUp = false;
public get_name() {
return this.isUp ? "key_up" : "key";
}
public get_args() {
return [this.key, this.isUp ? undefined : this.isHeld];
}
public static init(args: unknown[]): IEvent | undefined {
if (
!(typeof args[0] === "string") ||
(args[0] != "key" && args[0] != "key_up")
)
return undefined;
const ev = new KeyEvent();
ev.key = args[1] as number;
ev.isUp = (args[0] as string) == "key_up";
ev.isHeld = ev.isUp ? false : (args[2] as boolean);
return ev;
}
}
export class PasteEvent implements IEvent {
public text: string = "";
public get_name() {return "paste";}
public get_args() {return [(this.text as any)];}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string") || (args[0] as string) != "paste") return null;
let ev = new PasteEvent();
ev.text = (args[1] as string);
return ev;
}
public text = "";
public get_name() {
return "paste";
}
public get_args() {
return [this.text];
}
public static init(args: unknown[]): IEvent | undefined {
if (!(typeof args[0] === "string") || args[0] != "paste") return undefined;
const ev = new PasteEvent();
ev.text = args[1] as string;
return ev;
}
}
export class TimerEvent implements IEvent {
public id: number = 0;
public isAlarm: boolean = false;
public get_name() {return this.isAlarm ? "alarm" : "timer";}
public get_args() {return [this.id];}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string") || ((args[0] as string) != "timer" && (args[0] as string) != "alarm")) return null;
let ev = new TimerEvent();
ev.id = (args[1] as number);
ev.isAlarm = (args[0] as string) == "alarm";
return ev;
}
public id = 0;
public isAlarm = false;
public get_name() {
return this.isAlarm ? "alarm" : "timer";
}
public get_args() {
return [this.id];
}
public static init(args: unknown[]): IEvent | undefined {
if (
!(typeof args[0] === "string") ||
(args[0] != "timer" && args[0] != "alarm")
)
return undefined;
const ev = new TimerEvent();
ev.id = args[1] as number;
ev.isAlarm = args[0] == "alarm";
return ev;
}
}
export class TaskCompleteEvent implements IEvent {
public id: number = 0;
public success: boolean = false;
public error: string | null = null;
public params: any[] = [];
public get_name() {return "task_complete";}
public get_args() {
if (this.success) return [this.id, this.success].concat(this.params);
else return [this.id, this.success, this.error];
}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string") || (args[0] as string) != "task_complete") return null;
let ev = new TaskCompleteEvent();
ev.id = (args[1] as number);
ev.success = (args[2] as boolean);
if (ev.success) {
ev.error = null;
ev.params = args.slice(3);
} else {
ev.error = (args[3] as string);
ev.params = [];
}
return ev;
public id = 0;
public success = false;
public error: string | undefined = undefined;
public params: any[] = [];
public get_name() {
return "task_complete";
}
public get_args() {
if (this.success) return [this.id, this.success].concat(this.params);
else return [this.id, this.success, this.error];
}
public static init(args: unknown[]): IEvent | undefined {
if (!(typeof args[0] === "string") || args[0] != "task_complete")
return undefined;
const ev = new TaskCompleteEvent();
ev.id = args[1] as number;
ev.success = args[2] as boolean;
if (ev.success) {
ev.error = undefined;
ev.params = args.slice(3);
} else {
ev.error = args[3] as string;
ev.params = [];
}
return ev;
}
}
export class RedstoneEvent implements IEvent {
public get_name() {return "redstone";}
public get_args() {return [];}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string") || (args[0] as string) != "redstone") return null;
let ev = new RedstoneEvent();
return ev;
}
public get_name() {
return "redstone";
}
public get_args() {
return [];
}
public static init(args: any[]): IEvent | undefined {
if (!(typeof args[0] === "string") || (args[0] as string) != "redstone")
return undefined;
let ev = new RedstoneEvent();
return ev;
}
}
export class TerminateEvent implements IEvent {
public get_name() {return "terminate";}
public get_args() {return [];}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string") || (args[0] as string) != "terminate") return null;
let ev = new TerminateEvent();
return ev;
}
public get_name() {
return "terminate";
}
public get_args() {
return [];
}
public static init(args: any[]): IEvent | undefined {
if (!(typeof args[0] === "string") || (args[0] as string) != "terminate")
return undefined;
let ev = new TerminateEvent();
return ev;
}
}
export class DiskEvent implements IEvent {
public side: string = "";
public eject: boolean = false;
public get_name() {return this.eject ? "disk_eject" : "disk";}
public get_args() {return [this.side];}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string") || ((args[0] as string) != "disk" && (args[0] as string) != "disk_eject")) return null;
let ev = new DiskEvent();
ev.side = (args[1] as string);
ev.eject = (args[0] as string) == "disk_eject";
return ev;
}
public side: string = "";
public eject: boolean = false;
public get_name() {
return this.eject ? "disk_eject" : "disk";
}
public get_args() {
return [this.side];
}
public static init(args: any[]): IEvent | undefined {
if (
!(typeof args[0] === "string") ||
((args[0] as string) != "disk" && (args[0] as string) != "disk_eject")
)
return undefined;
let ev = new DiskEvent();
ev.side = args[1] as string;
ev.eject = (args[0] as string) == "disk_eject";
return ev;
}
}
export class PeripheralEvent implements IEvent {
public side: string = "";
public detach: boolean = false;
public get_name() {return this.detach ? "peripheral_detach" : "peripheral";}
public get_args() {return [this.side];}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string") || ((args[0] as string) != "peripheral" && (args[0] as string) != "peripheral_detach")) return null;
let ev = new PeripheralEvent();
ev.side = (args[1] as string);
ev.detach = (args[0] as string) == "peripheral_detach";
return ev;
}
public side: string = "";
public detach: boolean = false;
public get_name() {
return this.detach ? "peripheral_detach" : "peripheral";
}
public get_args() {
return [this.side];
}
public static init(args: any[]): IEvent | undefined {
if (
!(typeof args[0] === "string") ||
((args[0] as string) != "peripheral" &&
(args[0] as string) != "peripheral_detach")
)
return undefined;
let ev = new PeripheralEvent();
ev.side = args[1] as string;
ev.detach = (args[0] as string) == "peripheral_detach";
return ev;
}
}
export class RednetMessageEvent implements IEvent {
public sender: number = 0;
public message: any;
public protocol: string | null = null;
public get_name() {return "rednet_message";}
public get_args() {return [this.sender, this.message, this.protocol];}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string") || (args[0] as string) != "rednet_message") return null;
let ev = new RednetMessageEvent();
ev.sender = (args[1] as number);
ev.message = args[2];
ev.protocol = (args[3] as string);
return ev;
}
public sender: number = 0;
public message: any;
public protocol: string | undefined = undefined;
public get_name() {
return "rednet_message";
}
public get_args() {
return [this.sender, this.message, this.protocol];
}
public static init(args: any[]): IEvent | undefined {
if (
!(typeof args[0] === "string") ||
(args[0] as string) != "rednet_message"
)
return undefined;
let ev = new RednetMessageEvent();
ev.sender = args[1] as number;
ev.message = args[2];
ev.protocol = args[3] as string;
return ev;
}
}
export class ModemMessageEvent implements IEvent {
public side: string = "";
public channel: number = 0;
public replyChannel: number = 0;
public message: any;
public distance: number = 0;
public get_name() {return "modem_message";}
public get_args() {return [this.side, this.channel, this.replyChannel, this.message, this.distance];}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string") || (args[0] as string) != "modem_message") return null;
let ev = new ModemMessageEvent();
ev.side = (args[1] as string);
ev.channel = (args[2] as number);
ev.replyChannel = (args[3] as number);
ev.message = args[4];
ev.distance = (args[5] as number);
return ev;
}
public side: string = "";
public channel: number = 0;
public replyChannel: number = 0;
public message: any;
public distance: number = 0;
public get_name() {
return "modem_message";
}
public get_args() {
return [
this.side,
this.channel,
this.replyChannel,
this.message,
this.distance,
];
}
public static init(args: any[]): IEvent | undefined {
if (
!(typeof args[0] === "string") ||
(args[0] as string) != "modem_message"
)
return undefined;
let ev = new ModemMessageEvent();
ev.side = args[1] as string;
ev.channel = args[2] as number;
ev.replyChannel = args[3] as number;
ev.message = args[4];
ev.distance = args[5] as number;
return ev;
}
}
export class HTTPEvent implements IEvent {
public url: string = "";
public handle: HTTPResponse | null = null;
public error: string | null = null;
public get_name() {return this.error == null ? "http_success" : "http_failure";}
public get_args() {return [this.url, (this.error == null ? this.handle : this.error), (this.error != null ? this.handle : null)];}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string") || ((args[0] as string) != "http_success" && (args[0] as string) != "http_failure")) return null;
let ev = new HTTPEvent();
ev.url = (args[1] as string);
if ((args[0] as string) == "http_success") {
ev.error = null;
ev.handle = (args[2] as HTTPResponse);
} else {
ev.error = (args[2] as string);
if (ev.error == null) ev.error = "";
ev.handle = (args[3] as HTTPResponse);
}
return ev;
public url: string = "";
public handle: HTTPResponse | undefined = undefined;
public error: string | undefined = undefined;
public get_name() {
return this.error == undefined ? "http_success" : "http_failure";
}
public get_args() {
return [
this.url,
this.error == undefined ? this.handle : this.error,
this.error != undefined ? this.handle : undefined,
];
}
public static init(args: any[]): IEvent | undefined {
if (
!(typeof args[0] === "string") ||
((args[0] as string) != "http_success" &&
(args[0] as string) != "http_failure")
)
return undefined;
let ev = new HTTPEvent();
ev.url = args[1] as string;
if ((args[0] as string) == "http_success") {
ev.error = undefined;
ev.handle = args[2] as HTTPResponse;
} else {
ev.error = args[2] as string;
if (ev.error == undefined) ev.error = "";
ev.handle = args[3] as HTTPResponse;
}
return ev;
}
}
export class WebSocketEvent implements IEvent {
public handle: WebSocket | null = null;
public error: string | null = null;
public get_name() {return this.error == null ? "websocket_success" : "websocket_failure";}
public get_args() {return [this.handle == null ? this.error : this.handle];}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string") || ((args[0] as string) != "websocket_success" && (args[0] as string) != "websocket_failure")) return null;
let ev = new WebSocketEvent();
if ((args[0] as string) == "websocket_success") {
ev.handle = (args[1] as WebSocket);
ev.error = null;
} else {
ev.error = (args[1] as string);
ev.handle = null;
}
return ev;
public handle: WebSocket | undefined = undefined;
public error: string | undefined = undefined;
public get_name() {
return this.error == undefined ? "websocket_success" : "websocket_failure";
}
public get_args() {
return [this.handle == undefined ? this.error : this.handle];
}
public static init(args: any[]): IEvent | undefined {
if (
!(typeof args[0] === "string") ||
((args[0] as string) != "websocket_success" &&
(args[0] as string) != "websocket_failure")
)
return undefined;
let ev = new WebSocketEvent();
if ((args[0] as string) == "websocket_success") {
ev.handle = args[1] as WebSocket;
ev.error = undefined;
} else {
ev.error = args[1] as string;
ev.handle = undefined;
}
return ev;
}
}
export enum MouseEventType {
Click,
Up,
Scroll,
Drag,
Touch,
Move,
Click,
Up,
Scroll,
Drag,
Touch,
Move,
}
export class MouseEvent implements IEvent {
public button: number = 0;
public x: number = 0;
public y: number = 0;
public side: string | null = null;
public type: MouseEventType = MouseEventType.Click;
public get_name() {
return {
[MouseEventType.Click]: "mouse_click",
[MouseEventType.Up]: "mouse_up",
[MouseEventType.Scroll]: "mouse_scroll",
[MouseEventType.Drag]: "mouse_drag",
[MouseEventType.Touch]: "monitor_touch",
[MouseEventType.Move]: "mouse_move"
}[this.type];
}
public get_args() {return [(this.type == MouseEventType.Touch ? this.side : this.button), this.x, this.y];}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string")) return null;
let ev = new MouseEvent();
const type = args[0] as string;
if (type == "mouse_click") {ev.type = MouseEventType.Click; ev.button = (args[1] as number); ev.side = null;}
else if (type == "mouse_up") {ev.type = MouseEventType.Up; ev.button = (args[1] as number); ev.side = null;}
else if (type == "mouse_scroll") {ev.type = MouseEventType.Scroll; ev.button = (args[1] as number); ev.side = null;}
else if (type == "mouse_drag") {ev.type = MouseEventType.Drag; ev.button = (args[1] as number); ev.side = null;}
else if (type == "monitor_touch") {ev.type = MouseEventType.Touch; ev.button = 0; ev.side = (args[1] as string);}
else if (type == "mouse_move") {ev.type = MouseEventType.Move; ev.button = (args[1] as number); ev.side = null;}
else return null;
ev.x = (args[2] as number);
ev.y = (args[3] as number);
return ev;
}
public button: number = 0;
public x: number = 0;
public y: number = 0;
public side: string | undefined = undefined;
public type: MouseEventType = MouseEventType.Click;
public get_name() {
return {
[MouseEventType.Click]: "mouse_click",
[MouseEventType.Up]: "mouse_up",
[MouseEventType.Scroll]: "mouse_scroll",
[MouseEventType.Drag]: "mouse_drag",
[MouseEventType.Touch]: "monitor_touch",
[MouseEventType.Move]: "mouse_move",
}[this.type];
}
public get_args() {
return [
this.type == MouseEventType.Touch ? this.side : this.button,
this.x,
this.y,
];
}
public static init(args: any[]): IEvent | undefined {
if (!(typeof args[0] === "string")) return undefined;
let ev = new MouseEvent();
const type = args[0] as string;
if (type == "mouse_click") {
ev.type = MouseEventType.Click;
ev.button = args[1] as number;
ev.side = undefined;
} else if (type == "mouse_up") {
ev.type = MouseEventType.Up;
ev.button = args[1] as number;
ev.side = undefined;
} else if (type == "mouse_scroll") {
ev.type = MouseEventType.Scroll;
ev.button = args[1] as number;
ev.side = undefined;
} else if (type == "mouse_drag") {
ev.type = MouseEventType.Drag;
ev.button = args[1] as number;
ev.side = undefined;
} else if (type == "monitor_touch") {
ev.type = MouseEventType.Touch;
ev.button = 0;
ev.side = args[1] as string;
} else if (type == "mouse_move") {
ev.type = MouseEventType.Move;
ev.button = args[1] as number;
ev.side = undefined;
} else return undefined;
ev.x = args[2] as number;
ev.y = args[3] as number;
return ev;
}
}
export class ResizeEvent implements IEvent {
public side: string | null = null;
public get_name() {return this.side == null ? "term_resize" : "monitor_resize";}
public get_args() {return [this.side];}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string") || ((args[0] as string) != "term_resize" && (args[0] as string) != "monitor_resize")) return null;
let ev = new ResizeEvent();
if ((args[0] as string) == "monitor_resize") ev.side = (args[1] as string);
else ev.side = null;
return ev;
}
public side: string | undefined = undefined;
public get_name() {
return this.side == undefined ? "term_resize" : "monitor_resize";
}
public get_args() {
return [this.side];
}
public static init(args: any[]): IEvent | undefined {
if (
!(typeof args[0] === "string") ||
((args[0] as string) != "term_resize" &&
(args[0] as string) != "monitor_resize")
)
return undefined;
let ev = new ResizeEvent();
if ((args[0] as string) == "monitor_resize") ev.side = args[1] as string;
else ev.side = undefined;
return ev;
}
}
export class TurtleInventoryEvent implements IEvent {
public get_name() {return "turtle_inventory";}
public get_args() {return [];}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string") || (args[0] as string) != "turtle_inventory") return null;
let ev = new TurtleInventoryEvent();
return ev;
}
public get_name() {
return "turtle_inventory";
}
public get_args() {
return [];
}
public static init(args: any[]): IEvent | undefined {
if (
!(typeof args[0] === "string") ||
(args[0] as string) != "turtle_inventory"
)
return undefined;
let ev = new TurtleInventoryEvent();
return ev;
}
}
class SpeakerAudioEmptyEvent implements IEvent {
public side: string = "";
public get_name() {return "speaker_audio_empty";}
public get_args() {return [this.side];}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string") || (args[0] as string) != "speaker_audio_empty") return null;
let ev: SpeakerAudioEmptyEvent;
ev.side = args[1] as string;
return ev;
}
public side = "";
public get_name() {
return "speaker_audio_empty";
}
public get_args() {
return [this.side];
}
public static init(args: unknown[]): IEvent | undefined {
if (!(typeof args[0] === "string") || args[0] != "speaker_audio_empty")
return undefined;
const ev = new SpeakerAudioEmptyEvent();
ev.side = args[1] as string;
return ev;
}
}
class ComputerCommandEvent implements IEvent {
public args: string[] = [];
public get_name() {return "computer_command";}
public get_args() {return this.args;}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string") || (args[0] as string) != "computer_command") return null;
let ev: ComputerCommandEvent;
ev.args = args.slice(1);
return ev;
}
public args: string[] = [];
public get_name() {
return "computer_command";
}
public get_args() {
return this.args;
}
public static init(args: unknown[]): IEvent | undefined {
if (!(typeof args[0] === "string") || args[0] != "computer_command")
return undefined;
const ev = new ComputerCommandEvent();
ev.args = args.slice(1) as string[];
return ev;
}
}
/*
class Event implements IEvent {
public get_name() {return "";}
public get_args() {return [(: any)];}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string") || (args[0] as string) != "") return null;
public static init(args: any[]): IEvent | undefined {
if (!(typeof args[0] === "string") || (args[0] as string) != "") return undefined;
let ev: Event;
return ev;
@@ -315,60 +464,107 @@ class Event implements IEvent {
}
*/
export class GenericEvent implements IEvent {
public args: any[] = [];
public get_name() {return (this.args[0] as string);}
public get_args() {return this.args.slice(1);}
public static init(args: any[]): IEvent | null {
let ev = new GenericEvent();
ev.args = args;
return ev;
}
export class ChatBoxEvent implements IEvent {
public username: string = "";
public message: string = "";
public uuid: string = "";
public isHidden: boolean = false;
public messageUtf8: string = "";
public get_name() {
return "chat";
}
public get_args() {
return [
this.username,
this.message,
this.uuid,
this.isHidden,
this.messageUtf8,
];
}
public static init(args: any[]): IEvent | undefined {
if (!(typeof args[0] === "string") || (args[0] as string) != "chat")
return undefined;
let ev = new ChatBoxEvent();
ev.username = args[1] as string;
ev.message = args[2] as string;
ev.uuid = args[3] as string;
ev.isHidden = args[4] as boolean;
ev.messageUtf8 = args[5] as string;
return ev;
}
}
let eventInitializers: ((args: any[]) => IEvent | null)[] = [
CharEvent.init,
KeyEvent.init,
PasteEvent.init,
TimerEvent.init,
TaskCompleteEvent.init,
RedstoneEvent.init,
TerminateEvent.init,
DiskEvent.init,
PeripheralEvent.init,
RednetMessageEvent.init,
ModemMessageEvent.init,
HTTPEvent.init,
WebSocketEvent.init,
MouseEvent.init,
ResizeEvent.init,
TurtleInventoryEvent.init,
SpeakerAudioEmptyEvent.init,
ComputerCommandEvent.init,
GenericEvent.init
export class GenericEvent implements IEvent {
public args: any[] = [];
public get_name() {
return this.args[0] as string;
}
public get_args() {
return this.args.slice(1);
}
public static init(args: any[]): IEvent | undefined {
let ev = new GenericEvent();
ev.args = args;
return ev;
}
}
let eventInitializers: ((args: unknown[]) => IEvent | undefined)[] = [
(args) => CharEvent.init(args),
(args) => KeyEvent.init(args),
(args) => PasteEvent.init(args),
(args) => TimerEvent.init(args),
(args) => TaskCompleteEvent.init(args),
(args) => RedstoneEvent.init(args),
(args) => TerminateEvent.init(args),
(args) => DiskEvent.init(args),
(args) => PeripheralEvent.init(args),
(args) => RednetMessageEvent.init(args),
(args) => ModemMessageEvent.init(args),
(args) => HTTPEvent.init(args),
(args) => WebSocketEvent.init(args),
(args) => MouseEvent.init(args),
(args) => ResizeEvent.init(args),
(args) => TurtleInventoryEvent.init(args),
(args) => SpeakerAudioEmptyEvent.init(args),
(args) => ComputerCommandEvent.init(args),
(args) => ChatBoxEvent.init(args),
(args) => GenericEvent.init(args),
];
type Constructor<T extends {} = {}> = new (...args: any[]) => T;
export function pullEventRaw(filter: string | null = null): IEvent | null {
let args = table.pack(...coroutine.yield(filter));
for (let init of eventInitializers) {
let ev = init(args);
if (ev != null) return ev;
}
return GenericEvent.init(args);
export function pullEventRaw(
filter: string | undefined = undefined,
): IEvent | undefined {
let args = table.pack(...coroutine.yield(filter));
for (let init of eventInitializers) {
let ev = init(args);
if (ev != undefined) return ev;
}
return GenericEvent.init(args);
}
export function pullEvent(filter: string | null = null): IEvent | null {
let ev = pullEventRaw(filter);
if (ev instanceof TerminateEvent) throw "Terminated";
return ev;
export function pullEvent(
filter: string | undefined = undefined,
): IEvent | undefined {
let ev = pullEventRaw(filter);
if (ev instanceof TerminateEvent) throw "Terminated";
return ev;
}
export function pullEventRawAs<T extends IEvent>(type: Constructor<T>, filter: string | null = null): T | null {
let ev = pullEventRaw(filter);
if ((ev instanceof type)) return ev as T;
else return null;
export function pullEventRawAs<T extends IEvent>(
type: Constructor<T>,
filter: string | undefined = undefined,
): T | undefined {
let ev = pullEventRaw(filter);
if (ev instanceof type) return ev as T;
else return undefined;
}
export function pullEventAs<T extends IEvent>(type: Constructor<T>, filter: string | null = null): T | null {
let ev = pullEvent(filter);
if ((ev instanceof type)) return ev as T;
else return null;
export function pullEventAs<T extends IEvent>(
type: Constructor<T>,
filter: string | undefined = undefined,
): T | undefined {
let ev = pullEvent(filter);
if (ev instanceof type) return ev as T;
else return undefined;
}

3
src/test/main.ts Normal file
View File

@@ -0,0 +1,3 @@
import { testTimeBasedRotation } from "./testCcLog";
testTimeBasedRotation();

35
src/test/testCcLog.ts Normal file
View File

@@ -0,0 +1,35 @@
import { CCLog, MINUTE, HOUR, SECOND } from "@/lib/ccLog";
// Test the new time-based rotation functionality
function testTimeBasedRotation() {
print("Testing time-based log rotation functionality...");
// Test with default interval (1 day)
const logger1 = new CCLog("test_log_default.txt");
logger1.info("This is a test message with default interval (1 day)");
// Test with custom interval (1 hour)
const logger2 = new CCLog("test_log_hourly.txt", HOUR);
logger2.info("This is a test message with 1-hour interval");
// Test with custom interval (30 minutes)
const logger3 = new CCLog("test_log_30min.txt", 30 * MINUTE);
logger3.info("This is a test message with 30-minute interval");
// Test with custom interval (5 seconds)
const logger4 = new CCLog("test_log_5sec.txt", 5 * SECOND);
logger4.info("This is a test message with 5-second interval");
for (let i = 0; i < 10; i++) {
logger4.info(`This is a test message with 5-second interval ${i}`);
sleep(1);
}
logger1.close();
logger2.close();
logger3.close();
logger4.close();
print("Test completed successfully!");
}
export { testTimeBasedRotation };