feat(core): the file explorer, text editor and image viewer's file operations after Java, measured against Java
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>
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user