forked from I2P_Developers/i2p.i2p
* ClientAppManager: Add method to look up clients by class and args
* Console: Implement stopping of clients using the ClientApp interface (ticket #347)
This commit is contained in:
@ -11,9 +11,12 @@ import java.util.Map;
|
|||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import net.i2p.app.ClientApp;
|
||||||
|
import net.i2p.app.ClientAppState;
|
||||||
import net.i2p.router.client.ClientManagerFacadeImpl;
|
import net.i2p.router.client.ClientManagerFacadeImpl;
|
||||||
import net.i2p.router.startup.ClientAppConfig;
|
import net.i2p.router.startup.ClientAppConfig;
|
||||||
import net.i2p.router.startup.LoadClientAppsJob;
|
import net.i2p.router.startup.LoadClientAppsJob;
|
||||||
|
import net.i2p.router.startup.RouterAppManager;
|
||||||
import net.i2p.router.update.ConsoleUpdateManager;
|
import net.i2p.router.update.ConsoleUpdateManager;
|
||||||
import static net.i2p.update.UpdateType.*;
|
import static net.i2p.update.UpdateType.*;
|
||||||
|
|
||||||
@ -214,10 +217,12 @@ public class ConfigClientsHandler extends FormHandler {
|
|||||||
|
|
||||||
ClientAppConfig.writeClientAppConfig(_context, clients);
|
ClientAppConfig.writeClientAppConfig(_context, clients);
|
||||||
addFormNotice(_("Client configuration saved successfully"));
|
addFormNotice(_("Client configuration saved successfully"));
|
||||||
addFormNotice(_("Restart required to take effect"));
|
//addFormNotice(_("Restart required to take effect"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// STUB for stopClient, not completed yet.
|
/**
|
||||||
|
* @since Implemented in 0.9.6 using ClientAppManager
|
||||||
|
*/
|
||||||
private void stopClient(int i) {
|
private void stopClient(int i) {
|
||||||
List<ClientAppConfig> clients = ClientAppConfig.getClientApps(_context);
|
List<ClientAppConfig> clients = ClientAppConfig.getClientApps(_context);
|
||||||
if (i >= clients.size()) {
|
if (i >= clients.size()) {
|
||||||
@ -225,10 +230,23 @@ public class ConfigClientsHandler extends FormHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ClientAppConfig ca = clients.get(i);
|
ClientAppConfig ca = clients.get(i);
|
||||||
//
|
ClientApp clientApp = _context.clientAppManager().getClientApp(ca.className, LoadClientAppsJob.parseArgs(ca.args));
|
||||||
// What do we do here?
|
if (clientApp != null && clientApp.getState() == ClientAppState.RUNNING) {
|
||||||
//
|
try {
|
||||||
addFormNotice(_("Client") + ' ' + _(ca.clientName) + ' ' + _("stopped") + '.');
|
// todo parseArgs(ca.stopArgs) ?
|
||||||
|
clientApp.shutdown(null);
|
||||||
|
addFormNotice(_("Client {0} stopped", ca.clientName));
|
||||||
|
// Give a chance for status to update
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000);
|
||||||
|
} catch (InterruptedException ie) {}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
addFormError("Cannot stop client " + ca.className + ": " + t);
|
||||||
|
_log.error("Error stopping client " + ca.className, t);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addFormError("Cannot stop client " + i + ": " + ca.className);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startClient(int i) {
|
private void startClient(int i) {
|
||||||
@ -239,7 +257,11 @@ public class ConfigClientsHandler extends FormHandler {
|
|||||||
}
|
}
|
||||||
ClientAppConfig ca = clients.get(i);
|
ClientAppConfig ca = clients.get(i);
|
||||||
LoadClientAppsJob.runClient(ca.className, ca.clientName, LoadClientAppsJob.parseArgs(ca.args), _context, _log);
|
LoadClientAppsJob.runClient(ca.className, ca.clientName, LoadClientAppsJob.parseArgs(ca.args), _context, _log);
|
||||||
addFormNotice(_("Client") + ' ' + _(ca.clientName) + ' ' + _("started") + '.');
|
addFormNotice(_("Client {0} started", ca.clientName));
|
||||||
|
// Give a chance for status to update
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000);
|
||||||
|
} catch (InterruptedException ie) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteClient(int i) {
|
private void deleteClient(int i) {
|
||||||
@ -250,7 +272,7 @@ public class ConfigClientsHandler extends FormHandler {
|
|||||||
}
|
}
|
||||||
ClientAppConfig ca = clients.remove(i);
|
ClientAppConfig ca = clients.remove(i);
|
||||||
ClientAppConfig.writeClientAppConfig(_context, clients);
|
ClientAppConfig.writeClientAppConfig(_context, clients);
|
||||||
addFormNotice(_("Client") + ' ' + _(ca.clientName) + ' ' + _("deleted") + '.');
|
addFormNotice(_("Client {0} deleted", ca.clientName));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveWebAppChanges() {
|
private void saveWebAppChanges() {
|
||||||
|
@ -9,9 +9,13 @@ import java.util.Properties;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
|
|
||||||
|
import net.i2p.app.ClientApp;
|
||||||
|
import net.i2p.app.ClientAppState;
|
||||||
import net.i2p.data.DataHelper;
|
import net.i2p.data.DataHelper;
|
||||||
import net.i2p.router.client.ClientManagerFacadeImpl;
|
import net.i2p.router.client.ClientManagerFacadeImpl;
|
||||||
import net.i2p.router.startup.ClientAppConfig;
|
import net.i2p.router.startup.ClientAppConfig;
|
||||||
|
import net.i2p.router.startup.LoadClientAppsJob;
|
||||||
|
import net.i2p.router.startup.RouterAppManager;
|
||||||
import net.i2p.util.Addresses;
|
import net.i2p.util.Addresses;
|
||||||
|
|
||||||
public class ConfigClientsHelper extends HelperBase {
|
public class ConfigClientsHelper extends HelperBase {
|
||||||
@ -94,17 +98,33 @@ public class ConfigClientsHelper extends HelperBase {
|
|||||||
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);
|
||||||
renderForm(buf, ""+cur, ca.clientName, false, !ca.disabled,
|
boolean isConsole = ca.className.equals("net.i2p.router.web.RouterConsoleRunner");
|
||||||
|
boolean showStart;
|
||||||
|
boolean showStop;
|
||||||
|
if (isConsole) {
|
||||||
|
showStart = false;
|
||||||
|
showStop = false;
|
||||||
|
} else {
|
||||||
|
ClientApp clientApp = _context.clientAppManager().getClientApp(ca.className, LoadClientAppsJob.parseArgs(ca.args));
|
||||||
|
showStart = clientApp == null;
|
||||||
|
showStop = clientApp != null && clientApp.getState() == ClientAppState.RUNNING;
|
||||||
|
}
|
||||||
|
renderForm(buf, ""+cur, ca.clientName,
|
||||||
|
// urlify, enabled
|
||||||
|
false, !ca.disabled,
|
||||||
|
// read only
|
||||||
// dangerous, but allow editing the console args too
|
// dangerous, but allow editing the console args too
|
||||||
//"webConsole".equals(ca.clientName) || "Web console".equals(ca.clientName),
|
//"webConsole".equals(ca.clientName) || "Web console".equals(ca.clientName),
|
||||||
false,
|
false,
|
||||||
|
// description, edit
|
||||||
ca.className + ((ca.args != null) ? " " + ca.args : ""), (""+cur).equals(_edit),
|
ca.className + ((ca.args != null) ? " " + ca.args : ""), (""+cur).equals(_edit),
|
||||||
true, false,
|
// show edit button, show update button
|
||||||
// Enable this one and comment out the false below once the stub is filled in.
|
// Don't allow edit if it's running, or else we would lose the "handle" to the ClientApp to stop it.
|
||||||
//!ca.disabled && !("webConsole".equals(ca.clientName) || "Web console".equals(ca.clientName)),
|
!showStop, false,
|
||||||
false,
|
// show stop button
|
||||||
|
showStop,
|
||||||
true, ca.disabled);
|
// show delete button, show start button
|
||||||
|
true, showStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("new".equals(_edit))
|
if ("new".equals(_edit))
|
||||||
@ -258,10 +278,10 @@ public class ConfigClientsHelper extends HelperBase {
|
|||||||
if (showStartButton && (!ro) && !edit) {
|
if (showStartButton && (!ro) && !edit) {
|
||||||
buf.append("<button type=\"submit\" class=\"Xaccept\" name=\"action\" value=\"Start ").append(index).append("\" >" + _("Start") + "<span class=hide> ").append(index).append("</span></button>");
|
buf.append("<button type=\"submit\" class=\"Xaccept\" name=\"action\" value=\"Start ").append(index).append("\" >" + _("Start") + "<span class=hide> ").append(index).append("</span></button>");
|
||||||
}
|
}
|
||||||
if (showEditButton && (!edit) && !ro)
|
|
||||||
buf.append("<button type=\"submit\" class=\"Xadd\" name=\"edit\" value=\"Edit ").append(index).append("\" >" + _("Edit") + "<span class=hide> ").append(index).append("</span></button>");
|
|
||||||
if (showStopButton && (!edit))
|
if (showStopButton && (!edit))
|
||||||
buf.append("<button type=\"submit\" class=\"Xstop\" name=\"action\" value=\"Stop ").append(index).append("\" >" + _("Stop") + "<span class=hide> ").append(index).append("</span></button>");
|
buf.append("<button type=\"submit\" class=\"Xstop\" name=\"action\" value=\"Stop ").append(index).append("\" >" + _("Stop") + "<span class=hide> ").append(index).append("</span></button>");
|
||||||
|
if (showEditButton && (!edit) && !ro)
|
||||||
|
buf.append("<button type=\"submit\" class=\"Xadd\" name=\"edit\" value=\"Edit ").append(index).append("\" >" + _("Edit") + "<span class=hide> ").append(index).append("</span></button>");
|
||||||
if (showUpdateButton && (!edit) && !ro) {
|
if (showUpdateButton && (!edit) && !ro) {
|
||||||
buf.append("<button type=\"submit\" class=\"Xcheck\" name=\"action\" value=\"Check ").append(index).append("\" >" + _("Check for updates") + "<span class=hide> ").append(index).append("</span></button>");
|
buf.append("<button type=\"submit\" class=\"Xcheck\" name=\"action\" value=\"Check ").append(index).append("\" >" + _("Check for updates") + "<span class=hide> ").append(index).append("</span></button>");
|
||||||
buf.append("<button type=\"submit\" class=\"Xdownload\" name=\"action\" value=\"Update ").append(index).append("\" >" + _("Update") + "<span class=hide> ").append(index).append("</span></button>");
|
buf.append("<button type=\"submit\" class=\"Xdownload\" name=\"action\" value=\"Update ").append(index).append("\" >" + _("Update") + "<span class=hide> ").append(index).append("</span></button>");
|
||||||
|
@ -36,23 +36,28 @@ public interface ClientApp {
|
|||||||
* If previously running, client must call ClientAppManager.notify() at least once within this
|
* If previously running, client must call ClientAppManager.notify() at least once within this
|
||||||
* method to change the state to STOPPING or STOPPED.
|
* method to change the state to STOPPING or STOPPED.
|
||||||
* May be called multiple times on the same object, in any state.
|
* May be called multiple times on the same object, in any state.
|
||||||
|
*
|
||||||
|
* @param args generally null but could be stopArgs from clients.config
|
||||||
*/
|
*/
|
||||||
public void shutdown(String[] args) throws Throwable;
|
public void shutdown(String[] args) throws Throwable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current state of the ClientApp.
|
* The current state of the ClientApp.
|
||||||
|
* @return non-null
|
||||||
*/
|
*/
|
||||||
public ClientAppState getState();
|
public ClientAppState getState();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The generic name of the ClientApp, used for registration,
|
* The generic name of the ClientApp, used for registration,
|
||||||
* e.g. "console". Do not translate.
|
* e.g. "console". Do not translate.
|
||||||
|
* @return non-null
|
||||||
*/
|
*/
|
||||||
public String getName();
|
public String getName();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The dislplay name of the ClientApp, used in user interfaces.
|
* The dislplay name of the ClientApp, used in user interfaces.
|
||||||
* The app must translate.
|
* The app must translate.
|
||||||
|
* @return non-null
|
||||||
*/
|
*/
|
||||||
public String getDisplayName();
|
public String getDisplayName();
|
||||||
}
|
}
|
||||||
|
14
history.txt
14
history.txt
@ -1,3 +1,17 @@
|
|||||||
|
2013-04-16 zzz
|
||||||
|
* ClientAppManager: Add method to look up clients by class and args
|
||||||
|
* Console: Implement stopping of clients using the ClientApp interface
|
||||||
|
(ticket #347)
|
||||||
|
|
||||||
|
2013-04-15 zzz
|
||||||
|
* Console: Move from deprecated Jetty SSL methods to SslContextFactory
|
||||||
|
* i2psnark:
|
||||||
|
- Add data directory configuration to GUI (ticket #768)
|
||||||
|
- Add page size configuration to GUI
|
||||||
|
- Multiple instance DHT file cleanup
|
||||||
|
- Mime type fixes
|
||||||
|
- Remove web classes from jar
|
||||||
|
|
||||||
2013-04-14 zzz
|
2013-04-14 zzz
|
||||||
* i2psnark:
|
* i2psnark:
|
||||||
- Set unique tunnel nickname for additional instances
|
- Set unique tunnel nickname for additional instances
|
||||||
|
@ -18,7 +18,7 @@ public class RouterVersion {
|
|||||||
/** deprecated */
|
/** deprecated */
|
||||||
public final static String ID = "Monotone";
|
public final static String ID = "Monotone";
|
||||||
public final static String VERSION = CoreVersion.VERSION;
|
public final static String VERSION = CoreVersion.VERSION;
|
||||||
public final static long BUILD = 4;
|
public final static long BUILD = 5;
|
||||||
|
|
||||||
/** for example "-test" */
|
/** for example "-test" */
|
||||||
public final static String EXTRA = "";
|
public final static String EXTRA = "";
|
||||||
|
@ -87,8 +87,18 @@ public class LoadClientAppsJob extends JobImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse arg string into an array of args.
|
||||||
|
* Spaces or tabs separate args.
|
||||||
|
* Args may be single- or double-quoted if they contain spaces or tabs.
|
||||||
|
* There is no provision for escaping quotes.
|
||||||
|
* A quoted string may not contain a quote of any kind.
|
||||||
|
*
|
||||||
|
* @param args may be null
|
||||||
|
* @return non-null, 0-length if args is null
|
||||||
|
*/
|
||||||
public static String[] parseArgs(String args) {
|
public static String[] parseArgs(String args) {
|
||||||
List argList = new ArrayList(4);
|
List<String> argList = new ArrayList(4);
|
||||||
if (args != null) {
|
if (args != null) {
|
||||||
char data[] = args.toCharArray();
|
char data[] = args.toCharArray();
|
||||||
StringBuilder buf = new StringBuilder(32);
|
StringBuilder buf = new StringBuilder(32);
|
||||||
@ -130,8 +140,9 @@ public class LoadClientAppsJob extends JobImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
String rv[] = new String[argList.size()];
|
String rv[] = new String[argList.size()];
|
||||||
for (int i = 0; i < argList.size(); i++)
|
for (int i = 0; i < argList.size(); i++) {
|
||||||
rv[i] = (String)argList.get(i);
|
rv[i] = argList.get(i);
|
||||||
|
}
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,6 +162,7 @@ public class LoadClientAppsJob extends JobImpl {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Run client in this thread.
|
* Run client in this thread.
|
||||||
|
* Used for plugin sub-clients only. Does not register with the ClientAppManager.
|
||||||
*
|
*
|
||||||
* @param clientName can be null
|
* @param clientName can be null
|
||||||
* @param args can be null
|
* @param args can be null
|
||||||
@ -163,6 +175,7 @@ public class LoadClientAppsJob extends JobImpl {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Run client in this thread.
|
* Run client in this thread.
|
||||||
|
* Used for plugin sub-clients only. Does not register with the ClientAppManager.
|
||||||
*
|
*
|
||||||
* @param clientName can be null
|
* @param clientName can be null
|
||||||
* @param args can be null
|
* @param args can be null
|
||||||
@ -249,13 +262,13 @@ public class LoadClientAppsJob extends JobImpl {
|
|||||||
RouterAppManager mgr = _ctx.clientAppManager();
|
RouterAppManager mgr = _ctx.clientAppManager();
|
||||||
Object[] conArgs = new Object[] {_ctx, _ctx.clientAppManager(), _args};
|
Object[] conArgs = new Object[] {_ctx, _ctx.clientAppManager(), _args};
|
||||||
RouterApp app = (RouterApp) con.newInstance(conArgs);
|
RouterApp app = (RouterApp) con.newInstance(conArgs);
|
||||||
mgr.addAndStart(app);
|
mgr.addAndStart(app, _args);
|
||||||
} else if (isClientApp(cls)) {
|
} else if (isClientApp(cls)) {
|
||||||
Constructor con = cls.getConstructor(I2PAppContext.class, ClientAppManager.class, String[].class);
|
Constructor con = cls.getConstructor(I2PAppContext.class, ClientAppManager.class, String[].class);
|
||||||
RouterAppManager mgr = _ctx.clientAppManager();
|
RouterAppManager mgr = _ctx.clientAppManager();
|
||||||
Object[] conArgs = new Object[] {_ctx, _ctx.clientAppManager(), _args};
|
Object[] conArgs = new Object[] {_ctx, _ctx.clientAppManager(), _args};
|
||||||
ClientApp app = (ClientApp) con.newInstance(conArgs);
|
ClientApp app = (ClientApp) con.newInstance(conArgs);
|
||||||
mgr.addAndStart(app);
|
mgr.addAndStart(app, _args);
|
||||||
} else {
|
} else {
|
||||||
Method method = cls.getMethod("main", new Class[] { String[].class });
|
Method method = cls.getMethod("main", new Class[] { String[].class });
|
||||||
method.invoke(cls, new Object[] { _args });
|
method.invoke(cls, new Object[] { _args });
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
package net.i2p.router.startup;
|
package net.i2p.router.startup;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Arrays;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import net.i2p.app.*;
|
import net.i2p.app.*;
|
||||||
import static net.i2p.app.ClientAppState.*;
|
import static net.i2p.app.ClientAppState.*;
|
||||||
import net.i2p.router.RouterContext;
|
import net.i2p.router.RouterContext;
|
||||||
import net.i2p.util.ConcurrentHashSet;
|
|
||||||
import net.i2p.util.Log;
|
import net.i2p.util.Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -19,26 +19,59 @@ public class RouterAppManager implements ClientAppManager {
|
|||||||
|
|
||||||
private final RouterContext _context;
|
private final RouterContext _context;
|
||||||
private final Log _log;
|
private final Log _log;
|
||||||
private final Set<ClientApp> _clients;
|
// client to args
|
||||||
|
// this assumes clients do not override equals()
|
||||||
|
private final ConcurrentHashMap<ClientApp, String[]> _clients;
|
||||||
|
// registered name to client
|
||||||
private final ConcurrentHashMap<String, ClientApp> _registered;
|
private final ConcurrentHashMap<String, ClientApp> _registered;
|
||||||
|
|
||||||
public RouterAppManager(RouterContext ctx) {
|
public RouterAppManager(RouterContext ctx) {
|
||||||
_context = ctx;
|
_context = ctx;
|
||||||
_log = ctx.logManager().getLog(RouterAppManager.class);
|
_log = ctx.logManager().getLog(RouterAppManager.class);
|
||||||
_clients = new ConcurrentHashSet(16);
|
_clients = new ConcurrentHashMap(16);
|
||||||
_registered = new ConcurrentHashMap(8);
|
_registered = new ConcurrentHashMap(8);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addAndStart(ClientApp app) {
|
/**
|
||||||
_clients.add(app);
|
* @param args the args that were used to instantiate the app, non-null, may be zero-length
|
||||||
|
* @return success
|
||||||
|
* @throws IllegalArgumentException if already added
|
||||||
|
*/
|
||||||
|
public boolean addAndStart(ClientApp app, String[] args) {
|
||||||
|
if (_log.shouldLog(Log.INFO))
|
||||||
|
_log.info("Adding and starting " + app + " with class " + app.getClass().getName() + " and args " + Arrays.toString(args));
|
||||||
|
String[] old = _clients.put(app, args);
|
||||||
|
if (old != null)
|
||||||
|
throw new IllegalArgumentException("already added");
|
||||||
try {
|
try {
|
||||||
app.startup();
|
app.startup();
|
||||||
|
return true;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
_clients.remove(app);
|
_clients.remove(app);
|
||||||
_log.error("Client " + app + " failed to start", t);
|
_log.error("Client " + app + " failed to start", t);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the first known ClientApp with this class name and exact arguments.
|
||||||
|
* Caller may then retrieve or control the state of the returned client.
|
||||||
|
* A client will generally be found only if it is running or transitioning;
|
||||||
|
* after it is stopped it will not be tracked by the manager.
|
||||||
|
*
|
||||||
|
* @param args non-null, may be zero-length
|
||||||
|
* @return client app or null
|
||||||
|
* @since 0.9.6
|
||||||
|
*/
|
||||||
|
public ClientApp getClientApp(String className, String[] args) {
|
||||||
|
for (Map.Entry<ClientApp, String[]> e : _clients.entrySet()) {
|
||||||
|
if (e.getKey().getClass().getName().equals(className) &&
|
||||||
|
Arrays.equals(e.getValue(), args))
|
||||||
|
return e.getKey();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// ClientAppManager methods
|
// ClientAppManager methods
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -97,7 +130,7 @@ public class RouterAppManager implements ClientAppManager {
|
|||||||
* @return true if successful, false if duplicate name
|
* @return true if successful, false if duplicate name
|
||||||
*/
|
*/
|
||||||
public boolean register(ClientApp app) {
|
public boolean register(ClientApp app) {
|
||||||
if (!_clients.contains(app))
|
if (!_clients.containsKey(app))
|
||||||
return false;
|
return false;
|
||||||
// TODO if old app in there is not running and != this app, allow replacement
|
// TODO if old app in there is not running and != this app, allow replacement
|
||||||
return _registered.putIfAbsent(app.getName(), app) == null;
|
return _registered.putIfAbsent(app.getName(), app) == null;
|
||||||
|
Reference in New Issue
Block a user