diff --git a/src/main/java/BsK/server/Server.java b/src/main/java/BsK/server/Server.java index aab5115..14671d9 100644 --- a/src/main/java/BsK/server/Server.java +++ b/src/main/java/BsK/server/Server.java @@ -2,8 +2,6 @@ package BsK.server; import BsK.server.network.handler.ServerHandler; import BsK.server.service.GoogleDriveServiceOAuth; -import com.zaxxer.hikari.HikariConfig; -import com.zaxxer.hikari.HikariDataSource; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; @@ -25,32 +23,32 @@ import java.io.IOException; import java.io.InputStream; import java.net.InetSocketAddress; import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.sql.Connection; +import java.sql.DriverManager; import java.sql.SQLException; +import java.sql.Statement; import java.util.Properties; import java.util.concurrent.TimeUnit; import lombok.extern.slf4j.Slf4j; @Slf4j public class Server { - private static int PORT; - - // --- REFACTORED DATABASE CONNECTION --- - // The single, static connection pool (DataSource). This is thread-safe. - private static HikariDataSource dataSource; - - // These static fields are still okay as they are configured once at startup. - public static GoogleDriveServiceOAuth googleDriveService; - private static boolean googleDriveEnabled = true; - private static String googleDriveRootFolderName = "BSK_Clinic_Patient_Files"; - public static String imageDbPath = "img_db"; - public static String checkupMediaBaseDir = "src/main/resources/image/checkup_media"; + private static int PORT; + public static Connection connection; + public static GoogleDriveServiceOAuth googleDriveService; + private static boolean googleDriveEnabled = true; // Can be configured + private static String googleDriveRootFolderName = "BSK_Clinic_Patient_Files"; // Default + public static String imageDbPath = "img_db"; // Default + public static String checkupMediaBaseDir = "src/main/resources/image/checkup_media"; // Default - // Static initializer block to set up the server configuration and connection pool static { try { - // --- Load configuration --- + // Load configuration Properties props = new Properties(); + + // First try to load from external config file File externalConfig = new File("config/config.properties"); if (externalConfig.exists()) { try (var input = Files.newInputStream(externalConfig.toPath())) { @@ -58,6 +56,7 @@ public class Server { log.info("Loaded configuration from external file: {}", externalConfig.getAbsolutePath()); } } else { + // Fall back to internal config try (InputStream input = Server.class.getClassLoader().getResourceAsStream("config.properties")) { if (input == null) { log.error("Unable to find config.properties"); @@ -67,108 +66,67 @@ public class Server { log.info("Loaded configuration from internal resources"); } } + + // Get port configuration + String portStr = props.getProperty("server.port"); + String addressStr = props.getProperty("server.address"); + + // Log address configuration + if (addressStr != null) { + log.info("Server address configured as: {}", addressStr); + } else { + log.info("No server address configured, clients will need to use the correct address to connect"); + } + + // Get port configuration + if (portStr != null) { + PORT = Integer.parseInt(portStr); + log.info("Using configured port: {}", PORT); + } else { + PORT = 1999; + log.info("Using default port: {}", PORT); + } - PORT = Integer.parseInt(props.getProperty("server.port", "1999")); - log.info("Using port: {}", PORT); - - // Load other properties - googleDriveEnabled = Boolean.parseBoolean(props.getProperty("google.drive.enabled", "true")); - googleDriveRootFolderName = props.getProperty("google.drive.root.folder", "BSK_Clinic_Patient_Files").trim(); - imageDbPath = props.getProperty("storage.image_db_path", "img_db").trim(); - checkupMediaBaseDir = props.getProperty("storage.checkup_media_base_dir", "src/main/resources/image/checkup_media").trim(); - log.info("Google Drive enabled: {}", googleDriveEnabled); + // Get Google Drive configuration + String driveEnabledStr = props.getProperty("google.drive.enabled"); + if (driveEnabledStr != null) { + googleDriveEnabled = Boolean.parseBoolean(driveEnabledStr); + } + + String driveRootFolderStr = props.getProperty("google.drive.root.folder"); + if (driveRootFolderStr != null && !driveRootFolderStr.trim().isEmpty()) { + googleDriveRootFolderName = driveRootFolderStr.trim(); + } + + log.info("Google Drive integration enabled: {}", googleDriveEnabled); log.info("Google Drive root folder: {}", googleDriveRootFolderName); + + // Get Storage paths configuration + String imageDbPathStr = props.getProperty("storage.image_db_path"); + if (imageDbPathStr != null && !imageDbPathStr.trim().isEmpty()) { + imageDbPath = imageDbPathStr.trim(); + } + + String checkupMediaBaseDirStr = props.getProperty("storage.checkup_media_base_dir"); + if (checkupMediaBaseDirStr != null && !checkupMediaBaseDirStr.trim().isEmpty()) { + checkupMediaBaseDir = checkupMediaBaseDirStr.trim(); + } + log.info("Image DB path: {}", imageDbPath); log.info("Checkup media base dir: {}", checkupMediaBaseDir); - - // --- DATABASE POOL INITIALIZATION --- + // Directly use the provided database path String dbPath = "src/main/resources/database/BSK.db"; - - // 1. Configure HikariCP - HikariConfig config = new HikariConfig(); - config.setJdbcUrl("jdbc:sqlite:" + dbPath); - config.setConnectionTestQuery("SELECT 1"); - config.setMaximumPoolSize(10); // Max 10 concurrent connections, perfect for your use case - config.setPoolName("BsK-DB-Pool"); - config.addDataSourceProperty("cachePrepStmts", "true"); - config.addDataSourceProperty("prepStmtCacheSize", "250"); - config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); - - // 2. Create the DataSource (the pool itself) - dataSource = new HikariDataSource(config); - log.info("✅ Database connection pool initialized successfully for {}", dbPath); + connection = DriverManager.getConnection("jdbc:sqlite:" + dbPath); // Initialize Google Drive service if enabled initializeGoogleDriveService(); } catch (Exception e) { - log.error("❌ Failed to initialize server", e); throw new RuntimeException("Failed to initialize server", e); } } - /** - * This is the new, safe way for handlers to get a database connection. - * @return A database connection from the pool. - * @throws SQLException if a connection cannot be obtained. - */ - public static Connection getConnection() throws SQLException { - return dataSource.getConnection(); - } - - public static void main(String[] args) throws InterruptedException { - log.info("\n____ _____ _ __\n| _ \\ / ____| |/ \n| |_) | (___ | ' / \n| _ < \\___ \\| <\n| |_) |____) | . \\\n|____/|_____/|_|\\_\n"); - ServerDashboard dashboard = ServerDashboard.getInstance(); - dashboard.setVisible(true); - dashboard.updateStatus("Starting...", Color.ORANGE); - dashboard.updatePort(PORT); - dashboard.updateGoogleDriveStatus(isGoogleDriveConnected(), isGoogleDriveConnected() ? "Connected" : "Disconnected"); - - EventLoopGroup parentGroup = Epoll.isAvailable() ? new EpollEventLoopGroup() : new NioEventLoopGroup(); - EventLoopGroup childGroup = Epoll.isAvailable() ? new EpollEventLoopGroup() : new NioEventLoopGroup(); - try { - ServerBootstrap bootstrap = new ServerBootstrap() - .group(parentGroup, childGroup) - .channel(Epoll.isAvailable() ? EpollServerSocketChannel.class : NioServerSocketChannel.class) - .localAddress(new InetSocketAddress(PORT)) - .childHandler(new ChannelInitializer() { - @Override - protected void initChannel(SocketChannel ch) throws Exception { - ch.pipeline() - .addLast(new IdleStateHandler(60 * 30, 0, 0, TimeUnit.SECONDS)) - .addLast(new HttpServerCodec()) - .addLast(new ChunkedWriteHandler()) - .addLast(new HttpObjectAggregator(50 * 1024 * 1024)) - .addLast(new WebSocketServerProtocolHandler("/", null, true, 50 * 1024 * 1024)) - .addLast(new ServerHandler()); // A new handler for each client - } - }); - - ChannelFuture f = bootstrap.bind().sync(); - log.info("Server started on port {}", PORT); - dashboard.updateStatus("Running", Color.GREEN); - dashboard.addLog("Server started successfully on port " + PORT); - - f.channel().closeFuture().sync(); - } catch (Exception e) { - dashboard.updateStatus("Error", Color.RED); - dashboard.addLog("Server error: " + e.getMessage()); - log.error("Server crashed", e); - } finally { - dashboard.updateStatus("Shutting down", Color.ORANGE); - dashboard.addLog("Server shutting down..."); - parentGroup.shutdownGracefully(); - childGroup.shutdownGracefully(); - if (dataSource != null) { - dataSource.close(); // Close the connection pool on shutdown - log.info("Database connection pool closed."); - } - } - } - - // --- Google Drive and other helper methods --- - private static void initializeGoogleDriveService() { if (!googleDriveEnabled) { log.info("Google Drive integration is disabled"); @@ -181,6 +139,7 @@ public class Server { googleDriveService = new GoogleDriveServiceOAuth(googleDriveRootFolderName); log.info("✅ Google Drive OAuth service initialized successfully"); + // Update dashboard if it exists if (ServerDashboard.getInstance() != null) { ServerDashboard.getInstance().updateGoogleDriveStatus(true, "Connected"); } @@ -188,31 +147,48 @@ public class Server { } catch (Exception e) { log.error("❌ Failed to initialize Google Drive OAuth service: {}", e.getMessage()); log.info("💡 Server will continue without Google Drive integration"); + log.info("🔧 To fix this, ensure google-oauth-credentials.json is properly configured"); googleDriveService = null; + // Update dashboard if it exists if (ServerDashboard.getInstance() != null) { ServerDashboard.getInstance().updateGoogleDriveStatus(false, "Failed: " + e.getMessage()); } } } + /** + * Manually retry Google Drive connection + */ public static void retryGoogleDriveConnection() { log.info("🔄 Retrying Google Drive connection..."); initializeGoogleDriveService(); } + /** + * Check if Google Drive is connected + */ public static boolean isGoogleDriveConnected() { return googleDriveService != null; } + /** + * Get Google Drive service instance + */ public static GoogleDriveServiceOAuth getGoogleDriveService() { return googleDriveService; } + /** + * Get the current Google Drive root folder name + */ public static String getGoogleDriveRootFolderName() { return googleDriveRootFolderName; } + /** + * Update Google Drive root folder name and reinitialize service + */ public static void updateGoogleDriveRootFolder(String newRootFolderName) throws Exception { if (newRootFolderName == null || newRootFolderName.trim().isEmpty()) { throw new IllegalArgumentException("Root folder name cannot be empty"); @@ -221,17 +197,23 @@ public class Server { googleDriveRootFolderName = newRootFolderName.trim(); log.info("🔄 Updating Google Drive root folder to: {}", googleDriveRootFolderName); + // Save to configuration file saveGoogleDriveRootFolderToConfig(googleDriveRootFolderName); + // Reinitialize Google Drive service with new root folder if (googleDriveEnabled) { initializeGoogleDriveService(); } } + /** + * Save Google Drive root folder name to config file + */ private static void saveGoogleDriveRootFolderToConfig(String rootFolderName) { try { Properties props = new Properties(); + // Load existing properties File externalConfig = new File("config/config.properties"); if (externalConfig.exists()) { try (var input = Files.newInputStream(externalConfig.toPath())) { @@ -245,13 +227,16 @@ public class Server { } } + // Update the root folder property props.setProperty("google.drive.root.folder", rootFolderName); + // Create config directory if it doesn't exist File configDir = new File("config"); if (!configDir.exists()) { configDir.mkdirs(); } + // Save to external config file try (var output = Files.newOutputStream(externalConfig.toPath())) { props.store(output, "BSK Server Configuration - Updated by Dashboard"); log.info("💾 Saved Google Drive root folder setting to config file"); @@ -262,6 +247,96 @@ public class Server { } } + private static String extractDatabaseFile() throws IOException { + throw new UnsupportedOperationException("extractDatabaseFile is not used anymore. Database is read directly from the specified path."); + } + + public static Statement statement; + + static { + try { + statement = connection.createStatement(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + public Server() throws SQLException { + } + + public static void main(String[] args) throws InterruptedException, SQLException { + log.info("\n____ _____ _ __\n| _ \\ / ____| |/\n| |_) | (___ | ' /\n| _ < \\___ \\| <\n| |_) |____) | . \\\n|____/|_____/|_|\\_\n"); + // Initialize and show server dashboard + ServerDashboard dashboard = ServerDashboard.getInstance(); + dashboard.setVisible(true); + dashboard.updateStatus("Starting...", Color.ORANGE); + dashboard.updatePort(PORT); + + // Update Google Drive status on dashboard + dashboard.updateGoogleDriveStatus(isGoogleDriveConnected(), + isGoogleDriveConnected() ? "Connected" : "Disconnected"); + + EventLoopGroup parentGroup = + Epoll.isAvailable() ? new EpollEventLoopGroup() : new NioEventLoopGroup(); + EventLoopGroup childGroup = + Epoll.isAvailable() ? new EpollEventLoopGroup() : new NioEventLoopGroup(); + try { + ServerBootstrap bootstrap = + new ServerBootstrap() + .group(parentGroup, childGroup) + .channel( + Epoll.isAvailable() + ? EpollServerSocketChannel.class + : NioServerSocketChannel.class) + .localAddress(new InetSocketAddress(PORT)) + .childHandler( + new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) throws Exception { + ch.pipeline() + .addLast(new IdleStateHandler(60 * 30, 0, 0, TimeUnit.SECONDS)) + .addLast(new HttpServerCodec()) + .addLast(new ChunkedWriteHandler()) + .addLast(new HttpObjectAggregator(50 * 1024 * 1024)) + .addLast(new WebSocketServerProtocolHandler("/", null, true, 50 * 1024 * 1024)) + .addLast(new ServerHandler()); + } + }); + + ChannelFuture f = bootstrap.bind().sync(); + + log.info("Server started on port {}", PORT); + log.info("To connect, clients should use:"); + log.info(" - If on same machine: ws://localhost:{} or ws://127.0.0.1:{}", PORT, PORT); + log.info(" - If on network: ws://:{}", PORT); + + // Update dashboard status to running + dashboard.updateStatus("Running", Color.GREEN); + dashboard.addLog("Server started successfully on port " + PORT); + if (isGoogleDriveConnected()) { + dashboard.addLog("Google Drive integration: Connected ✅"); + } else { + dashboard.addLog("Google Drive integration: Disconnected ❌"); + } + + f.channel().closeFuture().sync(); + } catch (Exception e) { + dashboard.updateStatus("Error", Color.RED); + dashboard.addLog("Server error: " + e.getMessage()); + throw e; + } finally { + dashboard.updateStatus("Shutting down", Color.ORANGE); + dashboard.addLog("Server shutting down..."); + parentGroup.shutdownGracefully(); + childGroup.shutdownGracefully(); + } + } + + /** + * Initiates the backup of the main database file to Google Drive. + * This is intended to be called from the ServerDashboard. + * @throws IOException if the file cannot be found or the upload fails. + */ public static void backupDatabaseToDrive() throws IOException { ServerDashboard dashboard = ServerDashboard.getInstance(); @@ -270,6 +345,7 @@ public class Server { throw new IOException("Google Drive service is not available."); } + // Define the path to the database String dbPath = "src/main/resources/database/BSK.db"; java.io.File dbFile = new java.io.File(dbPath); @@ -278,6 +354,7 @@ public class Server { throw new IOException("Database file not found: " + dbPath); } + // Call the service to perform the backup googleDriveService.backupDatabaseFile(dbFile); - } -} + } +} \ No newline at end of file diff --git a/src/main/java/BsK/server/network/entity/User.java b/src/main/java/BsK/server/network/entity/User.java index 7f55646..81e3a3d 100644 --- a/src/main/java/BsK/server/network/entity/User.java +++ b/src/main/java/BsK/server/network/entity/User.java @@ -58,4 +58,4 @@ public class User { SessionManager.updateUserRole(channel.id().asLongText(), this.role.toString(), this.userId); log.info("User session {} has been de-authenticated.", sessionId); } -} +} \ No newline at end of file diff --git a/src/main/java/BsK/server/network/handler/ServerHandler.java b/src/main/java/BsK/server/network/handler/ServerHandler.java index 604466b..aca7547 100644 --- a/src/main/java/BsK/server/network/handler/ServerHandler.java +++ b/src/main/java/BsK/server/network/handler/ServerHandler.java @@ -596,7 +596,7 @@ public class ServerHandler extends SimpleChannelInboundHandler