Merge branch 'master' into 1.17
# Conflicts: # src/main/java/carpet/script/utils/AppStoreManager.java
This commit is contained in:
commit
4f77327b6c
7 files changed
+192
-81
No files matched your search
+37
-17
@@ -996,7 +996,7 @@ indicating if the given seed has been used or not.
|
||||
|
||||
returns a noise value from `0.0` to `1.0` (roughly) for 1, 2 or 3 dimensional coordinate. The default seed it samples
|
||||
from is `0`, but seed can be specified as a 4th argument as well. In case you need 1D or 2D noise values with custom
|
||||
seed, use `null` for `z`, or `y` and `z` arguments respectively.
|
||||
seed, use `null` for `y` and `z`, or `z` arguments respectively.
|
||||
|
||||
Perlin noise is based on a square grid and generates rougher maps comparing to Simplex, which is creamier.
|
||||
Querying for lower-dimensional result, rather than affixing unused dimensions to constants has a speed benefit,
|
||||
@@ -1680,8 +1680,7 @@ equivalent to `has(get(foo,a), b)` or `has(foo, a, b)`
|
||||
### `delete(container, address, ...), delete(lvalue)`
|
||||
|
||||
Removes specific entry from the container. For the lists - removes the element and shrinks it. For maps, it
|
||||
removes the key from the map, and for nbt - removes content from a given path. For lists and maps returns previous
|
||||
entry at the address, for nbt's - number of removed objects, with 0 indicating that the original value was unaffected.
|
||||
removes the key from the map, and for nbt - removes content from a given path.
|
||||
|
||||
Like with the `get` and `has`, `delete` can accept chained addresses, as well as l-value container access, removing
|
||||
the value from the leaf of the path provided, so `delete(foo, a, b)` is the
|
||||
@@ -2098,7 +2097,7 @@ baz(entities) -> // same thing
|
||||
```
|
||||
|
||||
It works similarly to the auto command, but arguments get their inferred types based on the argument
|
||||
names, looking at the full name, or the suffix after the last `_` that indicates the variable type. For instance, variable named `float` will
|
||||
names, looking at the full name, or any suffix when splitting on `_` that indicates the variable type. For instance, variable named `float` will
|
||||
be parsed as a floating point number, but it can be named `'first_float'` or `'other_float'` as well. Any variable that is not
|
||||
supported, will be parsed as a `'string'` type.
|
||||
|
||||
@@ -2150,7 +2149,7 @@ paths with functions to execute, and optionally, custom argument types. Commands
|
||||
the key (can be empty) consists of
|
||||
the execution path with the command syntax, which consists of literals (as is) and arguments (wrapped with `<>`), with the name / suffix
|
||||
of the name of the attribute indicating its type, and the value represent function to call, either function values,
|
||||
defined function names, or functions with some default arguments. Values extracted from commands will be passed to the
|
||||
defined function names, or functions with some default arguments. Argument names need to be unique for each command. Values extracted from commands will be passed to the
|
||||
functions and executed. By default, command list will be checked for ambiguities (commands with the same path up to some point
|
||||
that further use different attributes), causing app loading error if that happens, however this can be suppressed by specifying
|
||||
`'allow_command_conflicts'`.
|
||||
@@ -2163,8 +2162,14 @@ and less frequently used features, like forks and redirects, used pretty much on
|
||||
|
||||
### Command argument types
|
||||
|
||||
There are several default argument types that can be used directly without specifying custom types. Each argument can be
|
||||
customized in the `'arguments'` section of the app config, specifying its base type, via `'type'` that needs
|
||||
Argument types differ from actual argument names that the types are the suffixes of the used argument names, when separated with
|
||||
`'_'` symbol. For example argument name `'from_pos'` will be interpreted as a built-in type `'int'` and provided to the command system
|
||||
as a name `'from_pos'`, however if you define a custom type `'from_pos'`, your custom type will be used instead.
|
||||
Longer suffixes take priority over shorter prefixes, then user defined suffixes mask build-in prefixes.
|
||||
|
||||
There are several default argument types that can be used directly without specifying custom types.
|
||||
|
||||
Each argument can be customized in the `'arguments'` section of the app config, specifying its base type, via `'type'` that needs
|
||||
to match any of the built-in types, with a series of optional modifiers. Shared modifiers include:
|
||||
* `suggest` - static list of suggestions to show above the command while typing
|
||||
* `suggester` - function taking one map argument, indicating current state of attributes in the parsed command
|
||||
@@ -3960,7 +3965,10 @@ In case you want to pass an event handler that is not defined in your module, pl
|
||||
### `entity_load_handler(descriptor / descriptors, function)`, `entity_load_handler(descriptor / descriptors, call_name, ... args?)`
|
||||
|
||||
Attaches a callback to when any entity matching the following type / types is loaded in the game, allowing to grab a handle
|
||||
to an entity right when it is loaded to the world without querying them every tick. Callback expects one parameter - the entity.
|
||||
to an entity right when it is loaded to the world without querying them every tick. Callback expects two parameters - the entity,
|
||||
and a boolean indicating if the entity was newly created(`true`) or just loaded from disk. Single argument functions accepting
|
||||
only entities are allowed, but deprecated and will be removed at some point.
|
||||
|
||||
If callback is `null`, then the current entity handler, if present, is removed. Consecutive calls to `entity_load_handler` will add / subtract
|
||||
of the currently targeted entity types pool.
|
||||
|
||||
@@ -3970,10 +3978,10 @@ that it is not clear which player to use run the load call.
|
||||
```
|
||||
// veryfast method of getting rid of all the zombies. Callback is so early, its packets haven't reached yet the clients
|
||||
// so to save on log errors, removal of mobs needs to be scheduled for later.
|
||||
entity_load_handler('zombie', _(e) -> schedule(0, _(outer(e)) -> modify(e, 'remove')))
|
||||
entity_load_handler('zombie', _(e, new) -> schedule(0, _(outer(e)) -> modify(e, 'remove')))
|
||||
|
||||
// making all zombies immediately faster and less susceptible to friction of any sort
|
||||
entity_load_handler('zombie', _(e) -> entity_event(e, 'on_tick', _(e) -> modify(e, 'motion', 1.2*e~'motion')))
|
||||
entity_load_handler('zombie', _(e, new) -> entity_event(e, 'on_tick', _(e) -> modify(e, 'motion', 1.2*e~'motion')))
|
||||
```
|
||||
|
||||
Word of caution: entities can be loaded with chunks in various states, for instance when a chunk is being generated, this means
|
||||
@@ -3981,25 +3989,30 @@ that accessing world blocks would cause the game to freeze due to force generati
|
||||
sure to never assume the chunk is ready and use `entity_load_handler` to schedule actions around the loaded entity,
|
||||
or manipulate entity directly.
|
||||
|
||||
Also, it is possible that mobs that spawn with world generation, while being 'added' have their metadata serialized and cached
|
||||
internally (vanilla limitation), so some modifications to these entities may have no effect on them. This affects mobs created with
|
||||
world generation.
|
||||
|
||||
For instance the following handler is safe, as it only accesses the entity directly. It makes all spawned pigmen jump
|
||||
```
|
||||
/script run entity_load_handler('zombified_piglin', _(e) -> modify(e, 'motion', 0, 1, 0) )
|
||||
/script run entity_load_handler('zombified_piglin', _(e, new) -> if(new, modify(e, 'motion', 0, 1, 0)) )
|
||||
```
|
||||
But the following handler, attempting to despawn pigmen that spawn in portals, will cause the game to freeze due to cascading access to blocks that would cause neighbouring chunks
|
||||
to force generate, causing also error messages for all pigmen caused by packets send after entity is removed by script.
|
||||
```
|
||||
/script run entity_load_handler('zombified_piglin', _(e) -> if(block(pos(e))=='nether_portal', modify(e, 'remove') ) )
|
||||
/script run entity_load_handler('zombified_piglin', _(e, new) -> if(new && block(pos(e))=='nether_portal', modify(e, 'remove') ) )
|
||||
```
|
||||
Easiest method to circumvent these issues is delay the check, which may or may not cause cascade load to happen, but
|
||||
will definitely break the infinite chain.
|
||||
```
|
||||
/script run entity_load_handler('zombified_piglin', _(e) -> schedule(0, _(outer(e)) -> if(block(pos(e))=='nether_portal', modify(e, 'remove') ) ) )
|
||||
/script run entity_load_handler('zombified_piglin', _(e, new) -> if(new, schedule(0, _(outer(e)) -> if(block(pos(e))=='nether_portal', modify(e, 'remove') ) ) ) )
|
||||
```
|
||||
But the best is to perform the check first time the entity will be ticked - giving the game all the time to ensure chunk
|
||||
is fully loaded and entity processing, removing the tick handler
|
||||
is fully loaded and entity processing, removing the tick handler:
|
||||
```
|
||||
/script run entity_load_handler('zombified_piglin', _(e) -> entity_event(e, 'on_tick', _(e) -> ( if(block(pos(e))=='nether_portal', modify(e, 'remove')); entity_event(e, 'on_tick', null) ) ) )
|
||||
/script run entity_load_handler('zombified_piglin', _(e, new) -> if(new, entity_event(e, 'on_tick', _(e) -> ( if(block(pos(e))=='nether_portal', modify(e, 'remove')); entity_event(e, 'on_tick', null) ) ) ) )
|
||||
```
|
||||
Looks little convoluted, but that's the safest method to ensure your app won't crash.
|
||||
|
||||
### `entity_event(e, event, function)`, `entity_event(e, event, call_name, ... args?)`
|
||||
|
||||
@@ -4307,8 +4320,15 @@ Duplicate of `tick`, just automatically located in the end. Use `__on_tick() ->
|
||||
|
||||
### `__on_chunk_generated(x, z)`
|
||||
Called right after a chunk at a given coordinate is full generated. `x` and `z` correspond
|
||||
to the lowest x and z coords in the chunk. Event may (or may not) work with Optifine installed
|
||||
at the same time.
|
||||
to the lowest x and z coords in the chunk. Handling of this event is scheduled as an off-tick task happening after the
|
||||
chunk is confirmed to be generated and loaded to the game, so
|
||||
handling of this event is not technically guaranteed, if the game crashes while players are moving for example, and can
|
||||
be missed on the next start, if the game successfully saves it and fails to execute all remaining tasks. In normal operation
|
||||
this should not happen, but be warned.
|
||||
|
||||
### `__on_chunk_loaded(x, z)`
|
||||
Called right after a chunk at a given coordinate is loaded. All newly generated chunks are loaded as well.
|
||||
`x` and `z` correspond to the lowest x and z coordinates in the chunk.
|
||||
|
||||
### `__on_lightning(block, mode)`
|
||||
Triggered right after a lightning strikes. Lightning entity as well as potential horseman trap would
|
||||
|
||||
@@ -110,30 +110,46 @@ in a list and contain of map-like resources descriptors, looking like
|
||||
'resources' -> [
|
||||
{
|
||||
'source' -> 'https://raw.githubusercontent.com/gnembon/fabric-carpet/master/src/main/resources/assets/carpet/icon.png',
|
||||
'type' -> 'url',
|
||||
'target' -> 'foo/photos.zip/foo/cm.png',
|
||||
},
|
||||
{
|
||||
'source' -> 'survival/README.md',
|
||||
'type' -> 'store',
|
||||
'source' -> '/survival/README.md',
|
||||
'target' -> 'survival_readme.md',
|
||||
'shared' -> true,
|
||||
},
|
||||
{
|
||||
'source' -> 'carpets.sc',
|
||||
'type' -> 'app',
|
||||
'target' -> 'apps/flying_carpets.sc',
|
||||
'shared' -> true,
|
||||
'source' -> 'circle.sc', // Relative path
|
||||
'target' -> 'apps/circle.sc', // This won't install the app, use 'libraries' for that
|
||||
},
|
||||
]
|
||||
```
|
||||
`source` and `type` indicate resource location: either an arbitrary url (type `'url'`),
|
||||
absolute location of a file in the app store (type `'store'`),
|
||||
or a relative location in the same folder as the app in question (type `'app'`).
|
||||
`'target'` points to the path in app data, or shared app data folder
|
||||
`source` indicates resource location: either an arbitrary url (starting with `http://` or `https://`),
|
||||
absolute location of a file in the app store (starting with a slash `/`),
|
||||
or a relative location in the same folder as the app in question (the relative location directly).
|
||||
`'target'` points to the path in app data, or shared app data folder. If not specified it will place the app into the main data folder with the name it has.
|
||||
if `'shared'` is specified and `true`. When re-downloading the app, all resources will be re-downloaded as well.
|
||||
Currently, app resources
|
||||
are only downloaded when using `/carpet download` command.
|
||||
Currently, app resources are only downloaded when using `/carpet download` command.
|
||||
* `libraries` - list of libraries or apps to be downloaded when installing the app from the app store. It needs to be a list of map-like resource
|
||||
descriptors, like the above `resources` field.
|
||||
```
|
||||
'libraries' -> [
|
||||
{
|
||||
'source' -> '/tutorial/carpets.sc'
|
||||
},
|
||||
{
|
||||
'source' -> '/fundamentals/heap.sc',
|
||||
'target' -> 'heap-lib.sc'
|
||||
}
|
||||
]
|
||||
```
|
||||
`source` indicates resource location and must point to a scarpet app or library. It can be either an arbitrary url (starting with `http://`
|
||||
or `https://`), absolute location of a file in the app store (starting with a slash `/`), or a relative location in the same folder as the app
|
||||
in question (the relative location directly).
|
||||
`target` is an optional field indicating the new name of the app. If not specified it will place the app into the main data folder with the name it has.
|
||||
If the app has relative resources dependencies, Carpet will use the app's path for relatives if the app was loaded from the same app store, or none if the
|
||||
app was loaded from an external url.
|
||||
If you need to `import()` from dependencies indicated in this block, make sure to have the `__config()` map before any import that references your
|
||||
remote dependencies, in order to allow them to be downloaded and initialized before the import is executed.
|
||||
* `'arguments'` - defines custom argument types for legacy commands with `'legacy_command_type_support'` as well
|
||||
as for the custom commands defined with `'commands'`, see below.
|
||||
* `'commands'` - defines custom commands for the app to be executed with `/<app>` command, see below.
|
||||
@@ -314,6 +330,7 @@ Here is a list of built-in types, with their return value formats, as well as a
|
||||
* `'dimension'`: string representing a valid dimension in the world.
|
||||
* `'anchor'`: string of `feet` or `eyes`.
|
||||
* `'entitytype'`: string representing a type of entity
|
||||
* `'entities'`: entity selector, returns a list of entities directly. Can be configured with `'single'` to only accept a single entity (will return the entity instead of a singleton) and with `'players'` to only accept players.
|
||||
* `'floatrange'`: pair of two numbers where one is smaller than the other
|
||||
* `'players'`: returning a list of valid player name string, logged in or not. If configured with `'single'` returns only one player or `null`.
|
||||
* `'intrange'`: same as `'floatrange'`, but requiring integers.
|
||||
|
||||
@@ -141,6 +141,10 @@ but if symbols are used directly in the module body rather than functions, it ma
|
||||
Returns full list of available symbols that could be imported from this module, which can be used to debug import
|
||||
issues, and list contents of libraries.
|
||||
|
||||
You can load and import functions from dependencies in a remote app store's source specified in your config's `libraries` block, but make sure
|
||||
to place your config _before_ the import in order to allow the remote dependency to be downloaded (currently, app resources are only downloaded
|
||||
when using the `/carpet download` command).
|
||||
|
||||
### `call(function, ? args ...)`
|
||||
|
||||
calls a user defined function with specified arguments. It is equivalent to calling `function(args...)` directly
|
||||
|
||||
@@ -845,6 +845,7 @@ public class CarpetSettings
|
||||
"but this also means it will work on vanilla servers as well"
|
||||
},
|
||||
category = {CREATIVE, CLIENT},
|
||||
strict = false,
|
||||
validate = Validator.NONNEGATIVE_NUMBER.class
|
||||
)
|
||||
public static double creativeFlySpeed = 1.0;
|
||||
@@ -860,6 +861,7 @@ public class CarpetSettings
|
||||
"but this also means it will work on vanilla servers as well"
|
||||
},
|
||||
category = {CREATIVE, CLIENT},
|
||||
strict = false,
|
||||
validate = Validator.PROBABILITY.class
|
||||
)
|
||||
public static double creativeFlyDrag = 0.09;
|
||||
|
||||
@@ -110,7 +110,7 @@ public class ScriptCommand
|
||||
}
|
||||
|
||||
/**
|
||||
* A method to suggest the available scarpet scripts based off of the current player input and {@link AppStoreManager#appStoreRoot}
|
||||
* A method to suggest the available scarpet scripts based off of the current player input and {@link AppStoreManager#APP_STORE_ROOT}
|
||||
* variable.
|
||||
*/
|
||||
private static CompletableFuture<Suggestions> suggestDownloadableApps(
|
||||
|
||||
@@ -70,7 +70,7 @@ import static net.minecraft.server.command.CommandManager.literal;
|
||||
public class CarpetScriptHost extends ScriptHost
|
||||
{
|
||||
private final CarpetScriptServer scriptServer;
|
||||
ServerCommandSource responsibleSource;
|
||||
public ServerCommandSource responsibleSource;
|
||||
|
||||
private NbtElement globalState;
|
||||
private int saveTimeout;
|
||||
@@ -328,6 +328,15 @@ public class CarpetScriptHost extends ScriptHost
|
||||
AppStoreManager.addResource(this, storeSource, resource);
|
||||
}
|
||||
}
|
||||
Value libraries = config.get(new StringValue("libraries"));
|
||||
if (libraries != null)
|
||||
{
|
||||
if (!(libraries instanceof ListValue)) throw new InternalExpressionException("App libraries not defined as a list");
|
||||
for (Value library : ((ListValue) libraries).getItems())
|
||||
{
|
||||
AppStoreManager.addLibrary(this, storeSource, library);
|
||||
}
|
||||
}
|
||||
}
|
||||
appConfig = config;
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ import carpet.script.CarpetScriptHost;
|
||||
import carpet.script.CarpetScriptServer;
|
||||
import carpet.script.exception.InternalExpressionException;
|
||||
import carpet.script.value.MapValue;
|
||||
import carpet.script.value.StringValue;
|
||||
import carpet.script.value.Value;
|
||||
import carpet.settings.ParsedRule;
|
||||
import carpet.settings.Validator;
|
||||
import carpet.utils.Messenger;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
@@ -21,7 +21,7 @@ import net.minecraft.util.WorldSavePath;
|
||||
import org.apache.commons.lang3.tuple.Triple;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
@@ -32,7 +32,6 @@ import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@@ -41,7 +40,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
/**
|
||||
* A class used to save scarpet app store scripts to disk
|
||||
*/
|
||||
@@ -50,7 +48,7 @@ public class AppStoreManager
|
||||
/** A local copy of the scarpet repo's file structure, to avoid multiple queries to github.com while typing out the
|
||||
* {@code /script download} command and getting the suggestions.
|
||||
*/
|
||||
public static final StoreNode appStoreRoot = StoreNode.folder(null, "");
|
||||
public static final StoreNode APP_STORE_ROOT = StoreNode.folder(null, "");
|
||||
|
||||
/** This is the base link to the scarpet app repo from the github api.
|
||||
*/
|
||||
@@ -61,8 +59,8 @@ public class AppStoreManager
|
||||
{
|
||||
@Override public String validate(ServerCommandSource source, ParsedRule<String> currentRule, String newValue, String string)
|
||||
{
|
||||
appStoreRoot.sealed = false;
|
||||
appStoreRoot.children = new HashMap<>();
|
||||
APP_STORE_ROOT.sealed = false;
|
||||
APP_STORE_ROOT.children = new HashMap<>();
|
||||
if (newValue.equalsIgnoreCase("none"))
|
||||
{
|
||||
scarpetRepoLink = null;
|
||||
@@ -115,7 +113,7 @@ public class AppStoreManager
|
||||
}
|
||||
private StringBuilder createPrePath()
|
||||
{
|
||||
return this == appStoreRoot ? new StringBuilder() : parent.createPrePath().append(pathElement());
|
||||
return this == APP_STORE_ROOT ? new StringBuilder() : parent.createPrePath().append(pathElement());
|
||||
}
|
||||
private StoreNode(StoreNode parent, String name)
|
||||
{
|
||||
@@ -212,7 +210,7 @@ public class AppStoreManager
|
||||
public static List<String> suggestionsFromPath(String currentPath) throws IOException
|
||||
{
|
||||
String[] path = currentPath.split("/");
|
||||
StoreNode appKiosk = appStoreRoot;
|
||||
StoreNode appKiosk = APP_STORE_ROOT;
|
||||
for(String pathElement : path)
|
||||
{
|
||||
if (appKiosk.cannotContinueFor(pathElement)) break;
|
||||
@@ -237,6 +235,11 @@ public class AppStoreManager
|
||||
public static int downloadScript(ServerCommandSource source, String path)
|
||||
{
|
||||
Triple<String, String, StoreNode> nodeInfo = getFileNode(path);
|
||||
return downloadScript(source, path, nodeInfo, false);
|
||||
}
|
||||
|
||||
private static int downloadScript(ServerCommandSource source, String path, Triple<String, String, StoreNode> nodeInfo, boolean useTrash)
|
||||
{
|
||||
String code;
|
||||
try
|
||||
{
|
||||
@@ -246,7 +249,7 @@ public class AppStoreManager
|
||||
{
|
||||
throw new CommandException(Messenger.c("rb Failed to obtain app file content: "+e.getMessage()));
|
||||
}
|
||||
if (!saveScriptToFile(source, path, nodeInfo.getLeft(), code, false)) return 0;
|
||||
if (!saveScriptToFile(source, path, nodeInfo.getLeft(), code, false, useTrash)) return 0;
|
||||
boolean success = CarpetServer.scriptServer.addScriptHost(source, nodeInfo.getLeft().replaceFirst("\\.sc$", ""), null, true, false, false, nodeInfo.getRight());
|
||||
return success?1:0;
|
||||
}
|
||||
@@ -258,7 +261,7 @@ public class AppStoreManager
|
||||
*/
|
||||
public static Triple<String, String, StoreNode> getFileNode(String appPath)
|
||||
{
|
||||
return getFileNodeFrom(appStoreRoot, appPath);
|
||||
return getFileNodeFrom(APP_STORE_ROOT, appPath);
|
||||
}
|
||||
|
||||
public static Triple<String, String, StoreNode> getFileNodeFrom(StoreNode start, String appPath)
|
||||
@@ -280,32 +283,46 @@ public class AppStoreManager
|
||||
}
|
||||
|
||||
|
||||
public static boolean saveScriptToFile(ServerCommandSource source, String path, String appFileName, String code, boolean globalSavePath){
|
||||
String scriptPath;
|
||||
String location;
|
||||
if(globalSavePath && !source.getServer().isDedicated())
|
||||
public static boolean saveScriptToFile(ServerCommandSource source, String path, String appFileName, String code, boolean globalSavePath, boolean useTrash)
|
||||
{
|
||||
Path scriptLocation;
|
||||
if (globalSavePath && !source.getServer().isDedicated()) // never happens, this is always called with globalSavePath being false
|
||||
{ //cos config folder only is in clients
|
||||
scriptPath = FabricLoader.getInstance().getConfigDir().resolve("carpet/scripts/appstore").toAbsolutePath()+"/"+path;
|
||||
location = "global script config folder";
|
||||
scriptLocation = FabricLoader.getInstance().getConfigDir().resolve("carpet/scripts/appstore").toAbsolutePath().resolve(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
scriptPath = source.getServer().getSavePath(WorldSavePath.ROOT).resolve("scripts").toAbsolutePath()+"/"+appFileName;
|
||||
location = "world scripts folder";
|
||||
scriptLocation = source.getServer().getSavePath(WorldSavePath.ROOT).resolve("scripts").toAbsolutePath().resolve(appFileName);
|
||||
}
|
||||
try
|
||||
{
|
||||
Path scriptLocation = Paths.get(scriptPath);
|
||||
Files.createDirectories(scriptLocation.getParent());
|
||||
if (Files.exists(scriptLocation))
|
||||
Messenger.m(source, String.format("gi Note: replaced existing app '%s'", appFileName));
|
||||
FileWriter fileWriter = new FileWriter(scriptPath);
|
||||
fileWriter.write(code);
|
||||
fileWriter.close();
|
||||
{
|
||||
if (useTrash)
|
||||
{
|
||||
Files.createDirectories(scriptLocation.getParent().resolve("trash"));
|
||||
Path trashPath = scriptLocation.getParent().resolve("trash").resolve(path);
|
||||
int i = 0;
|
||||
while (Files.exists(trashPath))
|
||||
{
|
||||
String[] nameAndExtension = appFileName.split("\\.");
|
||||
String newFileName = String.format(nameAndExtension[0] + "%02d." + nameAndExtension[1],i);
|
||||
trashPath = trashPath.getParent().resolve(newFileName);
|
||||
i++;
|
||||
}
|
||||
Files.move(scriptLocation, trashPath);
|
||||
}
|
||||
Messenger.m(source, String.format("gi Note: replaced existing app '%s'"+(useTrash ? " (old moved to /trash folder)" : ""), appFileName));
|
||||
}
|
||||
BufferedWriter writer = Files.newBufferedWriter(scriptLocation);
|
||||
writer.write(code);
|
||||
writer.close();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
Messenger.m(source, "r Error in downloading app: "+e.getMessage());
|
||||
Messenger.m(source, "r Error while downloading app: " + e.toString());
|
||||
CarpetScriptServer.LOG.warn("Error while downloading app", e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -349,41 +366,31 @@ public class AppStoreManager
|
||||
}
|
||||
}
|
||||
|
||||
private static String getFullContentUrl(String original, StoreNode storeSource)
|
||||
{
|
||||
if (original.matches("^https?://.*$")) // We've got a full url here: Just use it
|
||||
return original;
|
||||
if (original.charAt(0) == '/') // We've got an absolute path: Use app store root
|
||||
return getFileNode(original.substring(1)).getMiddle();
|
||||
return getFileNodeFrom(storeSource, original).getMiddle(); // Relative path: Use download location
|
||||
}
|
||||
|
||||
public static void addResource(CarpetScriptHost carpetScriptHost, StoreNode storeSource, Value resource)
|
||||
{
|
||||
if (!(resource instanceof MapValue))
|
||||
throw new InternalExpressionException("This is not a valid resource map: "+resource.getString());
|
||||
Map<String, Value> resourceMap = ((MapValue) resource).getMap().entrySet().stream().collect(Collectors.toMap(e -> e.getKey().getString(), Map.Entry::getValue));
|
||||
for (String key: ImmutableList.of("source", "target", "type"))
|
||||
if (!resourceMap.containsKey(key)) throw new InternalExpressionException("Missing '"+key+"' field in resource descriptor: "+resource.getString());
|
||||
if (!resourceMap.containsKey("source")) throw new InternalExpressionException("Missing 'source' field in resource descriptor: "+resource.getString());
|
||||
String source = resourceMap.get("source").getString();
|
||||
String target = resourceMap.get("target").getString();
|
||||
String contentUrl = getFullContentUrl(source, storeSource);
|
||||
String target = resourceMap.computeIfAbsent("target", k -> new StringValue(contentUrl.substring(contentUrl.lastIndexOf('/') + 1))).getString();
|
||||
boolean shared = resourceMap.getOrDefault("shared", Value.FALSE).getBoolean();
|
||||
String type = resourceMap.get("type").getString();
|
||||
String contentUrl;
|
||||
if (type.equalsIgnoreCase("url"))
|
||||
{
|
||||
contentUrl = source;
|
||||
}
|
||||
else if (type.equalsIgnoreCase("store"))
|
||||
{
|
||||
Triple<String, String, StoreNode> nodeInfo = getFileNode(source);
|
||||
contentUrl = nodeInfo.getMiddle();
|
||||
}
|
||||
else if (type.equalsIgnoreCase("app"))
|
||||
{
|
||||
Triple<String, String, StoreNode> nodeInfo = getFileNodeFrom(storeSource, source);
|
||||
contentUrl = nodeInfo.getMiddle();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InternalExpressionException("Incorrect source type, expecting 'url', 'store' or 'app', got "+type);
|
||||
}
|
||||
|
||||
if (!carpetScriptHost.applyActionForResource(target, shared, p -> {
|
||||
try {
|
||||
writeUrlToFile(contentUrl, p);
|
||||
} catch (IOException e) {
|
||||
throw new InternalExpressionException("Unable to write resource "+target+": "+e.getMessage());
|
||||
throw new InternalExpressionException("Unable to write resource "+target+": "+e.toString());
|
||||
}
|
||||
}))
|
||||
{
|
||||
@@ -391,4 +398,56 @@ public class AppStoreManager
|
||||
}
|
||||
CarpetScriptServer.LOG.info("Downloaded resource "+target+" from "+contentUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a new StoreNode for an app's dependency with proper relativeness. Will be null if it comes from an external URL
|
||||
* @param originalSource The StoreNode from the container's app
|
||||
* @param sourceString The string the app specified as source
|
||||
* @param contentUrl The full content URL, from {@link #getFullContentUrl(String, StoreNode)}
|
||||
* @return A {@link StoreNode} that can be used in an app that came from the provided source
|
||||
*/
|
||||
private static StoreNode getNewStoreNode(StoreNode originalSource, String sourceString, String contentUrl)
|
||||
{
|
||||
StoreNode next = originalSource;
|
||||
if (sourceString == contentUrl) // External URL (check getFullUrlContent)
|
||||
return null;
|
||||
if (sourceString.charAt(0) == '/') // Absolute URL
|
||||
{
|
||||
next = APP_STORE_ROOT;
|
||||
sourceString = sourceString.substring(1);
|
||||
}
|
||||
String[] dirs = sourceString.split("/");
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < dirs.length - 1; i++)
|
||||
next = next.drillDown(dirs[i]);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
return null; // Should never happen, but let's not give a potentially incorrect node just in case
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
public static void addLibrary(CarpetScriptHost carpetScriptHost, StoreNode storeSource, Value library) {
|
||||
if (!(library instanceof MapValue))
|
||||
throw new InternalExpressionException("This is not a valid library map: "+library.getString());
|
||||
Map<String, String> libraryMap = ((MapValue) library).getMap().entrySet().stream().collect(Collectors.toMap(e -> e.getKey().getString(), e -> e.getValue().getString()));
|
||||
String source = libraryMap.get("source");
|
||||
String contentUrl = getFullContentUrl(source, storeSource);
|
||||
String target = libraryMap.computeIfAbsent("target", k -> contentUrl.substring(contentUrl.lastIndexOf('/') + 1));
|
||||
if (!(contentUrl.endsWith(".sc") || contentUrl.endsWith(".scl")))
|
||||
throw new InternalExpressionException("App resource type must download a scarpet app or library.");
|
||||
if (target.indexOf('/') != -1)
|
||||
throw new InternalExpressionException("App resource tried to leave script reserved space");
|
||||
try
|
||||
{
|
||||
downloadScript(carpetScriptHost.responsibleSource, target, Triple.of(target, contentUrl, getNewStoreNode(storeSource, source, contentUrl)), true);
|
||||
}
|
||||
catch (CommandException e)
|
||||
{
|
||||
throw new InternalExpressionException("Error when installing app dependencies: " + e.toString());
|
||||
}
|
||||
CarpetScriptServer.LOG.info("Downloaded app "+target+" from "+contentUrl);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user