mirror of
https://github.com/GravitLauncher/Launcher
synced 2024-12-22 16:41:46 +03:00
[ANY] New Sessions system completed
This commit is contained in:
parent
acf2d2d9cd
commit
c596c30ff6
10 changed files with 171 additions and 158 deletions
|
@ -10,6 +10,7 @@
|
|||
import pro.gravit.launcher.modules.events.ClosePhase;
|
||||
import pro.gravit.launcher.profiles.ClientProfile;
|
||||
import pro.gravit.launchserver.auth.AuthProviderPair;
|
||||
import pro.gravit.launchserver.auth.session.MemorySessionStorage;
|
||||
import pro.gravit.launchserver.binary.*;
|
||||
import pro.gravit.launchserver.config.LaunchServerConfig;
|
||||
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
|
||||
|
@ -128,6 +129,7 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La
|
|||
|
||||
runtime.verify();
|
||||
config.verify();
|
||||
if(config.sessions == null) config.sessions = new MemorySessionStorage();
|
||||
if (config.components != null) {
|
||||
LogHelper.debug("PreInit components");
|
||||
config.components.forEach((k, v) -> {
|
||||
|
@ -147,7 +149,8 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La
|
|||
pingServerManager = new PingServerManager(this);
|
||||
//Generate or set new Certificate API
|
||||
certificateManager.orgName = config.projectName;
|
||||
if (config.certificate != null && config.certificate.enabled) {
|
||||
/*
|
||||
if (false) {
|
||||
if (IOHelper.isFile(caCertFile) && IOHelper.isFile(caKeyFile)) {
|
||||
certificateManager.ca = certificateManager.readCertificate(caCertFile);
|
||||
certificateManager.caKey = certificateManager.readPrivateKey(caKeyFile);
|
||||
|
@ -175,6 +178,7 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La
|
|||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
config.init(ReloadType.FULL);
|
||||
registerObject("launchServer", this);
|
||||
GarbageManager.registerNeedGC(sessionManager);
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
package pro.gravit.launchserver.auth.session;
|
||||
|
||||
import pro.gravit.launcher.NeedGarbageCollection;
|
||||
import pro.gravit.launchserver.manangers.SessionManager;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class MemorySessionStorage extends SessionStorage implements NeedGarbageCollection {
|
||||
|
||||
private final Map<UUID, Entry> clientSet = new ConcurrentHashMap<>(128);
|
||||
private final Map<UUID, Set<Entry>> uuidIndex = new ConcurrentHashMap<>(32);
|
||||
|
||||
@Override
|
||||
public byte[] getSessionData(UUID session) {
|
||||
|
||||
Entry e = clientSet.get(session);
|
||||
if(e == null) return null;
|
||||
return e.data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<UUID> getSessionsFromUserUUID(UUID userUUID) {
|
||||
Set<Entry> set = uuidIndex.get(userUUID);
|
||||
if(set != null) return set.stream().map((e) -> e.sessionUuid);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean writeSession(UUID userUUID, UUID sessionUUID, byte[] data) {
|
||||
deleteSession(sessionUUID);
|
||||
Entry e = new Entry(data, sessionUUID);
|
||||
clientSet.put(sessionUUID, e);
|
||||
if(userUUID != null) {
|
||||
Set<Entry> uuidSet = uuidIndex.computeIfAbsent(userUUID, k -> ConcurrentHashMap.newKeySet());
|
||||
uuidSet.add(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteSession(UUID sessionUUID) {
|
||||
Entry e =clientSet.remove(sessionUUID);
|
||||
if(e != null) {
|
||||
Set<Entry> set = uuidIndex.get(sessionUUID);
|
||||
if(set != null) {
|
||||
removeUuidFromIndexSet(set, e, sessionUUID);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteSessionsByUserUUID(UUID userUUID) {
|
||||
Set<Entry> set = uuidIndex.get(userUUID);
|
||||
if(set != null) {
|
||||
for(Entry e : set) {
|
||||
clientSet.remove(e.sessionUuid);
|
||||
}
|
||||
set.clear();
|
||||
uuidIndex.remove(userUUID);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
clientSet.clear();
|
||||
uuidIndex.clear();
|
||||
}
|
||||
|
||||
private void removeUuidFromIndexSet(Set<Entry> set, Entry e, UUID session) {
|
||||
set.remove(e);
|
||||
if(set.isEmpty()) {
|
||||
uuidIndex.remove(session);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void garbageCollection() {
|
||||
long time = System.currentTimeMillis();
|
||||
long session_timeout = server.config.netty.performance.sessionLifetimeMs;
|
||||
Set<UUID> to_delete = new HashSet<>(32);
|
||||
clientSet.forEach((uuid, entry) -> {
|
||||
long timestamp = entry.timestamp;
|
||||
if(timestamp + session_timeout < time)
|
||||
to_delete.add(uuid);
|
||||
});
|
||||
for(UUID session : to_delete) {
|
||||
deleteSession(session);
|
||||
}
|
||||
to_delete.clear();
|
||||
}
|
||||
|
||||
private static class Entry {
|
||||
public byte[] data;
|
||||
public UUID sessionUuid;
|
||||
public long timestamp;
|
||||
|
||||
public Entry(byte[] data, UUID sessionUuid) {
|
||||
this.data = data;
|
||||
this.sessionUuid = sessionUuid;
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package pro.gravit.launchserver.auth.session;
|
||||
|
||||
import pro.gravit.launchserver.LaunchServer;
|
||||
import pro.gravit.utils.ProviderMap;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public abstract class SessionStorage {
|
||||
protected transient LaunchServer server;
|
||||
public static ProviderMap<SessionStorage> providers = new ProviderMap<>();
|
||||
public abstract byte[] getSessionData(UUID session);
|
||||
public abstract Stream<UUID> getSessionsFromUserUUID(UUID userUUID);
|
||||
public abstract boolean writeSession(UUID userUUID, UUID sessionUUID, byte[] data);
|
||||
public abstract boolean deleteSession(UUID sessionUUID);
|
||||
public boolean deleteSessionsByUserUUID(UUID userUUID) {
|
||||
getSessionsFromUserUUID(userUUID).forEach(this::deleteSession);
|
||||
return true;
|
||||
}
|
||||
public abstract void clear();
|
||||
public void init(LaunchServer server)
|
||||
{
|
||||
this.server = server;
|
||||
}
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
package pro.gravit.launchserver.command.dump;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import pro.gravit.launcher.Launcher;
|
||||
import pro.gravit.launchserver.LaunchServer;
|
||||
import pro.gravit.launchserver.command.Command;
|
||||
import pro.gravit.launchserver.socket.Client;
|
||||
import pro.gravit.utils.command.SubCommand;
|
||||
import pro.gravit.utils.helper.IOHelper;
|
||||
import pro.gravit.utils.helper.LogHelper;
|
||||
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class DumpSessionsCommand extends Command {
|
||||
public DumpSessionsCommand(LaunchServer server) {
|
||||
super(server);
|
||||
childCommands.put("load", new SubCommand() {
|
||||
@Override
|
||||
public void invoke(String... args) throws Exception {
|
||||
verifyArgs(args, 1);
|
||||
LogHelper.info("Sessions read from %s", args[0]);
|
||||
int size;
|
||||
try (Reader reader = IOHelper.newReader(Paths.get(args[0]))) {
|
||||
Type setType = new TypeToken<HashSet<Client>>() {
|
||||
}.getType();
|
||||
Set<Client> clientSet = Launcher.gsonManager.configGson.fromJson(reader, setType);
|
||||
size = clientSet.size();
|
||||
server.sessionManager.loadSessions(clientSet);
|
||||
}
|
||||
LogHelper.subInfo("Readed %d sessions", size);
|
||||
}
|
||||
});
|
||||
childCommands.put("unload", new SubCommand() {
|
||||
@Override
|
||||
public void invoke(String... args) throws Exception {
|
||||
verifyArgs(args, 1);
|
||||
LogHelper.info("Sessions write to %s", args[0]);
|
||||
Collection<Client> clientSet = server.sessionManager.getSessions();
|
||||
try (Writer writer = IOHelper.newWriter(Paths.get(args[0]))) {
|
||||
Launcher.gsonManager.configGson.toJson(clientSet, writer);
|
||||
}
|
||||
LogHelper.subInfo("Write %d sessions", clientSet.size());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getArgsDescription() {
|
||||
return "[load/unload] [filename]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsageDescription() {
|
||||
return "Load or unload sessions";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(String... args) throws Exception {
|
||||
invokeSubcommands(args);
|
||||
}
|
||||
}
|
|
@ -5,7 +5,6 @@
|
|||
import pro.gravit.launchserver.command.auth.UUIDToUsernameCommand;
|
||||
import pro.gravit.launchserver.command.auth.UsernameToUUIDCommand;
|
||||
import pro.gravit.launchserver.command.basic.*;
|
||||
import pro.gravit.launchserver.command.dump.DumpSessionsCommand;
|
||||
import pro.gravit.launchserver.command.hash.*;
|
||||
import pro.gravit.launchserver.command.install.CheckInstallCommand;
|
||||
import pro.gravit.launchserver.command.install.MultiCommand;
|
||||
|
@ -61,12 +60,6 @@ public static void registerCommands(pro.gravit.utils.command.CommandHandler hand
|
|||
Category authCategory = new Category(auth, "auth", "User Management");
|
||||
handler.registerCategory(authCategory);
|
||||
|
||||
//Register dump commands
|
||||
BaseCommandCategory dump = new BaseCommandCategory();
|
||||
dump.registerCommand("dumpSessions", new DumpSessionsCommand(server));
|
||||
Category dumpCategory = new Category(dump, "dump", "Dump runtime data");
|
||||
handler.registerCategory(dumpCategory);
|
||||
|
||||
//Register service commands
|
||||
BaseCommandCategory service = new BaseCommandCategory();
|
||||
service.registerCommand("config", new ConfigCommand(server));
|
||||
|
|
|
@ -38,7 +38,7 @@ public void invoke(String... args) {
|
|||
for (CommandHandler.Category category : server.commandHandler.getCategories()) {
|
||||
commands += category.category.commandsMap().size();
|
||||
}
|
||||
LogHelper.info("Sessions: %d | Commands: %d(%d categories)", server.sessionManager.getSessions().size(), commands, server.commandHandler.getCategories().size() + 1);
|
||||
LogHelper.info("Commands: %d(%d categories)", commands, server.commandHandler.getCategories().size() + 1);
|
||||
for (AuthProviderPair pair : server.config.auth.values()) {
|
||||
if (pair.handler instanceof CachedAuthHandler) {
|
||||
LogHelper.info("AuthHandler %s: EntryCache: %d | usernameCache: %d", pair.name, ((CachedAuthHandler) pair.handler).getEntryCache().size(), ((CachedAuthHandler) pair.handler).getUsernamesCache().size());
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
import pro.gravit.launchserver.auth.protect.ProtectHandler;
|
||||
import pro.gravit.launchserver.auth.protect.StdProtectHandler;
|
||||
import pro.gravit.launchserver.auth.provider.RejectAuthProvider;
|
||||
import pro.gravit.launchserver.auth.session.MemorySessionStorage;
|
||||
import pro.gravit.launchserver.auth.session.SessionStorage;
|
||||
import pro.gravit.launchserver.auth.texture.RequestTextureProvider;
|
||||
import pro.gravit.launchserver.binary.tasks.exe.Launch4JTask;
|
||||
import pro.gravit.launchserver.components.AuthLimiterComponent;
|
||||
|
@ -33,6 +35,7 @@ public final class LaunchServerConfig {
|
|||
public LauncherConfig.LauncherEnvironment env;
|
||||
public Map<String, AuthProviderPair> auth;
|
||||
public DaoProvider dao;
|
||||
public SessionStorage sessions;
|
||||
|
||||
// Handlers & Providers
|
||||
public ProtectHandler protectHandler;
|
||||
|
@ -40,7 +43,6 @@ public final class LaunchServerConfig {
|
|||
public ExeConf launch4j;
|
||||
public NettyConfig netty;
|
||||
public LauncherConf launcher;
|
||||
public CertificateConf certificate;
|
||||
public JarSignerConf sign;
|
||||
public String startScript;
|
||||
private transient LaunchServer server = null;
|
||||
|
@ -71,6 +73,7 @@ public static LaunchServerConfig getDefault(LaunchServer.LaunchServerEnv env) {
|
|||
a.displayName = "Default";
|
||||
newConfig.auth.put("std", a);
|
||||
newConfig.protectHandler = new StdProtectHandler();
|
||||
newConfig.sessions = new MemorySessionStorage();
|
||||
newConfig.binaryName = "Launcher";
|
||||
|
||||
newConfig.netty = new NettyConfig();
|
||||
|
@ -97,8 +100,6 @@ public static LaunchServerConfig getDefault(LaunchServer.LaunchServerEnv env) {
|
|||
newConfig.launcher.stripLineNumbers = true;
|
||||
newConfig.launcher.proguardGenMappings = true;
|
||||
|
||||
newConfig.certificate = new LaunchServerConfig.CertificateConf();
|
||||
newConfig.certificate.enabled = false;
|
||||
newConfig.sign = new JarSignerConf();
|
||||
|
||||
newConfig.components = new HashMap<>();
|
||||
|
@ -187,6 +188,10 @@ public void init(LaunchServer.ReloadType type) {
|
|||
protectHandler.init(server);
|
||||
protectHandler.checkLaunchServerLicense();
|
||||
}
|
||||
if(sessions != null) {
|
||||
sessions.init(server);
|
||||
server.registerObject("sessions", sessions);
|
||||
}
|
||||
if (components != null) {
|
||||
components.forEach((k, v) -> server.registerObject("component.".concat(k), v));
|
||||
}
|
||||
|
@ -229,6 +234,9 @@ public void close(LaunchServer.ReloadType type) {
|
|||
server.unregisterObject("protectHandler", protectHandler);
|
||||
protectHandler.close();
|
||||
}
|
||||
if(sessions != null) {
|
||||
server.unregisterObject("sessions", sessions);
|
||||
}
|
||||
if (dao != null) {
|
||||
server.unregisterObject("dao", dao);
|
||||
if (dao instanceof AutoCloseable) {
|
||||
|
@ -260,10 +268,6 @@ public static class ExeConf {
|
|||
public String txtProductVersion;
|
||||
}
|
||||
|
||||
public static class CertificateConf {
|
||||
public boolean enabled;
|
||||
}
|
||||
|
||||
public static class JarSignerConf {
|
||||
public boolean enabled = false;
|
||||
public String keyStore = "pathToKey";
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
import pro.gravit.launchserver.auth.protect.ProtectHandler;
|
||||
import pro.gravit.launchserver.auth.protect.hwid.HWIDProvider;
|
||||
import pro.gravit.launchserver.auth.provider.AuthProvider;
|
||||
import pro.gravit.launchserver.auth.session.SessionStorage;
|
||||
import pro.gravit.launchserver.auth.texture.TextureProvider;
|
||||
import pro.gravit.launchserver.components.Component;
|
||||
import pro.gravit.launchserver.dao.provider.DaoProvider;
|
||||
|
@ -41,6 +42,7 @@ public void registerAdapters(GsonBuilder builder) {
|
|||
builder.registerTypeAdapter(AuthRequest.AuthPasswordInterface.class, new UniversalJsonAdapter<>(AuthRequest.providers));
|
||||
builder.registerTypeAdapter(HWIDProvider.class, new UniversalJsonAdapter<>(HWIDProvider.providers));
|
||||
builder.registerTypeAdapter(OptionalAction.class, new UniversalJsonAdapter<>(OptionalAction.providers));
|
||||
builder.registerTypeAdapter(SessionStorage.class, new UniversalJsonAdapter<>(SessionStorage.providers));
|
||||
modulesManager.invokeEvent(new PreGsonPhase(builder));
|
||||
//ClientWebSocketService.appendTypeAdapters(builder);
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import pro.gravit.utils.HookSet;
|
||||
import pro.gravit.utils.helper.LogHelper;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
@ -15,8 +16,6 @@
|
|||
|
||||
public class SessionManager implements NeedGarbageCollection {
|
||||
|
||||
private final Map<UUID, Entry> clientSet = new ConcurrentHashMap<>(128);
|
||||
private final Map<UUID, Set<Entry>> uuidIndex = new ConcurrentHashMap<>(32);
|
||||
private final LaunchServer server;
|
||||
public HookSet<Client> clientRestoreHook = new HookSet<>();
|
||||
|
||||
|
@ -27,52 +26,35 @@ public SessionManager(LaunchServer server) {
|
|||
|
||||
public boolean addClient(Client client) {
|
||||
if(client == null || client.session == null) return false;
|
||||
remove(client.session);
|
||||
Entry e = new Entry(compressClient(client), client.session);
|
||||
clientSet.put(client.session, e);
|
||||
if(client.isAuth && client.uuid != null) {
|
||||
Set<Entry> uuidSet = uuidIndex.computeIfAbsent(client.uuid, k -> ConcurrentHashMap.newKeySet());
|
||||
uuidSet.add(e);
|
||||
}
|
||||
return true;
|
||||
return server.config.sessions.writeSession(client.uuid, client.session, compressClient(client));
|
||||
}
|
||||
|
||||
public Stream<UUID> findSessionsByUUID(UUID uuid) {
|
||||
Set<Entry> set = uuidIndex.get(uuid);
|
||||
if(set != null) return set.stream().map((e) -> e.sessionUuid);
|
||||
return null;
|
||||
return server.config.sessions.getSessionsFromUserUUID(uuid);
|
||||
}
|
||||
|
||||
public boolean removeByUUID(UUID uuid) {
|
||||
Set<Entry> set = uuidIndex.get(uuid);
|
||||
if(set != null) {
|
||||
for(Entry e : set) {
|
||||
clientSet.remove(e.sessionUuid);
|
||||
}
|
||||
set.clear();
|
||||
uuidIndex.remove(uuid);
|
||||
}
|
||||
return false;
|
||||
return server.config.sessions.deleteSessionsByUserUUID(uuid);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public Set<UUID> getSavedUUIDs()
|
||||
{
|
||||
return uuidIndex.keySet();
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
clientSet.clear();
|
||||
uuidIndex.clear();
|
||||
server.config.sessions.clear();
|
||||
}
|
||||
|
||||
private String compressClient(Client client) {
|
||||
return Launcher.gsonManager.gson.toJson(client); //Compress using later
|
||||
private byte[] compressClient(Client client) {
|
||||
return Launcher.gsonManager.gson.toJson(client).getBytes(StandardCharsets.UTF_8); //Compress using later
|
||||
}
|
||||
|
||||
private Client decompressClient(String client) {
|
||||
return Launcher.gsonManager.gson.fromJson(client, Client.class); //Compress using later
|
||||
private Client decompressClient(byte[] client) {
|
||||
return Launcher.gsonManager.gson.fromJson(new String(client, StandardCharsets.UTF_8), Client.class); //Compress using later
|
||||
}
|
||||
private Client restoreFromString(String data) {
|
||||
private Client restoreFromString(byte[] data) {
|
||||
Client result = decompressClient(data);
|
||||
result.updateAuth(server);
|
||||
if(result.auth != null && (result.username != null)) {
|
||||
|
@ -87,25 +69,11 @@ private Client restoreFromString(String data) {
|
|||
|
||||
@Override
|
||||
public void garbageCollection() {
|
||||
long time = System.currentTimeMillis();
|
||||
long session_timeout = server.config.netty.performance.sessionLifetimeMs;
|
||||
Set<UUID> to_delete = new HashSet<>(32);
|
||||
clientSet.forEach((uuid, entry) -> {
|
||||
long timestamp = entry.timestamp;
|
||||
if(timestamp + session_timeout < time)
|
||||
to_delete.add(uuid);
|
||||
});
|
||||
for(UUID session : to_delete) {
|
||||
remove(session);
|
||||
}
|
||||
to_delete.clear();
|
||||
}
|
||||
|
||||
|
||||
public Client getClient(UUID session) {
|
||||
Entry e = clientSet.get(session);
|
||||
if(e == null) return null;
|
||||
return restoreFromString(e.data);
|
||||
return restoreFromString(server.config.sessions.getSessionData(session));
|
||||
}
|
||||
|
||||
|
||||
|
@ -115,52 +83,27 @@ public Client getOrNewClient(UUID session) {
|
|||
}
|
||||
|
||||
public boolean remove(UUID session) {
|
||||
Entry e =clientSet.remove(session);
|
||||
if(e != null) {
|
||||
Set<Entry> set = uuidIndex.get(session);
|
||||
if(set != null) {
|
||||
removeUuidFromIndexSet(set, e, session);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return server.config.sessions.deleteSession(session);
|
||||
}
|
||||
|
||||
private void removeUuidFromIndexSet(Set<Entry> set, Entry e, UUID session) {
|
||||
set.remove(e);
|
||||
if(set.isEmpty()) {
|
||||
uuidIndex.remove(session);
|
||||
}
|
||||
}
|
||||
@Deprecated
|
||||
public void removeClient(UUID session) {
|
||||
remove(session);
|
||||
}
|
||||
|
||||
|
||||
@Deprecated
|
||||
public void updateClient(UUID session) {
|
||||
LogHelper.warning("Using deprecated method: sessionManager.updateClient");
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public Set<Client> getSessions() {
|
||||
// TODO: removeme
|
||||
LogHelper.warning("Using deprecated method: sessionManager.getSession");
|
||||
return new HashSet<>();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void loadSessions(Set<Client> set) {
|
||||
LogHelper.warning("Using deprecated method: sessionManager.loadSessions");
|
||||
//clientSet.putAll(set.stream().collect(Collectors.toMap(c -> c.session, Function.identity())));
|
||||
}
|
||||
private static class Entry {
|
||||
public String data;
|
||||
public UUID sessionUuid;
|
||||
public long timestamp;
|
||||
|
||||
public Entry(String data, UUID sessionUuid) {
|
||||
this.data = data;
|
||||
this.sessionUuid = sessionUuid;
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
2
modules
2
modules
|
@ -1 +1 @@
|
|||
Subproject commit e990b35b4001e29793b383dfa7b179aa0b27b307
|
||||
Subproject commit 5c4f6850bd4feeee0caff5561564b7e54bb94774
|
Loading…
Reference in a new issue