Compare commits

..

15 Commits

Author SHA1 Message Date
7ba5bfbf9e Add shadowJar instead of application, bump the project's version to 1.0.5 2025-01-12 18:07:15 +03:00
e96b02357d Add serverside caching and improve sanitizing. 2025-01-12 17:26:21 +03:00
c2e8927c42 Fix server throwing an ArrayOutOfBoundsException when someone tries to leave 2025-01-12 15:06:43 +03:00
a7ad8770c9 Add a terrible and hacky implementation of scrolling and bump the project's version to 1.0.4. 2025-01-12 04:03:34 +03:00
5c55999771 Revamp network logic. Closes #2 2025-01-12 04:00:34 +03:00
6e1d843f79 Bump the project's version to 1.0.3 2025-01-12 02:06:39 +03:00
57b20f69cd Implement message caching (Only send LOGS packet to server when the LOGS_SIZE returned by server is different from cached one) 2025-01-12 02:05:34 +03:00
bdf7e6bbae bump version to 1.0.2 2025-01-11 19:52:17 +03:00
7a116c6789 Make CRAB work much more smoother (clear screen only after receiving new logs) 2025-01-11 19:51:38 +03:00
6dace63cb0 Fix some vladcode 2025-01-11 16:28:43 +03:00
162c4bdf53 bump version to 1.0.1 2025-01-10 23:58:22 +03:00
488a3f4e54 Merge branch 'master' of https://gitea.bedohswe.eu.org/pixtaded/crab
Merge with local changes
2025-01-10 23:48:07 +03:00
b914364c49 Auto-close DB and fix server closing algorithm 2025-01-10 23:47:19 +03:00
44d11f18d6 Merge pull request 'Add cli arguments, nick support and add data.db to .gitignore.' (#1) from bedohswe/crab:master into master
Reviewed-on: #1
2025-01-10 15:42:36 +00:00
c0acc1a254 Add cli arguments, nick support and add data.db to .gitignore. 2025-01-10 20:38:30 +05:00
10 changed files with 164 additions and 42 deletions

4
.gitignore vendored
View File

@ -1,3 +1,5 @@
data.db
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
@ -39,4 +41,4 @@ bin/
.vscode/
### Mac OS ###
.DS_Store
.DS_Store

View File

@ -1,19 +1,31 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
plugins {
id 'java'
id 'application'
id 'com.gradleup.shadow' version '9.0.0-beta4'
}
group = 'net.pixtaded'
version = '1.0-SNAPSHOT'
version = projectVersion
repositories {
mavenCentral()
}
application {
mainClass = 'net.pixtaded.crab.Main'
}
dependencies {
implementation('org.xerial:sqlite-jdbc:3.47.1.0')
}
tasks.build.dependsOn tasks.shadowJar
tasks.named('jar', Jar) {
manifest {
attributes 'Main-Class': 'net.pixtaded.crab.Main'
}
}
tasks.named('shadowJar', ShadowJar) {
archiveBaseName = 'crab'
archiveClassifier = ''
archiveVersion = projectVersion
}

1
gradle.properties Normal file
View File

@ -0,0 +1 @@
projectVersion=1.0.5

View File

@ -7,9 +7,43 @@ import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
chooseMode(scanner);
scanner.close();
if (args.length == 0) {
Scanner scanner = new Scanner(System.in);
chooseMode(scanner);
scanner.close();
return;
}
switch (args[0]) {
case "help" -> {
System.out.println("crab help - print this message.");
System.out.println("crab client <ip> <port> [nick] - connect to a server.");
System.out.println("crab server <port> - start a server.");
}
case "client" -> {
CrabClient client;
try {
client = new CrabClient(args[1], Integer.parseInt(args[2]), args.length == 4 ? args[3] : null);
} catch (NumberFormatException e) {
System.err.println("Port is not a number.");
return;
}
client.run();
}
case "server" -> {
CrabServer server;
try {
server = new CrabServer(Integer.parseInt(args[1]));
} catch (NumberFormatException e) {
System.err.println("Port is not a number.");
return;
}
server.run();
}
default -> {
System.err.println("Unknown argument");
return;
}
}
}
private static void chooseMode(Scanner scanner) {
@ -28,4 +62,4 @@ public class Main {
}
}
}
}

