FileCases does what the windows of FILIUS' file explorer and text editor do to a host's files: new folder, delete, copy, cut and paste, rename, import, save and save as. The core's filePrograms.ts does the same on the node's file system; the file system renames an entry in its place, as Java's tree does. Departures from Java: a folder name that comes to no name makes nothing, a paste onto a name there already is refused, a rename to a name with a slash or blanks at its ends is refused and one of the case only is done, save as and import onto a folder's name are refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
183 lines
9.0 KiB
Java
183 lines
9.0 KiB
Java
// Feeds what FILIUS' file explorer, text editor and image viewer do to a
|
|
// host's files, as their windows call FILIUS (GUIApplicationFileExplorerWindow,
|
|
// GUIApplicationTextEditorWindow; the image viewer only reads), without a
|
|
// running network, and writes after each step what it gave back (a message,
|
|
// an exception) and every file and directory of the host, and the explorer's
|
|
// messages as Java loads them: the reference for the core's file programs
|
|
// (packages/core/src/runtime/apps/filePrograms.ts).
|
|
// Each case starts on host 10.0.0.2 of a scenario with TerminalCases' files.
|
|
// The cases file is ASCII, with Java-style unicode escapes; a step is an
|
|
// operation and its fields, separated by tabs:
|
|
//
|
|
// mkdir <folder> <name> the explorer's "new folder" (an empty name: nothing)
|
|
// delete <folder> <name> the explorer's "delete" of an entry of the folder
|
|
// copy|cut <folder> <name> the explorer's clipboard
|
|
// paste <folder> the explorer's "paste" into the folder
|
|
// rename <folder> <name> <new> the explorer's "rename"
|
|
// import <folder> <new> <local name> <content>
|
|
// the explorer's import of a local file (FileExplorer.addFile);
|
|
// content @image (TerminalCases' image), @binary (bytes 0 to 255),
|
|
// @bytes:N (N times 'a'), else the text
|
|
// save <path> <text> the editor's "save" of the open file
|
|
// saveas <folder> <name> <text> the editor's "save as"
|
|
//
|
|
// Usage: java -cp filius.jar:lib/*:classes FileCases <de|en> <scenario.json> <cases txt> <output json>
|
|
|
|
import filius.rahmenprogramm.Base64;
|
|
import filius.rahmenprogramm.Information;
|
|
import filius.software.lokal.FileExplorer;
|
|
import filius.software.system.Datei;
|
|
import filius.software.system.Dateisystem;
|
|
import filius.software.system.InternetKnotenBetriebssystem;
|
|
|
|
import java.io.ByteArrayInputStream;
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.FileOutputStream;
|
|
import java.io.ObjectInputStream;
|
|
import java.io.ObjectOutputStream;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.LinkedHashMap;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.Map;
|
|
import javax.swing.tree.DefaultMutableTreeNode;
|
|
|
|
public class FileCases {
|
|
static DefaultMutableTreeNode clipboard;
|
|
static boolean cut;
|
|
|
|
public static void main(String[] args) throws Exception {
|
|
Information.getInformation().setLocale(args[0].equals("en") ? new Locale("en", "GB") : Locale.GERMANY);
|
|
ScenarioNetwork network = ScenarioNetwork.read(args[1]);
|
|
InternetKnotenBetriebssystem system = network.system(TerminalCases.HOST);
|
|
system.installApp("filius.software.lokal.FileExplorer", false);
|
|
FileExplorer explorer = (FileExplorer) system.holeSoftware("filius.software.lokal.FileExplorer");
|
|
explorer.setSystemSoftware(system);
|
|
Dateisystem files = system.getDateisystem();
|
|
Path local = Files.createTempDirectory("filecases");
|
|
List<Object> cases = new ArrayList<>();
|
|
List<Object> steps = null;
|
|
for (String line : Files.readAllLines(Path.of(args[2]), StandardCharsets.US_ASCII)) {
|
|
if (line.startsWith("#")) continue;
|
|
if (line.startsWith("case ")) {
|
|
Map<String, Object> current = new LinkedHashMap<>();
|
|
steps = new ArrayList<>();
|
|
current.put("name", line.substring("case ".length()));
|
|
current.put("steps", steps);
|
|
cases.add(current);
|
|
TerminalCases.files(files);
|
|
clipboard = null;
|
|
current.put("start", TerminalCases.tree(files));
|
|
continue;
|
|
}
|
|
String[] fields = line.split("\t", -1);
|
|
for (int index = 0; index < fields.length; index++) fields[index] = IpCheckCases.unescape(fields[index]);
|
|
Map<String, Object> step = new LinkedHashMap<>();
|
|
step.put("step", Arrays.asList(fields));
|
|
Object returned;
|
|
try {
|
|
returned = run(fields, files, explorer, local);
|
|
} catch (Exception error) {
|
|
returned = "!" + error.getClass().getSimpleName();
|
|
}
|
|
step.put("returned", returned == null ? null : returned.toString());
|
|
step.put("files", TerminalCases.tree(files));
|
|
steps.add(step);
|
|
}
|
|
Map<String, Object> out = new LinkedHashMap<>();
|
|
out.put("cases", cases);
|
|
// The messages the explorer shows (its import's, and its paste's refusal), as Java loads them.
|
|
Map<String, Object> texts = new LinkedHashMap<>();
|
|
for (String key : new String[] { "sw_fileexplorer_msg1", "sw_fileexplorer_msg2", "sw_fileexplorer_msg3", "fileexplorer_msg19" }) {
|
|
texts.put(key, explorer.messages.getString(key));
|
|
}
|
|
out.put("texts", texts);
|
|
try (FileOutputStream stream = new FileOutputStream(args[3])) {
|
|
stream.write((FlsOracle.Json.write(out, "") + "\n").getBytes(StandardCharsets.UTF_8));
|
|
}
|
|
System.out.println("file cases (" + args[0] + "): " + cases.size());
|
|
System.exit(0);
|
|
}
|
|
|
|
static Object run(String[] fields, Dateisystem files, FileExplorer explorer, Path local) throws Exception {
|
|
DefaultMutableTreeNode folder = files.verzeichnisKnoten(fields[1]);
|
|
switch (fields[0]) {
|
|
case "mkdir":
|
|
// "Neuer Ordner": the name asked for, nothing when it is empty.
|
|
if (fields[2].equals("")) return null;
|
|
return files.erstelleVerzeichnis(folder, fields[2]);
|
|
case "delete":
|
|
// "Loeschen": the selected entry, found by its name, removed from the folder.
|
|
folder.remove(Dateisystem.verzeichnisKnoten(folder, fields[2]));
|
|
return null;
|
|
case "copy":
|
|
case "cut":
|
|
clipboard = Dateisystem.verzeichnisKnoten(folder, fields[2]);
|
|
cut = fields[0].equals("cut");
|
|
return null;
|
|
case "paste":
|
|
// "Einfuegen": not into the copied entry itself or below it; a deep copy added, a cut one removed.
|
|
if (clipboard.isNodeDescendant(folder)) return "fileexplorer_msg19";
|
|
folder.add(deepCopy(clipboard));
|
|
if (cut) {
|
|
((DefaultMutableTreeNode) clipboard.getParent()).remove(clipboard);
|
|
clipboard = null;
|
|
}
|
|
return null;
|
|
case "rename": {
|
|
// "Umbenennen": a name that is not blank and not there yet in the folder.
|
|
String name = fields[3];
|
|
if (name.trim().isEmpty() || files.dateiVorhanden(folder, name)) return null;
|
|
DefaultMutableTreeNode node = Dateisystem.verzeichnisKnoten(folder, fields[2]);
|
|
if (node.getUserObject() instanceof Datei) ((Datei) node.getUserObject()).setName(name);
|
|
else node.setUserObject(name);
|
|
return null;
|
|
}
|
|
case "import": {
|
|
Path file = local.resolve(fields[3]);
|
|
Files.write(file, content(fields[4]));
|
|
return explorer.addFile(local.toString() + "/", fields[3], folder, fields[2]);
|
|
}
|
|
case "save":
|
|
// "Speichern": the open file's content.
|
|
files.holeDatei(fields[1]).setDateiInhalt(fields[2]);
|
|
return null;
|
|
case "saveas":
|
|
// "Speichern unter": a new text file (texteditor_msg8) stored in the folder chosen.
|
|
return files.speicherDatei(fields[1], new Datei(fields[2], explorer.messages.getString("texteditor_msg8"), fields[3]));
|
|
default:
|
|
throw new IllegalArgumentException("unknown step " + fields[0]);
|
|
}
|
|
}
|
|
|
|
static byte[] content(String spec) throws Exception {
|
|
if (spec.equals("@image")) return Base64.decode(TerminalCases.IMAGE);
|
|
if (spec.equals("@binary")) {
|
|
byte[] bytes = new byte[256];
|
|
for (int index = 0; index < bytes.length; index++) bytes[index] = (byte) index;
|
|
return bytes;
|
|
}
|
|
if (spec.startsWith("@bytes:")) {
|
|
byte[] bytes = new byte[Integer.parseInt(spec.substring("@bytes:".length()))];
|
|
Arrays.fill(bytes, (byte) 'a');
|
|
return bytes;
|
|
}
|
|
return spec.getBytes(StandardCharsets.UTF_8);
|
|
}
|
|
|
|
/** GUIApplicationFileExplorerWindow.tiefesKopieren: the node with all below it, serialized and read back. */
|
|
static DefaultMutableTreeNode deepCopy(DefaultMutableTreeNode original) throws Exception {
|
|
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
|
try (ObjectOutputStream out = new ObjectOutputStream(buffer)) {
|
|
out.writeObject(original);
|
|
}
|
|
try (ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buffer.toByteArray()))) {
|
|
return (DefaultMutableTreeNode) in.readObject();
|
|
}
|
|
}
|
|
}
|