diff --git a/app/src/main/java/io/xpipe/app/resources/ContainerAutoSystemIcon.java b/app/src/main/java/io/xpipe/app/resources/ContainerAutoSystemIcon.java new file mode 100644 index 000000000..13be90b57 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/resources/ContainerAutoSystemIcon.java @@ -0,0 +1,44 @@ +package io.xpipe.app.resources; + +import io.xpipe.app.ext.ContainerImageStore; +import io.xpipe.core.process.ShellControl; +import io.xpipe.core.store.DataStore; +import lombok.EqualsAndHashCode; +import lombok.Value; + +import java.util.function.Predicate; + +@Value +@EqualsAndHashCode(callSuper=true) +public class ContainerAutoSystemIcon extends SystemIcon { + + Predicate imageCheck; + + public ContainerAutoSystemIcon(String iconName, String displayName, Predicate imageCheck) { + super(iconName, displayName); + this.imageCheck = imageCheck; + } + + @Override + public boolean isApplicable(ShellControl sc) throws Exception { + var source = sc.getSourceStore(); + if (source.isEmpty()) { + return false; + } + + return isApplicable(source.get()); + } + + @Override + public boolean isApplicable(DataStore store) { + if (!(store instanceof ContainerImageStore containerImageStore)) { + return false; + } + + if (containerImageStore.getImageName() == null) { + return false; + } + + return imageCheck.test(containerImageStore.getImageName()); + } +} diff --git a/app/src/main/java/io/xpipe/app/resources/AutoSystemIcon.java b/app/src/main/java/io/xpipe/app/resources/ShellAutoSystemIcon.java similarity index 72% rename from app/src/main/java/io/xpipe/app/resources/AutoSystemIcon.java rename to app/src/main/java/io/xpipe/app/resources/ShellAutoSystemIcon.java index 9359ac82a..fd5d67988 100644 --- a/app/src/main/java/io/xpipe/app/resources/AutoSystemIcon.java +++ b/app/src/main/java/io/xpipe/app/resources/ShellAutoSystemIcon.java @@ -7,11 +7,11 @@ import lombok.Value; @Value @EqualsAndHashCode(callSuper=true) -public class AutoSystemIcon extends SystemIcon { +public class ShellAutoSystemIcon extends SystemIcon { FailableFunction applicable; - public AutoSystemIcon(String iconName, String displayName, FailableFunction applicable) { + public ShellAutoSystemIcon(String iconName, String displayName, FailableFunction applicable) { super(iconName, displayName); this.applicable = applicable; } diff --git a/app/src/main/java/io/xpipe/app/resources/SystemIcon.java b/app/src/main/java/io/xpipe/app/resources/SystemIcon.java index a663c161e..577e7c20c 100644 --- a/app/src/main/java/io/xpipe/app/resources/SystemIcon.java +++ b/app/src/main/java/io/xpipe/app/resources/SystemIcon.java @@ -1,6 +1,7 @@ package io.xpipe.app.resources; import io.xpipe.core.process.ShellControl; +import io.xpipe.core.store.DataStore; import lombok.Value; import lombok.experimental.NonFinal; @@ -14,4 +15,8 @@ public class SystemIcon { public boolean isApplicable(ShellControl sc) throws Exception { return false; } + + public boolean isApplicable(DataStore store) { + return false; + } } diff --git a/app/src/main/java/io/xpipe/app/resources/SystemIcons.java b/app/src/main/java/io/xpipe/app/resources/SystemIcons.java index 5fb1d1d56..d8c2a8497 100644 --- a/app/src/main/java/io/xpipe/app/resources/SystemIcons.java +++ b/app/src/main/java/io/xpipe/app/resources/SystemIcons.java @@ -2,6 +2,9 @@ package io.xpipe.app.resources; import io.xpipe.core.process.ShellControl; import io.xpipe.core.process.ShellDialects; +import io.xpipe.core.process.ShellStoreState; +import io.xpipe.core.store.DataStore; +import io.xpipe.core.store.StatefulDataStore; import org.apache.commons.io.FilenameUtils; import java.nio.file.Files; @@ -13,9 +16,25 @@ import java.util.Optional; public class SystemIcons { - private static final List AUTO_SYSTEM_ICONS = List.of(new AutoSystemIcon("opnsense", "OpnSense",sc -> { - return sc.getOriginalShellDialect() == ShellDialects.OPNSENSE; - })); + private static final List AUTO_SYSTEM_ICONS = List.of( + new SystemIcon("opnsense", "OpnSense") { + @Override + public boolean isApplicable(DataStore store) { + return store instanceof StatefulDataStore statefulDataStore && + statefulDataStore.getState() instanceof ShellStoreState shellStoreState && + shellStoreState.getShellDialect() == ShellDialects.OPNSENSE; + } + }, + new SystemIcon("pfsense", "PfSense") { + @Override + public boolean isApplicable(DataStore store) { + return store instanceof StatefulDataStore statefulDataStore && + statefulDataStore.getState() instanceof ShellStoreState shellStoreState && + shellStoreState.getShellDialect() == ShellDialects.PFSENSE; + } + }, + new ContainerAutoSystemIcon("file-browser", "File Browser", name -> name.contains("filebrowser")) + ); private static final List SYSTEM_ICONS = new ArrayList<>(); private static boolean loaded = false; @@ -69,7 +88,7 @@ public class SystemIcons { } public static Optional detectForSystem(ShellControl sc) throws Exception { - for (AutoSystemIcon autoSystemIcon : AUTO_SYSTEM_ICONS) { + for (var autoSystemIcon : AUTO_SYSTEM_ICONS) { if (autoSystemIcon.isApplicable(sc)) { return Optional.of(autoSystemIcon); } @@ -77,6 +96,19 @@ public class SystemIcons { return Optional.empty(); } + public static Optional detectForStore(DataStore store) { + if (store == null) { + return Optional.empty(); + } + + for (var autoSystemIcon : AUTO_SYSTEM_ICONS) { + if (autoSystemIcon.isApplicable(store)) { + return Optional.of(autoSystemIcon); + } + } + return Optional.empty(); + } + public static List getSystemIcons() { return SYSTEM_ICONS; } diff --git a/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java b/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java index 3d8fb597c..65bdbb8f6 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java @@ -9,6 +9,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import io.xpipe.app.ext.DataStoreProvider; import io.xpipe.app.ext.DataStoreProviders; import io.xpipe.app.issue.ErrorEvent; +import io.xpipe.app.resources.SystemIcons; import io.xpipe.app.util.FixedHierarchyStore; import io.xpipe.core.store.*; import io.xpipe.core.util.JacksonMapper; @@ -87,7 +88,8 @@ public class DataStoreEntry extends StorageElement { boolean expanded, DataColor color, String notes, - Order explicitOrder, String icon + Order explicitOrder, + String icon ) { super(directory, uuid, name, lastUsed, lastModified, color, expanded, dirty); this.categoryUuid = categoryUuid; @@ -152,6 +154,7 @@ public class DataStoreEntry extends StorageElement { var validity = storeFromNode == null ? Validity.LOAD_FAILED : store.isComplete() ? Validity.COMPLETE : Validity.INCOMPLETE; + var icon = SystemIcons.detectForStore(store); var entry = new DataStoreEntry( null, uuid, @@ -169,10 +172,21 @@ public class DataStoreEntry extends StorageElement { null, null, null, - null); + icon.map(systemIcon -> systemIcon.getIconName()).orElse(null)); return entry; } + private void refreshIcon() { + if (icon != null) { + return; + } + + var icon = SystemIcons.detectForStore(store); + if (icon.isPresent()) { + setIcon(icon.get().getIconName()); + } + } + public static Optional fromDirectory(Path dir) throws Exception { ObjectMapper mapper = JacksonMapper.getDefault(); @@ -204,8 +218,12 @@ public class DataStoreEntry extends StorageElement { } }) .orElse(null); + var iconNode = json.get("icon"); - String icon = iconNode != null ? iconNode.asText() : null; + String icon = iconNode != null && !iconNode.isNull() ? iconNode.asText() : null; + if (icon != null && SystemIcons.getForId(icon).isEmpty()) { + icon = null; + } var persistentState = stateJson.get("persistentState"); var lastUsed = Optional.ofNullable(stateJson.get("lastUsed")) @@ -261,6 +279,9 @@ public class DataStoreEntry extends StorageElement { // Store loading is prone to errors. JsonNode storeNode = DataStorageEncryption.readPossiblyEncryptedNode(mapper.readTree(storeFile.toFile())); var store = JacksonMapper.getDefault().treeToValue(storeNode, DataStore.class); + if (icon == null) { + icon = SystemIcons.detectForStore(store).map(systemIcon -> systemIcon.getIconName()).orElse(null); + } return Optional.of(new DataStoreEntry( dir, uuid, @@ -363,6 +384,7 @@ public class DataStoreEntry extends StorageElement { this.storePersistentState = value; this.storePersistentStateNode = JacksonMapper.getDefault().valueToTree(value); if (changed) { + refreshIcon(); notifyUpdate(false, true); } } @@ -447,6 +469,7 @@ public class DataStoreEntry extends StorageElement { validity = store == null ? Validity.LOAD_FAILED : store.isComplete() ? Validity.COMPLETE : Validity.INCOMPLETE; storePersistentState = e.storePersistentState; storePersistentStateNode = e.storePersistentStateNode; + icon = e.icon; notifyUpdate(false, true); }