From fb3a5cfa6472abc9b7c981965053dfa14efd0ece Mon Sep 17 00:00:00 2001 From: gnembon Date: Sun, 7 Jun 2020 01:52:38 -0300 Subject: [PATCH] read_file, delete_file, and write_file Deprecated some uses of load_app_data and store_app_data --- docs/scarpet/Full.md | 83 ++++++++++-- docs/scarpet/api/Auxiliary.md | 68 +++++++++- .../java/carpet/script/CarpetExpression.java | 128 ++++++++++++++---- .../java/carpet/script/CarpetScriptHost.java | 23 +++- src/main/java/carpet/script/Tokenizer.java | 6 +- .../carpet/script/bundled/FileModule.java | 49 ++++++- .../java/carpet/script/bundled/Module.java | 49 +++++-- 7 files changed, 351 insertions(+), 55 deletions(-) diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md index 717784c7..0e1a614e 100644 --- a/docs/scarpet/Full.md +++ b/docs/scarpet/Full.md @@ -3154,25 +3154,23 @@ Optional shared shape attributes: in the form of `0xRRGGBBAA`, with the default of `-1`, so white opaque, or `0xFFFFFFFF`. * `player` - name or player entity to send the shape to. If specified, the shapes will appear only for the specified player, otherwise it will be send to all players in the dimension. + * `width` - line thickness, defaults to 2.0pt + * `fill` - color for the faces, defaults to no fill. Use `color` attribute format + * `follow` - entity, or player name. Shape will follow an entity instead of being static. + Follow attribute requires all positional arguments to be relative to the entity and disallow + of using entity or block as position markers. You must specify positions as a triple. Available shapes: * `'line'` - draws a straight line between two points * Required attributes: * `from` - triple coordinates, entity, or block value indicating one end of the line * `to` - other end of the line, same format as `from` - * Optional attributes: - * `width` - line thickness, defaults to 2.0pt - * `follow` - entity, or player name. Shape will follow an entity instead of being static. - Follow attribute requires all positional arguments to be relative to the entity and disallow - of using entity or block as position markers. You must specify positions as a triple. * `'box'` - draws a box with corners in specified points * Required attributes: * `from` - triple coordinates, entity, or block value indicating one corner of the box * `to` - other corner, same format as `from` - * Optional attributes: - * `width` - mesh line thickness, defaults to 2.0pt - * `fill` - color for the box faces, defaults to no fill, use shared color attribute format + * `'sphere'` - draws a sphere * Required attributes: * `center` - center of the sphere @@ -3180,7 +3178,6 @@ Available shapes: * Optional attributes: * `level` - level of details, or grid size. The more the denser your sphere. Default level of 0, means that the level of detail will be selected automatically based on radius. - * `width` - line thickness ### `create_marker(text, pos, rotation?, block?)` @@ -3269,6 +3266,63 @@ Example usages: Prints the message to system logs, and not to chat. +### read_file(resource, type) +### delete_file(resource, type) +### write_file(resource, type, data, ...) + +With the specified `resource` in the scripts folder, of a specific `type`, writes/appends `data` to it, reads its + content, or deletes the resource. + +Resource is identified by a path to the file. +A path can contain letters, numbers and folder separator: `'/'`. Any other characters are stripped +from the name. Empty descriptors are invalid. Do not add file extensions to the descriptor - extensions are inferred +based on the `type` of the file. + +Resources can be located in the app specific space, or a shared space for all the apps. Accessing of app-specific +resources is guaranteed to be isolated from other apps. Shared resources are... well, shared across all apes, meaning +they can eat of each others file, however all access to files is synchronized, and files are never left open, so +this should not lead to any access problems. + +If the app's name is `'foo'`, the script location would +be `world/scripts/foo.sc`, app +specific data directory is under `world/scripts/foo.data/...`, and shared data space is under +`world/scripts/shared/...`. + +The default no-name app, via `/script run` command can only save/load/read files from the shared space. + +Functions return `null` if an error is encounter or no file is present (for read and delete operations). Returns `true` +for success writes and deletes, and requested data, based on the file type, for read operations. + +NBT files can be written once as they an store one tag at a time. Consecutive writes will overwrite previous data. + +Write operations to text files always result in appending to the existing file, so consecutive writes will increase +the size of the file and add data to it. Since files are closed after each write, sending multiple lines of data to +write is beneficial for writing speed. To send multiple packs of data, either provide them flat or as a list in the +third argument. + * `write_file('temp', 'text', 'foo', 'bar', 'baz')` or + * write_file('temp', 'text', l('foo', 'bar', 'baz')) + +To log a single line of string + +Supported values for resource `type` is: + * `nbt` - NBT tag + * `text` - text resource with automatic newlines added + * `raw` - text resource without implied newlines + * `shared_nbt`, `shared_text`, `shared_raw` - shared versions of the above + +NBT files have extension `.nbt`, store one NBT tag, and return a NBT type value. Text files have `.txt` extension, +stores multiple lines of text and returns lists of all lines from the file. With `write_file`, multiple lines can be +sent to the file at once. The only difference between `raw` and `text` types are automatic newlines added after each +record to the file. + +
+write_file('foo', 'shared_text, l('one', 'two'));
+write_file('foo', 'shared_text', 'three\n', 'four\n');
+write_file('foo', 'shared_raw', 'five\n', 'six\n');
+
+read_file('foo', 'shared_text')     => ['one', 'two', 'three', '', 'four', '', 'five', 'six']
+
+ ### `run(expr)` Runs a vanilla command from the string result of the `expr` and returns its success count @@ -3283,7 +3337,10 @@ run('give @s stone 4') -> 1 // this operation was successful once Performs autosave, saves all chunks, player data, etc. Useful for programs where autosave is disabled due to performance reasons and saves the world only on demand. -### `load_app_data(), load_app_data(file), load_app_data(file, shared?)` +### `load_app_data()` + +NOTE: usages with arguments, so `load_app_data(file)` and `load_app_data(file, shared?)` are deprecated. +Use `read_file` instead. Loads the app data associated with the app from the world /scripts folder. Without argument returns the memory managed and buffered / throttled NBT tag. With a file name, reads explicitly a file with that name from the @@ -3291,7 +3348,7 @@ scripts folder that belongs exclusively to the app. if `shared` is true, the fil to the app anymore, but located in a shared app space. File descriptor can contain letters, numbers and folder separator: `'/'`. Any other characters are stripped -from the name before saving/loading. Empty descriptors are invalid. +from the name before saving/loading. Empty descriptors are invalid. Do not add file extensions to the descriptor Function returns nbt value with the file content, or `null` if the file is missing or there were problems with retrieving the data. @@ -3305,7 +3362,9 @@ specific data directory is under `world/scripts/foo.data/bar/../baz.nbt`, and sh You can use app data to save non-vanilla information separately from the world and other scripts. -### `store_app_data(tag), store_app_data(tag, file), store_app_data(tag, file, shared?)` +### `store_app_data(tag)` + +Note: `store_app_data(tag, file)` and `store_app_data(tag, file, shared?)` usages deprecated. Use `write_file` instead. Stores the app data associated with the app from the world `/scripts` folder. With the `file` parameter saves immediately and with every call to a specific file defined by the `file`, either in app space, or in the scripts diff --git a/docs/scarpet/api/Auxiliary.md b/docs/scarpet/api/Auxiliary.md index b55c9f5a..d4de9940 100644 --- a/docs/scarpet/api/Auxiliary.md +++ b/docs/scarpet/api/Auxiliary.md @@ -172,6 +172,63 @@ Example usages: Prints the message to system logs, and not to chat. +### read_file(resource, type) +### delete_file(resource, type) +### write_file(resource, type, data, ...) + +With the specified `resource` in the scripts folder, of a specific `type`, writes/appends `data` to it, reads its + content, or deletes the resource. + +Resource is identified by a path to the file. +A path can contain letters, numbers and folder separator: `'/'`. Any other characters are stripped +from the name. Empty descriptors are invalid. Do not add file extensions to the descriptor - extensions are inferred +based on the `type` of the file. + +Resources can be located in the app specific space, or a shared space for all the apps. Accessing of app-specific +resources is guaranteed to be isolated from other apps. Shared resources are... well, shared across all apes, meaning +they can eat of each others file, however all access to files is synchronized, and files are never left open, so +this should not lead to any access problems. + +If the app's name is `'foo'`, the script location would +be `world/scripts/foo.sc`, app +specific data directory is under `world/scripts/foo.data/...`, and shared data space is under +`world/scripts/shared/...`. + +The default no-name app, via `/script run` command can only save/load/read files from the shared space. + +Functions return `null` if an error is encounter or no file is present (for read and delete operations). Returns `true` +for success writes and deletes, and requested data, based on the file type, for read operations. + +NBT files can be written once as they an store one tag at a time. Consecutive writes will overwrite previous data. + +Write operations to text files always result in appending to the existing file, so consecutive writes will increase +the size of the file and add data to it. Since files are closed after each write, sending multiple lines of data to +write is beneficial for writing speed. To send multiple packs of data, either provide them flat or as a list in the +third argument. + * `write_file('temp', 'text', 'foo', 'bar', 'baz')` or + * write_file('temp', 'text', l('foo', 'bar', 'baz')) + +To log a single line of string + +Supported values for resource `type` is: + * `nbt` - NBT tag + * `text` - text resource with automatic newlines added + * `raw` - text resource without implied newlines + * `shared_nbt`, `shared_text`, `shared_raw` - shared versions of the above + +NBT files have extension `.nbt`, store one NBT tag, and return a NBT type value. Text files have `.txt` extension, +stores multiple lines of text and returns lists of all lines from the file. With `write_file`, multiple lines can be +sent to the file at once. The only difference between `raw` and `text` types are automatic newlines added after each +record to the file. + +
+write_file('foo', 'shared_text, l('one', 'two'));
+write_file('foo', 'shared_text', 'three\n', 'four\n');
+write_file('foo', 'shared_raw', 'five\n', 'six\n');
+
+read_file('foo', 'shared_text')     => ['one', 'two', 'three', '', 'four', '', 'five', 'six']
+
+ ### `run(expr)` Runs a vanilla command from the string result of the `expr` and returns its success count @@ -186,7 +243,10 @@ run('give @s stone 4') -> 1 // this operation was successful once Performs autosave, saves all chunks, player data, etc. Useful for programs where autosave is disabled due to performance reasons and saves the world only on demand. -### `load_app_data(), load_app_data(file), load_app_data(file, shared?)` +### `load_app_data()` + +NOTE: usages with arguments, so `load_app_data(file)` and `load_app_data(file, shared?)` are deprecated. +Use `read_file` instead. Loads the app data associated with the app from the world /scripts folder. Without argument returns the memory managed and buffered / throttled NBT tag. With a file name, reads explicitly a file with that name from the @@ -194,7 +254,7 @@ scripts folder that belongs exclusively to the app. if `shared` is true, the fil to the app anymore, but located in a shared app space. File descriptor can contain letters, numbers and folder separator: `'/'`. Any other characters are stripped -from the name before saving/loading. Empty descriptors are invalid. +from the name before saving/loading. Empty descriptors are invalid. Do not add file extensions to the descriptor Function returns nbt value with the file content, or `null` if the file is missing or there were problems with retrieving the data. @@ -208,7 +268,9 @@ specific data directory is under `world/scripts/foo.data/bar/../baz.nbt`, and sh You can use app data to save non-vanilla information separately from the world and other scripts. -### `store_app_data(tag), store_app_data(tag, file), store_app_data(tag, file, shared?)` +### `store_app_data(tag)` + +Note: `store_app_data(tag, file)` and `store_app_data(tag, file, shared?)` usages deprecated. Use `write_file` instead. Stores the app data associated with the app from the world `/scripts` folder. With the `file` parameter saves immediately and with every call to a specific file defined by the `file`, either in app space, or in the scripts diff --git a/src/main/java/carpet/script/CarpetExpression.java b/src/main/java/carpet/script/CarpetExpression.java index 6b9e45e8..06d324b3 100644 --- a/src/main/java/carpet/script/CarpetExpression.java +++ b/src/main/java/carpet/script/CarpetExpression.java @@ -2476,6 +2476,18 @@ public class CarpetExpression } + public static String recognizeResource(Value value) + { + String origfile = value.getString(); + String file = origfile.toLowerCase(Locale.ROOT).replaceAll("[^A-Za-z0-9/]", ""); + file = Arrays.stream(file.split("/+")).filter(s -> !s.isEmpty()).collect(Collectors.joining("/")); + if (file.isEmpty()) + { + throw new InternalExpressionException("Cannot use "+origfile+" as resource name - must have some letters and numbers"); + } + return file; + } + private void API_AuxiliaryAspects() { this.expr.addLazyFunction("sound", -1, (c, t, lv) -> { @@ -2864,13 +2876,6 @@ public class CarpetExpression // implmenetation should dock the task on the main thread. }); - this.expr.addLazyFunction("logger", 1, (c, t, lv) -> - { - Value res = lv.get(0).evalValue(c); - CarpetSettings.LOG.error(res.getString()); - return (_c, _t) -> res; // pass through for variables - }); - this.expr.addLazyFunction("run", 1, (c, t, lv) -> { BlockPos target = ((CarpetContext)c).origin; Vec3d posf = new Vec3d((double)target.getX()+0.5D,(double)target.getY(),(double)target.getZ()+0.5D); @@ -3057,25 +3062,108 @@ public class CarpetExpression return (c_, t_) -> Value.TRUE; }); + this.expr.addLazyFunction("logger", 1, (c, t, lv) -> + { + Value res = lv.get(0).evalValue(c); + CarpetSettings.LOG.error(res.getString()); + return (_c, _t) -> res; // pass through for variables + }); + + this.expr.addLazyFunction("read_file", 2, (c, t, lv) -> { + String resource = recognizeResource(lv.get(0).evalValue(c)); + String origtype = lv.get(1).evalValue(c).getString().toLowerCase(Locale.ROOT); + boolean shared = origtype.startsWith("shared_"); + String type = shared ? origtype.substring(7) : origtype; //len(shared_) + if (!type.equals("raw") && !type.equals("text") && !type.equals("nbt")) + throw new InternalExpressionException("Unsupported file type: "+origtype); + Value retVal; + if (type.equals("nbt")) + { + Tag state = ((CarpetScriptHost)((CarpetContext)c).host).readFileTag(resource, shared); + if (state == null) return LazyValue.NULL; + retVal = new NBTSerializableValue(state); + } + else + { + List content = ((CarpetScriptHost) ((CarpetContext) c).host).readTextResource(resource, shared); + if (content == null) return LazyValue.NULL; + retVal = ListValue.wrap(content.stream().map(StringValue::new).collect(Collectors.toList())); + } + return (cc, tt) -> retVal; + }); + + this.expr.addLazyFunction("delete_file", 2, (c, t, lv) -> { + String resource = recognizeResource(lv.get(0).evalValue(c)); + String origtype = lv.get(1).evalValue(c).getString().toLowerCase(Locale.ROOT); + boolean shared = origtype.startsWith("shared_"); + String type = shared ? origtype.substring(7) : origtype; //len(shared_) + if (!type.equals("raw") && !type.equals("text") && !type.equals("nbt")) + throw new InternalExpressionException("Unsupported file type: "+origtype); + boolean success = ((CarpetScriptHost)((CarpetContext)c).host).removeResourceFile(resource, shared, type); + return success?LazyValue.TRUE:LazyValue.FALSE; + }); + + this.expr.addLazyFunction("write_file", -1, (c, t, lv) -> { + if (lv.size() < 3) throw new InternalExpressionException("'write_file' requires three or more arguments"); + String resource = recognizeResource(lv.get(0).evalValue(c)); + String origtype = lv.get(1).evalValue(c).getString().toLowerCase(Locale.ROOT); + boolean shared = origtype.startsWith("shared_"); + String type = shared ? origtype.substring(7) : origtype; //len(shared_) + if (!type.equals("raw") && !type.equals("text") && !type.equals("nbt")) + throw new InternalExpressionException("Unsupported file type: "+origtype); + boolean success; + if (type.equals("nbt")) + { + Value val = lv.get(2).evalValue(c); + NBTSerializableValue tagValue = (val instanceof NBTSerializableValue) + ? (NBTSerializableValue) val + : new NBTSerializableValue(val.getString()); + Tag tag = tagValue.getTag(); + success = ((CarpetScriptHost)((CarpetContext)c).host).writeTagFile(tag, resource, shared); + } + else + { + List data = new ArrayList<>(); + if (lv.size()==3) + { + Value val = lv.get(2).evalValue(c); + if (val instanceof ListValue) + { + List lval = ((ListValue) val).getItems(); + lval.forEach(v -> data.add(v.getString())); + } + else + { + data.add(val.getString()); + } + } + else + { + for(int i = 2; i < lv.size(); i++) + { + data.add(lv.get(i).evalValue(c).getString()); + } + } + success = ((CarpetScriptHost) ((CarpetContext) c).host).appendLogFile(resource, shared, type, data); + } + return success?LazyValue.TRUE:LazyValue.FALSE; + }); + + //write_file + this.expr.addLazyFunction("load_app_data", -1, (c, t, lv) -> { String file = null; boolean shared = false; if (lv.size()>0) { - String origfile = lv.get(0).evalValue(c).getString(); - file = origfile.toLowerCase(Locale.ROOT).replaceAll("[^A-Za-z0-9/]", ""); - file = Arrays.stream(file.split("/+")).filter(s -> !s.isEmpty()).collect(Collectors.joining("/")); - if (file.isEmpty()) - { - throw new InternalExpressionException("Cannot use "+origfile+" as resource name - must have at least some letters and numbers"); - } + file = recognizeResource(lv.get(0).evalValue(c)); if (lv.size() > 1) { shared = lv.get(1).evalValue(c).getBoolean(); } } - Tag state = ((CarpetScriptHost)((CarpetContext)c).host).getGlobalState(file, shared); + Tag state = ((CarpetScriptHost)((CarpetContext)c).host).readFileTag(file, shared); if (state == null) return (cc, tt) -> Value.NULL; Value retVal = new NBTSerializableValue(state); @@ -3091,13 +3179,7 @@ public class CarpetExpression boolean shared = false; if (lv.size()>1) { - String origfile = lv.get(1).evalValue(c).getString(); - file = origfile.toLowerCase(Locale.ROOT).replaceAll("[^A-Za-z0-9/]", ""); - file = Arrays.stream(file.split("/+")).filter(s -> !s.isEmpty()).collect(Collectors.joining("/")); - if (file.isEmpty()) - { - throw new InternalExpressionException("Cannot use "+origfile+" as resource name - must have some letters and numbers"); - } + file = recognizeResource(lv.get(1).evalValue(c)); if (lv.size() > 2) { shared = lv.get(2).evalValue(c).getBoolean(); @@ -3107,7 +3189,7 @@ public class CarpetExpression ? (NBTSerializableValue) val : new NBTSerializableValue(val.getString()); Tag tag = tagValue.getTag(); - boolean success = ((CarpetScriptHost)((CarpetContext)c).host).setGlobalState(tag, file, shared); + boolean success = ((CarpetScriptHost)((CarpetContext)c).host).writeTagFile(tag, file, shared); return success?LazyValue.TRUE:LazyValue.FALSE; }); diff --git a/src/main/java/carpet/script/CarpetScriptHost.java b/src/main/java/carpet/script/CarpetScriptHost.java index 8803d86f..25892565 100644 --- a/src/main/java/carpet/script/CarpetScriptHost.java +++ b/src/main/java/carpet/script/CarpetScriptHost.java @@ -369,7 +369,7 @@ public class CarpetScriptHost extends ScriptHost return Module.getData(main, null, false); } - public Tag getGlobalState(String file, boolean isShared) + public Tag readFileTag(String file, boolean isShared) { if (getName() == null && !isShared) return null; if (file != null) @@ -379,7 +379,7 @@ public class CarpetScriptHost extends ScriptHost return ((CarpetScriptHost)parent).globalState; } - public boolean setGlobalState(Tag tag, String file, boolean isShared) + public boolean writeTagFile(Tag tag, String file, boolean isShared) { if (getName() == null && !isShared) return false; // if belongs to an app, cannot be default host. @@ -398,6 +398,25 @@ public class CarpetScriptHost extends ScriptHost return true; } + public boolean removeResourceFile(String resource, boolean isShared, String type) + { + if (getName() == null && !isShared) return false; // + return Module.dropExistingFile(main, resource, type.equals("nbt")?"nbt":"txt", isShared); + } + + public boolean appendLogFile(String resource, boolean isShared, String type, List data) + { + if (getName() == null && !isShared) return false; // if belongs to an app, cannot be default host. + return Module.appendToTextFile(main, resource, type, isShared, data); + } + + public List readTextResource(String resource, boolean isShared) + { + if (getName() == null && !isShared) return null; // + return Module.listFile(main, resource, "txt", isShared); + } + + public void tick() { if (this.saveTimeout > 0) diff --git a/src/main/java/carpet/script/Tokenizer.java b/src/main/java/carpet/script/Tokenizer.java index f0c1d4eb..ef4c0d5e 100644 --- a/src/main/java/carpet/script/Tokenizer.java +++ b/src/main/java/carpet/script/Tokenizer.java @@ -173,9 +173,9 @@ public class Tokenizer implements Iterator } else if (nextChar == 't') { - throw new ExpressionException(context, this.expression, token, - "Tab character is not supported"); - //token.append('\t'); + //throw new ExpressionException(context, this.expression, token, + // "Tab character is not supported"); + token.append('\t'); } else if (nextChar == 'r') { diff --git a/src/main/java/carpet/script/bundled/FileModule.java b/src/main/java/carpet/script/bundled/FileModule.java index ef44a292..4c116eec 100644 --- a/src/main/java/carpet/script/bundled/FileModule.java +++ b/src/main/java/carpet/script/bundled/FileModule.java @@ -1,17 +1,24 @@ package carpet.script.bundled; -import carpet.CarpetServer; import net.minecraft.nbt.PositionTracker; import net.minecraft.nbt.Tag; import net.minecraft.nbt.TagReaders; +import java.io.BufferedReader; +import java.io.BufferedWriter; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.List; import java.util.Locale; public class FileModule extends Module @@ -96,4 +103,44 @@ public class FileModule extends Module if (!file.exists()) file_2.renameTo(file); return true; } + + public static boolean appendText(File filePath, boolean addNewLines, List data) + { + try + { + OutputStream out = Files.newOutputStream(filePath.toPath(), StandardOpenOption.APPEND, StandardOpenOption.CREATE); + try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8))) + { + for (String line: data) + { + writer.append(line); + if (addNewLines) writer.newLine(); + } + } + } + catch (IOException e) + { + return false; + } + return true; + } + + + public static List listFileContent(File filePath) + { + try (BufferedReader reader = Files.newBufferedReader(filePath.toPath(), StandardCharsets.UTF_8)) { + List result = new ArrayList<>(); + for (;;) { + String line = reader.readLine(); + if (line == null) + break; + result.add(line.replaceAll("[\n\r]+","")); + } + return result; + } + catch (IOException e) + { + return null; + } + } } diff --git a/src/main/java/carpet/script/bundled/Module.java b/src/main/java/carpet/script/bundled/Module.java index c15cebd4..deca890e 100644 --- a/src/main/java/carpet/script/bundled/Module.java +++ b/src/main/java/carpet/script/bundled/Module.java @@ -5,15 +5,14 @@ import net.minecraft.nbt.Tag; import java.io.File; import java.nio.file.Files; - -import static carpet.script.bundled.FileModule.read; -import static carpet.script.bundled.FileModule.write; +import java.util.List; public abstract class Module { public abstract String getName(); public abstract String getCode(); public abstract boolean isLibrary(); + public final static Object writeIOSync = new Object(); private static String getDescriptor(Module module, String file, boolean isShared) { @@ -33,20 +32,48 @@ public abstract class Module public static Tag getData(Module module, String file, boolean isShared) { - if (!isShared && (module == null || module.getName() == null)) return null; - File dataFile = CarpetServer.minecraft_server.getLevelStorage().resolveFile( - CarpetServer.minecraft_server.getLevelName(), "scripts/"+getDescriptor(module, file, isShared)+".nbt"); + File dataFile = resolveResource(module, file, "nbt", isShared); + if (dataFile == null) return null; if (!Files.exists(dataFile.toPath()) || !(dataFile.isFile())) return null; - return read(dataFile); + synchronized (writeIOSync) { return FileModule.read(dataFile); } } public static boolean saveData(Module module, String file, Tag globalState, boolean isShared) { - if (!isShared && (module == null || module.getName() == null)) return false; - File dataFile =CarpetServer.minecraft_server.getLevelStorage().resolveFile( - CarpetServer.minecraft_server.getLevelName(), "scripts/"+getDescriptor(module, file, isShared)+".nbt"); + File dataFile = resolveResource(module, file, "nbt", isShared); + if (dataFile == null) return false; if (!Files.exists(dataFile.toPath().getParent()) && !dataFile.getParentFile().mkdirs()) return false; - return write(globalState, dataFile); + synchronized (writeIOSync) { return FileModule.write(globalState, dataFile); } + } + + public static boolean appendToTextFile(Module module, String resourceName, String type, boolean isShared, List message) + { + File dataFile = resolveResource(module, resourceName, "txt", isShared); + if (dataFile == null) return false; + if (!Files.exists(dataFile.toPath().getParent()) && !dataFile.getParentFile().mkdirs()) return false; + synchronized (writeIOSync) { return FileModule.appendText(dataFile, type.equals("text"), message); } + } + + public static boolean dropExistingFile(Module module, String resourceName, String ext, boolean isShared) + { + File dataFile = resolveResource(module, resourceName, ext, isShared); + if (dataFile == null) return false; + synchronized (writeIOSync) { return dataFile.delete(); } + } + + public static List listFile(Module module, String resourceName, String ext, boolean isShared) + { + File dataFile = resolveResource(module, resourceName, ext, isShared); + if (dataFile == null) return null; + if (!dataFile.exists()) return null; + synchronized (writeIOSync) { return FileModule.listFileContent(dataFile); } + } + + private static File resolveResource(Module module, String resourceName, String ext, boolean isShared) + { + if (!isShared && (module == null || module.getName() == null)) return null; + return CarpetServer.minecraft_server.getLevelStorage().resolveFile( + CarpetServer.minecraft_server.getLevelName(), "scripts/"+getDescriptor(module, resourceName, isShared)+"."+ext); } @Override