diff --git a/core/src/main/java/io/xpipe/core/process/ShellDialect.java b/core/src/main/java/io/xpipe/core/process/ShellDialect.java index a91cd43e..8819305d 100644 --- a/core/src/main/java/io/xpipe/core/process/ShellDialect.java +++ b/core/src/main/java/io/xpipe/core/process/ShellDialect.java @@ -62,6 +62,8 @@ public interface ShellDialect { String prepareProperTerminalCommands(); + String appendToPathVariableCommand(String entry); + default String applyRcFileCommand() { return null; } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptGroupStoreProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptGroupStoreProvider.java index b9465059..aba69a8b 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptGroupStoreProvider.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptGroupStoreProvider.java @@ -27,12 +27,20 @@ public class ScriptGroupStoreProvider implements DataStoreProvider { @Override public Comp customEntryComp(StoreSection sec, boolean preferLarge) { ScriptGroupStore s = sec.getWrapper().getEntry().getStore().asNeeded(); + var def = new StoreToggleComp("base.isDefaultGroup", sec, s.getState().isDefault(), aBoolean -> { var state = s.getState(); state.setDefault(aBoolean); s.setState(state); }); - var dropdown = new DropdownComp(List.of(def)); + + var bring = new StoreToggleComp("base.bringToShells", sec, s.getState().isBringToShell(), aBoolean -> { + var state = s.getState(); + state.setBringToShell(aBoolean); + s.setState(state); + }); + + var dropdown = new DropdownComp(List.of(def, bring)); return new DenseStoreEntryComp(sec.getWrapper(), true, dropdown); } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptStore.java b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptStore.java index 778cfd49..f50a70f3 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptStore.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptStore.java @@ -6,8 +6,10 @@ import io.xpipe.app.util.Validators; import io.xpipe.core.process.ShellControl; import io.xpipe.core.store.DataStore; import io.xpipe.core.store.DataStoreState; +import io.xpipe.core.store.FileNames; import io.xpipe.core.store.StatefulDataStore; import io.xpipe.core.util.JacksonizedValue; +import io.xpipe.core.util.XPipeInstallation; import lombok.*; import lombok.experimental.FieldDefaults; import lombok.experimental.SuperBuilder; @@ -15,6 +17,7 @@ import lombok.extern.jackson.Jacksonized; import java.util.LinkedHashSet; import java.util.List; +import java.util.Locale; import java.util.Objects; import java.util.stream.Collectors; @@ -24,13 +27,13 @@ import java.util.stream.Collectors; public abstract class ScriptStore extends JacksonizedValue implements DataStore, StatefulDataStore { public static ShellControl controlWithDefaultScripts(ShellControl pc) { - return controlWithScripts(pc, getDefaultScripts()); + return controlWithScripts(pc, getDefaultInitScripts(), getDefaultBringScripts()); } - public static ShellControl controlWithScripts(ShellControl pc, List> refs) { + public static ShellControl controlWithScripts(ShellControl pc, List> initScripts, List> bringScripts) { pc.onInit(shellControl -> { - var flattened = flatten(refs); - var scripts = flattened.stream() + var initFlattened = flatten(initScripts); + var scripts = initFlattened.stream() .map(simpleScriptStore -> simpleScriptStore.prepareDumbScript(shellControl)) .filter(Objects::nonNull) .collect(Collectors.joining("\n")); @@ -38,7 +41,7 @@ public abstract class ScriptStore extends JacksonizedValue implements DataStore, shellControl.executeSimpleBooleanCommand(scripts); } - var terminalCommands = flattened.stream() + var terminalCommands = initFlattened.stream() .map(simpleScriptStore -> simpleScriptStore.prepareTerminalScript(shellControl)) .filter(Objects::nonNull) .collect(Collectors.joining("\n")); @@ -46,10 +49,55 @@ public abstract class ScriptStore extends JacksonizedValue implements DataStore, shellControl.initWithTerminal(terminalCommands); } }); + pc.onInit(shellControl -> { + var bringFlattened = flatten(bringScripts); + var dir = initScriptsDirectory(shellControl, bringFlattened); + if (dir != null) { + shellControl.initWithTerminal(shellControl.getShellDialect().appendToPathVariableCommand(dir)); + } + }); return pc; } - private static List> getDefaultScripts() { + private static String initScriptsDirectory(ShellControl proc, List scriptStores) throws Exception { + if (scriptStores.size() == 0) { + return null; + } + + var refs = scriptStores.stream().map(scriptStore -> { + return DataStorage.get().getStoreEntries().stream().filter(dataStoreEntry -> dataStoreEntry.getStore() == scriptStore).findFirst().orElseThrow().ref(); + }).toList(); + var hash = refs.stream().mapToInt(value -> value.get().getName().hashCode() + value.getStore().hashCode()).sum(); + var xpipeHome = XPipeInstallation.getDataDir(proc); + var targetDir = FileNames.join(xpipeHome, "scripts"); + var hashFile = FileNames.join(targetDir, "hash"); + var d = proc.getShellDialect(); + if (d.createFileExistsCommand(proc, hashFile).executeAndCheck()) { + var read = d.getFileReadCommand(proc, hashFile).readStdoutOrThrow(); + var readHash = Integer.parseInt(read); + if (hash == readHash) { + return targetDir; + } + } + + d.deleteFileOrDirectory(proc, targetDir).execute(); + proc.executeSimpleCommand(d.getMkdirsCommand(targetDir)); + + for (DataStoreEntryRef scriptStore : refs) { + var content = d.prepareScriptContent(scriptStore.getStore().getCommands()); + var fileName = scriptStore.get().getName().toLowerCase(Locale.ROOT).replaceAll(" ", "_"); + var scriptFile = FileNames.join(targetDir, fileName + "." + d.getScriptFileEnding()); + d.createScriptTextFileWriteCommand(proc, content, scriptFile).execute(); + + var chmod = d.getScriptPermissionsCommand(scriptFile); + proc.executeSimpleBooleanCommand(chmod); + } + + d.createTextFileWriteCommand(proc, String.valueOf(hash), hashFile).execute(); + return targetDir; + } + + public static List> getDefaultInitScripts() { return DataStorage.get().getStoreEntries().stream() .filter(dataStoreEntry -> dataStoreEntry.getStore() instanceof ScriptStore scriptStore && scriptStore.getState().isDefault()) @@ -57,6 +105,14 @@ public abstract class ScriptStore extends JacksonizedValue implements DataStore, .toList(); } + public static List> getDefaultBringScripts() { + return DataStorage.get().getStoreEntries().stream() + .filter(dataStoreEntry -> dataStoreEntry.getStore() instanceof ScriptStore scriptStore + && scriptStore.getState().isBringToShell()) + .map(e -> e.ref()) + .toList(); + } + public static List flatten(List> scripts) { var seen = new LinkedHashSet(); scripts.forEach(scriptStoreDataStoreEntryRef -> @@ -78,6 +134,7 @@ public abstract class ScriptStore extends JacksonizedValue implements DataStore, @Jacksonized public static class State extends DataStoreState { boolean isDefault; + boolean bringToShell; } @Override diff --git a/ext/base/src/main/java/io/xpipe/ext/base/script/SimpleScriptStoreProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/script/SimpleScriptStoreProvider.java index eff27cc7..a1367a7b 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/script/SimpleScriptStoreProvider.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/script/SimpleScriptStoreProvider.java @@ -44,18 +44,32 @@ public class SimpleScriptStoreProvider implements DataStoreProvider { public Comp customEntryComp(StoreSection sec, boolean preferLarge) { SimpleScriptStore s = sec.getWrapper().getEntry().getStore().asNeeded(); var groupWrapper = StoreViewState.get().getEntryWrapper(s.getGroup().getEntry()); + var def = new StoreToggleComp("base.isDefault", sec, s.getState().isDefault(), aBoolean -> { var state = s.getState(); state.setDefault(aBoolean); s.setState(state); }); + var bring = new StoreToggleComp("base.bringToShells", sec, s.getState().isBringToShell(), aBoolean -> { + var state = s.getState(); + state.setBringToShell(aBoolean); + s.setState(state); + }); + // Disable selection if parent group is already made default def.disable(BindingsHelper.map(groupWrapper.getPersistentState(), o -> { ScriptStore.State state = (ScriptStore.State) o; return state.isDefault(); })); - var dropdown = new DropdownComp(List.of(def)); + + // Disable selection if parent group is already brings + bring.disable(BindingsHelper.map(groupWrapper.getPersistentState(), o -> { + ScriptStore.State state = (ScriptStore.State) o; + return state.isBringToShell(); + })); + + var dropdown = new DropdownComp(List.of(def, bring)); return new DenseStoreEntryComp(sec.getWrapper(), true, dropdown); } diff --git a/ext/base/src/main/resources/io/xpipe/ext/base/resources/lang/translations_en.properties b/ext/base/src/main/resources/io/xpipe/ext/base/resources/lang/translations_en.properties index 256732a6..01bbddb9 100644 --- a/ext/base/src/main/resources/io/xpipe/ext/base/resources/lang/translations_en.properties +++ b/ext/base/src/main/resources/io/xpipe/ext/base/resources/lang/translations_en.properties @@ -73,6 +73,7 @@ snippets=Script dependencies snippetsDescription=Other scripts to run first snippetsDependenciesDescription=All possible scripts that should be run if applicable isDefault=Enable in all compatible shells +bringToShells=Bring to all compatible shells isDefaultGroup=Enable all group scripts executionType=Execution type executionTypeDescription=When to run this snippet