diff --git a/README.md b/README.md
index 78cd851..dff7ecf 100644
--- a/README.md
+++ b/README.md
@@ -90,12 +90,15 @@ If you want to quickly get up and running with your application then feel free t
Sending requests is also straightforward with the database, everything is on JSON format.
Also, everything is sent via webhooks which means the address to use is something like: `ws://127.0.0.1:5995`
+## Notice
+All requests towards RoseDB v1.1.0 must have the Authorization header which is validated at connection, this is not something
+that will be backward compatible since we want to enforce this as soon as possible.
+
**GET REQUESTS**
To send a GET request, you can do:
```json
{
- "authorization": "Authorization here",
"method": "get",
"database": "Database here",
"collection": "Collection Here",
@@ -107,7 +110,7 @@ To send a GET request, you can do:
It should reply with:
```json
{
- "response": "{//entire json data here}",
+ "response": "json",
"kode": 1,
"replyTo": "Unique identifier from request"
}
@@ -120,7 +123,6 @@ To send a AGGREGATE request, there are two ways:
* Collection aggregation sample request:
```json
{
- "authorization": "Authorization here",
"method":"aggregate",
"database":"rose_db",
"collection":"test",
@@ -131,7 +133,6 @@ To send a AGGREGATE request, there are two ways:
* Database aggregation sample request:
```json
{
- "authorization": "Authorization here",
"method":"aggregate",
"database":"rose_db",
"unique":"Unique Identifier Here"
@@ -174,7 +175,6 @@ To send a AGGREGATE request, there are two ways:
* UPDATE and ADD are both the same except UPDATE uses `"method":"update"`
```json
{
- "authorization": "Authorization here",
"method": "add",
"database": "Database here",
"collection": "Collection Here",
@@ -187,7 +187,6 @@ To send a AGGREGATE request, there are two ways:
Update also allows you to update (and add) values and keys, for example:
```json
{
- "authorization": "ca72b368-0c4a-4a73-9e4c-9e22474b359c",
"method": "update",
"database": "rose_db",
"collection": "mana",
@@ -220,7 +219,6 @@ It should reply with something like:
To send a DELETE request, you can do:
```json
{
- "authorization": "Authorization here",
"method": "delete",
"database": "Database here",
"collection": "Collection Here",
@@ -241,7 +239,6 @@ It should reply with:
Similar to UPDATE requests, you can also delete multiple or single keys from the data, for example:
```json
{
- "authorization": "ca72b368-0c4a-4a73-9e4c-9e22474b359c",
"method": "delete",
"database": "rose_db",
"collection": "mana",
@@ -272,7 +269,6 @@ There are two ways for DROP requests, one is for dropping collections and the ot
Example of a collection drop
```json
{
- "authorization": "8a4b93a0-a6d8-4403-a44f-5cff82a537e5",
"method": "drop",
"database": "rose_db",
"collection": "Mana",
@@ -292,7 +288,6 @@ It should reply with:
Example of a database drop:
```json
{
- "authorization": "8a4b93a0-a6d8-4403-a44f-5cff82a537e5",
"method": "drop",
"database": "rose_db",
"unique": "Unique identifier to receive from callback."
@@ -308,6 +303,41 @@ The expected response should be
}
```
+**Revert Request**
+You can revert an add or update request by simply sending a revert request to the server which looks like:
+```
+{
+ "method": "revert",
+ "database": "rose_db",
+ "collection": "mana",
+ "idenitifer": "revert_test",
+ "unique": "5"
+}
+```
+
+The response of this request will be the last version of the file.
+```
+{
+ "response": "{\"test\":1}",
+ "kode": 1,
+ "replyTo": "Unique ID here."
+}
+```
+
+You can also disable this feature via the config for maybe a little performance boost but I doubt it would add much, disabling the feature
+would then give out this reply to any attempt to revert:
+```
+{
+ "response": "This method is unsupported by the server.",
+ "kode": 0,
+ "replyTo": "Unique ID here"
+}
+```
+
+Limitations of revert:
+* The versions are saved on application-level cache and is dumped when the application is closed normally and not abruptly.
+* Each item is limited to one version and will be overriden each time an *ADD* or *UPDATE* request is sent that will override the item.
+
## Image Examples
* Collection Drop: 
* Database Drop: 
diff --git a/pom.xml b/pom.xml
index 5056ce3..7b47a34 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
pw.mihou
RoseDB
- 1.0.7
+ 1.1.0
@@ -72,13 +72,18 @@
org.slf4j
- slf4j-simple
- 1.8.0-beta4
+ slf4j-api
+ 1.7.30
- org.slf4j
- slf4j-api
- 1.8.0-beta4
+ ch.qos.logback
+ logback-classic
+ 1.2.3
+
+
+ com.github.ben-manes.caffeine
+ caffeine
+ 3.0.2
diff --git a/src/main/java/pw/mihou/rosedb/RoseDB.java b/src/main/java/pw/mihou/rosedb/RoseDB.java
index f1c5e6b..980bcb5 100644
--- a/src/main/java/pw/mihou/rosedb/RoseDB.java
+++ b/src/main/java/pw/mihou/rosedb/RoseDB.java
@@ -1,11 +1,15 @@
package pw.mihou.rosedb;
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
import org.json.JSONException;
import org.json.JSONObject;
+import org.slf4j.LoggerFactory;
import pw.mihou.rosedb.connections.RoseServer;
import pw.mihou.rosedb.enums.Levels;
import pw.mihou.rosedb.io.FileHandler;
import pw.mihou.rosedb.io.Scheduler;
+import pw.mihou.rosedb.utility.ColorPalette;
import pw.mihou.rosedb.utility.Terminal;
import pw.mihou.rosedb.utility.UpdateChecker;
import java.io.File;
@@ -24,16 +28,37 @@ public class RoseDB {
public static boolean preload;
public static int buffer;
public static int heartbeat;
+ public static boolean versioning;
public static int size;
public static void main(String[] args) throws URISyntaxException {
- System.out.println(" ______ ______ ______ ______ _____ ______ \n" +
- "/\\ == \\/\\ __ \\/\\ ___\\/\\ ___\\/\\ __-./\\ == \\ \n" +
- "\\ \\ __<\\ \\ \\/\\ \\ \\___ \\ \\ __\\\\ \\ \\/\\ \\ \\ __< \n" +
- " \\ \\_\\ \\_\\ \\_____\\/\\_____\\ \\_____\\ \\____-\\ \\_____\\ \n" +
- " \\/_/ /_/\\/_____/\\/_____/\\/_____/\\/____/ \\/_____/");
-
- Terminal.setLoggingLevel(Levels.ERROR);
+ String vanity = "\tr ______ ______ ______ ______ _____ ______ \n" +
+ "\tb/\\ == \\/\\ __ \\/\\ ___\\/\\ ___\\/\\ __-./\\ == \\ \n" +
+ "\ty\\ \\ __<\\ \\ \\/\\ \\ \\___ \\ \\ __\\\\ \\ \\/\\ \\ \\ __< \n" +
+ "\tc \\ \\_\\ \\_\\ \\_____\\/\\_____\\ \\_____\\ \\____-\\ \\_____\\ \n" +
+ "\tr \\/_/ /_/\\/_____/\\/_____/\\/_____/\\/____/ \\/_____/n";
+ vanity = vanity.replaceAll("r", ColorPalette.ANSI_RED)
+ .replaceAll("g", ColorPalette.ANSI_GREEN)
+ .replaceAll("b", ColorPalette.ANSI_BLUE)
+ .replaceAll("y", ColorPalette.ANSI_YELLOW)
+ .replaceAll("c", ColorPalette.ANSI_CYAN)
+ .replaceAll("n", ColorPalette.ANSI_RESET);
+ System.out.println(vanity);
+ System.out.printf("Version: %s, Build: %s, Configuration Version: %s, Creator: %s\n", UpdateChecker.VERSION, UpdateChecker.BUILD, UpdateChecker.CONFIG_VERSION, "Shindou Mihou");
+
+ ((Logger) LoggerFactory.getLogger("io.javalin.Javalin")).setLevel(Level.ERROR);
+ ((Logger) LoggerFactory.getLogger("org.eclipse.jetty.util.log")).setLevel(Level.ERROR);
+ System.setProperty("org.eclipse.jetty.util.log.class", "org.eclipse.jetty.util.log.StdErrLog");
+ System.setProperty("org.eclipse.jetty.LEVEL", "OFF");
+ System.setProperty("org.eclipse.jetty.util.log.announce", "false");
+ ((Logger) LoggerFactory.getLogger("io.javalin.Javalin")).setLevel(Level.ERROR);
+ ((Logger) LoggerFactory.getLogger("org.eclipse.jetty.util.log")).setLevel(Level.ERROR);
+ ((Logger) LoggerFactory.getLogger("org.eclipse.jetty")).setLevel(Level.ERROR);
+ ((Logger) LoggerFactory.getLogger("o.e.jetty.io")).setLevel(Level.ERROR);
+ ((Logger) LoggerFactory.getLogger("o.e.j.w.common")).setLevel(Level.ERROR);
+
+
+ Terminal.setLoggingLevel(Level.ERROR);
if (!new File("config.json").exists()) {
FileHandler.writeToFile("config.json", new JSONObject()
.put("directory", new File(RoseDB.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath()).getParentFile().getPath() + File.separator + "Database" + File.separator)
@@ -45,6 +70,7 @@ public static void main(String[] args) throws URISyntaxException {
.put("preload", true)
.put("maxTextMessageBufferSizeMB", 5)
.put("maxTextMessageSizeMB", 5)
+ .put("versioning", true)
.put("heartbeatIntervalSeconds", 30)
.put("configVersion", UpdateChecker.CONFIG_VERSION)
.toString()).join();
@@ -79,6 +105,7 @@ public static void main(String[] args) throws URISyntaxException {
buffer = config.getInt("maxTextMessageBufferSizeMB");
size = config.getInt("maxTextMessageSizeMB");
heartbeat = config.getInt("heartbeatIntervalSeconds");
+ versioning = config.getBoolean("versioning");
if(heartbeat > 300 || heartbeat < 25){
Terminal.log(Levels.ERROR, "Minimum heartbeat interval should be at 25 seconds to prevent overloading clients.");
@@ -98,7 +125,7 @@ public static void main(String[] args) throws URISyntaxException {
boolean mkdirs = new File(directory).mkdirs();
if(!mkdirs){
- Terminal.log(Levels.ERROR, "We couldn't create the folders on " + directory);
+ Terminal.log(Levels.ERROR, "We couldn't create the folders on {}", directory);
}
}
@@ -113,32 +140,32 @@ public static void main(String[] args) throws URISyntaxException {
FileHandler.setDirectory(directory);
RoseServer.run(port);
-
} else {
- Terminal.log(Levels.ERROR, "Rose cannot write on read or read on " + directory);
+ Terminal.log(Levels.ERROR, "Rose cannot write on read or read on {}", directory);
}
- } catch (JSONException e){
- Terminal.log(Levels.ERROR, e.getMessage());
- Terminal.log(Levels.ERROR, "An error from this side is caused by a misconfiguration of config.json, please fix your config.json.");
- } catch (ArithmeticException e){
- Terminal.log(Levels.ERROR, e.getMessage());
- Terminal.log(Levels.ERROR, "If this exception was thrown at the start, please check your config.json whether everything meets 32 bit integer limit.");
+ } catch (JSONException | ArithmeticException e){
+ Terminal.log(Levels.ERROR, "An error occurred, if this is sent from startup, " +
+ "please check config.json otherwise please send an issue at https://github.com/ShindouMihou/RoseDB/issues." +
+ "\nAvailable Processors (cores): {}, Free memory (MB): {}, Maximum Memory allocated to JVM (MB): {}, JDK Version: {}, JDK Vendor: {}, OS: {}, Architecture: {}, OS Version: {}" +
+ ".\n\nError: {}",
+ Runtime.getRuntime().availableProcessors(), (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / (1000 * 1000),
+ Runtime.getRuntime().totalMemory(), System.getProperty("java.vm.version"), System.getProperty("java.vm.vendor"),
+ System.getProperty("os.name"), System.getProperty("os.arch"), System.getProperty("os.version"), e.getMessage());
}
}
private static void startUpdateChecker(){
Scheduler.schedule(() -> {
- boolean update = UpdateChecker.check();
- if(update){
+ if(UpdateChecker.check()){
Terminal.log(Levels.INFO, "There is a newer version of RoseDB available, please update on https://github.com/ShindouMihou/RoseDB/releases");
}
}, 0, 12, TimeUnit.HOURS);
}
- public static Levels rootLevel(String configValue) {
- return configValue.equalsIgnoreCase("DEBUG") ? Levels.DEBUG : (configValue.equalsIgnoreCase("INFO") ? Levels.INFO :
- (configValue.equalsIgnoreCase("ERROR") ? Levels.ERROR : Levels.WARNING));
+ public static Level rootLevel(String configValue) {
+ return configValue.equalsIgnoreCase("DEBUG") ? Level.DEBUG : (configValue.equalsIgnoreCase("INFO") ? Level.INFO :
+ (configValue.equalsIgnoreCase("ERROR") ? Level.ERROR : Level.WARN));
}
private static JSONObject updateConfig(JSONObject original) throws URISyntaxException, JSONException {
@@ -148,8 +175,9 @@ private static JSONObject updateConfig(JSONObject original) throws URISyntaxExce
.put("authorization", Optional.ofNullable(original.getString("authorization")).orElse(UUID.randomUUID().toString()))
.put("loggingLevel", Optional.ofNullable(original.getString("loggingLevel")).orElse("INFO"))
.put("cores", original.isNull("cores") ? 1 : original.getInt("cores"))
- .put("updateChecker", original.isNull("updateChecker") ? true : original.getBoolean("updateChecker"))
+ .put("updateChecker", original.isNull("updateChecker") || original.getBoolean("updateChecker"))
.put("preload", true)
+ .put("versioning", true)
.put("maxTextMessageBufferSizeMB", original.isNull("maxTextMessageBufferSizeMB") ? 5 : original.getInt("maxTextMessageBufferSizeMB"))
.put("maxTextMessageSizeMB", original.isNull("maxTextMessageSizeMB") ? 5 : original.getInt("maxTextMessageSizeMB"))
.put("heartbeatIntervalSeconds", original.isNull("heartbeatIntervalSeconds") ? 30 : original.getInt("heartbeatIntervalSeconds"))
diff --git a/src/main/java/pw/mihou/rosedb/connections/RoseServer.java b/src/main/java/pw/mihou/rosedb/connections/RoseServer.java
index c227f2c..620df30 100644
--- a/src/main/java/pw/mihou/rosedb/connections/RoseServer.java
+++ b/src/main/java/pw/mihou/rosedb/connections/RoseServer.java
@@ -1,5 +1,8 @@
package pw.mihou.rosedb.connections;
+import ch.qos.logback.classic.Level;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import com.github.benmanes.caffeine.cache.LoadingCache;
import io.javalin.Javalin;
import io.javalin.core.compression.CompressionStrategy;
import io.javalin.websocket.WsContext;
@@ -12,6 +15,7 @@
import pw.mihou.rosedb.io.FileHandler;
import pw.mihou.rosedb.io.Scheduler;
import pw.mihou.rosedb.listeners.*;
+import pw.mihou.rosedb.manager.RoseCollections;
import pw.mihou.rosedb.manager.RoseDatabase;
import pw.mihou.rosedb.manager.RoseListenerManager;
import pw.mihou.rosedb.utility.Terminal;
@@ -19,6 +23,7 @@
import java.io.File;
import java.io.IOException;
import java.util.Map;
+import java.util.Optional;
import java.util.Scanner;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
@@ -26,7 +31,8 @@
public class RoseServer {
private static final Map context = new ConcurrentHashMap<>();
- private static final Map database = new ConcurrentHashMap<>();
+ private static final LoadingCache database = Caffeine.newBuilder()
+ .build(key -> FileHandler.readDatabase(key.toLowerCase()).join());
private static final Scanner scanner = new Scanner(System.in).useDelimiter("\n");
public static void reply(WsContext context, String response, int kode) {
@@ -56,15 +62,11 @@ public static void reply(WsContext context, String response, String unique, int
}
public static RoseDatabase getDatabase(String db) {
- if(!database.containsKey(db.toLowerCase())) {
- database.put(db.toLowerCase(), FileHandler.readDatabase(db.toLowerCase()).join());
- }
-
return database.get(db.toLowerCase());
}
- public static void removeDatabase(String db) throws IOException {
- database.remove(db.toLowerCase());
+ public static synchronized void removeDatabase(String db) throws IOException {
+ database.invalidate(db.toLowerCase());
FileUtils.deleteDirectory(new File(String.format(RoseDB.directory + "/%s/", db)));
}
@@ -75,6 +77,7 @@ private static void register() {
RoseListenerManager.register(new UpdateListener());
RoseListenerManager.register(new DropListener());
RoseListenerManager.register(new AggregateListener());
+ RoseListenerManager.register(new RevertListener());
}
private static void startHeartbeat() {
@@ -87,11 +90,15 @@ private static void startHeartbeat() {
Terminal.log(Levels.DEBUG, "Heartbeat listener is now active.");
}
+ private static void startWriter(){
+ Scheduler.schedule(FileHandler::write, 5, 5, TimeUnit.SECONDS);
+ }
+
public static void run(int port) {
register();
Terminal.log(Levels.DEBUG, "All listeners are registered.");
- if (Terminal.root.id == Levels.DEBUG.id) {
+ if (Terminal.root == Level.DEBUG) {
Terminal.log(Levels.WARNING, "For maximum performance, we recommend turning off DEBUG mode unless needed (especially when requests can reach large sizes).");
}
@@ -111,26 +118,46 @@ public static void run(int port) {
ws.getPolicy().setMaxTextMessageSize(RoseDB.size * 1024 * 1024);
});
- if (Levels.DEBUG.id <= Terminal.root.id) {
- config.wsLogger(wsHandler -> {
- wsHandler.onConnect(wsConnectContext -> Terminal.log(Levels.DEBUG, "Received connection from " + wsConnectContext.session.getRemoteAddress().toString()));
- wsHandler.onClose(wsCloseContext -> Terminal.log(Levels.DEBUG, "Received closed connection from " + wsCloseContext.session.getRemoteAddress().toString() + " for " + wsCloseContext.reason()));
- wsHandler.onMessage(ctx -> Terminal.log(Levels.DEBUG, "Received request: " + ctx.message() + " from " + ctx.session.getRemoteAddress().toString()));
- });
- }
+ config.wsLogger(wsHandler -> {
+ wsHandler.onConnect(wsConnectContext -> Terminal.log(Levels.DEBUG, "Received connection from {}", wsConnectContext.session.getRemoteAddress().toString()));
+ wsHandler.onClose(wsCloseContext -> Terminal.log(Levels.DEBUG, "Received closed connection from {} for {}", wsCloseContext.session.getRemoteAddress().toString(), wsCloseContext.reason()));
+ wsHandler.onMessage(ctx -> Terminal.log(Levels.DEBUG, "Received request: {} from {}", ctx.message(), ctx.session.getRemoteAddress().toString()));
+ });
});
app.events(event -> {
- if (Levels.DEBUG.id <= Terminal.root.id) {
- event.serverStarting(() -> Terminal.log(Levels.DEBUG, "The server is now starting at port: " + port));
- event.serverStarted(() -> Terminal.log(Levels.DEBUG, "The server has started on port: " + port));
- }
-
+ event.serverStarting(() -> Terminal.log(Levels.DEBUG, "The server is now starting at port: {}", port));
+ event.serverStarted(() -> Terminal.log(Levels.DEBUG, "The server has started on port: {}", port));
event.serverStartFailed(() -> Terminal.log(Levels.ERROR, "The server failed to start, possibly another instance at the same port is running."));
event.serverStopping(() -> Terminal.log(Levels.INFO, "The server is now shutting off."));
event.serverStopped(() -> Terminal.log(Levels.INFO, "The server has successfully closed."));
}).start(port);
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> context.values().stream().filter(wsContext -> wsContext.session.isOpen())
+ .forEachOrdered(wsContext -> wsContext.session.close(4002, "RoseDB is closing."))));
+
+ if(RoseDB.versioning) {
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> database.asMap().values().forEach(roseDatabase -> roseDatabase.getCollections()
+ .forEach(collections -> collections.getVersions().forEach((s, s2) -> {
+ String location = new StringBuilder(".rose_versions")
+ .append(File.separator)
+ .append(roseDatabase.getDatabase())
+ .append(File.separator).append(collections.collection)
+ .append(File.separator).append(s)
+ .append(".rose").toString();
+
+ if (!new File(location).exists()) {
+ boolean mkdirs = new File(location).mkdirs();
+ if (!mkdirs) {
+ Terminal.setLoggingLevel(Level.ERROR);
+ Terminal.log(Levels.ERROR, "Failed to create folders for " + location + ", possibly we do not have permission to write.");
+ }
+ }
+
+ FileHandler.writeGzip(location, s2);
+ })))));
+ }
+
Runtime.getRuntime().addShutdownHook(new Thread(app::stop));
Runtime.getRuntime().addShutdownHook(new Thread(FileHandler::write));
Runtime.getRuntime().addShutdownHook(new Thread(Scheduler::shutdown));
@@ -138,23 +165,29 @@ public static void run(int port) {
Terminal.log(Levels.DEBUG, "All events and handlers are now ready.");
app.ws("/", ws -> {
- ws.onConnect(ctx -> context.put(ctx.getSessionId(), ctx));
+ ws.onConnect(ctx -> {
+ if(Optional.ofNullable(ctx.header("Authorization")).orElse("").equals(RoseDB.authorization))
+ context.put(ctx.getSessionId(), ctx);
+ else
+ ctx.session.close(4001, "Missing or invalid Authorization header.");
+ });
ws.onClose(ctx -> context.remove(ctx.getSessionId()));
ws.onMessage(ctx -> {
try {
RoseListenerManager.execute(new JSONObject(ctx.message()), ctx);
} catch (JSONException e) {
reply(ctx, "The request was considered as invalid: " + e.getMessage(), -1);
- Terminal.log(Levels.DEBUG, "Received invalid JSON request: " + ctx.message() + " from " + ctx.session.getRemoteAddress().toString());
+ Terminal.log(Levels.DEBUG, "Received invalid JSON request: {} from {}", ctx.message(), ctx.session.getRemoteAddress().toString());
} catch (MessageTooLargeException e){
reply(ctx, "The request was canceled by force: " + e.getMessage(), -1);
- Terminal.log(Levels.ERROR, "Received message that was too large from" + ctx.session.getRemoteAddress().toString());
+ Terminal.log(Levels.ERROR, "Received message that was too large from {}", ctx.session.getRemoteAddress().toString());
}
});
});
- Terminal.log(Levels.INFO, "RoseDB is now running on port: " + port);
+ Terminal.log(Levels.INFO, "RoseDB is now running on port: {}", port);
startHeartbeat();
+ startWriter();
while(scanner.hasNextLine()){
String request = scanner.nextLine();
@@ -162,7 +195,7 @@ public static void run(int port) {
RoseListenerManager.execute(new JSONObject(request), null);
} catch (JSONException e) {
reply(null, "The request was considered as invalid: " + request, -1);
- Terminal.log(Levels.DEBUG, "Received invalid JSON request: " + request + " from terminal.");
+ Terminal.log(Levels.DEBUG, "Received invalid JSON request: {} from terminal", request);
} catch (MessageTooLargeException e){
reply(null, "The request was canceled by force: " + e.getMessage(), -1);
Terminal.log(Levels.ERROR, "Received message that was too large from terminal.");
diff --git a/src/main/java/pw/mihou/rosedb/enums/Listening.java b/src/main/java/pw/mihou/rosedb/enums/Listening.java
index a9e6481..66d561b 100644
--- a/src/main/java/pw/mihou/rosedb/enums/Listening.java
+++ b/src/main/java/pw/mihou/rosedb/enums/Listening.java
@@ -2,7 +2,7 @@
public enum Listening {
- GET("GET"), DELETE("DELETE"), UPDATE("UPDATE"), ADD("ADD"), DROP("DROP"), AGGREGATE("AGGREGATE");
+ GET("GET"), DELETE("DELETE"), UPDATE("UPDATE"), ADD("ADD"), DROP("DROP"), AGGREGATE("AGGREGATE"), REVERT("REVERT");
public final String value;
diff --git a/src/main/java/pw/mihou/rosedb/io/FileHandler.java b/src/main/java/pw/mihou/rosedb/io/FileHandler.java
index ceb17ad..ab0a456 100644
--- a/src/main/java/pw/mihou/rosedb/io/FileHandler.java
+++ b/src/main/java/pw/mihou/rosedb/io/FileHandler.java
@@ -1,5 +1,6 @@
package pw.mihou.rosedb.io;
+import ch.qos.logback.classic.Level;
import org.apache.commons.io.FilenameUtils;
import pw.mihou.rosedb.RoseDB;
import pw.mihou.rosedb.connections.RoseServer;
@@ -7,6 +8,7 @@
import pw.mihou.rosedb.io.entities.RoseRequest;
import pw.mihou.rosedb.manager.RoseCollections;
import pw.mihou.rosedb.manager.RoseDatabase;
+import pw.mihou.rosedb.utility.Pair;
import pw.mihou.rosedb.utility.Terminal;
import java.io.*;
@@ -14,10 +16,12 @@
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
+import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Collectors;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
@@ -98,32 +102,25 @@ private static boolean filter(RoseRequest roseRequest, String database, String c
}
public static void write(String database, String collection, String identifier, String json) {
+ Terminal.log(Levels.DEBUG, "Added to queue: {}/{}/{}.rose: {}", database, collection, identifier, json);
queue.add(new RoseRequest(database, collection, identifier, json));
-
- if (queue.stream().filter(roseRequest -> filter(roseRequest, database, collection, identifier)).count() == 1) {
- write();
- }
}
- public static void write() {
- if (!threadFull.get()) {
- Scheduler.getExecutorService().submit(() -> {
- if (!queue.isEmpty()) {
- threadFull.set(true);
+ public static synchronized void write() {
+ if(!threadFull.get()) {
+ threadFull.set(true);
- RoseRequest request = queue.poll();
- writeGzip(format(request.database, request.collection, request.identifier), request.json).join();
+ while (!queue.isEmpty()) {
+ RoseRequest request = queue.poll();
+ Terminal.log(Levels.DEBUG, "Writing {}/{}/{}.rose: {}", request.database, request.collection, request.identifier, request.json);
+ writeGzip(format(request.database, request.collection, request.identifier), request.json).join();
+ }
- threadFull.set(false);
- if (!queue.isEmpty()) {
- write();
- }
- }
- });
+ threadFull.set(false);
}
}
- private static CompletableFuture write(String path, String value, boolean gzip) {
+ private static synchronized CompletableFuture write(String path, String value, boolean gzip) {
return CompletableFuture.runAsync(() -> {
try {
if (!gzip) {
@@ -146,7 +143,7 @@ public static CompletableFuture readCollection(String database,
if (!new File(location).exists()) {
boolean mkdirs = new File(location).mkdirs();
if (!mkdirs) {
- Terminal.setLoggingLevel(Levels.ERROR);
+ Terminal.setLoggingLevel(Level.ERROR);
Terminal.log(Levels.ERROR, "Failed to create folders for " + location + ", possibly we do not have permission to write.");
return new RoseCollections(collection, database);
}
@@ -159,7 +156,22 @@ public static CompletableFuture readCollection(String database,
Arrays.stream(contents).forEach(file -> collections.cache(FilenameUtils.getBaseName(file.getName()), readGzip(file.getPath()).join()));
}
return collections;
- });
+ }, Scheduler.getExecutorService());
+ }
+
+ public static Optional readVersion(String database, String collection, String identifier) {
+ String location = new StringBuilder(".rose_versions")
+ .append(File.separator)
+ .append(database)
+ .append(File.separator).append(collection)
+ .append(File.separator).append(identifier)
+ .append(".rose").toString();
+
+ if (!new File(location).exists()) {
+ return Optional.empty();
+ }
+
+ return Optional.of(readGzip(new File(location).getPath()).join());
}
public static CompletableFuture readDatabase(String database) {
@@ -168,7 +180,7 @@ public static CompletableFuture readDatabase(String database) {
if (!new File(location).exists()) {
boolean mkdirs = new File(location).mkdirs();
if (!mkdirs) {
- Terminal.setLoggingLevel(Levels.ERROR);
+ Terminal.setLoggingLevel(Level.ERROR);
Terminal.log(Levels.ERROR, "Failed to create folders for " + location + ", possibly we do not have permission to write.");
return new RoseDatabase(database);
}
@@ -184,10 +196,11 @@ public static CompletableFuture readDatabase(String database) {
}
return data;
- });
+ }, Scheduler.getExecutorService());
}
public static Optional readData(String database, String collection, String identifier) {
+ Terminal.log(Levels.DEBUG, "Attempting to read {}/{}/{}.rose", database, collection, identifier);
String location = format(database, collection, identifier);
if (!new File(location).exists())
return Optional.empty();
@@ -203,7 +216,7 @@ public static CompletableFuture preloadAll() {
Arrays.stream(contents).filter(File::isDirectory)
.forEachOrdered(file -> RoseServer.getDatabase(FilenameUtils.getBaseName(file.getName())));
}
- });
+ }, Scheduler.getExecutorService());
}
public static CompletableFuture migrateAll() {
@@ -221,7 +234,7 @@ public static CompletableFuture migrateAll() {
}
});
}
- });
+ }, Scheduler.getExecutorService());
}
public static CompletableFuture migrateCollection(String database, String collection) {
diff --git a/src/main/java/pw/mihou/rosedb/io/Scheduler.java b/src/main/java/pw/mihou/rosedb/io/Scheduler.java
index 552bda8..a206b7b 100644
--- a/src/main/java/pw/mihou/rosedb/io/Scheduler.java
+++ b/src/main/java/pw/mihou/rosedb/io/Scheduler.java
@@ -1,75 +1,79 @@
package pw.mihou.rosedb.io;
-
+import org.jetbrains.annotations.NotNull;
import pw.mihou.rosedb.RoseDB;
import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
public class Scheduler {
+ private static final int CORE_POOL_SIZE = RoseDB.cores;
private static final int MAXIMUM_POOL_SIZE = Integer.MAX_VALUE;
- private static final int KEEP_ALIVE_TIME = 120;
+ private static final int KEEP_ALIVE_TIME = 240;
private static final TimeUnit TIME_UNIT = TimeUnit.SECONDS;
- private static final ExecutorService executorService = new ThreadPoolExecutor(
- RoseDB.cores, MAXIMUM_POOL_SIZE, KEEP_ALIVE_TIME, TIME_UNIT, new SynchronousQueue<>());
- private static final ScheduledExecutorService executor =
- Executors.newScheduledThreadPool(RoseDB.cores);
+ public static final ExecutorService executorService = new ThreadPoolExecutor(
+ CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_TIME, TIME_UNIT, new SynchronousQueue<>(),
+ new ThreadFactory("main", false));
- public static ExecutorService getExecutorService() {
- return executorService;
- }
+ public static final ScheduledExecutorService scheduledExecutorService =
+ Executors.newScheduledThreadPool(CORE_POOL_SIZE, new ThreadFactory("main", false));
- /**
- * Shutdowns the scheduled executor service.
- * Already called when exiting.
- */
- public static void shutdown() {
- executor.shutdown();
+ public static ScheduledFuture> schedule(Runnable task, long delay, long time, TimeUnit measurement) {
+ return scheduledExecutorService.scheduleAtFixedRate(task, delay, time, measurement);
}
- /**
- * Schedules a task at a fixed rate.
- *
- * @param task the task to schedule.
- * @param delay the delay before the first run.
- * @param time the repeat time.
- * @param measurement the time measurement.
- * @return ScheduledFuture>
- */
- public static ScheduledFuture> schedule(Runnable task, long delay, long time, TimeUnit measurement) {
- return executor.scheduleAtFixedRate(task, delay, time, measurement);
+ public static ExecutorService getExecutorService(){
+ return executorService;
}
- /**
- * Runs a single task on a delay.
- *
- * @param task the task to run.
- * @param delay the delay before the first execution.
- * @param measurement the time measurement.
- * @return ScheduledFuture>
- */
public static ScheduledFuture> schedule(Runnable task, long delay, TimeUnit measurement) {
- return executor.schedule(task, delay, measurement);
+ return scheduledExecutorService.schedule(task, delay, measurement);
}
- /**
- * Submits a task to run asynchronous.
- *
- * @param task the task to run.
- * @return CompletableFuture
- */
- public static CompletableFuture submitTask(Runnable task) {
- return CompletableFuture.runAsync(task, executor);
+ public static CompletableFuture submit(Runnable task) {
+ return CompletableFuture.runAsync(task, executorService);
}
- /**
- * Returns the ScheduledExecutorService.
- *
- * @return ScheduledExecutorService.
- */
- public ScheduledExecutorService getScheduler() {
- return executor;
+ public static void shutdown(){
+ executorService.shutdown();
+ scheduledExecutorService.shutdown();
}
+}
+
+ class ThreadFactory implements java.util.concurrent.ThreadFactory {
+
+ /**
+ * The numbering counter.
+ */
+ private final AtomicInteger counter = new AtomicInteger();
+
+ /**
+ * The name pattern.
+ */
+ private final String namePattern;
+
+ /**
+ * Whether to create daemon threads.
+ */
+ private final boolean daemon;
+
+ /**
+ * Creates a new thread factory.
+ *
+ * @param namePattern The name pattern, may contain a {@code %d} wildcard where the counter gets filled in.
+ * @param daemon Whether to create daemon or non-daemon threads.
+ */
+ public ThreadFactory(String namePattern, boolean daemon) {
+ this.namePattern = namePattern;
+ this.daemon = daemon;
+ }
-}
\ No newline at end of file
+ @Override
+ public Thread newThread(@NotNull Runnable r) {
+ Thread thread = new Thread(r, String.format(namePattern, counter.incrementAndGet()));
+ thread.setDaemon(daemon);
+ return thread;
+ }
+}
diff --git a/src/main/java/pw/mihou/rosedb/listeners/AddListener.java b/src/main/java/pw/mihou/rosedb/listeners/AddListener.java
index 925dd61..a40fde8 100644
--- a/src/main/java/pw/mihou/rosedb/listeners/AddListener.java
+++ b/src/main/java/pw/mihou/rosedb/listeners/AddListener.java
@@ -3,9 +3,11 @@
import io.javalin.websocket.WsContext;
import org.json.JSONObject;
import pw.mihou.rosedb.connections.RoseServer;
+import pw.mihou.rosedb.enums.Levels;
import pw.mihou.rosedb.enums.Listening;
import pw.mihou.rosedb.manager.RoseCollections;
import pw.mihou.rosedb.manager.entities.RoseListener;
+import pw.mihou.rosedb.utility.Terminal;
public class AddListener implements RoseListener {
@Override
@@ -19,10 +21,9 @@ public void execute(JSONObject request, WsContext context, String unique) {
|| request.isNull("database") || request.isNull("collection")) {
RoseServer.reply(context, "Missing parameters either: [value], [identifier], [database], [collection]", unique, -1);
} else {
+ Terminal.log(Levels.DEBUG, "Request to add {} with value {}", request.getString("identifier"), request.getString("value"));
RoseCollections collections = RoseServer.getDatabase(request.getString("database")).getCollection(request.getString("collection"));
- collections.add(request.getString("identifier"), request.getString("value"));
- collections.get(request.getString("identifier")).ifPresentOrElse(roseEntity -> RoseServer.reply(context, roseEntity, unique, 1),
- () -> RoseServer.reply(context, "Failed to update value.", unique, 0));
+ RoseServer.reply(context, collections.add(request.getString("identifier"), request.getString("value")), unique, 1);
}
}
}
diff --git a/src/main/java/pw/mihou/rosedb/listeners/RevertListener.java b/src/main/java/pw/mihou/rosedb/listeners/RevertListener.java
new file mode 100644
index 0000000..c003d15
--- /dev/null
+++ b/src/main/java/pw/mihou/rosedb/listeners/RevertListener.java
@@ -0,0 +1,36 @@
+package pw.mihou.rosedb.listeners;
+
+import io.javalin.websocket.WsContext;
+import org.json.JSONObject;
+import pw.mihou.rosedb.RoseDB;
+import pw.mihou.rosedb.connections.RoseServer;
+import pw.mihou.rosedb.enums.Listening;
+import pw.mihou.rosedb.manager.entities.RoseListener;
+import pw.mihou.rosedb.utility.Pair;
+
+public class RevertListener implements RoseListener {
+
+ @Override
+ public Listening type() {
+ return Listening.REVERT;
+ }
+
+ @Override
+ public void execute(JSONObject request, WsContext context, String unique) {
+ if(RoseDB.versioning)
+ if (request.isNull("identifier") || request.isNull("database") || request.isNull("collection")) {
+ RoseServer.reply(context, "Missing parameters either: [identifier], [database], [collection]", unique, -1);
+ } else {
+ Pair response = RoseServer.getDatabase(request.getString("database"))
+ .getCollection(request.getString("collection"))
+ .revert(request.getString("identifier"));
+
+ if(response.getLeft())
+ RoseServer.reply(context, response.getRight(), unique, 1);
+ else
+ RoseServer.reply(context, "There are no backup versions for the requested item.", unique, 0);
+ }
+ else
+ RoseServer.reply(context, "This method is unsupported by the server.", unique, 0);
+ }
+}
diff --git a/src/main/java/pw/mihou/rosedb/listeners/UpdateListener.java b/src/main/java/pw/mihou/rosedb/listeners/UpdateListener.java
index 54002b5..55a38cc 100644
--- a/src/main/java/pw/mihou/rosedb/listeners/UpdateListener.java
+++ b/src/main/java/pw/mihou/rosedb/listeners/UpdateListener.java
@@ -21,10 +21,10 @@ public void execute(JSONObject request, WsContext context, String unique) {
RoseServer.reply(context, "Missing parameters either: [value], [key], [identifier], [database], [collection]", unique, -1);
} else {
if (request.isNull("key") && !request.isNull("value")) {
- RoseCollections collections = RoseServer.getDatabase(request.getString("database")).getCollection(request.getString("collection"));
- collections.add(request.getString("identifier"), request.getString("value"));
- collections.get(request.getString("identifier")).ifPresentOrElse(roseEntity -> RoseServer.reply(context, roseEntity, unique, 1),
- () -> RoseServer.reply(context, "Failed to update value.", unique, 0));
+ RoseServer.reply(context, RoseServer
+ .getDatabase(request.getString("database"))
+ .getCollection(request.getString("collection"))
+ .add(request.getString("identifier"), request.getString("value")), unique, 1);
} else if (!request.isNull("key") && !request.isNull("value")) {
RoseCollections collections = RoseServer.getDatabase(request.getString("database")).getCollection(request.getString("collection"));
collections.get(request.getString("identifier")).ifPresentOrElse(roseEntity -> {
diff --git a/src/main/java/pw/mihou/rosedb/manager/RoseCollections.java b/src/main/java/pw/mihou/rosedb/manager/RoseCollections.java
index e5cd31b..0bfa61f 100644
--- a/src/main/java/pw/mihou/rosedb/manager/RoseCollections.java
+++ b/src/main/java/pw/mihou/rosedb/manager/RoseCollections.java
@@ -1,46 +1,79 @@
package pw.mihou.rosedb.manager;
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import com.github.benmanes.caffeine.cache.LoadingCache;
+import pw.mihou.rosedb.RoseDB;
+import pw.mihou.rosedb.enums.Levels;
import pw.mihou.rosedb.io.FileHandler;
import pw.mihou.rosedb.io.Scheduler;
+import pw.mihou.rosedb.utility.Pair;
+import pw.mihou.rosedb.utility.Terminal;
import java.util.Map;
import java.util.Optional;
-import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
public class RoseCollections {
- private final Map data = new ConcurrentHashMap<>();
+ private final LoadingCache data;
+ private LoadingCache versions;
public final String collection;
private final String database;
public RoseCollections(String collection, String database) {
this.collection = collection;
this.database = database;
+ // We are returning null since Caffeine will not add null values.
+ this.data = Caffeine.newBuilder()
+ .build(key -> FileHandler.readData(database, collection, key)
+ .orElse(null));
+
+ if(RoseDB.versioning) {
+ versions = Caffeine.newBuilder().expireAfterWrite(6, TimeUnit.HOURS).build(key ->
+ FileHandler.readVersion(database, collection, key).orElse(null));
+ }
}
public void cache(String identifier, String json) {
this.data.put(identifier, json);
}
+ public Pair revert(String identifier){
+ if(versions.get(identifier) == null)
+ return new Pair<>(false, "");
+
+ String json = versions.get(identifier);
+ versions.invalidate(identifier);
+
+ add(identifier, json);
+ return new Pair<>(true, json);
+ }
+
public void delete(String identifier) {
- this.data.remove(identifier);
- Scheduler.getExecutorService().submit(() -> FileHandler.delete(database, collection, identifier));
+ this.data.invalidate(identifier);
+ Scheduler.submit(() -> FileHandler.delete(database, collection, identifier));
}
public Map getData(){
- return data;
+ return data.asMap();
}
- public void add(String identifier, String json) {
+ public Map getVersions(){
+ return versions.asMap();
+ }
+
+ public String add(String identifier, String json) {
+ if(RoseDB.versioning && this.data.get(identifier) != null) {
+ this.versions.put(identifier, this.data.get(identifier));
+ }
+
this.data.put(identifier, json);
FileHandler.write(database, collection, identifier, json);
+ return json;
}
public Optional get(String identifier) {
- if (!data.containsKey(identifier)) {
- FileHandler.readData(database, collection, identifier).ifPresent(roseEntity -> data.put(identifier, roseEntity));
- }
-
return Optional.ofNullable(data.get(identifier));
}
diff --git a/src/main/java/pw/mihou/rosedb/manager/RoseDatabase.java b/src/main/java/pw/mihou/rosedb/manager/RoseDatabase.java
index 335c8cb..572118f 100644
--- a/src/main/java/pw/mihou/rosedb/manager/RoseDatabase.java
+++ b/src/main/java/pw/mihou/rosedb/manager/RoseDatabase.java
@@ -1,5 +1,7 @@
package pw.mihou.rosedb.manager;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import com.github.benmanes.caffeine.cache.LoadingCache;
import org.apache.commons.io.FileUtils;
import pw.mihou.rosedb.RoseDB;
import pw.mihou.rosedb.io.FileHandler;
@@ -7,16 +9,16 @@
import java.io.File;
import java.io.IOException;
import java.util.Collection;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
public class RoseDatabase {
private final String database;
- private final Map rosy = new ConcurrentHashMap<>();
+ private final LoadingCache rosy;
public RoseDatabase(String database) {
this.database = database;
+ this.rosy = Caffeine.newBuilder()
+ .build(key -> FileHandler.readCollection(database, key).join());
}
public void cache(String collection, RoseCollections collections) {
@@ -24,20 +26,20 @@ public void cache(String collection, RoseCollections collections) {
}
public Collection getCollections(){
- return rosy.values();
+ return rosy.asMap().values();
}
public RoseCollections getCollection(String collection) {
- if(!rosy.containsKey(collection)) {
- rosy.put(collection, FileHandler.readCollection(database, collection).join());
- }
-
return rosy.get(collection);
}
+ public String getDatabase(){
+ return database;
+ }
+
public void removeCollection(String collection) throws IOException {
FileUtils.deleteDirectory(new File(RoseDB.directory + "/" + database + "/" + collection + "/"));
- this.rosy.remove(collection);
+ this.rosy.invalidate(collection);
}
}
diff --git a/src/main/java/pw/mihou/rosedb/manager/RoseListenerManager.java b/src/main/java/pw/mihou/rosedb/manager/RoseListenerManager.java
index 9eb7833..6c39c8b 100644
--- a/src/main/java/pw/mihou/rosedb/manager/RoseListenerManager.java
+++ b/src/main/java/pw/mihou/rosedb/manager/RoseListenerManager.java
@@ -2,8 +2,6 @@
import io.javalin.websocket.WsContext;
import org.json.JSONObject;
-import pw.mihou.rosedb.RoseDB;
-import pw.mihou.rosedb.connections.RoseServer;
import pw.mihou.rosedb.io.Scheduler;
import pw.mihou.rosedb.manager.entities.RoseListener;
@@ -18,17 +16,9 @@ public static void register(RoseListener listener) {
listeners.add(listener);
}
- public static void remove(RoseListener listener) {
- listeners.remove(listener);
- }
-
public static void execute(JSONObject request, WsContext context) {
- if (!request.isNull("unique") && !(request.isNull("authorization")) && request.getString("authorization").equalsIgnoreCase(RoseDB.authorization)) {
- listeners.stream().filter(roseListener -> roseListener.type().value.equalsIgnoreCase(request.getString("method")))
- .forEach(roseListener -> Scheduler.getExecutorService().submit(() -> roseListener.execute(request, context, request.getString("unique"))));
- } else {
- RoseServer.reply(context, "Please validate: correct authorization code or unique value on request.", -1);
- }
+ listeners.stream().filter(roseListener -> roseListener.type().value.equalsIgnoreCase(request.getString("method")))
+ .forEachOrdered(roseListener -> Scheduler.submit(() -> roseListener.execute(request, context, request.getString("unique"))));
}
}
diff --git a/src/main/java/pw/mihou/rosedb/utility/ColorPalette.java b/src/main/java/pw/mihou/rosedb/utility/ColorPalette.java
new file mode 100644
index 0000000..c4ac458
--- /dev/null
+++ b/src/main/java/pw/mihou/rosedb/utility/ColorPalette.java
@@ -0,0 +1,15 @@
+package pw.mihou.rosedb.utility;
+
+public class ColorPalette {
+
+ public static final String ANSI_RESET = "\u001B[0m";
+ public static final String ANSI_BLACK = "\u001B[30m";
+ public static final String ANSI_RED = "\u001B[31m";
+ public static final String ANSI_GREEN = "\u001B[32m";
+ public static final String ANSI_YELLOW = "\u001B[33m";
+ public static final String ANSI_BLUE = "\u001B[34m";
+ public static final String ANSI_PURPLE = "\u001B[35m";
+ public static final String ANSI_CYAN = "\u001B[36m";
+ public static final String ANSI_WHITE = "\u001B[37m";
+
+}
diff --git a/src/main/java/pw/mihou/rosedb/utility/Pair.java b/src/main/java/pw/mihou/rosedb/utility/Pair.java
new file mode 100644
index 0000000..9d5b4ce
--- /dev/null
+++ b/src/main/java/pw/mihou/rosedb/utility/Pair.java
@@ -0,0 +1,21 @@
+package pw.mihou.rosedb.utility;
+
+public class Pair {
+
+ private final T left;
+ private final Y right;
+
+ public Pair(T left, Y right){
+ this.left = left;
+ this.right = right;
+ }
+
+ public T getLeft(){
+ return left;
+ }
+
+ public Y getRight(){
+ return right;
+ }
+
+}
diff --git a/src/main/java/pw/mihou/rosedb/utility/Terminal.java b/src/main/java/pw/mihou/rosedb/utility/Terminal.java
index 715bc2e..725f103 100644
--- a/src/main/java/pw/mihou/rosedb/utility/Terminal.java
+++ b/src/main/java/pw/mihou/rosedb/utility/Terminal.java
@@ -1,27 +1,36 @@
package pw.mihou.rosedb.utility;
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+import org.slf4j.LoggerFactory;
import pw.mihou.rosedb.enums.Levels;
-import java.time.LocalDateTime;
-import java.time.format.DateTimeFormatter;
-
public class Terminal {
- private static final String format = "[%s] [RoseDB: %s] > %s\n";
- public static Levels root = Levels.ERROR;
+ public static Logger log = (Logger) LoggerFactory.getLogger("RoseDB");
+ static {
+ log.setLevel(Level.INFO);
+ }
+
+ public static Level root = Level.INFO;
- public static void setLoggingLevel(Levels level) {
+ public static void setLoggingLevel(Level level) {
+ log.setLevel(level);
root = level;
}
- private static String getTime() {
- return DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss").format(LocalDateTime.now());
+ public static void log(Levels level, String message) {
+ if(level == Levels.DEBUG) log.debug(message);
+ if(level == Levels.ERROR) log.error(message);
+ if(level == Levels.INFO) log.info(message);
+ if(level == Levels.WARNING) log.warn(message);
}
- public static void log(Levels level, String message) {
- if (level.id <= root.id) {
- System.out.printf(format, getTime(), level.name, message);
- }
+ public static void log(Levels level, String message, Object... values) {
+ if(level == Levels.DEBUG) log.debug(message, values);
+ if(level == Levels.ERROR) log.error(message, values);
+ if(level == Levels.INFO) log.info(message, values);
+ if(level == Levels.WARNING) log.warn(message, values);
}
}
diff --git a/src/main/java/pw/mihou/rosedb/utility/UpdateChecker.java b/src/main/java/pw/mihou/rosedb/utility/UpdateChecker.java
index 4617b16..49c591e 100644
--- a/src/main/java/pw/mihou/rosedb/utility/UpdateChecker.java
+++ b/src/main/java/pw/mihou/rosedb/utility/UpdateChecker.java
@@ -14,7 +14,9 @@ public class UpdateChecker {
// This will not be checked through the online version and will instead be
// done on the application itself.
public static final String CONFIG_VERSION = "1.2";
- private static final String ENCODED_VERSION = URLEncoder.encode("1.0.7", StandardCharsets.UTF_8);
+ public static final String VERSION = "1.1.0";
+ public static final String BUILD = "DEV-SNAPSHOT";
+ private static final String ENCODED_VERSION = URLEncoder.encode(VERSION, StandardCharsets.UTF_8);
public static boolean check(){
try {
diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml
new file mode 100644
index 0000000..4bc31e0
--- /dev/null
+++ b/src/main/resources/logback.xml
@@ -0,0 +1,27 @@
+
+
+
+
+ UTF-8
+ [%date{dd MMM yyyy HH:mm:ss}] [%thread] [%yellow(%logger{35}):%highlight(%-5level)] > %msg %n
+
+
+
+
+
+ logs/rose-%d{yyyy-MM-dd}.%i.log
+ 10MB
+ 30
+ 1GB
+
+
+ [%date{dd MMM yyyy HH:mm:ss}] [%thread] [%logger:%level] > %msg%n
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/simplelogger.properties b/src/main/resources/simplelogger.properties
deleted file mode 100644
index c8bc581..0000000
--- a/src/main/resources/simplelogger.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-# SLF4J's SimpleLogger configuration file
-# Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err.
-# Default logging detail level for all instances of SimpleLogger.
-# Must be one of ("trace", "debug", "info", "warn", or "error").
-# If not specified, defaults to "info".
-org.slf4j.simpleLogger.defaultLogLevel=error
\ No newline at end of file