View File

@ -1,6 +1,7 @@
package net.pixtaded.crab.client;
import net.pixtaded.crab.common.Crab;
import net.pixtaded.crab.common.Logs;
import net.pixtaded.crab.common.Sanitizer;
import java.io.*;
@ -17,16 +18,27 @@ public class CrabClient implements Crab {
private PrintWriter out;
private BufferedReader in;
private int lastBufferLength = 0;
private String nickname;
private Logs cache = new Logs(0, "");
public CrabClient() {
this.nickname = "";
}
public CrabClient(String serverAddress, int port, String nickname) {
this.serverAddress = serverAddress;
this.port = port;
if (nickname != null)
this.nickname = "<" + nickname + "> ";
else
this.nickname = "";
}
@Override
public void run() {
try {
setup();
connect();
if (this.serverAddress == null)
setup();
communicate();
} catch (IOException e) {
System.err.println("Error connecting to the server: " + e.getMessage());
@ -54,6 +66,11 @@ public class CrabClient implements Crab {
System.out.println("Enter a correct port number: ");
}
}
System.out.print("Enter your nickname (leave empty for no nickname): ");
nickname = scanner.nextLine();
if (!nickname.isEmpty())
nickname = "<" + nickname + "> ";
}
private void connect() throws IOException {
@ -66,9 +83,7 @@ public class CrabClient implements Crab {
private void communicate() throws IOException {
Scanner scanner = new Scanner(System.in);
String message;
while (true) {
System.out.print("\033[H\033[2J");
getLogs();
System.out.print("Enter a message (or type '/exit' to exit): ");
message = scanner.nextLine();
@ -77,17 +92,19 @@ public class CrabClient implements Crab {
break;
}
if (!message.isEmpty()) sendPacket(MESSAGE, message);
if (!message.isEmpty()) sendPacket(MESSAGE, this.nickname + message);
}
}
private void sendPacket(byte PID, String argument) throws IOException {
connect();
String formattedMessage = String.valueOf((char) PID) + argument + "\n";
out.print(formattedMessage);
out.flush();
receiveResponse(PID);
closeConnection();
}
private void receiveResponse(byte PID) throws IOException {
@ -97,13 +114,15 @@ public class CrabClient implements Crab {
int response = in.read(buffer);
lastBufferLength = Integer.parseInt(new String(buffer).trim());
} case LOGS -> {
byte[] bytes = socket.getInputStream().readNBytes(lastBufferLength);
System.out.print(Sanitizer.sanitizeString(new String(bytes, StandardCharsets.UTF_8), false));
if (cache.sizeInBytes() != lastBufferLength) {
byte[] bytes = socket.getInputStream().readNBytes(lastBufferLength);
cache = new Logs(lastBufferLength, new String(bytes, StandardCharsets.UTF_8));
}
clearScreen();
System.out.print(Sanitizer.sanitizeString(cache.content(), false));
} default -> {
}
}
closeConnection();
connect();
}
private void closeConnection() {
@ -120,4 +139,9 @@ public class CrabClient implements Crab {
sendPacket(LOGS_SIZE, "");
sendPacket(LOGS, "");
}
}
private void clearScreen() {
System.out.print("\033[999999S\033[H\033[2J");
}
}

View File

@ -0,0 +1,4 @@
package net.pixtaded.crab.common;
public record Logs(int sizeInBytes, String content) {
}

View File

@ -2,11 +2,15 @@ package net.pixtaded.crab.common;
public class Sanitizer {
public static String sanitizeString(String s, boolean sanitizeNewlines) {
String sanitized = s.replaceAll("\033", "");
String sanitized = s.replaceAll("[\010\015\033]", "");
if (sanitizeNewlines) {
sanitized = sanitized.replaceAll("\n", "\\\\n");
if (!s.endsWith("\n")) sanitized += '\n';
}
return sanitized;
}
public static String formatMessage(long timeMillis, String address, String content) {
return String.format("[%td.%1$tm.%1$tY %1$tR] {%s} %s", timeMillis, address, content);
}
}

View File

@ -1,5 +1,6 @@
package net.pixtaded.crab.server;
import net.pixtaded.crab.common.Crab;
import net.pixtaded.crab.common.Logs;
import java.io.IOException;
import java.net.ServerSocket;
@ -12,16 +13,24 @@ public class CrabServer implements Crab {
private Socket socket;
private boolean isStopped = false;
private int port;
private Database db;
private final Database db;
public Logs cache = new Logs(0, "");
public CrabServer() {
this.db = new Database("data.db");
}
public CrabServer(int port) {
this.db = new Database("data.db");
this.port = port;
}
@Override
public void run() {
try {
setup();
if (this.port == 0)
setup();
this.cache = getDb().getLogs();
listen();
} catch (IOException e) {
if (!isStopped) System.err.println("Error starting server: " + e.getMessage());
@ -60,9 +69,9 @@ public class CrabServer implements Crab {
}
}
public void stop() {
public synchronized void stop() {
isStopped = true;
try {
isStopped = true;
if (socket != null) socket.close();
if (serverSocket != null) serverSocket.close();
} catch (IOException e) {

View File

@ -1,15 +1,14 @@
package net.pixtaded.crab.server;
import net.pixtaded.crab.common.Logs;
import net.pixtaded.crab.common.Sanitizer;
import java.sql.*;
import java.util.Date;
import java.util.Locale;
public class Database {
public class Database implements AutoCloseable {
private Connection connection;
private String logs = "";
public Database(String dbName) {
try {
@ -50,22 +49,23 @@ public class Database {
}
}
public String getLogs() {
public Logs getLogs() {
StringBuilder s = new StringBuilder();
try (ResultSet rs = query("SELECT time, address, msg FROM messages")) {
while (rs.next()) {
s.append(String.format("[%td.%1$tm.%1$tY %1$tR] {%s} %s", rs.getLong(1), rs.getString(2), rs.getString(3)));
s.append(Sanitizer.formatMessage(rs.getLong(1), rs.getString(2), rs.getString(3)));
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
logs = s.toString();
return logs;
String logsString = s.toString();
return new Logs(logsString.isEmpty() ? 0 : logsString.getBytes().length, logsString);
}
public int getLogSize() {
if (logs.isEmpty()) return 0;
else return logs.getBytes().length;
@Override
public void close() throws SQLException {
if (connection != null && !connection.isClosed()) {
connection.close();
}
}
}

View File

@ -1,5 +1,8 @@
package net.pixtaded.crab.server;
import net.pixtaded.crab.common.Logs;
import net.pixtaded.crab.common.Sanitizer;
import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
@ -30,18 +33,30 @@ public class ServerThread implements Runnable {
public void run() {
try {
byte[] PID = input.readNBytes(1);
if (PID.length == 0) {
socket.close();
return;
}
switch (PID[0]) {
case MESSAGE -> {
server.getDb().logMessage(new Date(), socket.getInetAddress().getHostAddress(), new String(input.readNBytes(4096), StandardCharsets.UTF_8).trim());
socket.close();
Date date = new Date();
String msg = new String(input.readNBytes(4096), StandardCharsets.UTF_8).trim();
String address = socket.getInetAddress().getHostAddress();
String s = Sanitizer.sanitizeString(msg, true);
String newContent = server.cache.content() + Sanitizer.formatMessage(date.getTime(), address, s);
server.cache = new Logs(newContent.getBytes().length, newContent);
new Thread(new LogDBThread(date, address, msg)).start();
} case LOGS -> {
respond(server.getDb().getLogs());
respond(server.cache.content());
} case LOGS_SIZE -> {
respond(String.valueOf(server.getDb().getLogSize()));
respond(String.valueOf(server.cache.sizeInBytes()));
} default -> {
System.out.println("PID not implemented: " + PID[0]);
}
}
socket.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
@ -50,10 +65,27 @@ public class ServerThread implements Runnable {
private void respond(byte[] data) throws IOException {
socket.getOutputStream().write(data);
socket.getOutputStream().flush();
socket.close();
}
private void respond(String utf8) throws IOException {
respond(utf8.getBytes(StandardCharsets.UTF_8));
}
private class LogDBThread implements Runnable {
Date date;
String msg;
String address;
public LogDBThread(Date date, String address, String msg) {
this.date = date;
this.msg = msg;
this.address = address;
}
@Override
public void run() {
server.getDb().logMessage(date, address, msg);
}
}
}