forked from I2P_Developers/i2p.i2p
Begin i2p.i2p.zzz.confsplit branch
Code from Jan. 2018, never checked in, probably untested, definitely incomplete.
This commit is contained in:
@ -789,14 +789,19 @@ public class TunnelController implements Logging {
|
|||||||
* As of 0.9.1, updates the options on an existing session
|
* As of 0.9.1, updates the options on an existing session
|
||||||
*/
|
*/
|
||||||
public void setConfig(Properties config, String prefix) {
|
public void setConfig(Properties config, String prefix) {
|
||||||
Properties props = new Properties();
|
Properties props;
|
||||||
for (Map.Entry<Object, Object> e : config.entrySet()) {
|
if (prefix.length() > 0) {
|
||||||
String key = (String) e.getKey();
|
props = new Properties();
|
||||||
if (key.startsWith(prefix)) {
|
for (Map.Entry<Object, Object> e : config.entrySet()) {
|
||||||
key = key.substring(prefix.length());
|
String key = (String) e.getKey();
|
||||||
String val = (String) e.getValue();
|
if (key.startsWith(prefix)) {
|
||||||
props.setProperty(key, val);
|
key = key.substring(prefix.length());
|
||||||
|
String val = (String) e.getValue();
|
||||||
|
props.setProperty(key, val);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
props = config;
|
||||||
}
|
}
|
||||||
Properties oldConfig = _config;
|
Properties oldConfig = _config;
|
||||||
_config = props;
|
_config = props;
|
||||||
@ -921,10 +926,14 @@ public class TunnelController implements Logging {
|
|||||||
*/
|
*/
|
||||||
public Properties getConfig(String prefix) {
|
public Properties getConfig(String prefix) {
|
||||||
Properties rv = new Properties();
|
Properties rv = new Properties();
|
||||||
for (Map.Entry<Object, Object> e : _config.entrySet()) {
|
if (prefix.length() > 0) {
|
||||||
String key = (String) e.getKey();
|
for (Map.Entry<Object, Object> e : _config.entrySet()) {
|
||||||
String val = (String) e.getValue();
|
String key = (String) e.getKey();
|
||||||
rv.setProperty(prefix + key, val);
|
String val = (String) e.getValue();
|
||||||
|
rv.setProperty(prefix + key, val);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rv.putAll(_config);
|
||||||
}
|
}
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package net.i2p.i2ptunnel;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -24,9 +25,11 @@ import static net.i2p.app.ClientAppState.*;
|
|||||||
import net.i2p.client.I2PSession;
|
import net.i2p.client.I2PSession;
|
||||||
import net.i2p.client.I2PSessionException;
|
import net.i2p.client.I2PSessionException;
|
||||||
import net.i2p.data.DataHelper;
|
import net.i2p.data.DataHelper;
|
||||||
|
import net.i2p.util.FileUtil;
|
||||||
import net.i2p.util.I2PAppThread;
|
import net.i2p.util.I2PAppThread;
|
||||||
import net.i2p.util.Log;
|
import net.i2p.util.Log;
|
||||||
import net.i2p.util.OrderedProperties;
|
import net.i2p.util.OrderedProperties;
|
||||||
|
import net.i2p.util.SecureDirectory;
|
||||||
import net.i2p.util.SystemVersion;
|
import net.i2p.util.SystemVersion;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -42,6 +45,8 @@ public class TunnelControllerGroup implements ClientApp {
|
|||||||
private final ClientAppManager _mgr;
|
private final ClientAppManager _mgr;
|
||||||
private static volatile TunnelControllerGroup _instance;
|
private static volatile TunnelControllerGroup _instance;
|
||||||
static final String DEFAULT_CONFIG_FILE = "i2ptunnel.config";
|
static final String DEFAULT_CONFIG_FILE = "i2ptunnel.config";
|
||||||
|
private static final String CONFIG_DIR = "i2ptunnel.config.d";
|
||||||
|
private static final String PREFIX = "tunnel.";
|
||||||
|
|
||||||
private final List<TunnelController> _controllers;
|
private final List<TunnelController> _controllers;
|
||||||
private final ReadWriteLock _controllersLock;
|
private final ReadWriteLock _controllersLock;
|
||||||
@ -265,18 +270,80 @@ public class TunnelControllerGroup implements ClientApp {
|
|||||||
public synchronized void loadControllers(String configFile) {
|
public synchronized void loadControllers(String configFile) {
|
||||||
if (_controllersLoaded)
|
if (_controllersLoaded)
|
||||||
return;
|
return;
|
||||||
|
boolean shouldMigrate = _context.isRouterContext() && !SystemVersion.isAndroid();
|
||||||
Properties cfg = loadConfig(configFile);
|
loadControllers(configFile, shouldMigrate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param shouldMigrate migrate to, and load from, i2ptunnel.config.d
|
||||||
|
* @since 0.9.34
|
||||||
|
* @throws IllegalArgumentException if unable to load from file
|
||||||
|
*/
|
||||||
|
private synchronized void loadControllers(String configFile, boolean shouldMigrate) {
|
||||||
|
File cfgFile = new File(configFile);
|
||||||
|
if (!cfgFile.isAbsolute())
|
||||||
|
cfgFile = new File(_context.getConfigDir(), configFile);
|
||||||
|
File dir = new SecureDirectory(cfgFile.getParent(), CONFIG_DIR);
|
||||||
|
List<Properties> props = null;
|
||||||
|
if (cfgFile.exists()) {
|
||||||
|
try {
|
||||||
|
List<Properties> cfgs = loadConfig(cfgFile);
|
||||||
|
if (shouldMigrate) {
|
||||||
|
boolean ok = migrate(cfgs, cfgFile, dir);
|
||||||
|
if (!ok)
|
||||||
|
shouldMigrate = false;
|
||||||
|
}
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
_log.error("Unable to load the controllers from " + cfgFile.getAbsolutePath());
|
||||||
|
throw new IllegalArgumentException("Unable to load the controllers from " + cfgFile, ioe);
|
||||||
|
}
|
||||||
|
} else if (!shouldMigrate) {
|
||||||
|
throw new IllegalArgumentException("Unable to load the controllers from " + cfgFile);
|
||||||
|
}
|
||||||
int i = 0;
|
int i = 0;
|
||||||
_controllersLock.writeLock().lock();
|
_controllersLock.writeLock().lock();
|
||||||
try {
|
try {
|
||||||
while (true) {
|
if (shouldMigrate && dir.isDirectory()) {
|
||||||
String type = cfg.getProperty("tunnel." + i + ".type");
|
File[] files = dir.listFiles();
|
||||||
if (type == null)
|
if (files != null && files.length > 0) {
|
||||||
break;
|
// sort so the returned order is consistent
|
||||||
TunnelController controller = new TunnelController(cfg, "tunnel." + i + ".");
|
Arrays.sort(files);
|
||||||
_controllers.add(controller);
|
for (File f : files) {
|
||||||
i++;
|
if (!f.getName().endsWith(".config"))
|
||||||
|
continue;
|
||||||
|
if (!f.isFile())
|
||||||
|
continue;
|
||||||
|
try {
|
||||||
|
props = loadConfig(f);
|
||||||
|
if (!props.isEmpty()) {
|
||||||
|
for (Properties cfg : props) {
|
||||||
|
String type = cfg.getProperty("type");
|
||||||
|
if (type == null)
|
||||||
|
continue;
|
||||||
|
TunnelController controller = new TunnelController(cfg, "");
|
||||||
|
_controllers.add(controller);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_log.error("Error loading the client app properties from " + f);
|
||||||
|
System.out.println("Error loading the client app properties from " + f);
|
||||||
|
}
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
_log.error("Error loading the client app properties from " + f, ioe);
|
||||||
|
System.out.println("Error loading the client app properties from " + f + ' ' + ioe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// use what we got from i2ptunnel.config
|
||||||
|
for (Properties cfg : props) {
|
||||||
|
String type = cfg.getProperty("type");
|
||||||
|
if (type == null)
|
||||||
|
continue;
|
||||||
|
TunnelController controller = new TunnelController(cfg, "");
|
||||||
|
_controllers.add(controller);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
_controllersLock.writeLock().unlock();
|
_controllersLock.writeLock().unlock();
|
||||||
@ -284,13 +351,55 @@ public class TunnelControllerGroup implements ClientApp {
|
|||||||
|
|
||||||
_controllersLoaded = true;
|
_controllersLoaded = true;
|
||||||
if (i > 0) {
|
if (i > 0) {
|
||||||
|
_controllersLoaded = true;
|
||||||
if (_log.shouldLog(Log.INFO))
|
if (_log.shouldLog(Log.INFO))
|
||||||
_log.info(i + " controllers loaded from " + configFile);
|
_log.info(i + " controllers loaded from " + configFile);
|
||||||
} else {
|
} else {
|
||||||
_log.logAlways(Log.WARN, "No i2ptunnel configurations found in " + configFile);
|
_log.logAlways(Log.WARN, "No i2ptunnel configurations found in " + cfgFile + " or " + dir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Migrate tunnels from file to individual files in dir
|
||||||
|
*
|
||||||
|
* @return success
|
||||||
|
* @since 0.9.34
|
||||||
|
*/
|
||||||
|
private boolean migrate(List<Properties> tunnels, File from, File dir) {
|
||||||
|
if (!dir.isDirectory() && !dir.mkdirs())
|
||||||
|
return false;
|
||||||
|
boolean ok = true;
|
||||||
|
for (int i = 0; i < tunnels.size(); i++) {
|
||||||
|
Properties props = tunnels.get(i);
|
||||||
|
String tname = props.getProperty("name");
|
||||||
|
if (tname == null)
|
||||||
|
tname = "tunnel";
|
||||||
|
String name = i + "-" + tname + "-i2ptunnel.config";
|
||||||
|
if (i < 10)
|
||||||
|
name = '0' + name;
|
||||||
|
File f = new File(dir, name);
|
||||||
|
props.setProperty("configFile", f.getAbsolutePath());
|
||||||
|
Properties save = new OrderedProperties();
|
||||||
|
for (Map.Entry<Object, Object> e : props.entrySet()) {
|
||||||
|
String key = (String) e.getKey();
|
||||||
|
String val = (String) e.getValue();
|
||||||
|
save.setProperty(PREFIX + i + '.' + key, val);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
DataHelper.storeProps(save, f);
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
_log.error("Error migrating the i2ptunnel configuration to " + f, ioe);
|
||||||
|
System.out.println("Error migrating the i2ptunnel configuration to " + f + ' ' + ioe);
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ok) {
|
||||||
|
if (!FileUtil.rename(from, new File(from.getAbsolutePath() + ".bak")))
|
||||||
|
from.delete();
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start all of the tunnels. Must call loadControllers() first.
|
* Start all of the tunnels. Must call loadControllers() first.
|
||||||
* @since 0.9.20
|
* @since 0.9.20
|
||||||
@ -375,6 +484,7 @@ public class TunnelControllerGroup implements ClientApp {
|
|||||||
/**
|
/**
|
||||||
* Stop and remove the given tunnel.
|
* Stop and remove the given tunnel.
|
||||||
* Side effect - clears all messages the controller.
|
* Side effect - clears all messages the controller.
|
||||||
|
* Does NOT delete the configuration - must call saveConfig() or removeConfig() also.
|
||||||
*
|
*
|
||||||
* @return list of messages from the controller as it is stopped
|
* @return list of messages from the controller as it is stopped
|
||||||
*/
|
*/
|
||||||
@ -500,19 +610,22 @@ public class TunnelControllerGroup implements ClientApp {
|
|||||||
* Save the configuration of all known tunnels to the default config
|
* Save the configuration of all known tunnels to the default config
|
||||||
* file
|
* file
|
||||||
*
|
*
|
||||||
|
* @deprecated use saveConfig(TunnelController) or removeConfig(TunnelController)
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public void saveConfig() throws IOException {
|
public void saveConfig() throws IOException {
|
||||||
saveConfig(_configFile);
|
saveConfig(_configFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save the configuration of all known tunnels to the given file
|
* Save the configuration of all known tunnels to the given file
|
||||||
*
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public synchronized void saveConfig(String configFile) throws IOException {
|
public synchronized void saveConfig(String configFile) throws IOException {
|
||||||
File cfgFile = new File(configFile);
|
File cfgFile = new File(configFile);
|
||||||
if (!cfgFile.isAbsolute())
|
if (!cfgFile.isAbsolute())
|
||||||
cfgFile = new File(I2PAppContext.getGlobalContext().getConfigDir(), configFile);
|
cfgFile = new File(_context.getConfigDir(), configFile);
|
||||||
File parent = cfgFile.getParentFile();
|
File parent = cfgFile.getParentFile();
|
||||||
if ( (parent != null) && (!parent.exists()) )
|
if ( (parent != null) && (!parent.exists()) )
|
||||||
parent.mkdirs();
|
parent.mkdirs();
|
||||||
@ -522,7 +635,7 @@ public class TunnelControllerGroup implements ClientApp {
|
|||||||
try {
|
try {
|
||||||
for (int i = 0; i < _controllers.size(); i++) {
|
for (int i = 0; i < _controllers.size(); i++) {
|
||||||
TunnelController controller = _controllers.get(i);
|
TunnelController controller = _controllers.get(i);
|
||||||
Properties cur = controller.getConfig("tunnel." + i + ".");
|
Properties cur = controller.getConfig(PREFIX + i + ".");
|
||||||
map.putAll(cur);
|
map.putAll(cur);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@ -531,32 +644,50 @@ public class TunnelControllerGroup implements ClientApp {
|
|||||||
|
|
||||||
DataHelper.storeProps(map, cfgFile);
|
DataHelper.storeProps(map, cfgFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the configuration of this tunnel only, may be new
|
||||||
|
* @since 0.9.34
|
||||||
|
*/
|
||||||
|
public synchronized void saveConfig(TunnelController tc) throws IOException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the configuration of this tunnel only
|
||||||
|
* @since 0.9.34
|
||||||
|
*/
|
||||||
|
public synchronized void removeConfig(TunnelController tc) throws IOException {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load up the config data from the file
|
* Load up the config data from the file
|
||||||
*
|
*
|
||||||
* @return properties loaded
|
* @return non-null, properties loaded, one for each tunnel
|
||||||
* @throws IllegalArgumentException if unable to load from file
|
* @throws IOException if unable to load from file
|
||||||
*/
|
*/
|
||||||
private synchronized Properties loadConfig(String configFile) {
|
private synchronized List<Properties> loadConfig(File cfgFile) throws IOException {
|
||||||
File cfgFile = new File(configFile);
|
Properties config = new Properties();
|
||||||
if (!cfgFile.isAbsolute())
|
DataHelper.loadProps(config, cfgFile);
|
||||||
cfgFile = new File(I2PAppContext.getGlobalContext().getConfigDir(), configFile);
|
List<Properties> rv = new ArrayList<Properties>();
|
||||||
if (!cfgFile.exists()) {
|
int i = 0;
|
||||||
if (_log.shouldLog(Log.ERROR))
|
while (true) {
|
||||||
_log.error("Unable to load the controllers from " + cfgFile.getAbsolutePath());
|
String prefix = PREFIX + i + '.';
|
||||||
throw new IllegalArgumentException("Unable to load the controllers from " + cfgFile.getAbsolutePath());
|
Properties p = new Properties();
|
||||||
}
|
for (Map.Entry<Object, Object> e : config.entrySet()) {
|
||||||
|
String key = (String) e.getKey();
|
||||||
Properties props = new Properties();
|
if (key.startsWith(prefix)) {
|
||||||
try {
|
key = key.substring(prefix.length());
|
||||||
DataHelper.loadProps(props, cfgFile);
|
String val = (String) e.getValue();
|
||||||
return props;
|
p.setProperty(key, val);
|
||||||
} catch (IOException ioe) {
|
}
|
||||||
if (_log.shouldLog(Log.ERROR))
|
}
|
||||||
_log.error("Error reading the controllers from " + cfgFile.getAbsolutePath(), ioe);
|
if (p.isEmpty())
|
||||||
throw new IllegalArgumentException("Error reading the controllers from " + cfgFile.getAbsolutePath(), ioe);
|
break;
|
||||||
|
p.setProperty("configFile", cfgFile.getAbsolutePath());
|
||||||
|
rv.add(p);
|
||||||
|
i++;
|
||||||
}
|
}
|
||||||
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -82,6 +82,7 @@ public class GeneralHelper {
|
|||||||
public static List<String> saveTunnel(
|
public static List<String> saveTunnel(
|
||||||
I2PAppContext context, TunnelControllerGroup tcg, int tunnel, TunnelConfig config) {
|
I2PAppContext context, TunnelControllerGroup tcg, int tunnel, TunnelConfig config) {
|
||||||
List<String> msgs = updateTunnelConfig(tcg, tunnel, config);
|
List<String> msgs = updateTunnelConfig(tcg, tunnel, config);
|
||||||
|
///////////////
|
||||||
msgs.addAll(saveConfig(context, tcg));
|
msgs.addAll(saveConfig(context, tcg));
|
||||||
return msgs;
|
return msgs;
|
||||||
}
|
}
|
||||||
@ -175,6 +176,7 @@ public class GeneralHelper {
|
|||||||
protected static List<String> saveConfig(I2PAppContext context, TunnelControllerGroup tcg) {
|
protected static List<String> saveConfig(I2PAppContext context, TunnelControllerGroup tcg) {
|
||||||
List<String> rv = tcg.clearAllMessages();
|
List<String> rv = tcg.clearAllMessages();
|
||||||
try {
|
try {
|
||||||
|
////////////////
|
||||||
tcg.saveConfig();
|
tcg.saveConfig();
|
||||||
rv.add(0, _t("Configuration changes saved", context));
|
rv.add(0, _t("Configuration changes saved", context));
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
@ -205,6 +207,7 @@ public class GeneralHelper {
|
|||||||
return msgs;
|
return msgs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////
|
||||||
msgs = tcg.removeController(cur);
|
msgs = tcg.removeController(cur);
|
||||||
msgs.addAll(saveConfig(context, tcg));
|
msgs.addAll(saveConfig(context, tcg));
|
||||||
|
|
||||||
|
@ -342,23 +342,29 @@ public class ConfigServiceHandler extends FormHandler {
|
|||||||
|
|
||||||
private void browseOnStartup(boolean shouldLaunchBrowser) {
|
private void browseOnStartup(boolean shouldLaunchBrowser) {
|
||||||
List<ClientAppConfig> clients = ClientAppConfig.getClientApps(_context);
|
List<ClientAppConfig> clients = ClientAppConfig.getClientApps(_context);
|
||||||
boolean found = false;
|
ClientAppConfig ca = null;
|
||||||
for (int cur = 0; cur < clients.size(); cur++) {
|
for (int cur = 0; cur < clients.size(); cur++) {
|
||||||
ClientAppConfig ca = clients.get(cur);
|
ClientAppConfig cac = clients.get(cur);
|
||||||
if (UrlLauncher.class.getName().equals(ca.className)) {
|
if (UrlLauncher.class.getName().equals(cac.className)) {
|
||||||
|
ca = cac;
|
||||||
ca.disabled = !shouldLaunchBrowser;
|
ca.disabled = !shouldLaunchBrowser;
|
||||||
found = true;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// releases <= 0.6.5 deleted the entry completely
|
// releases <= 0.6.5 deleted the entry completely
|
||||||
if (shouldLaunchBrowser && !found) {
|
if (shouldLaunchBrowser && ca == null) {
|
||||||
String url = _context.portMapper().getConsoleURL();
|
String url = _context.portMapper().getConsoleURL();
|
||||||
ClientAppConfig ca = new ClientAppConfig(UrlLauncher.class.getName(), "consoleBrowser",
|
ca = new ClientAppConfig(UrlLauncher.class.getName(), "consoleBrowser",
|
||||||
url, 5, false);
|
url, 5, false);
|
||||||
clients.add(ca);
|
}
|
||||||
|
try {
|
||||||
|
if (ca != null)
|
||||||
|
ClientAppConfig.writeClientAppConfig(_context, ca);
|
||||||
|
addFormNotice(_t("Configuration saved successfully"));
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
addFormError(_t("Error saving the configuration (applied but not saved) - please see the error logs"));
|
||||||
|
addFormError(ioe.getLocalizedMessage());
|
||||||
}
|
}
|
||||||
ClientAppConfig.writeClientAppConfig(_context, clients);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -244,6 +244,18 @@ public class ConfigClientsHandler extends FormHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void saveClientChanges() {
|
private void saveClientChanges() {
|
||||||
|
try {
|
||||||
|
synchronized(ClientAppConfig.class) {
|
||||||
|
saveClientChanges2();
|
||||||
|
}
|
||||||
|
addFormNotice(_t("Client configuration saved successfully"));
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
addFormError(_t("Error saving the configuration (applied but not saved) - please see the error logs"));
|
||||||
|
addFormError(ioe.getLocalizedMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveClientChanges2() throws IOException {
|
||||||
List<ClientAppConfig> clients = ClientAppConfig.getClientApps(_context);
|
List<ClientAppConfig> clients = ClientAppConfig.getClientApps(_context);
|
||||||
for (int cur = 0; cur < clients.size(); cur++) {
|
for (int cur = 0; cur < clients.size(); cur++) {
|
||||||
ClientAppConfig ca = clients.get(cur);
|
ClientAppConfig ca = clients.get(cur);
|
||||||
@ -287,14 +299,12 @@ public class ConfigClientsHandler extends FormHandler {
|
|||||||
if (name == null || name.trim().length() <= 0) name = "new client";
|
if (name == null || name.trim().length() <= 0) name = "new client";
|
||||||
ClientAppConfig ca = new ClientAppConfig(clss, name, args, 2*60*1000,
|
ClientAppConfig ca = new ClientAppConfig(clss, name, args, 2*60*1000,
|
||||||
_settings.get(newClient + ".enabled") == null); // true for disabled
|
_settings.get(newClient + ".enabled") == null); // true for disabled
|
||||||
clients.add(ca);
|
ClientAppConfig.writeClientAppConfig(_context, ca);
|
||||||
addFormNotice(_t("New client added") + ": " + name + " (" + clss + ").");
|
addFormNotice(_t("New client added") + ": " + name + " (" + clss + ").");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Always save, as any of the disabled flags could have changed
|
||||||
ClientAppConfig.writeClientAppConfig(_context, clients);
|
ClientAppConfig.writeClientAppConfig(_context, clients);
|
||||||
addFormNotice(_t("Client configuration saved successfully"));
|
|
||||||
//addFormNotice(_t("Restart required to take effect"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -347,9 +357,14 @@ public class ConfigClientsHandler extends FormHandler {
|
|||||||
addFormError(_t("Bad client index."));
|
addFormError(_t("Bad client index."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ClientAppConfig ca = clients.remove(i);
|
ClientAppConfig ca = clients.get(i);
|
||||||
ClientAppConfig.writeClientAppConfig(_context, clients);
|
try {
|
||||||
addFormNotice(_t("Client {0} deleted", ca.clientName));
|
ClientAppConfig.deleteClientAppConfig(ca);
|
||||||
|
addFormNotice(_t("Client {0} deleted", ca.clientName));
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
addFormError(_t("Error saving the configuration (applied but not saved) - please see the error logs"));
|
||||||
|
addFormError(ioe.getLocalizedMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveWebAppChanges() {
|
private void saveWebAppChanges() {
|
||||||
|
@ -1,17 +1,22 @@
|
|||||||
package net.i2p.router.startup;
|
package net.i2p.router.startup;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import net.i2p.I2PAppContext;
|
import net.i2p.I2PAppContext;
|
||||||
import net.i2p.data.DataHelper;
|
import net.i2p.data.DataHelper;
|
||||||
import net.i2p.router.RouterContext;
|
import net.i2p.router.RouterContext;
|
||||||
import net.i2p.util.SecureFileOutputStream;
|
import net.i2p.util.FileUtil;
|
||||||
|
import net.i2p.util.Log;
|
||||||
|
import net.i2p.util.ObjectCounter;
|
||||||
|
import net.i2p.util.OrderedProperties;
|
||||||
|
import net.i2p.util.SecureDirectory;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -71,6 +76,7 @@ public class ClientAppConfig {
|
|||||||
|
|
||||||
private static final String PROP_CLIENT_CONFIG_FILENAME = "router.clientConfigFile";
|
private static final String PROP_CLIENT_CONFIG_FILENAME = "router.clientConfigFile";
|
||||||
private static final String DEFAULT_CLIENT_CONFIG_FILENAME = "clients.config";
|
private static final String DEFAULT_CLIENT_CONFIG_FILENAME = "clients.config";
|
||||||
|
private static final String CLIENT_CONFIG_DIR = "clients.config.d";
|
||||||
private static final String PREFIX = "clientApp.";
|
private static final String PREFIX = "clientApp.";
|
||||||
|
|
||||||
// let's keep this really simple
|
// let's keep this really simple
|
||||||
@ -86,6 +92,8 @@ public class ClientAppConfig {
|
|||||||
public final String stopargs;
|
public final String stopargs;
|
||||||
/** @since 0.7.12 */
|
/** @since 0.7.12 */
|
||||||
public final String uninstallargs;
|
public final String uninstallargs;
|
||||||
|
/** @since 0.0.34 */
|
||||||
|
private File configFile;
|
||||||
|
|
||||||
public ClientAppConfig(String cl, String client, String a, long d, boolean dis) {
|
public ClientAppConfig(String cl, String client, String a, long d, boolean dis) {
|
||||||
this(cl, client, a, d, dis, null, null, null);
|
this(cl, client, a, d, dis, null, null, null);
|
||||||
@ -111,51 +119,104 @@ public class ClientAppConfig {
|
|||||||
return cfgFile;
|
return cfgFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Properties getClientAppProps(RouterContext ctx) {
|
|
||||||
Properties rv = new Properties();
|
|
||||||
File cfgFile = configFile(ctx);
|
|
||||||
|
|
||||||
// fall back to use router.config's clientApp.* lines
|
|
||||||
if (!cfgFile.exists()) {
|
|
||||||
System.out.println("Warning - No client config file " + cfgFile.getAbsolutePath());
|
|
||||||
rv.putAll(ctx.router().getConfigMap());
|
|
||||||
return rv;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
DataHelper.loadProps(rv, cfgFile);
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
System.out.println("Error loading the client app properties from " + cfgFile.getAbsolutePath() + ' ' + ioe);
|
|
||||||
}
|
|
||||||
|
|
||||||
return rv;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Go through the properties, and return a List of ClientAppConfig structures
|
* Go through the files, and return a List of ClientAppConfig structures
|
||||||
* This is for the router.
|
* This is for the router.
|
||||||
*/
|
*/
|
||||||
public static List<ClientAppConfig> getClientApps(RouterContext ctx) {
|
public synchronized static List<ClientAppConfig> getClientApps(RouterContext ctx) {
|
||||||
Properties clientApps = getClientAppProps(ctx);
|
File dir = new SecureDirectory(ctx.getConfigDir(), CLIENT_CONFIG_DIR);
|
||||||
List<ClientAppConfig> rv = getClientApps(clientApps);
|
// clients.config
|
||||||
MigrateJetty.migrate(ctx, rv);
|
List<ClientAppConfig> rv = new ArrayList<ClientAppConfig>(8);
|
||||||
|
File cf = configFile(ctx);
|
||||||
|
try {
|
||||||
|
List<ClientAppConfig> cacs = getClientApps(cf);
|
||||||
|
if (!cacs.isEmpty()) {
|
||||||
|
MigrateJetty.migrate(ctx, cacs);
|
||||||
|
boolean ok = migrate(ctx, cacs, cf, dir);
|
||||||
|
if (!ok)
|
||||||
|
rv.addAll(cacs);
|
||||||
|
}
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
ctx.logManager().getLog(ClientAppConfig.class).error("Error loading the client app properties from " + cf, ioe);
|
||||||
|
System.out.println("Error loading the client app properties from " + cf + ' ' + ioe);
|
||||||
|
}
|
||||||
|
// clients.config.d
|
||||||
|
if (dir.isDirectory()) {
|
||||||
|
File[] files = dir.listFiles();
|
||||||
|
if (files != null && files.length > 0) {
|
||||||
|
// sort so the returned order is consistent
|
||||||
|
Arrays.sort(files);
|
||||||
|
for (File f : files) {
|
||||||
|
if (!f.getName().endsWith(".config"))
|
||||||
|
continue;
|
||||||
|
if (!f.isFile())
|
||||||
|
continue;
|
||||||
|
try {
|
||||||
|
List<ClientAppConfig> cacs = getClientApps(f);
|
||||||
|
if (!cacs.isEmpty()) {
|
||||||
|
rv.addAll(cacs);
|
||||||
|
} else {
|
||||||
|
ctx.logManager().getLog(ClientAppConfig.class).error("Error loading the client app properties from " + f);
|
||||||
|
System.out.println("Error loading the client app properties from " + f);
|
||||||
|
}
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
ctx.logManager().getLog(ClientAppConfig.class).error("Error loading the client app properties from " + f, ioe);
|
||||||
|
System.out.println("Error loading the client app properties from " + f + ' ' + ioe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Go through the properties, and return a List of ClientAppConfig structures
|
* Go through the file, and return a List of ClientAppConfig structures
|
||||||
* This is for plugins.
|
|
||||||
*
|
*
|
||||||
* @since 0.7.12
|
* @since 0.7.12
|
||||||
*/
|
*/
|
||||||
public static List<ClientAppConfig> getClientApps(File cfgFile) {
|
public synchronized static List<ClientAppConfig> getClientApps(File cfgFile) throws IOException {
|
||||||
|
if (!cfgFile.isFile())
|
||||||
|
return new ArrayList<ClientAppConfig>();
|
||||||
Properties clientApps = new Properties();
|
Properties clientApps = new Properties();
|
||||||
try {
|
DataHelper.loadProps(clientApps, cfgFile);
|
||||||
DataHelper.loadProps(clientApps, cfgFile);
|
List<ClientAppConfig> rv = getClientApps(clientApps);
|
||||||
} catch (IOException ioe) {
|
for (ClientAppConfig cac : rv) {
|
||||||
return Collections.emptyList();
|
cac.configFile = cfgFile;
|
||||||
}
|
}
|
||||||
return getClientApps(clientApps);
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Migrate apps from file to individual files in dir
|
||||||
|
*
|
||||||
|
* @return success
|
||||||
|
* @since 0.9.34
|
||||||
|
*/
|
||||||
|
private static boolean migrate(I2PAppContext ctx, List<ClientAppConfig> apps, File from, File dir) {
|
||||||
|
if (!dir.isDirectory() && !dir.mkdirs())
|
||||||
|
return false;
|
||||||
|
boolean ok = true;
|
||||||
|
for (int i = 0; i < apps.size(); i++) {
|
||||||
|
ClientAppConfig cac = apps.get(i);
|
||||||
|
String name = i + "-" + cac.className + "-clients.config";
|
||||||
|
if (i < 10)
|
||||||
|
name = '0' + name;
|
||||||
|
File f = new File(dir, name);
|
||||||
|
cac.configFile = f;
|
||||||
|
try {
|
||||||
|
writeClientAppConfig(ctx, cac);
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
ctx.logManager().getLog(ClientAppConfig.class).error("Error migrating the client app properties to " + f, ioe);
|
||||||
|
System.out.println("Error migrating the client app properties to " + f + ' ' + ioe);
|
||||||
|
cac.configFile = from;
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ok) {
|
||||||
|
if (!FileUtil.rename(from, new File(from.getAbsolutePath() + ".bak")))
|
||||||
|
from.delete();
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -167,18 +228,34 @@ public class ClientAppConfig {
|
|||||||
List<ClientAppConfig> rv = new ArrayList<ClientAppConfig>(8);
|
List<ClientAppConfig> rv = new ArrayList<ClientAppConfig>(8);
|
||||||
int i = 0;
|
int i = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
String className = clientApps.getProperty(PREFIX + i + ".main");
|
ClientAppConfig cac = getClientApp(clientApps, PREFIX + i);
|
||||||
if (className == null)
|
if (cac == null)
|
||||||
break;
|
break;
|
||||||
String clientName = clientApps.getProperty(PREFIX + i + ".name");
|
|
||||||
String args = clientApps.getProperty(PREFIX + i + ".args");
|
|
||||||
String delayStr = clientApps.getProperty(PREFIX + i + ".delay");
|
|
||||||
String onBoot = clientApps.getProperty(PREFIX + i + ".onBoot");
|
|
||||||
String disabled = clientApps.getProperty(PREFIX + i + ".startOnLoad");
|
|
||||||
String classpath = clientApps.getProperty(PREFIX + i + ".classpath");
|
|
||||||
String stopargs = clientApps.getProperty(PREFIX + i + ".stopargs");
|
|
||||||
String uninstallargs = clientApps.getProperty(PREFIX + i + ".uninstallargs");
|
|
||||||
i++;
|
i++;
|
||||||
|
rv.add(cac);
|
||||||
|
}
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Go through the properties, and get a single ClientAppConfig structure
|
||||||
|
* with this prefix
|
||||||
|
*
|
||||||
|
* @return null if none
|
||||||
|
* @since 0.9.34 split out from above
|
||||||
|
*/
|
||||||
|
private static ClientAppConfig getClientApp(Properties clientApps, String prefix) {
|
||||||
|
String className = clientApps.getProperty(prefix + ".main");
|
||||||
|
if (className == null)
|
||||||
|
return null;
|
||||||
|
String clientName = clientApps.getProperty(prefix + ".name");
|
||||||
|
String args = clientApps.getProperty(prefix + ".args");
|
||||||
|
String delayStr = clientApps.getProperty(prefix + ".delay");
|
||||||
|
String onBoot = clientApps.getProperty(prefix + ".onBoot");
|
||||||
|
String disabled = clientApps.getProperty(prefix + ".startOnLoad");
|
||||||
|
String classpath = clientApps.getProperty(prefix + ".classpath");
|
||||||
|
String stopargs = clientApps.getProperty(prefix + ".stopargs");
|
||||||
|
String uninstallargs = clientApps.getProperty(prefix + ".uninstallargs");
|
||||||
boolean dis = disabled != null && "false".equals(disabled);
|
boolean dis = disabled != null && "false".equals(disabled);
|
||||||
|
|
||||||
boolean onStartup = false;
|
boolean onStartup = false;
|
||||||
@ -196,33 +273,143 @@ public class ClientAppConfig {
|
|||||||
if (delayStr != null)
|
if (delayStr != null)
|
||||||
try { delay = 1000*Integer.parseInt(delayStr); } catch (NumberFormatException nfe) {}
|
try { delay = 1000*Integer.parseInt(delayStr); } catch (NumberFormatException nfe) {}
|
||||||
}
|
}
|
||||||
rv.add(new ClientAppConfig(className, clientName, args, delay, dis,
|
return new ClientAppConfig(className, clientName, args, delay, dis,
|
||||||
classpath, stopargs, uninstallargs));
|
classpath, stopargs, uninstallargs);
|
||||||
}
|
|
||||||
return rv;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** classpath and stopargs not supported */
|
/**
|
||||||
public static void writeClientAppConfig(RouterContext ctx, List<ClientAppConfig> apps) {
|
* Classpath and stopargs not supported.
|
||||||
File cfgFile = configFile(ctx);
|
* All other apps in the file will be deleted.
|
||||||
FileOutputStream fos = null;
|
* Do not use if multiple apps in a single file - use writeClientAppConfig(ctx, apps).
|
||||||
try {
|
* If app.configFile is null, a new file will be created and assigned.
|
||||||
fos = new SecureFileOutputStream(cfgFile);
|
*
|
||||||
StringBuilder buf = new StringBuilder(2048);
|
* @since 0.9.34
|
||||||
for(int i = 0; i < apps.size(); i++) {
|
*/
|
||||||
ClientAppConfig app = apps.get(i);
|
public synchronized static void writeClientAppConfig(I2PAppContext ctx, ClientAppConfig app) throws IOException {
|
||||||
buf.append(PREFIX).append(i).append(".main=").append(app.className).append("\n");
|
if (app.configFile == null) {
|
||||||
buf.append(PREFIX).append(i).append(".name=").append(app.clientName).append("\n");
|
File dir = new SecureDirectory(ctx.getConfigDir(), CLIENT_CONFIG_DIR);
|
||||||
if (app.args != null)
|
if (!dir.isDirectory() && !dir.mkdirs())
|
||||||
buf.append(PREFIX).append(i).append(".args=").append(app.args).append("\n");
|
throw new IOException("Can't create " + dir);
|
||||||
buf.append(PREFIX).append(i).append(".delay=").append(app.delay / 1000).append("\n");
|
int i = 0;
|
||||||
buf.append(PREFIX).append(i).append(".startOnLoad=").append(!app.disabled).append("\n");
|
String[] files = dir.list();
|
||||||
}
|
if (files != null)
|
||||||
fos.write(buf.toString().getBytes("UTF-8"));
|
i = files.length;
|
||||||
} catch (IOException ioe) {
|
File f;
|
||||||
} finally {
|
do {
|
||||||
if (fos != null) try { fos.close(); } catch (IOException ioe) {}
|
String name = i + "-" + app.className + "-clients.config";
|
||||||
|
if (i < 10)
|
||||||
|
name = '0' + name;
|
||||||
|
f = new File(dir, name);
|
||||||
|
i++;
|
||||||
|
} while (f.exists());
|
||||||
|
app.configFile = f;
|
||||||
}
|
}
|
||||||
|
writeClientAppConfig(Collections.singletonList(app), app.configFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classpath and stopargs not supported.
|
||||||
|
* All other apps in the files will be deleted.
|
||||||
|
* Do not add apps with this method - use writeClientAppConfig(ctx, app).
|
||||||
|
* Do not delete apps with this method - use deleteClientAppConfig().
|
||||||
|
*
|
||||||
|
* @since 0.9.34 split out from above
|
||||||
|
*/
|
||||||
|
public synchronized static void writeClientAppConfig(I2PAppContext ctx, List<ClientAppConfig> apps) throws IOException {
|
||||||
|
// Gather the set of config files
|
||||||
|
ObjectCounter<File> counter = new ObjectCounter<File>();
|
||||||
|
for (ClientAppConfig cac : apps) {
|
||||||
|
File f = cac.configFile;
|
||||||
|
if (f == null)
|
||||||
|
throw new IllegalArgumentException("No file for " + cac.className);
|
||||||
|
counter.increment(f);
|
||||||
|
}
|
||||||
|
IOException e = null;
|
||||||
|
// Write the config files
|
||||||
|
Set<File> files = counter.objects();
|
||||||
|
// For each file, write all the configs for that file
|
||||||
|
for (File f : files) {
|
||||||
|
// Gather configs for this file
|
||||||
|
List<ClientAppConfig> cacs = new ArrayList<ClientAppConfig>(8);
|
||||||
|
for (ClientAppConfig cac : apps) {
|
||||||
|
if (cac.configFile.equals(f))
|
||||||
|
cacs.add(cac);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
writeClientAppConfig(cacs, f);
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
if (e == null)
|
||||||
|
e = ioe;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (e != null)
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All to a single file, apps.configFile ignored
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException if null cfgFile
|
||||||
|
* @since 0.9.34 split out from above
|
||||||
|
*/
|
||||||
|
private static void writeClientAppConfig(List<ClientAppConfig> apps, File cfgFile) throws IOException {
|
||||||
|
if (cfgFile == null)
|
||||||
|
throw new IllegalArgumentException("No file");
|
||||||
|
Properties props = new OrderedProperties();
|
||||||
|
for(int i = 0; i < apps.size(); i++) {
|
||||||
|
ClientAppConfig app = apps.get(i);
|
||||||
|
String pfx = PREFIX + i;
|
||||||
|
props.setProperty(pfx + ".main", app.className);
|
||||||
|
props.setProperty(pfx + ".name", app.clientName);
|
||||||
|
if (app.args != null)
|
||||||
|
props.setProperty(pfx + ".args", app.args);
|
||||||
|
props.setProperty(pfx + ".delay", Long.toString(app.delay / 1000));
|
||||||
|
props.setProperty(pfx + ".startOnLoad", Boolean.toString(!app.disabled));
|
||||||
|
}
|
||||||
|
DataHelper.storeProps(props, cfgFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return success
|
||||||
|
* @throws IllegalArgumentException if cac has a null configfile
|
||||||
|
* @since 0.9.34
|
||||||
|
*/
|
||||||
|
public synchronized static boolean deleteClientAppConfig(ClientAppConfig cac) throws IOException {
|
||||||
|
File f = cac.configFile;
|
||||||
|
if (f == null)
|
||||||
|
throw new IllegalArgumentException("No file for " + cac.className);
|
||||||
|
List<ClientAppConfig> cacs = getClientApps(f);
|
||||||
|
if (cacs.remove(cac)) {
|
||||||
|
if (cacs.isEmpty())
|
||||||
|
return f.delete();
|
||||||
|
writeClientAppConfig(cacs, f);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 0.9.34
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return DataHelper.hashCode(className);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matches on class, args, and name only
|
||||||
|
* @since 0.9.34
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o == this) return true;
|
||||||
|
if (o == null) return false;
|
||||||
|
if (o instanceof ClientAppConfig) {
|
||||||
|
ClientAppConfig cac = (ClientAppConfig) o;
|
||||||
|
return DataHelper.eq(className, cac.className) &&
|
||||||
|
DataHelper.eq(clientName, cac.clientName) &&
|
||||||
|
DataHelper.eq(args, cac.args);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,8 +196,17 @@ abstract class MigrateJetty {
|
|||||||
File cfgFile = ClientAppConfig.configFile(ctx);
|
File cfgFile = ClientAppConfig.configFile(ctx);
|
||||||
boolean ok = backupFile(cfgFile);
|
boolean ok = backupFile(cfgFile);
|
||||||
if (ok) {
|
if (ok) {
|
||||||
ClientAppConfig.writeClientAppConfig(ctx, apps);
|
try {
|
||||||
System.err.println("WARNING: Migrated clients config file " + cfgFile +
|
ClientAppConfig.writeClientAppConfig(ctx, apps);
|
||||||
|
System.err.println("WARNING: Migrated clients config file " + cfgFile +
|
||||||
|
" from Jetty 5/6 " + OLD_CLASS + '/' + OLD_CLASS_6 +
|
||||||
|
" to Jetty 9 " + NEW_CLASS);
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!ok) {
|
||||||
|
System.err.println("WARNING: Failed to migrate clients config file " + cfgFile +
|
||||||
" from Jetty 5/6 " + OLD_CLASS + '/' + OLD_CLASS_6 +
|
" from Jetty 5/6 " + OLD_CLASS + '/' + OLD_CLASS_6 +
|
||||||
" to Jetty 9 " + NEW_CLASS);
|
" to Jetty 9 " + NEW_CLASS);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user