mirror of
https://github.com/gmbrax/MediaManager-core.git
synced 2026-11-09 22:10:17 +00:00
Compare commits
14
Commits
master
...
3998b18671
+9
-1
@@ -39,8 +39,16 @@ build/
|
||||
.DS_Store
|
||||
|
||||
### Application Specific ###
|
||||
application-local.properties
|
||||
config-local.properties
|
||||
*.db
|
||||
pipes/
|
||||
*.log
|
||||
/.idea/
|
||||
|
||||
Configuration files with sensitive credentials
|
||||
# These files contain database passwords and API keys and should never be committed
|
||||
src/main/resources/config.properties
|
||||
src/main/resources/application.properties
|
||||
|
||||
# Allow example configuration files to be committed
|
||||
!src/main/resources/*.properties.example
|
||||
@@ -34,7 +34,7 @@ MediaManager-Core/
|
||||
│ │ ├── util/ # Utility classes
|
||||
│ │ └── MediaManagerApplication.java
|
||||
│ └── resources/
|
||||
│ ├── application.properties # App configuration
|
||||
│ ├── config.properties # App configuration
|
||||
│ ├── log4j2.xml # Logging configuration
|
||||
│ └── META-INF/
|
||||
│ └── persistence.xml # JPA configuration
|
||||
@@ -55,7 +55,7 @@ GRANT ALL PRIVILEGES ON DATABASE mediamanager TO mediamanager;
|
||||
|
||||
### 2. Configuration
|
||||
|
||||
Edit `src/main/resources/application.properties` and update the database credentials:
|
||||
Edit `src/main/resources/config.properties` and update the database credentials:
|
||||
|
||||
```properties
|
||||
db.url=jdbc:postgresql://localhost:5432/mediamanager
|
||||
@@ -90,7 +90,7 @@ The application creates named pipes for inter-process communication. Default con
|
||||
- Pipe name: `mediamanager-pipe`
|
||||
- Buffer size: 8192 bytes
|
||||
|
||||
You can modify these settings in `application.properties`.
|
||||
You can modify these settings in `config.properties`.
|
||||
|
||||
## Logging
|
||||
|
||||
@@ -116,8 +116,8 @@ mvn test
|
||||
|
||||
## Dependencies
|
||||
|
||||
- PostgreSQL Driver: 42.7.3
|
||||
- Hibernate ORM: 6.4.4.Final
|
||||
- PostgreSQL Driver: 42.7.5
|
||||
- Hibernate ORM: 7.1.7.Final
|
||||
- HikariCP: 5.1.0
|
||||
- Log4j 2: 2.23.1
|
||||
- Jackson: 2.16.1
|
||||
|
||||
@@ -96,6 +96,28 @@
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.2.5</version>
|
||||
</plugin>
|
||||
|
||||
<!-- Maven Shade Plugin - Creates executable JAR with all dependencies -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.5.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>com.mediamanager.MediaManagerApplication</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.mediamanager;
|
||||
|
||||
import com.mediamanager.service.ipc.IPCManager;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
@@ -7,9 +8,13 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
import com.mediamanager.service.database.DatabaseManager;
|
||||
|
||||
public class MediaManagerApplication {
|
||||
private static final Logger logger = LogManager.getLogger(MediaManagerApplication.class);
|
||||
private static Properties config;
|
||||
private static DatabaseManager databaseManager;
|
||||
private static IPCManager ipcManager;
|
||||
|
||||
public static void main(String[] args) {
|
||||
logger.info("Starting MediaManager Core Application...");
|
||||
@@ -17,19 +22,59 @@ public class MediaManagerApplication {
|
||||
try {
|
||||
// Load configuration
|
||||
loadConfiguration();
|
||||
databaseManager = new DatabaseManager(config);
|
||||
databaseManager.init();
|
||||
|
||||
ipcManager = new IPCManager(config);
|
||||
ipcManager.init();
|
||||
|
||||
// TODO: Initialize database connection
|
||||
// TODO: Initialize IPC server with named pipes
|
||||
// TODO: Start application services
|
||||
|
||||
logger.info("MediaManager Core started successfully");
|
||||
logger.info("IPC Pipe: {}", config.getProperty("ipc.pipe.path") + "/" + config.getProperty("ipc.pipe.name"));
|
||||
logger.info("IPC Socket: {}", ipcManager.getSocketPath().toAbsolutePath().toString());
|
||||
|
||||
// Keep application running
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
|
||||
logger.info("Shutting down MediaManager Core...");
|
||||
// TODO: Cleanup resources
|
||||
|
||||
|
||||
if (databaseManager != null) {
|
||||
databaseManager.close();
|
||||
}
|
||||
|
||||
if (ipcManager != null) {
|
||||
try {
|
||||
ipcManager.close();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
logger.info("MediaManager Core shutdown successfully");
|
||||
logger.info("Goodbye!");
|
||||
|
||||
|
||||
// Give Log4j2 time to write all pending messages before shutting down
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
// Now shutdown Log4j2
|
||||
org.apache.logging.log4j.LogManager.shutdown();
|
||||
}));
|
||||
logger.info("Application is running");
|
||||
logger.info("Press Ctrl+C to exit");
|
||||
Thread.currentThread().join();
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
|
||||
logger.info("Application interrupted, initiating shutdown...");
|
||||
|
||||
Thread.currentThread().interrupt();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to start MediaManager Core", e);
|
||||
@@ -39,10 +84,9 @@ public class MediaManagerApplication {
|
||||
|
||||
private static void loadConfiguration() throws IOException {
|
||||
config = new Properties();
|
||||
try (InputStream input = MediaManagerApplication.class.getClassLoader()
|
||||
.getResourceAsStream("application.properties")) {
|
||||
try (InputStream input = MediaManagerApplication.class.getClassLoader().getResourceAsStream("config.properties")) {
|
||||
if (input == null) {
|
||||
throw new IOException("Unable to find application.properties");
|
||||
throw new IOException("Unable to find config.properties");
|
||||
}
|
||||
config.load(input);
|
||||
logger.info("Configuration loaded successfully");
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.mediamanager.service.database;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.Properties;
|
||||
|
||||
|
||||
public class DatabaseManager {
|
||||
private final Properties configuration;
|
||||
private Connection connection;
|
||||
private static final Logger logger = LogManager.getLogger(DatabaseManager.class);
|
||||
public DatabaseManager(Properties config) {
|
||||
this.configuration = config;
|
||||
logger.debug("DatabaseManager created with configuration:");
|
||||
|
||||
}
|
||||
|
||||
public void init() throws Exception {
|
||||
logger.info("Initializing database connection...");
|
||||
validateConfiguration();
|
||||
|
||||
String databaseType = configuration.getProperty("database.type");
|
||||
String databaseUrl = configuration.getProperty("database.url");
|
||||
String databaseUsername = configuration.getProperty("database.username");
|
||||
String databasePassword = configuration.getProperty("database.password");
|
||||
String databasePort = configuration.getProperty("database.port");
|
||||
String databaseName = configuration.getProperty("database.name");
|
||||
|
||||
|
||||
String connectionString = String.format("jdbc:postgresql://%s:%s/%s", databaseUrl, databasePort, databaseName);
|
||||
logger.debug("Attempting to connect to: {}", connectionString);
|
||||
try {
|
||||
connection = DriverManager.getConnection(connectionString, databaseUsername, databasePassword);
|
||||
logger.info("Database connection established successfully");
|
||||
|
||||
performSanityChecks();
|
||||
logger.info("Database sanity checks passed successfully");
|
||||
|
||||
|
||||
} catch (SQLException e) {
|
||||
logger.error("Failed to connect to database", e);
|
||||
throw new Exception("Database connection failed: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
private void performSanityChecks() throws SQLException {
|
||||
logger.debug("Performing sanity checks...");
|
||||
try (Statement stmt = connection.createStatement()) {
|
||||
stmt.execute("SELECT 1");
|
||||
|
||||
}
|
||||
String databaseProductName = connection.getMetaData().getDatabaseProductName();
|
||||
String databaseProductVersion = connection.getMetaData().getDatabaseProductVersion();
|
||||
logger.info("Connected to database: {} v{}", databaseProductName, databaseProductVersion);
|
||||
}
|
||||
private void validateConfiguration() throws Exception {
|
||||
String[] requiredProperties = {
|
||||
"database.url",
|
||||
"database.username",
|
||||
"database.password",
|
||||
"database.port",
|
||||
"database.name"
|
||||
};
|
||||
|
||||
for (String property : requiredProperties) {
|
||||
if (configuration.getProperty(property) == null ||
|
||||
configuration.getProperty(property).trim().isEmpty()) {
|
||||
throw new Exception("Required database configuration missing: " + property);
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug("Database configuration validated successfully");
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Connection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (connection != null) {
|
||||
try {
|
||||
logger.info("Closing database connection...");
|
||||
connection.close();
|
||||
logger.info("Database connection closed successfully");
|
||||
} catch (SQLException e) {
|
||||
logger.error("Error closing database connection: {}", e.getMessage());
|
||||
}
|
||||
} else {
|
||||
logger.debug("No database connection to close");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
package com.mediamanager.service.ipc;
|
||||
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.StandardProtocolFamily;
|
||||
import java.net.UnixDomainSocketAddress;
|
||||
import java.nio.channels.ServerSocketChannel;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.PosixFilePermission;
|
||||
import java.nio.file.attribute.PosixFilePermissions;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class IPCManager {
|
||||
private final Properties configuration;
|
||||
private static final Logger logger = LogManager.getLogger(IPCManager.class);
|
||||
private Path socketPath;
|
||||
private UnixDomainSocketAddress socketAddress;
|
||||
private ServerSocketChannel serverChannel;
|
||||
private ExecutorService clientThreadPool;
|
||||
private final AtomicBoolean running = new AtomicBoolean(false);
|
||||
private final ConcurrentHashMap<Integer, ClientHandler> activeClients = new ConcurrentHashMap<>();
|
||||
private final AtomicInteger clientIdCounter = new AtomicInteger(0);
|
||||
|
||||
|
||||
public IPCManager(Properties config){
|
||||
configuration = config;
|
||||
logger.debug("IPCManager created with configuration:");
|
||||
|
||||
|
||||
}
|
||||
public void init() throws Exception {
|
||||
logger.info("Initializing IPC connection...");
|
||||
validateConfiguration();
|
||||
socketPath = Path.of(configuration.getProperty("ipc.socket.path")).resolve("mediamanager.sock");
|
||||
|
||||
if (Files.exists(socketPath)) {
|
||||
logger.warn("Socket file already exists at: {}", socketPath);
|
||||
logger.info("Deleting existing socket...");
|
||||
Files.deleteIfExists(socketPath);
|
||||
}
|
||||
|
||||
Path parentDir = socketPath.getParent();
|
||||
if (parentDir != null && !Files.exists(parentDir)) {
|
||||
logger.info("Creating parent directory for socket: {}", parentDir);
|
||||
Files.createDirectories(parentDir);
|
||||
}
|
||||
|
||||
try {
|
||||
socketAddress = UnixDomainSocketAddress.of(socketPath);
|
||||
logger.debug("Socket address created");
|
||||
|
||||
serverChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX);
|
||||
serverChannel.bind(socketAddress);
|
||||
logger.info("Server bound to socket - file created at: {}", socketPath);
|
||||
|
||||
// ESTA É A MUDANÇA CRÍTICA
|
||||
// Configura o canal para modo não-bloqueante
|
||||
// Isso faz accept() retornar null imediatamente se não houver cliente
|
||||
// ao invés de bloquear esperando indefinidamente
|
||||
serverChannel.configureBlocking(false);
|
||||
logger.debug("Server channel configured for non-blocking mode");
|
||||
|
||||
Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rw-------");
|
||||
Files.setPosixFilePermissions(socketPath, perms);
|
||||
logger.debug("Socket permissions set to: rw-------");
|
||||
|
||||
clientThreadPool = Executors.newCachedThreadPool(runnable -> {
|
||||
Thread thread = new Thread(runnable);
|
||||
thread.setName("IPC-Client-Handler-" + thread.getId());
|
||||
thread.setDaemon(true);
|
||||
return thread;
|
||||
});
|
||||
logger.debug("Client thread pool created");
|
||||
|
||||
running.set(true);
|
||||
|
||||
Thread serverThread = new Thread(this::acceptConnectionsLoop, "IPC-Server-Accept-Thread");
|
||||
serverThread.setDaemon(true);
|
||||
serverThread.start();
|
||||
logger.info("Server thread started - accepting connections");
|
||||
|
||||
logger.info("IPC server initialized successfully on {}", socketPath.toAbsolutePath());
|
||||
|
||||
} catch (IOException e) {
|
||||
logger.error("Failed to initialize IPC server: {}", e.getMessage());
|
||||
throw new Exception("Failed to initialize IPC server: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
private void validateConfiguration() throws Exception {
|
||||
String[] requiredProperties = {
|
||||
"ipc.socket.path"
|
||||
};
|
||||
for (String property : requiredProperties) {
|
||||
if (configuration.getProperty(property) == null) {
|
||||
throw new Exception("Missing required configuration property: " + property);
|
||||
}
|
||||
}
|
||||
logger.debug("IPC configuration validated successfully");
|
||||
}
|
||||
|
||||
public Path getSocketPath(){
|
||||
return socketPath;
|
||||
}
|
||||
|
||||
public void close() throws Exception {
|
||||
if(!running.get()){
|
||||
logger.warn("IPC connection is already closed");
|
||||
}
|
||||
|
||||
|
||||
logger.info("Closing IPC connection...");
|
||||
|
||||
running.set(false);
|
||||
if (serverChannel != null && serverChannel.isOpen()) {
|
||||
serverChannel.close();
|
||||
logger.debug("Server channel closed");
|
||||
}
|
||||
|
||||
if (clientThreadPool != null) {
|
||||
clientThreadPool.shutdown();
|
||||
try {
|
||||
if (!clientThreadPool.awaitTermination(30, TimeUnit.SECONDS)) {
|
||||
logger.warn("Some client handlers did not finish in time, forcing shutdown");
|
||||
clientThreadPool.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
logger.error("Interrupted while waiting for client handlers", e);
|
||||
clientThreadPool.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
if (socketPath != null && Files.exists(socketPath)) {
|
||||
Files.deleteIfExists(socketPath);
|
||||
logger.info("Socket file deleted successfully");
|
||||
}
|
||||
|
||||
logger.info("IPC server closed successfully");
|
||||
}
|
||||
|
||||
private void acceptConnectionsLoop() {
|
||||
logger.info("Preparing to accept connections...");
|
||||
|
||||
while (running.get()) {
|
||||
try {
|
||||
// Em modo não-bloqueante, accept() retorna imediatamente
|
||||
// Se há um cliente esperando, retorna o SocketChannel
|
||||
// Se não há cliente, retorna null
|
||||
SocketChannel clientChannel = serverChannel.accept();
|
||||
|
||||
if (clientChannel != null) {
|
||||
// Um cliente realmente se conectou!
|
||||
int clientId = clientIdCounter.incrementAndGet();
|
||||
logger.info("Client {} connected", clientId);
|
||||
|
||||
ClientHandler handler = new ClientHandler(clientId, clientChannel);
|
||||
activeClients.put(clientId, handler);
|
||||
|
||||
clientThreadPool.submit(() -> {
|
||||
try {
|
||||
handler.handle();
|
||||
} finally {
|
||||
activeClients.remove(clientId);
|
||||
logger.info("Client {} disconnected", clientId);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Nenhum cliente conectado no momento
|
||||
// Dorme por um curto período antes de verificar novamente
|
||||
// Isso evita consumir CPU desnecessariamente em um loop vazio
|
||||
Thread.sleep(100); // 100 milissegundos
|
||||
}
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
// Thread foi interrompida, provavelmente durante shutdown
|
||||
logger.debug("Accept loop interrupted");
|
||||
break;
|
||||
|
||||
} catch (IOException e) {
|
||||
// Erros de I/O reais devem ser logados
|
||||
if (running.get()) {
|
||||
logger.error("Error accepting client connection", e);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Connection loop stopped gracefully");
|
||||
}
|
||||
|
||||
|
||||
private class ClientHandler {
|
||||
private final int clientId;
|
||||
private final SocketChannel channel;
|
||||
|
||||
public ClientHandler(int clientId, SocketChannel channel) {
|
||||
this.clientId = clientId;
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Método principal que processa a comunicação com o cliente.
|
||||
* Aqui é onde vamos ler mensagens JSON, processá-las, e enviar respostas.
|
||||
*/
|
||||
public void handle() {
|
||||
logger.debug("Client {} handler thread started", clientId);
|
||||
|
||||
try {
|
||||
// TODO: No próximo passo, vamos implementar:
|
||||
// 1. Ler mensagens JSON do SocketChannel
|
||||
// 2. Parsear o JSON para objetos Java
|
||||
// 3. Processar a requisição
|
||||
// 4. Criar uma resposta JSON
|
||||
// 5. Escrever a resposta de volta no SocketChannel
|
||||
|
||||
// Por enquanto, apenas mantém a conexão aberta brevemente
|
||||
// para testar que o sistema de aceitação e threads está funcionando
|
||||
Thread.sleep(100);
|
||||
|
||||
logger.debug("Client {} processing complete", clientId);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error handling client {}", clientId, e);
|
||||
} finally {
|
||||
// SEMPRE fecha o canal quando terminar
|
||||
// O finally garante que isso acontece mesmo se houver exceção
|
||||
try {
|
||||
channel.close();
|
||||
logger.debug("Client {} channel closed", clientId);
|
||||
} catch (IOException e) {
|
||||
logger.error("Error closing client {} channel", clientId, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
+3
@@ -21,3 +21,6 @@ hibernate.format_sql=true
|
||||
ipc.pipe.name=mediamanager-pipe
|
||||
ipc.pipe.path=/tmp/mediamanager
|
||||
ipc.buffer.size=8192
|
||||
|
||||
ipc.socket.path=/tmp/mediamanager
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration status="WARN">
|
||||
<Configuration status="WARN" shutdownHook="disable">
|
||||
<Appenders>
|
||||
<Console name="Console" target="SYSTEM_OUT">
|
||||
<Console name="Console" target="SYSTEM_OUT" immediateFlush="true">
|
||||
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n"/>
|
||||
</Console>
|
||||
|
||||
<RollingFile name="FileAppender" fileName="logs/mediamanager.log"
|
||||
filePattern="logs/mediamanager-%d{yyyy-MM-dd}-%i.log">
|
||||
filePattern="logs/mediamanager-%d{yyyy-MM-dd}-%i.log"
|
||||
immediateFlush="true">
|
||||
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n"/>
|
||||
<Policies>
|
||||
<TimeBasedTriggeringPolicy interval="1"/>
|
||||
|
||||
Reference in New Issue
Block a user