Change how resource type works, allow proper app and library installation (#956)
* Change how app store types work, allow proper app and library installation * Add node for resources in dependencies and docs * Fix NPE without `type` * Fix docs trying to change location of app type resource * Separate libraries and apps and add trash for them * Update docs * Create trash directory if it doesn't exist
This commit is contained in:
1 parent
d92b412c0a
commit
97e69d347e
5 files changed
+153
-65
No files matched your search
@@ -110,30 +110,46 @@ in a list and contain of map-like resources descriptors, looking like
|
|||||||
'resources' -> [
|
'resources' -> [
|
||||||
{
|
{
|
||||||
'source' -> 'https://raw.githubusercontent.com/gnembon/fabric-carpet/master/src/main/resources/assets/carpet/icon.png',
|
'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',
|
'target' -> 'foo/photos.zip/foo/cm.png',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'source' -> 'survival/README.md',
|
'source' -> '/survival/README.md',
|
||||||
'type' -> 'store',
|
|
||||||
'target' -> 'survival_readme.md',
|
'target' -> 'survival_readme.md',
|
||||||
'shared' -> true,
|
'shared' -> true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'source' -> 'carpets.sc',
|
'source' -> 'circle.sc', // Relative path
|
||||||
'type' -> 'app',
|
'target' -> 'apps/circle.sc', // This won't install the app, use 'libraries' for that
|
||||||
'target' -> 'apps/flying_carpets.sc',
|
|
||||||
'shared' -> true,
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
`source` and `type` indicate resource location: either an arbitrary url (type `'url'`),
|
`source` indicates resource location: either an arbitrary url (starting with `http://` or `https://`),
|
||||||
absolute location of a file in the app store (type `'store'`),
|
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 (type `'app'`).
|
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
|
`'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.
|
if `'shared'` is specified and `true`. When re-downloading the app, all resources will be re-downloaded as well.
|
||||||
Currently, app resources
|
Currently, app resources are only downloaded when using `/carpet download` command.
|
||||||
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
|
* `'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.
|
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.
|
* `'commands'` - defines custom commands for the app to be executed with `/<app>` command, see below.
|
||||||
|
|||||||
@@ -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
|
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.
|
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 ...)`
|
### `call(function, ? args ...)`
|
||||||
|
|
||||||
calls a user defined function with specified arguments. It is equivalent to calling `function(args...)` directly
|
calls a user defined function with specified arguments. It is equivalent to calling `function(args...)` directly
|
||||||
|
|||||||
@@ -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.
|
* variable.
|
||||||
*/
|
*/
|
||||||
private static CompletableFuture<Suggestions> suggestDownloadableApps(
|
private static CompletableFuture<Suggestions> suggestDownloadableApps(
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ import static net.minecraft.server.command.CommandManager.literal;
|
|||||||
public class CarpetScriptHost extends ScriptHost
|
public class CarpetScriptHost extends ScriptHost
|
||||||
{
|
{
|
||||||
private final CarpetScriptServer scriptServer;
|
private final CarpetScriptServer scriptServer;
|
||||||
ServerCommandSource responsibleSource;
|
public ServerCommandSource responsibleSource;
|
||||||
|
|
||||||
private Tag globalState;
|
private Tag globalState;
|
||||||
private int saveTimeout;
|
private int saveTimeout;
|
||||||
@@ -328,6 +328,15 @@ public class CarpetScriptHost extends ScriptHost
|
|||||||
AppStoreManager.addResource(this, storeSource, resource);
|
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;
|
appConfig = config;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import carpet.script.CarpetScriptHost;
|
|||||||
import carpet.script.CarpetScriptServer;
|
import carpet.script.CarpetScriptServer;
|
||||||
import carpet.script.exception.InternalExpressionException;
|
import carpet.script.exception.InternalExpressionException;
|
||||||
import carpet.script.value.MapValue;
|
import carpet.script.value.MapValue;
|
||||||
|
import carpet.script.value.StringValue;
|
||||||
import carpet.script.value.Value;
|
import carpet.script.value.Value;
|
||||||
import carpet.settings.ParsedRule;
|
import carpet.settings.ParsedRule;
|
||||||
import carpet.settings.Validator;
|
import carpet.settings.Validator;
|
||||||
import carpet.utils.Messenger;
|
import carpet.utils.Messenger;
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import com.google.gson.JsonArray;
|
import com.google.gson.JsonArray;
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
@@ -21,7 +21,7 @@ import net.minecraft.util.WorldSavePath;
|
|||||||
import org.apache.commons.lang3.tuple.Triple;
|
import org.apache.commons.lang3.tuple.Triple;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.FileWriter;
|
import java.io.BufferedWriter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
@@ -32,7 +32,6 @@ import java.net.URL;
|
|||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.nio.file.StandardCopyOption;
|
import java.nio.file.StandardCopyOption;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -41,7 +40,6 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class used to save scarpet app store scripts to disk
|
* 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
|
/** 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.
|
* {@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.
|
/** 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)
|
@Override public String validate(ServerCommandSource source, ParsedRule<String> currentRule, String newValue, String string)
|
||||||
{
|
{
|
||||||
appStoreRoot.sealed = false;
|
APP_STORE_ROOT.sealed = false;
|
||||||
appStoreRoot.children = new HashMap<>();
|
APP_STORE_ROOT.children = new HashMap<>();
|
||||||
if (newValue.equalsIgnoreCase("none"))
|
if (newValue.equalsIgnoreCase("none"))
|
||||||
{
|
{
|
||||||
scarpetRepoLink = null;
|
scarpetRepoLink = null;
|
||||||
@@ -115,7 +113,7 @@ public class AppStoreManager
|
|||||||
}
|
}
|
||||||
private StringBuilder createPrePath()
|
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)
|
private StoreNode(StoreNode parent, String name)
|
||||||
{
|
{
|
||||||
@@ -212,7 +210,7 @@ public class AppStoreManager
|
|||||||
public static List<String> suggestionsFromPath(String currentPath) throws IOException
|
public static List<String> suggestionsFromPath(String currentPath) throws IOException
|
||||||
{
|
{
|
||||||
String[] path = currentPath.split("/");
|
String[] path = currentPath.split("/");
|
||||||
StoreNode appKiosk = appStoreRoot;
|
StoreNode appKiosk = APP_STORE_ROOT;
|
||||||
for(String pathElement : path)
|
for(String pathElement : path)
|
||||||
{
|
{
|
||||||
if (appKiosk.cannotContinueFor(pathElement)) break;
|
if (appKiosk.cannotContinueFor(pathElement)) break;
|
||||||
@@ -237,6 +235,11 @@ public class AppStoreManager
|
|||||||
public static int downloadScript(ServerCommandSource source, String path)
|
public static int downloadScript(ServerCommandSource source, String path)
|
||||||
{
|
{
|
||||||
Triple<String, String, StoreNode> nodeInfo = getFileNode(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;
|
String code;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -246,7 +249,7 @@ public class AppStoreManager
|
|||||||
{
|
{
|
||||||
throw new CommandException(Messenger.c("rb Failed to obtain app file content: "+e.getMessage()));
|
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());
|
boolean success = CarpetServer.scriptServer.addScriptHost(source, nodeInfo.getLeft().replaceFirst("\\.sc$", ""), null, true, false, false, nodeInfo.getRight());
|
||||||
return success?1:0;
|
return success?1:0;
|
||||||
}
|
}
|
||||||
@@ -258,7 +261,7 @@ public class AppStoreManager
|
|||||||
*/
|
*/
|
||||||
public static Triple<String, String, StoreNode> getFileNode(String appPath)
|
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)
|
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){
|
public static boolean saveScriptToFile(ServerCommandSource source, String path, String appFileName, String code, boolean globalSavePath, boolean useTrash)
|
||||||
String scriptPath;
|
{
|
||||||
String location;
|
Path scriptLocation;
|
||||||
if(globalSavePath && !source.getMinecraftServer().isDedicated())
|
if (globalSavePath && !source.getMinecraftServer().isDedicated()) // never happens, this is always called with globalSavePath being false
|
||||||
{ //cos config folder only is in clients
|
{ //cos config folder only is in clients
|
||||||
scriptPath = FabricLoader.getInstance().getConfigDir().resolve("carpet/scripts/appstore").toAbsolutePath()+"/"+path;
|
scriptLocation = FabricLoader.getInstance().getConfigDir().resolve("carpet/scripts/appstore").toAbsolutePath().resolve(path);
|
||||||
location = "global script config folder";
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
scriptPath = source.getMinecraftServer().getSavePath(WorldSavePath.ROOT).resolve("scripts").toAbsolutePath()+"/"+appFileName;
|
scriptLocation = source.getMinecraftServer().getSavePath(WorldSavePath.ROOT).resolve("scripts").toAbsolutePath().resolve(appFileName);
|
||||||
location = "world scripts folder";
|
|
||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Path scriptLocation = Paths.get(scriptPath);
|
|
||||||
Files.createDirectories(scriptLocation.getParent());
|
Files.createDirectories(scriptLocation.getParent());
|
||||||
if(Files.exists(scriptLocation))
|
if (Files.exists(scriptLocation))
|
||||||
Messenger.m(source, String.format("gi Note: replaced existing app '%s'", appFileName));
|
{
|
||||||
FileWriter fileWriter = new FileWriter(scriptPath);
|
if (useTrash)
|
||||||
fileWriter.write(code);
|
{
|
||||||
fileWriter.close();
|
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)
|
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 false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -348,42 +365,32 @@ public class AppStoreManager
|
|||||||
Files.copy(in, destination, StandardCopyOption.REPLACE_EXISTING);
|
Files.copy(in, destination, StandardCopyOption.REPLACE_EXISTING);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
public static void addResource(CarpetScriptHost carpetScriptHost, StoreNode storeSource, Value resource)
|
||||||
{
|
{
|
||||||
if (!(resource instanceof MapValue))
|
if (!(resource instanceof MapValue))
|
||||||
throw new InternalExpressionException("This is not a valid resource map: "+resource.getString());
|
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));
|
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("source")) throw new InternalExpressionException("Missing 'source' field in resource descriptor: "+resource.getString());
|
||||||
if (!resourceMap.containsKey(key)) throw new InternalExpressionException("Missing '"+key+"' field in resource descriptor: "+resource.getString());
|
|
||||||
String source = resourceMap.get("source").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();
|
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 -> {
|
if (!carpetScriptHost.applyActionForResource(target, shared, p -> {
|
||||||
try {
|
try {
|
||||||
writeUrlToFile(contentUrl, p);
|
writeUrlToFile(contentUrl, p);
|
||||||
} catch (IOException e) {
|
} 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);
|
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