FILES: Path rework & typesafety (#479)

* Added new types for various file paths, all in the Paths folder.
* TypeSafety and other helper functions related to these types
* Added basic globbing support with * and ?. Currently only implemented for Script/Text, on nano and download terminal commands
* Enforcing the new types throughout the codebase, plus whatever rewrites happened along the way
* Server.textFiles is now a map
* TextFile no longer uses a fn property, now it is filename
* Added a shared ContentFile interface for shared functionality between TextFile and Script.
* related to ContentFile change above, the player is now allowed to move a text file to a script file and vice versa.
* File paths no longer conditionally start with slashes, and all directory names other than root have ending slashes. The player is still able to provide paths starting with / but this now indicates that the player is specifying an absolute path instead of one relative to root.
* Singularized the MessageFilename and LiteratureName enums
* Because they now only accept correct types, server.writeToXFile functions now always succeed (the only reasons they could fail before were invalid filepath).
* Fix several issues with tab completion, which included pretty much a complete rewrite
* Changed the autocomplete display options so there's less chance it clips outside the display area.
* Turned CompletedProgramName into an enum.
* Got rid of programsMetadata, and programs and DarkWebItems are now initialized immediately instead of relying on initializers called from the engine.
* For any executable (program, cct, or script file) pathing can be used directly to execute without using the run command (previously the command had to start with ./ and it wasn't actually using pathing).
This commit is contained in:
Snarling
2023-04-24 10:26:57 -04:00
committed by GitHub
parent 6f56f35943
commit e0272ad4af
93 changed files with 3293 additions and 4297 deletions
+169 -266
View File
@@ -14,9 +14,7 @@ import {
import { netscriptCanGrow, netscriptCanWeaken } from "./Hacking/netscriptCanHack";
import { Terminal } from "./Terminal";
import { Player } from "@player";
import { Programs } from "./Programs/Programs";
import { Script } from "./Script/Script";
import { isScriptFilename } from "./Script/isScriptFilename";
import { CompletedProgramName } from "./Programs/Programs";
import { PromptEvent } from "./ui/React/PromptManager";
import { GetServer, DeleteServer, AddToAllServers, createUniqueRandomIp } from "./Server/AllServers";
import {
@@ -36,8 +34,6 @@ import {
} from "./Server/ServerPurchases";
import { Server } from "./Server/Server";
import { influenceStockThroughServerGrow } from "./StockMarket/PlayerInfluencing";
import { isValidFilePath, removeLeadingSlash } from "./Terminal/DirectoryHelpers";
import { TextFile, getTextFile, createTextFile } from "./TextFile";
import { runScriptFromScript } from "./NetscriptWorker";
import { killWorkerScript } from "./Netscript/killWorkerScript";
import { workerScripts } from "./Netscript/WorkerScripts";
@@ -56,7 +52,6 @@ import {
import { convertTimeMsToTimeElapsedString } from "./utils/StringHelperFunctions";
import { LogBoxEvents, LogBoxCloserEvents, LogBoxPositionEvents, LogBoxSizeEvents } from "./ui/React/LogBoxManager";
import { arrayToString } from "./utils/helpers/arrayToString";
import { isString } from "./utils/helpers/isString";
import { NetscriptGang } from "./NetscriptFunctions/Gang";
import { NetscriptSleeve } from "./NetscriptFunctions/Sleeve";
import { NetscriptExtra } from "./NetscriptFunctions/Extra";
@@ -91,6 +86,11 @@ import { cloneDeep } from "lodash";
import { FactionWorkType } from "./Enums";
import numeral from "numeral";
import { clearPort, peekPort, portHandle, readPort, tryWritePort, writePort } from "./NetscriptPort";
import { FilePath, resolveFilePath } from "./Paths/FilePath";
import { hasScriptExtension, resolveScriptFilePath } from "./Paths/ScriptFilePath";
import { hasTextExtension } from "./Paths/TextFilePath";
import { ContentFilePath } from "./Paths/ContentFile";
import { LiteratureName } from "./Literature/data/LiteratureNames";
export const enums: NSEnums = {
CityName,
@@ -446,22 +446,22 @@ export const ns: InternalAPI<NSFull> = {
}
const str = helpers.argsToString(args);
if (str.startsWith("ERROR") || str.startsWith("FAIL")) {
Terminal.error(`${ctx.workerScript.scriptRef.filename}: ${str}`);
Terminal.error(`${ctx.workerScript.name}: ${str}`);
return;
}
if (str.startsWith("SUCCESS")) {
Terminal.success(`${ctx.workerScript.scriptRef.filename}: ${str}`);
Terminal.success(`${ctx.workerScript.name}: ${str}`);
return;
}
if (str.startsWith("WARN")) {
Terminal.warn(`${ctx.workerScript.scriptRef.filename}: ${str}`);
Terminal.warn(`${ctx.workerScript.name}: ${str}`);
return;
}
if (str.startsWith("INFO")) {
Terminal.info(`${ctx.workerScript.scriptRef.filename}: ${str}`);
Terminal.info(`${ctx.workerScript.name}: ${str}`);
return;
}
Terminal.print(`${ctx.workerScript.scriptRef.filename}: ${str}`);
Terminal.print(`${ctx.workerScript.name}: ${str}`);
},
tprintf:
(ctx) =>
@@ -583,7 +583,7 @@ export const ns: InternalAPI<NSFull> = {
helpers.log(ctx, () => `Already have root access to '${server.hostname}'.`);
return true;
}
if (!Player.hasProgram(Programs.NukeProgram.name)) {
if (!Player.hasProgram(CompletedProgramName.nuke)) {
throw helpers.makeRuntimeErrorMsg(ctx, "You do not have the NUKE.exe virus!");
}
if (server.openPortCount < server.numOpenPortsRequired) {
@@ -600,7 +600,7 @@ export const ns: InternalAPI<NSFull> = {
helpers.log(ctx, () => "Cannot be executed on this server.");
return false;
}
if (!Player.hasProgram(Programs.BruteSSHProgram.name)) {
if (!Player.hasProgram(CompletedProgramName.bruteSsh)) {
throw helpers.makeRuntimeErrorMsg(ctx, "You do not have the BruteSSH.exe program!");
}
if (!server.sshPortOpen) {
@@ -619,7 +619,7 @@ export const ns: InternalAPI<NSFull> = {
helpers.log(ctx, () => "Cannot be executed on this server.");
return false;
}
if (!Player.hasProgram(Programs.FTPCrackProgram.name)) {
if (!Player.hasProgram(CompletedProgramName.ftpCrack)) {
throw helpers.makeRuntimeErrorMsg(ctx, "You do not have the FTPCrack.exe program!");
}
if (!server.ftpPortOpen) {
@@ -638,7 +638,7 @@ export const ns: InternalAPI<NSFull> = {
helpers.log(ctx, () => "Cannot be executed on this server.");
return false;
}
if (!Player.hasProgram(Programs.RelaySMTPProgram.name)) {
if (!Player.hasProgram(CompletedProgramName.relaySmtp)) {
throw helpers.makeRuntimeErrorMsg(ctx, "You do not have the relaySMTP.exe program!");
}
if (!server.smtpPortOpen) {
@@ -657,7 +657,7 @@ export const ns: InternalAPI<NSFull> = {
helpers.log(ctx, () => "Cannot be executed on this server.");
return false;
}
if (!Player.hasProgram(Programs.HTTPWormProgram.name)) {
if (!Player.hasProgram(CompletedProgramName.httpWorm)) {
throw helpers.makeRuntimeErrorMsg(ctx, "You do not have the HTTPWorm.exe program!");
}
if (!server.httpPortOpen) {
@@ -676,7 +676,7 @@ export const ns: InternalAPI<NSFull> = {
helpers.log(ctx, () => "Cannot be executed on this server.");
return false;
}
if (!Player.hasProgram(Programs.SQLInjectProgram.name)) {
if (!Player.hasProgram(CompletedProgramName.sqlInject)) {
throw helpers.makeRuntimeErrorMsg(ctx, "You do not have the SQLInject.exe program!");
}
if (!server.sqlPortOpen) {
@@ -691,30 +691,30 @@ export const ns: InternalAPI<NSFull> = {
run:
(ctx) =>
(_scriptname, _thread_or_opt = 1, ..._args) => {
const scriptname = helpers.string(ctx, "scriptname", _scriptname);
const path = resolveScriptFilePath(helpers.string(ctx, "scriptname", _scriptname), ctx.workerScript.name);
if (!path) throw helpers.makeRuntimeErrorMsg(ctx, `${path} is not a valid script file path.`);
const runOpts = helpers.runOptions(ctx, _thread_or_opt);
const args = helpers.scriptArgs(ctx, _args);
const scriptServer = GetServer(ctx.workerScript.hostname);
if (scriptServer == null) {
throw helpers.makeRuntimeErrorMsg(ctx, "Could not find server. This is a bug. Report to dev.");
}
const scriptServer = ctx.workerScript.getServer();
return runScriptFromScript("run", scriptServer, scriptname, args, ctx.workerScript, runOpts);
return runScriptFromScript("run", scriptServer, path, args, ctx.workerScript, runOpts);
},
exec:
(ctx) =>
(_scriptname, _hostname, _thread_or_opt = 1, ..._args) => {
const scriptname = helpers.string(ctx, "scriptname", _scriptname);
const path = resolveScriptFilePath(helpers.string(ctx, "scriptname", _scriptname), ctx.workerScript.name);
if (!path) throw helpers.makeRuntimeErrorMsg(ctx, `${path} is not a valid script file path.`);
const hostname = helpers.string(ctx, "hostname", _hostname);
const runOpts = helpers.runOptions(ctx, _thread_or_opt);
const args = helpers.scriptArgs(ctx, _args);
const server = helpers.getServer(ctx, hostname);
return runScriptFromScript("exec", server, scriptname, args, ctx.workerScript, runOpts);
return runScriptFromScript("exec", server, path, args, ctx.workerScript, runOpts);
},
spawn:
(ctx) =>
(_scriptname, _thread_or_opt = 1, ..._args) => {
const scriptname = helpers.string(ctx, "scriptname", _scriptname);
const path = resolveScriptFilePath(helpers.string(ctx, "scriptname", _scriptname), ctx.workerScript.name);
if (!path) throw helpers.makeRuntimeErrorMsg(ctx, `${path} is not a valid script file path.`);
const runOpts = helpers.runOptions(ctx, _thread_or_opt);
const args = helpers.scriptArgs(ctx, _args);
const spawnDelay = 10;
@@ -724,10 +724,10 @@ export const ns: InternalAPI<NSFull> = {
throw helpers.makeRuntimeErrorMsg(ctx, "Could not find server. This is a bug. Report to dev");
}
return runScriptFromScript("spawn", scriptServer, scriptname, args, ctx.workerScript, runOpts);
return runScriptFromScript("spawn", scriptServer, path, args, ctx.workerScript, runOpts);
}, spawnDelay * 1e3);
helpers.log(ctx, () => `Will execute '${scriptname}' in ${spawnDelay} seconds`);
helpers.log(ctx, () => `Will execute '${path}' in ${spawnDelay} seconds`);
if (killWorkerScript(ctx.workerScript)) {
helpers.log(ctx, () => "Exiting...");
@@ -804,108 +804,72 @@ export const ns: InternalAPI<NSFull> = {
killWorkerScript(ctx.workerScript);
throw new ScriptDeath(ctx.workerScript);
},
scp:
(ctx) =>
(_files, _destination, _source = ctx.workerScript.hostname) => {
const destination = helpers.string(ctx, "destination", _destination);
const source = helpers.string(ctx, "source", _source);
const destServer = helpers.getServer(ctx, destination);
const sourceServ = helpers.getServer(ctx, source);
const files = Array.isArray(_files) ? _files : [_files];
scp: (ctx) => (_files, _destination, _source) => {
const destination = helpers.string(ctx, "destination", _destination);
const source = helpers.string(ctx, "source", _source ?? ctx.workerScript.hostname);
const destServer = helpers.getServer(ctx, destination);
const sourceServer = helpers.getServer(ctx, source);
const files = Array.isArray(_files) ? _files : [_files];
const lits: (FilePath & LiteratureName)[] = [];
const contentFiles: ContentFilePath[] = [];
//First loop through filenames to find all errors before moving anything.
for (const file of files) {
// Not a string
if (typeof file !== "string") {
throw helpers.makeRuntimeErrorMsg(ctx, "files should be a string or an array of strings.");
}
if (hasScriptExtension(file) || hasTextExtension(file)) {
const path = resolveScriptFilePath(file, ctx.workerScript.name);
if (!path) throw helpers.makeRuntimeErrorMsg(ctx, `Invalid filepath: ${file}`);
contentFiles.push(path);
continue;
}
if (!file.endsWith(".lit")) {
throw helpers.makeRuntimeErrorMsg(ctx, "Only works for scripts, .lit and .txt files.");
}
const sanitizedPath = resolveFilePath(file, ctx.workerScript.name);
if (!sanitizedPath || !checkEnum(LiteratureName, sanitizedPath)) {
throw helpers.makeRuntimeErrorMsg(ctx, `Invalid filename: '${file}'`);
}
lits.push(sanitizedPath);
}
//First loop through filenames to find all errors before moving anything.
for (const file of files) {
// Not a string
if (typeof file !== "string")
throw helpers.makeRuntimeErrorMsg(ctx, "files should be a string or an array of strings.");
let noFailures = true;
// --- Scripts and Text Files---
for (const contentFilePath of contentFiles) {
const sourceContentFile = sourceServer.getContentFile(contentFilePath);
if (!sourceContentFile) {
helpers.log(ctx, () => `File '${contentFilePath}' does not exist.`);
noFailures = false;
continue;
}
// Overwrite script if it already exists
const result = destServer.writeToContentFile(contentFilePath, sourceContentFile.content);
helpers.log(ctx, () => `Copied file ${contentFilePath} from ${sourceServer} to ${destServer}`);
if (result.overwritten) helpers.log(ctx, () => `Warning: ${contentFilePath} was overwritten on ${destServer}`);
}
// Invalid file name
if (!isValidFilePath(file)) throw helpers.makeRuntimeErrorMsg(ctx, `Invalid filename: '${file}'`);
// Invalid file type
if (!file.endsWith(".lit") && !isScriptFilename(file) && !file.endsWith(".txt")) {
throw helpers.makeRuntimeErrorMsg(ctx, "Only works for scripts, .lit and .txt files.");
}
// --- Literature Files ---
for (const litFilePath of lits) {
const sourceMessage = sourceServer.messages.find((message) => message === litFilePath);
if (!sourceMessage) {
helpers.log(ctx, () => `File '${litFilePath}' does not exist.`);
noFailures = false;
continue;
}
let noFailures = true;
//ts detects files as any[] here even though we would have thrown in the above loop if it wasn't string[]
for (let file of files as string[]) {
// cut off the leading / for files in the root of the server; this assumes that the filename is somewhat normalized and doesn't look like `//file.js`
if (file.startsWith("/") && file.indexOf("/", 1) === -1) file = file.slice(1);
// Scp for lit files
if (file.endsWith(".lit")) {
const sourceMessage = sourceServ.messages.find((message) => message === file);
if (!sourceMessage) {
helpers.log(ctx, () => `File '${file}' does not exist.`);
noFailures = false;
continue;
}
const destMessage = destServer.messages.find((message) => message === file);
if (destMessage) {
helpers.log(ctx, () => `File '${file}' was already on '${destServer?.hostname}'.`);
continue;
}
destServer.messages.push(file);
helpers.log(ctx, () => `File '${file}' copied over to '${destServer?.hostname}'.`);
continue;
}
// Scp for text files
if (file.endsWith(".txt")) {
const sourceTextFile = sourceServ.textFiles.find((textFile) => textFile.fn === file);
if (!sourceTextFile) {
helpers.log(ctx, () => `File '${file}' does not exist.`);
noFailures = false;
continue;
}
const destTextFile = destServer.textFiles.find((textFile) => textFile.fn === file);
if (destTextFile) {
destTextFile.text = sourceTextFile.text;
helpers.log(ctx, () => `File '${file}' overwritten on '${destServer?.hostname}'.`);
continue;
}
const newFile = new TextFile(sourceTextFile.fn, sourceTextFile.text);
destServer.textFiles.push(newFile);
helpers.log(ctx, () => `File '${file}' copied over to '${destServer?.hostname}'.`);
continue;
}
// Scp for script files
const sourceScript = sourceServ.scripts.get(file);
if (!sourceScript) {
helpers.log(ctx, () => `File '${file}' does not exist.`);
noFailures = false;
continue;
}
// Overwrite script if it already exists
const destScript = destServer.scripts.get(file);
if (destScript) {
if (destScript.code === sourceScript.code) {
helpers.log(ctx, () => `Identical file '${file}' was already on '${destServer?.hostname}'`);
continue;
}
destScript.code = sourceScript.code;
// Set ramUsage to null in order to force a recalculation prior to next run.
destScript.invalidateModule();
helpers.log(ctx, () => `WARNING: File '${file}' overwritten on '${destServer?.hostname}'`);
continue;
}
// Create new script if it does not already exist
const newScript = new Script(file, sourceScript.code, destServer.hostname);
destServer.scripts.set(file, newScript);
helpers.log(ctx, () => `File '${file}' copied over to '${destServer?.hostname}'.`);
const destMessage = destServer.messages.find((message) => message === litFilePath);
if (destMessage) {
helpers.log(ctx, () => `File '${litFilePath}' was already on '${destServer?.hostname}'.`);
continue;
}
return noFailures;
},
destServer.messages.push(litFilePath);
helpers.log(ctx, () => `File '${litFilePath}' copied over to '${destServer?.hostname}'.`);
continue;
}
return noFailures;
},
ls: (ctx) => (_hostname, _substring) => {
const hostname = helpers.string(ctx, "hostname", _hostname);
const substring = helpers.string(ctx, "substring", _substring ?? "");
@@ -916,7 +880,7 @@ export const ns: InternalAPI<NSFull> = {
...server.messages,
...server.programs,
...server.scripts.keys(),
...server.textFiles.map((textFile) => textFile.filename),
...server.textFiles.keys(),
];
if (!substring) return allFilenames.sort();
@@ -1147,7 +1111,7 @@ export const ns: InternalAPI<NSFull> = {
const filename = helpers.string(ctx, "filename", _filename);
const hostname = helpers.string(ctx, "hostname", _hostname);
const server = helpers.getServer(ctx, hostname);
if (server.scripts.has(filename)) return true;
if (server.scripts.has(filename) || server.textFiles.has(filename)) return true;
for (let i = 0; i < server.programs.length; ++i) {
if (filename.toLowerCase() == server.programs[i].toLowerCase()) {
return true;
@@ -1160,8 +1124,7 @@ export const ns: InternalAPI<NSFull> = {
}
const contract = server.contracts.find((c) => c.fn.toLowerCase() === filename.toLowerCase());
if (contract) return true;
const txtFile = getTextFile(filename, server);
return txtFile != null;
return false;
},
isRunning:
(ctx) =>
@@ -1363,43 +1326,34 @@ export const ns: InternalAPI<NSFull> = {
return writePort(portNumber, data);
},
write: (ctx) => (_filename, _data, _mode) => {
let filename = helpers.string(ctx, "handle", _filename);
const filepath = resolveFilePath(helpers.string(ctx, "filename", _filename), ctx.workerScript.name);
const data = helpers.string(ctx, "data", _data ?? "");
const mode = helpers.string(ctx, "mode", _mode ?? "a");
if (!isValidFilePath(filename)) throw helpers.makeRuntimeErrorMsg(ctx, `Invalid filepath: ${filename}`);
if (filename.lastIndexOf("/") === 0) filename = removeLeadingSlash(filename);
if (!filepath) throw helpers.makeRuntimeErrorMsg(ctx, `Invalid filepath: ${filepath}`);
const server = helpers.getServer(ctx, ctx.workerScript.hostname);
if (isScriptFilename(filename)) {
// Write to script
let script = ctx.workerScript.getScriptOnServer(filename, server);
if (!script) {
// Create a new script
script = new Script(filename, String(data), server.hostname);
server.scripts.set(filename, script);
return;
}
mode === "w" ? (script.code = data) : (script.code += data);
// Set ram to null so a recalc is performed the next time ram usage is needed
script.invalidateModule();
return;
} else {
// Write to text file
if (!filename.endsWith(".txt")) throw helpers.makeRuntimeErrorMsg(ctx, `Invalid filename: ${filename}`);
const txtFile = getTextFile(filename, server);
if (txtFile == null) {
createTextFile(filename, String(data), server);
return;
}
if (hasScriptExtension(filepath)) {
if (mode === "w") {
txtFile.write(String(data));
} else {
txtFile.append(String(data));
server.writeToScriptFile(filepath, data);
return;
}
const existingScript = server.scripts.get(filepath);
const existingCode = existingScript ? existingScript.code : "";
server.writeToScriptFile(filepath, existingCode + data);
return;
}
return;
if (!hasTextExtension(filepath)) {
throw helpers.makeRuntimeErrorMsg(ctx, `File path should be a text file or script. ${filepath} is invalid.`);
}
if (mode === "w") {
server.writeToTextFile(filepath, data);
return;
}
const existingTextFile = server.textFiles.get(filepath);
const existingText = existingTextFile?.text ?? "";
server.writeToTextFile(filepath, mode === "w" ? data : existingText + data);
},
tryWritePort: (ctx) => (_portNumber, data) => {
const portNumber = helpers.portNumber(ctx, _portNumber);
@@ -1416,48 +1370,25 @@ export const ns: InternalAPI<NSFull> = {
return readPort(portNumber);
},
read: (ctx) => (_filename) => {
const fn = helpers.string(ctx, "filename", _filename);
const server = GetServer(ctx.workerScript.hostname);
if (server == null) {
throw helpers.makeRuntimeErrorMsg(ctx, "Error getting Server. This is a bug. Report to dev.");
}
if (isScriptFilename(fn)) {
// Read from script
const script = ctx.workerScript.getScriptOnServer(fn, server);
if (script == null) {
return "";
}
return script.code;
} else {
// Read from text file
const txtFile = getTextFile(fn, server);
if (txtFile !== null) {
return txtFile.text;
} else {
return "";
}
}
const path = resolveFilePath(helpers.string(ctx, "filename", _filename), ctx.workerScript.name);
if (!path || (!hasScriptExtension(path) && !hasTextExtension(path))) return "";
const server = ctx.workerScript.getServer();
return server.getContentFile(path)?.content ?? "";
},
peek: (ctx) => (_portNumber) => {
const portNumber = helpers.portNumber(ctx, _portNumber);
return peekPort(portNumber);
},
clear: (ctx) => (_file) => {
const file = helpers.string(ctx, "file", _file);
if (isString(file)) {
// Clear text file
const fn = file;
const server = GetServer(ctx.workerScript.hostname);
if (server == null) {
throw helpers.makeRuntimeErrorMsg(ctx, "Error getting Server. This is a bug. Report to dev.");
}
const txtFile = getTextFile(fn, server);
if (txtFile != null) {
txtFile.write("");
}
} else {
throw helpers.makeRuntimeErrorMsg(ctx, `Invalid argument: ${file}`);
const path = resolveFilePath(helpers.string(ctx, "file", _file), ctx.workerScript.name);
if (!path || (!hasScriptExtension(path) && !hasTextExtension(path))) {
throw helpers.makeRuntimeErrorMsg(ctx, `Invalid file path or extension: ${_file}`);
}
const server = ctx.workerScript.getServer();
const file = server.getContentFile(path);
if (!file) throw helpers.makeRuntimeErrorMsg(ctx, `${path} does not exist on ${server.hostname}`);
// The content setter handles invalidating script modules where applicable.
file.content = "";
},
clearPort: (ctx) => (_portNumber) => {
const portNumber = helpers.portNumber(ctx, _portNumber);
@@ -1467,20 +1398,22 @@ export const ns: InternalAPI<NSFull> = {
const portNumber = helpers.portNumber(ctx, _portNumber);
return portHandle(portNumber);
},
rm:
(ctx) =>
(_fn, _hostname = ctx.workerScript.hostname) => {
const fn = helpers.string(ctx, "fn", _fn);
const hostname = helpers.string(ctx, "hostname", _hostname);
const s = helpers.getServer(ctx, hostname);
rm: (ctx) => (_fn, _hostname) => {
const filepath = resolveFilePath(helpers.string(ctx, "fn", _fn), ctx.workerScript.name);
const hostname = helpers.string(ctx, "hostname", _hostname ?? ctx.workerScript.hostname);
const s = helpers.getServer(ctx, hostname);
if (!filepath) {
helpers.log(ctx, () => `Error while parsing filepath ${filepath}`);
return false;
}
const status = s.removeFile(fn);
if (!status.res) {
helpers.log(ctx, () => status.msg + "");
}
const status = s.removeFile(filepath);
if (!status.res) {
helpers.log(ctx, () => status.msg + "");
}
return status.res;
},
return status.res;
},
scriptRunning: (ctx) => (_scriptname, _hostname) => {
const scriptname = helpers.string(ctx, "scriptname", _scriptname);
const hostname = helpers.string(ctx, "hostname", _hostname);
@@ -1506,18 +1439,17 @@ export const ns: InternalAPI<NSFull> = {
}
return suc;
},
getScriptName: (ctx) => () => {
return ctx.workerScript.name;
},
getScriptName: (ctx) => () => ctx.workerScript.name,
getScriptRam: (ctx) => (_scriptname, _hostname) => {
const scriptname = helpers.string(ctx, "scriptname", _scriptname);
const path = resolveScriptFilePath(helpers.string(ctx, "scriptname", _scriptname), ctx.workerScript.name);
if (!path) throw helpers.makeRuntimeErrorMsg(ctx, `Could not parse file path ${_scriptname}`);
const hostname = helpers.string(ctx, "hostname", _hostname ?? ctx.workerScript.hostname);
const server = helpers.getServer(ctx, hostname);
const script = server.scripts.get(scriptname);
const script = server.scripts.get(path);
if (!script) return 0;
const ramUsage = script.getRamUsage(server.scripts);
if (!ramUsage) {
helpers.log(ctx, () => `Could not calculate ram usage for ${scriptname} on ${hostname}.`);
helpers.log(ctx, () => `Could not calculate ram usage for ${path} on ${hostname}.`);
return 0;
}
return ramUsage;
@@ -1704,26 +1636,22 @@ export const ns: InternalAPI<NSFull> = {
},
wget: (ctx) => async (_url, _target, _hostname) => {
const url = helpers.string(ctx, "url", _url);
const target = helpers.string(ctx, "target", _target);
const target = resolveFilePath(helpers.string(ctx, "target", _target), ctx.workerScript.name);
const hostname = _hostname ? helpers.string(ctx, "hostname", _hostname) : ctx.workerScript.hostname;
if (!isScriptFilename(target) && !target.endsWith(".txt")) {
const server = helpers.getServer(ctx, hostname);
if (!target || (!hasTextExtension(target) && !hasScriptExtension(target))) {
helpers.log(ctx, () => `Invalid target file: '${target}'. Must be a script or text file.`);
return Promise.resolve(false);
}
const s = helpers.getServer(ctx, hostname);
return new Promise(function (resolve) {
$.get(
url,
function (data) {
let res;
if (isScriptFilename(target)) {
res = s.writeToScriptFile(target, data);
if (hasScriptExtension(target)) {
res = server.writeToScriptFile(target, data);
} else {
res = s.writeToTextFile(target, data);
}
if (!res.success) {
helpers.log(ctx, () => "Failed.");
return resolve(false);
res = server.writeToTextFile(target, data);
}
if (res.overwritten) {
helpers.log(ctx, () => `Successfully retrieved content and overwrote '${target}' on '${hostname}'`);
@@ -1790,59 +1718,34 @@ export const ns: InternalAPI<NSFull> = {
},
mv: (ctx) => (_host, _source, _destination) => {
const hostname = helpers.string(ctx, "host", _host);
const source = helpers.string(ctx, "source", _source);
const destination = helpers.string(ctx, "destination", _destination);
const server = helpers.getServer(ctx, hostname);
const sourcePath = resolveFilePath(helpers.string(ctx, "source", _source));
if (!sourcePath) throw helpers.makeRuntimeErrorMsg(ctx, `Invalid source filename: '${_source}'`);
const destinationPath = resolveFilePath(helpers.string(ctx, "destination", _destination), sourcePath);
if (!destinationPath) throw helpers.makeRuntimeErrorMsg(ctx, `Invalid destination filename: '${destinationPath}'`);
if (!isValidFilePath(source)) throw helpers.makeRuntimeErrorMsg(ctx, `Invalid filename: '${source}'`);
if (!isValidFilePath(destination)) throw helpers.makeRuntimeErrorMsg(ctx, `Invalid filename: '${destination}'`);
const source_is_txt = source.endsWith(".txt");
const dest_is_txt = destination.endsWith(".txt");
if (!isScriptFilename(source) && !source_is_txt)
if (
(!hasTextExtension(sourcePath) && !hasScriptExtension(sourcePath)) ||
(!hasTextExtension(destinationPath) && !hasScriptExtension(destinationPath))
) {
throw helpers.makeRuntimeErrorMsg(ctx, `'mv' can only be used on scripts and text files (.txt)`);
if (source_is_txt != dest_is_txt)
throw helpers.makeRuntimeErrorMsg(ctx, `Source and destination files must have the same type`);
if (source === destination) {
}
if (sourcePath === destinationPath) {
helpers.log(ctx, () => "WARNING: Did nothing, source and destination paths were the same.");
return;
}
const server = helpers.getServer(ctx, hostname);
if (!source_is_txt && server.isRunning(source))
throw helpers.makeRuntimeErrorMsg(ctx, `Cannot use 'mv' on a script that is running`);
interface File {
filename: string;
const sourceContentFile = server.getContentFile(sourcePath);
if (!sourceContentFile) {
throw helpers.makeRuntimeErrorMsg(ctx, `Source text file ${sourcePath} does not exist on ${hostname}`);
}
let source_file: File | undefined;
let dest_file: File | undefined;
if (source_is_txt) {
// Traverses twice potentially. Inefficient but will soon be replaced with a map.
source_file = server.textFiles.find((textFile) => textFile.filename === source);
dest_file = server.textFiles.find((textFile) => textFile.filename === destination);
} else {
source_file = server.scripts.get(source);
dest_file = server.scripts.get(destination);
}
if (!source_file) throw helpers.makeRuntimeErrorMsg(ctx, `Source file ${source} does not exist`);
if (dest_file) {
if (dest_file instanceof TextFile && source_file instanceof TextFile) {
dest_file.text = source_file.text;
} else if (dest_file instanceof Script && source_file instanceof Script) {
dest_file.code = source_file.code;
// Source needs to be invalidated as well, to invalidate its dependents
source_file.invalidateModule();
dest_file.invalidateModule();
}
server.removeFile(source);
} else {
source_file.filename = destination;
if (source_file instanceof Script) source_file.invalidateModule();
const success = sourceContentFile.deleteFromServer(server);
if (success) {
const { overwritten } = server.writeToContentFile(destinationPath, sourceContentFile.content);
if (overwritten) helpers.log(ctx, () => `WARNING: Overwriting file ${destinationPath} on ${hostname}`);
helpers.log(ctx, () => `Moved ${sourcePath} to ${destinationPath} on ${hostname}`);
return;
}
helpers.log(ctx, () => `ERROR: Failed. Was unable to remove file ${sourcePath} from its original location.`);
},
flags: Flags,
...NetscriptExtra(),