Usable Browser
This commit is contained in:
@@ -0,0 +1,192 @@
|
||||
package org.openautonomousconnection.webclient.settings;
|
||||
|
||||
import org.openautonomousconnection.luascript.security.LuaExecutionPolicy;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* In-memory settings model.
|
||||
*
|
||||
* <p>Persisted by {@link SettingsManager}.</p>
|
||||
*/
|
||||
public final class AppSettings {
|
||||
|
||||
private final List<InsEndpoint> insEndpoints = new ArrayList<>();
|
||||
private final List<String> favorites = new ArrayList<>();
|
||||
private String startPageUrl = "web://info.oac/";
|
||||
private boolean sslEnabled = true;
|
||||
private boolean luaEnabled = true;
|
||||
private boolean historyEnabled = true;
|
||||
private InsEndpoint selectedIns;
|
||||
private LuaExecutionPolicy luaPolicy = LuaExecutionPolicy.uiDefault();
|
||||
|
||||
/**
|
||||
* Creates settings with defaults.
|
||||
*/
|
||||
public AppSettings() {
|
||||
// Defaults: include the current INSList defaults as initial endpoint.
|
||||
insEndpoints.add(new InsEndpoint("open-autonomous-connection.org", 1026));
|
||||
selectedIns = insEndpoints.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configured start page URL.
|
||||
*
|
||||
* @return start page URL
|
||||
*/
|
||||
public String getStartPageUrl() {
|
||||
return startPageUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the start page URL.
|
||||
*
|
||||
* @param startPageUrl URL (non-null, non-blank)
|
||||
*/
|
||||
public void setStartPageUrl(String startPageUrl) {
|
||||
String s = Objects.requireNonNull(startPageUrl, "startPageUrl").trim();
|
||||
if (s.isEmpty()) throw new IllegalArgumentException("startPageUrl must not be blank");
|
||||
this.startPageUrl = s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether SSL is enabled for protocol connections.
|
||||
*
|
||||
* @return true if enabled
|
||||
*/
|
||||
public boolean isSslEnabled() {
|
||||
return sslEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables/disables SSL.
|
||||
*
|
||||
* @param sslEnabled enabled
|
||||
*/
|
||||
public void setSslEnabled(boolean sslEnabled) {
|
||||
this.sslEnabled = sslEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether Lua runtime is enabled in WebView.
|
||||
*
|
||||
* @return true if enabled
|
||||
*/
|
||||
public boolean isLuaEnabled() {
|
||||
return luaEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables/disables Lua.
|
||||
*
|
||||
* @param luaEnabled enabled
|
||||
*/
|
||||
public void setLuaEnabled(boolean luaEnabled) {
|
||||
this.luaEnabled = luaEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether history is enabled.
|
||||
*
|
||||
* @return true if enabled
|
||||
*/
|
||||
public boolean isHistoryEnabled() {
|
||||
return historyEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables/disables history tracking.
|
||||
*
|
||||
* @param historyEnabled enabled
|
||||
*/
|
||||
public void setHistoryEnabled(boolean historyEnabled) {
|
||||
this.historyEnabled = historyEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a mutable INS endpoint list.
|
||||
*
|
||||
* @return list (mutable)
|
||||
*/
|
||||
public List<InsEndpoint> getInsEndpointsMutable() {
|
||||
return insEndpoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an immutable view of INS endpoints.
|
||||
*
|
||||
* @return endpoints
|
||||
*/
|
||||
public List<InsEndpoint> getInsEndpoints() {
|
||||
return Collections.unmodifiableList(insEndpoints);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns currently selected INS.
|
||||
*
|
||||
* @return selected endpoint
|
||||
*/
|
||||
public InsEndpoint getSelectedIns() {
|
||||
return selectedIns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets selected INS endpoint.
|
||||
*
|
||||
* @param selectedIns endpoint (must exist in list or will be added)
|
||||
*/
|
||||
public void setSelectedIns(InsEndpoint selectedIns) {
|
||||
Objects.requireNonNull(selectedIns, "selectedIns");
|
||||
if (!insEndpoints.contains(selectedIns)) {
|
||||
insEndpoints.add(selectedIns);
|
||||
}
|
||||
this.selectedIns = selectedIns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a mutable favorites list.
|
||||
*
|
||||
* @return favorites (mutable)
|
||||
*/
|
||||
public List<String> getFavoritesMutable() {
|
||||
return favorites;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns immutable favorites.
|
||||
*
|
||||
* @return favorites
|
||||
*/
|
||||
public List<String> getFavorites() {
|
||||
return Collections.unmodifiableList(favorites);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Lua execution policy.
|
||||
*
|
||||
* @return policy
|
||||
*/
|
||||
public LuaExecutionPolicy getLuaPolicy() {
|
||||
return luaPolicy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets Lua execution policy.
|
||||
*
|
||||
* @param luaPolicy policy (non-null)
|
||||
*/
|
||||
public void setLuaPolicy(LuaExecutionPolicy luaPolicy) {
|
||||
this.luaPolicy = Objects.requireNonNull(luaPolicy, "luaPolicy");
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the Lua policy back to ui default.
|
||||
*/
|
||||
public void resetLuaPolicyToUiDefault() {
|
||||
this.luaPolicy = new LuaExecutionPolicy(Duration.ofMillis(50L), 200_000L, 5_000);
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,7 @@ public final class FxEngine implements AutoCloseable {
|
||||
*
|
||||
* @param engine web engine
|
||||
* @param webView web view
|
||||
* @param logger web logger
|
||||
*/
|
||||
public FxEngine(WebEngine engine, WebView webView, WebLogger logger) {
|
||||
this(engine, webView, LuaExecutionPolicy.uiDefault(), logger);
|
||||
@@ -50,12 +51,13 @@ public final class FxEngine implements AutoCloseable {
|
||||
* @param engine web engine
|
||||
* @param webView web view
|
||||
* @param policy execution policy
|
||||
* @param logger web logger
|
||||
*/
|
||||
public FxEngine(WebEngine engine, WebView webView, LuaExecutionPolicy policy, WebLogger logger) {
|
||||
this.engine = Objects.requireNonNull(engine, "engine");
|
||||
this.webView = webView;
|
||||
this.webView = Objects.requireNonNull(webView, "webView");
|
||||
this.policy = Objects.requireNonNull(policy, "policy");
|
||||
this.logger = logger;
|
||||
this.logger = Objects.requireNonNull(logger, "logger");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,32 +83,24 @@ public final class FxEngine implements AutoCloseable {
|
||||
|
||||
closeRuntimeQuietly();
|
||||
|
||||
// DOM host must exist before event/UI tables, and must ensure stable ids.
|
||||
FxDomHost dom = new FxDomHost(engine);
|
||||
dom.ensureAllElementsHaveId();
|
||||
|
||||
// Create per-page globals; harden sandbox in production.
|
||||
Globals globals = LuaGlobalsFactory.create(
|
||||
new LuaGlobalsFactory.Options()
|
||||
.enableDebug(false)
|
||||
.sandbox(true)
|
||||
);
|
||||
|
||||
// Create runtime first (router lives inside it).
|
||||
ConsoleHostImpl console = new ConsoleHostImpl(logger);
|
||||
UiHostImpl uiHost = new UiHostImpl(engine, webView, dom);
|
||||
FxWebViewResourceHost resourceHost = new FxWebViewResourceHost(engine);
|
||||
|
||||
// runtime depends on services; events depends on runtime router.
|
||||
// We'll create eventHost after runtime, then build HostServices with it.
|
||||
LuaRuntime rt = new LuaRuntime(globals, new HostServices.Default(uiHost, dom, null, resourceHost, console), policy);
|
||||
|
||||
FxEventHost eventHost = new FxEventHost(dom, rt.eventRouter());
|
||||
|
||||
// Rebuild services including eventHost and reinstall tables.
|
||||
HostServices services = new HostServices.Default(uiHost, dom, eventHost, resourceHost, console);
|
||||
|
||||
// Replace runtime with correct services (clean and deterministic).
|
||||
rt.close();
|
||||
rt = new LuaRuntime(globals, services, policy);
|
||||
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package org.openautonomousconnection.webclient.settings;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* In-memory history tracker (URL + timestamp).
|
||||
*
|
||||
* <p>Persistence can be added later; for now it follows settings toggle and supports clearing.</p>
|
||||
*/
|
||||
public final class HistoryManager {
|
||||
|
||||
private final List<Entry> entries = new ArrayList<>();
|
||||
private volatile boolean enabled = true;
|
||||
|
||||
/**
|
||||
* Returns whether history is enabled.
|
||||
*
|
||||
* @return enabled
|
||||
*/
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether history is enabled.
|
||||
*
|
||||
* @param enabled enabled
|
||||
*/
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
if (!enabled) {
|
||||
clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a URL to history if enabled.
|
||||
*
|
||||
* @param url url (non-null, non-blank)
|
||||
*/
|
||||
public void add(String url) {
|
||||
if (!enabled) return;
|
||||
String s = Objects.requireNonNull(url, "url").trim();
|
||||
if (s.isEmpty()) return;
|
||||
|
||||
// De-dup consecutive duplicates
|
||||
if (!entries.isEmpty()) {
|
||||
Entry last = entries.get(entries.size() - 1);
|
||||
if (last.url().equals(s)) return;
|
||||
}
|
||||
entries.add(new Entry(s, Instant.now()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears history.
|
||||
*/
|
||||
public void clear() {
|
||||
entries.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns immutable entries.
|
||||
*
|
||||
* @return entries
|
||||
*/
|
||||
public List<Entry> entries() {
|
||||
return Collections.unmodifiableList(entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Single history entry.
|
||||
*
|
||||
* @param url visited URL
|
||||
* @param visitedAt timestamp
|
||||
*/
|
||||
public record Entry(String url, Instant visitedAt) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package org.openautonomousconnection.webclient.settings;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Persists browsing history to disk.
|
||||
*
|
||||
* <p>Format per line: {@code <epochMillis>\t<url>}</p>
|
||||
*/
|
||||
public final class HistoryStore {
|
||||
private static final String FILE_NAME = "history.log";
|
||||
private final File file;
|
||||
|
||||
/**
|
||||
* Creates a history store in the default user settings directory.
|
||||
*/
|
||||
public HistoryStore() {
|
||||
this(historyFile());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a history store for a specific file.
|
||||
*
|
||||
* @param file history file
|
||||
*/
|
||||
public HistoryStore(File file) {
|
||||
this.file = Objects.requireNonNull(file, "file");
|
||||
File dir = file.getParentFile();
|
||||
if (dir != null && !dir.isDirectory()) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
dir.mkdirs();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default history file location.
|
||||
*
|
||||
* @return file
|
||||
*/
|
||||
public static File historyFile() {
|
||||
return new File(FILE_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads history entries from disk.
|
||||
*
|
||||
* @return entries (immutable)
|
||||
*/
|
||||
public List<Entry> load() {
|
||||
if (!file.isFile()) return List.of();
|
||||
|
||||
List<Entry> out = new ArrayList<>();
|
||||
try (BufferedReader br = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) {
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
int tab = line.indexOf('\t');
|
||||
if (tab <= 0) continue;
|
||||
String tsS = line.substring(0, tab).trim();
|
||||
String url = line.substring(tab + 1).trim();
|
||||
if (url.isEmpty()) continue;
|
||||
|
||||
long ms;
|
||||
try {
|
||||
ms = Long.parseLong(tsS);
|
||||
} catch (Exception ignored) {
|
||||
continue;
|
||||
}
|
||||
out.add(new Entry(url, Instant.ofEpochMilli(ms)));
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
return List.of();
|
||||
}
|
||||
return Collections.unmodifiableList(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a single entry to disk (best-effort).
|
||||
*
|
||||
* @param url visited URL
|
||||
* @param at timestamp
|
||||
*/
|
||||
public void append(String url, Instant at) {
|
||||
Objects.requireNonNull(url, "url");
|
||||
Objects.requireNonNull(at, "at");
|
||||
String u = url.trim();
|
||||
if (u.isEmpty()) return;
|
||||
|
||||
try (Writer w = new BufferedWriter(new OutputStreamWriter(
|
||||
new FileOutputStream(file, true), StandardCharsets.UTF_8))) {
|
||||
w.write(Long.toString(at.toEpochMilli()));
|
||||
w.write('\t');
|
||||
w.write(u.replace('\n', ' ').replace('\r', ' '));
|
||||
w.write('\n');
|
||||
} catch (Exception ignored) {
|
||||
// Best-effort
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears history file (best-effort).
|
||||
*/
|
||||
public void clear() {
|
||||
try {
|
||||
Files.deleteIfExists(file.toPath());
|
||||
} catch (Exception ignored) {
|
||||
// Best-effort
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* History entry.
|
||||
*
|
||||
* @param url visited URL
|
||||
* @param visitedAt timestamp
|
||||
*/
|
||||
public record Entry(String url, Instant visitedAt) {
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package org.openautonomousconnection.webclient.settings;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public class INSList {
|
||||
|
||||
public static String DEFAULT_INS = "open-autonomous-connection.org";
|
||||
public static int DEFAULT_PORT = 1026;
|
||||
|
||||
private static HashMap<String, Integer> insList = new HashMap<>();
|
||||
|
||||
public static void registerINS(String host, int tcpPort) {
|
||||
insList.put(host, tcpPort);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package org.openautonomousconnection.webclient.settings;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Represents an INS endpoint (host + TCP port).
|
||||
*/
|
||||
public record InsEndpoint(String host, int port) {
|
||||
|
||||
/**
|
||||
* Creates an INS endpoint.
|
||||
*
|
||||
* @param host endpoint host (non-null, non-blank)
|
||||
* @param port tcp port (1..65535)
|
||||
*/
|
||||
public InsEndpoint(String host, int port) {
|
||||
String h = Objects.requireNonNull(host, "host").trim();
|
||||
if (h.isEmpty()) throw new IllegalArgumentException("host must not be blank");
|
||||
if (port < 1 || port > 65535) throw new IllegalArgumentException("port out of range: " + port);
|
||||
this.host = h;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the host.
|
||||
*
|
||||
* @return host
|
||||
*/
|
||||
@Override
|
||||
public String host() {
|
||||
return host;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the tcp port.
|
||||
*
|
||||
* @return port
|
||||
*/
|
||||
@Override
|
||||
public int port() {
|
||||
return port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return host + ":" + port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof InsEndpoint other)) return false;
|
||||
return host.equalsIgnoreCase(other.host) && port == other.port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return host.toLowerCase().hashCode() * 31 + port;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
package org.openautonomousconnection.webclient.settings;
|
||||
|
||||
import org.openautonomousconnection.luascript.security.LuaExecutionPolicy;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Loads/saves {@link AppSettings} to a simple properties file.
|
||||
*
|
||||
* <p>Location: {@code settings.properties}</p>
|
||||
*/
|
||||
public final class SettingsManager {
|
||||
|
||||
private static final String FILE_NAME = "settings.properties";
|
||||
|
||||
private SettingsManager() {
|
||||
// Utility class
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the settings file location.
|
||||
*
|
||||
* @return settings file
|
||||
*/
|
||||
public static File settingsFile() {
|
||||
return new File(FILE_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads settings from disk. If missing, returns defaults.
|
||||
*
|
||||
* @return settings
|
||||
*/
|
||||
public static AppSettings load() {
|
||||
AppSettings s = new AppSettings();
|
||||
File f = settingsFile();
|
||||
if (!f.isFile()) return s;
|
||||
|
||||
Properties p = new Properties();
|
||||
try (InputStream in = Files.newInputStream(f.toPath())) {
|
||||
p.load(new InputStreamReader(in, StandardCharsets.UTF_8));
|
||||
} catch (Exception ignored) {
|
||||
return s;
|
||||
}
|
||||
|
||||
trySetString(p, "startPageUrl", s::setStartPageUrl);
|
||||
s.setSslEnabled(parseBool(p.getProperty("sslEnabled"), s.isSslEnabled()));
|
||||
s.setLuaEnabled(parseBool(p.getProperty("luaEnabled"), s.isLuaEnabled()));
|
||||
s.setHistoryEnabled(parseBool(p.getProperty("historyEnabled"), s.isHistoryEnabled()));
|
||||
|
||||
// INS endpoints
|
||||
List<InsEndpoint> endpoints = new ArrayList<>();
|
||||
int count = parseInt(p.getProperty("ins.count"), 0);
|
||||
for (int i = 0; i < count; i++) {
|
||||
String host = p.getProperty("ins." + i + ".host");
|
||||
int port = parseInt(p.getProperty("ins." + i + ".port"), -1);
|
||||
if (host == null || host.isBlank() || port < 1 || port > 65535) continue;
|
||||
endpoints.add(new InsEndpoint(host.trim(), port));
|
||||
}
|
||||
if (!endpoints.isEmpty()) {
|
||||
s.getInsEndpointsMutable().clear();
|
||||
s.getInsEndpointsMutable().addAll(endpoints);
|
||||
}
|
||||
|
||||
String selHost = p.getProperty("ins.selected.host");
|
||||
int selPort = parseInt(p.getProperty("ins.selected.port"), -1);
|
||||
if (selHost != null && !selHost.isBlank() && selPort >= 1 && selPort <= 65535) {
|
||||
s.setSelectedIns(new InsEndpoint(selHost.trim(), selPort));
|
||||
} else {
|
||||
// Keep default selection, but ensure it exists in list.
|
||||
s.setSelectedIns(s.getSelectedIns());
|
||||
}
|
||||
|
||||
// Favorites
|
||||
int favCount = parseInt(p.getProperty("favorites.count"), 0);
|
||||
s.getFavoritesMutable().clear();
|
||||
for (int i = 0; i < favCount; i++) {
|
||||
String url = p.getProperty("favorites." + i);
|
||||
if (url != null && !url.isBlank()) s.getFavoritesMutable().add(url.trim());
|
||||
}
|
||||
|
||||
// Lua policy
|
||||
long timeoutMs = parseLong(p.getProperty("lua.timeoutMs"), 50L);
|
||||
long instr = parseLong(p.getProperty("lua.instructionLimit"), 200_000L);
|
||||
int hook = parseInt(p.getProperty("lua.hookStep"), 5_000);
|
||||
try {
|
||||
s.setLuaPolicy(new LuaExecutionPolicy(Duration.ofMillis(timeoutMs), instr, hook));
|
||||
} catch (Exception ignored) {
|
||||
s.resetLuaPolicyToUiDefault();
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves settings to disk (best-effort).
|
||||
*
|
||||
* @param s settings
|
||||
*/
|
||||
public static void save(AppSettings s) {
|
||||
Objects.requireNonNull(s, "s");
|
||||
|
||||
File f = settingsFile();
|
||||
File dir = f.getParentFile();
|
||||
if (dir != null && !dir.isDirectory()) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
dir.mkdirs();
|
||||
}
|
||||
|
||||
Properties p = new Properties();
|
||||
p.setProperty("startPageUrl", s.getStartPageUrl());
|
||||
p.setProperty("sslEnabled", Boolean.toString(s.isSslEnabled()));
|
||||
p.setProperty("luaEnabled", Boolean.toString(s.isLuaEnabled()));
|
||||
p.setProperty("historyEnabled", Boolean.toString(s.isHistoryEnabled()));
|
||||
|
||||
List<InsEndpoint> endpoints = s.getInsEndpoints();
|
||||
p.setProperty("ins.count", Integer.toString(endpoints.size()));
|
||||
for (int i = 0; i < endpoints.size(); i++) {
|
||||
InsEndpoint ep = endpoints.get(i);
|
||||
p.setProperty("ins." + i + ".host", ep.host());
|
||||
p.setProperty("ins." + i + ".port", Integer.toString(ep.port()));
|
||||
}
|
||||
|
||||
InsEndpoint sel = s.getSelectedIns();
|
||||
if (sel != null) {
|
||||
p.setProperty("ins.selected.host", sel.host());
|
||||
p.setProperty("ins.selected.port", Integer.toString(sel.port()));
|
||||
}
|
||||
|
||||
List<String> fav = s.getFavorites();
|
||||
p.setProperty("favorites.count", Integer.toString(fav.size()));
|
||||
for (int i = 0; i < fav.size(); i++) {
|
||||
p.setProperty("favorites." + i, fav.get(i));
|
||||
}
|
||||
|
||||
p.setProperty("lua.timeoutMs", Long.toString(s.getLuaPolicy().timeout().toMillis()));
|
||||
p.setProperty("lua.instructionLimit", Long.toString(s.getLuaPolicy().instructionLimit()));
|
||||
p.setProperty("lua.hookStep", Integer.toString(s.getLuaPolicy().hookStep()));
|
||||
|
||||
try (OutputStream out = Files.newOutputStream(f.toPath())) {
|
||||
p.store(new OutputStreamWriter(out, StandardCharsets.UTF_8), "OAC WebClient Settings");
|
||||
} catch (Exception ignored) {
|
||||
// Best-effort persistence
|
||||
}
|
||||
}
|
||||
|
||||
private static void trySetString(Properties p, String key, java.util.function.Consumer<String> setter) {
|
||||
String v = p.getProperty(key);
|
||||
if (v != null && !v.isBlank()) {
|
||||
try {
|
||||
setter.accept(v.trim());
|
||||
} catch (Exception ignored) {
|
||||
// Ignore malformed value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean parseBool(String v, boolean def) {
|
||||
if (v == null) return def;
|
||||
String s = v.trim().toLowerCase();
|
||||
if (s.equals("true")) return true;
|
||||
if (s.equals("false")) return false;
|
||||
return def;
|
||||
}
|
||||
|
||||
private static int parseInt(String v, int def) {
|
||||
if (v == null) return def;
|
||||
try {
|
||||
return Integer.parseInt(v.trim());
|
||||
} catch (Exception e) {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
private static long parseLong(String v, long def) {
|
||||
if (v == null) return def;
|
||||
try {
|
||||
return Long.parseLong(v.trim());
|
||||
} catch (Exception e) {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user