mirror of
https://github.com/gmbrax/MediaManager-core.git
synced 2026-11-09 22:10:17 +00:00
Compare commits
18
Commits
fc09c4e257
..
master
@@ -0,0 +1,100 @@
|
|||||||
|
name: Qodana
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
target_ref:
|
||||||
|
description: 'Branch, tag, or commit SHA to analyze (default: develop)'
|
||||||
|
required: false
|
||||||
|
default: 'develop'
|
||||||
|
type: string
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches: # Specify your branches here
|
||||||
|
- master
|
||||||
|
- main # The 'main' branch
|
||||||
|
- develop
|
||||||
|
- 'releases/*' # The release branches
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
qodana:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
checks: write
|
||||||
|
steps:
|
||||||
|
# Checkout for pull_request events: use the PR head SHA for accurate analysis
|
||||||
|
- name: Checkout (pull_request)
|
||||||
|
if: ${{ github.event_name == 'pull_request' }}
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
|
fetch-depth: 0 # a full history is required for pull request analysis
|
||||||
|
|
||||||
|
# Checkout for manual runs: use the selected branch/ref (default: develop)
|
||||||
|
- name: Checkout (workflow_dispatch)
|
||||||
|
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ inputs.target_ref }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
# Checkout for push events (e.g., merges to develop): use the current commit
|
||||||
|
- name: Checkout (push)
|
||||||
|
if: ${{ github.event_name == 'push' }}
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
# Set up JDK for potential code generation (e.g., protobuf) prior to Qodana analysis
|
||||||
|
- name: Set up Java 17
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: 'temurin'
|
||||||
|
java-version: '17'
|
||||||
|
cache: 'maven'
|
||||||
|
|
||||||
|
# Generate sources so that Qodana resolves imports from generated code
|
||||||
|
- name: Generate sources (protobuf)
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
if [ -f ./mvnw ]; then chmod +x ./mvnw || true; fi
|
||||||
|
if [ -x ./mvnw ]; then
|
||||||
|
./mvnw -B -DskipTests -DskipITs=true generate-sources || true
|
||||||
|
elif command -v mvn >/dev/null 2>&1; then
|
||||||
|
mvn -B -DskipTests -DskipITs=true generate-sources || true
|
||||||
|
else
|
||||||
|
echo "[Qodana prep] Maven not available; skipping generate-sources"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Optionally compile (skip tests) to ensure classes are available for resolution
|
||||||
|
- name: Compile (skip tests)
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
if [ -x ./mvnw ]; then
|
||||||
|
./mvnw -B -DskipTests -DskipITs=true compile || true
|
||||||
|
elif command -v mvn >/dev/null 2>&1; then
|
||||||
|
mvn -B -DskipTests -DskipITs=true compile || true
|
||||||
|
else
|
||||||
|
echo "[Qodana prep] Maven not available; skipping compile"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Helpful debug: list generated sources if present
|
||||||
|
- name: Show generated sources
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "=== target/generated-sources (tree) ===" || true
|
||||||
|
if [ -d target/generated-sources ]; then
|
||||||
|
find target/generated-sources -maxdepth 3 -type d -print || true
|
||||||
|
else
|
||||||
|
echo "(none)" || true
|
||||||
|
fi
|
||||||
|
- name: 'Qodana Scan'
|
||||||
|
uses: JetBrains/qodana-action@v2025.2
|
||||||
|
with:
|
||||||
|
# Enable PR-specific reporting only for pull_request events
|
||||||
|
pr-mode: ${{ github.event_name == 'pull_request' }}
|
||||||
|
# No extra args needed; linter is defined in qodana.yaml (qodana-jvm)
|
||||||
|
env:
|
||||||
|
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN_268690425 }}
|
||||||
|
QODANA_ENDPOINT: 'https://qodana.cloud'
|
||||||
+1
-9
@@ -39,16 +39,8 @@ build/
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
### Application Specific ###
|
### Application Specific ###
|
||||||
config-local.properties
|
application-local.properties
|
||||||
*.db
|
*.db
|
||||||
pipes/
|
pipes/
|
||||||
*.log
|
*.log
|
||||||
/.idea/
|
/.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
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Contributor Code of Conduct
|
||||||
|
|
||||||
|
This project adheres to No Code of Conduct. We are all adults. We accept anyone's contributions. Nothing else matters.
|
||||||
|
|
||||||
|
For more information please visit the [No Code of Conduct](https://nocodeofconduct.com) homepage.
|
||||||
@@ -34,7 +34,7 @@ MediaManager-Core/
|
|||||||
│ │ ├── util/ # Utility classes
|
│ │ ├── util/ # Utility classes
|
||||||
│ │ └── MediaManagerApplication.java
|
│ │ └── MediaManagerApplication.java
|
||||||
│ └── resources/
|
│ └── resources/
|
||||||
│ ├── config.properties # App configuration
|
│ ├── application.properties # App configuration
|
||||||
│ ├── log4j2.xml # Logging configuration
|
│ ├── log4j2.xml # Logging configuration
|
||||||
│ └── META-INF/
|
│ └── META-INF/
|
||||||
│ └── persistence.xml # JPA configuration
|
│ └── persistence.xml # JPA configuration
|
||||||
@@ -55,7 +55,7 @@ GRANT ALL PRIVILEGES ON DATABASE mediamanager TO mediamanager;
|
|||||||
|
|
||||||
### 2. Configuration
|
### 2. Configuration
|
||||||
|
|
||||||
Edit `src/main/resources/config.properties` and update the database credentials:
|
Edit `src/main/resources/application.properties` and update the database credentials:
|
||||||
|
|
||||||
```properties
|
```properties
|
||||||
db.url=jdbc:postgresql://localhost:5432/mediamanager
|
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`
|
- Pipe name: `mediamanager-pipe`
|
||||||
- Buffer size: 8192 bytes
|
- Buffer size: 8192 bytes
|
||||||
|
|
||||||
You can modify these settings in `config.properties`.
|
You can modify these settings in `application.properties`.
|
||||||
|
|
||||||
## Logging
|
## Logging
|
||||||
|
|
||||||
@@ -116,8 +116,8 @@ mvn test
|
|||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
- PostgreSQL Driver: 42.7.5
|
- PostgreSQL Driver: 42.7.3
|
||||||
- Hibernate ORM: 7.1.7.Final
|
- Hibernate ORM: 6.4.4.Final
|
||||||
- HikariCP: 5.1.0
|
- HikariCP: 5.1.0
|
||||||
- Log4j 2: 2.23.1
|
- Log4j 2: 2.23.1
|
||||||
- Jackson: 2.16.1
|
- Jackson: 2.16.1
|
||||||
|
|||||||
+103
@@ -0,0 +1,103 @@
|
|||||||
|
# Refactoring History—MediaManager Core
|
||||||
|
|
||||||
|
This document records the refactorings applied during development.
|
||||||
|
|
||||||
|
## Refactoring #1: Connection Pool Implementation
|
||||||
|
|
||||||
|
**Date**: 2025-11-13
|
||||||
|
**Commits**: ```22476abb27d95e88ae0439d97ef97467b6a4f985```
|
||||||
|
|
||||||
|
### Problem Identified
|
||||||
|
- Database connections were created multiple times
|
||||||
|
- No centralized control
|
||||||
|
- Possible resource leaks
|
||||||
|
|
||||||
|
### Technique Applied
|
||||||
|
- **Pattern**: Singleton Pattern
|
||||||
|
- **Refactoring**: Extract Class + Introduce Singleton
|
||||||
|
|
||||||
|
### Solution
|
||||||
|
Created `DatabaseManager` class that:
|
||||||
|
- Maintains single instance (Singleton)
|
||||||
|
- Manages connection pool
|
||||||
|
- Provides centralized `getConnection()` method
|
||||||
|
|
||||||
|
### Result
|
||||||
|
- ✅ Single reusable connection
|
||||||
|
- ✅ Code centralized in one class
|
||||||
|
- ✅ Thread-safe
|
||||||
|
- ✅ Easier to test
|
||||||
|
|
||||||
|
### Extracted Component
|
||||||
|
`DatabaseManager` - available in `mediamanager-components`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Refactoring #2: Thread Pool for IPC
|
||||||
|
|
||||||
|
**Date**: 2025-11-14
|
||||||
|
**Commits**: ```f1071dca03da33f7355a6927497509b5aedaa4a2```
|
||||||
|
|
||||||
|
### Problem Identified
|
||||||
|
- New thread created for each IPC client
|
||||||
|
- Doesn't scale well
|
||||||
|
- Thread creation overhead
|
||||||
|
|
||||||
|
### Technique Applied
|
||||||
|
- **Pattern**: Thread Pool Pattern
|
||||||
|
- **Refactoring**: Replace Thread Creation with Thread Pool
|
||||||
|
|
||||||
|
### Solution
|
||||||
|
Implemented `ExecutorService` with:
|
||||||
|
- Fixed thread pool
|
||||||
|
- Thread reuse
|
||||||
|
- Ordered shutdown
|
||||||
|
|
||||||
|
### Result
|
||||||
|
- ✅ Better performance
|
||||||
|
- ✅ Controlled resource usage
|
||||||
|
- ✅ Scalability
|
||||||
|
|
||||||
|
### Extracted Component
|
||||||
|
`IPCServer` - available in `mediamanager-components`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Refactoring #3: Graceful Shutdown
|
||||||
|
|
||||||
|
**Date**: 2025-11-13
|
||||||
|
**Commits**: ```65e14930076db71a312feae373d6c5daee3c4d29```,```b7ff9d1e16c85bf986a770676827bee390f0d53c```
|
||||||
|
|
||||||
|
### Problem Identified
|
||||||
|
- Resources weren't released on exit
|
||||||
|
- Database connections remained open
|
||||||
|
- Thread pool didn't terminate correctly
|
||||||
|
|
||||||
|
### Technique Applied
|
||||||
|
- **Pattern**: Shutdown Hook Pattern
|
||||||
|
- **Refactoring**: Introduce Shutdown Protocol
|
||||||
|
|
||||||
|
### Solution
|
||||||
|
Implemented shutdown hooks that:
|
||||||
|
- Close database connections
|
||||||
|
- Terminate thread pools
|
||||||
|
- Release sockets
|
||||||
|
- In specific order with timeouts
|
||||||
|
|
||||||
|
### Result
|
||||||
|
- ✅ Ordered cleanup
|
||||||
|
- ✅ No resource leaks
|
||||||
|
- ✅ Shutdown logging
|
||||||
|
- ✅ Timeout to prevent hanging
|
||||||
|
|
||||||
|
### Extracted Component
|
||||||
|
Logic incorporated in `DatabaseManager` and `IPCServer`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Applied Principles
|
||||||
|
|
||||||
|
- **DRY** (Don't Repeat Yourself): Elimination of duplicated code
|
||||||
|
- **SOLID**: Especially Single Responsibility Principle
|
||||||
|
- **Design Patterns**: Singleton, Thread Pool, Shutdown Hook
|
||||||
|
- **Clean Code**: Clear names, small methods, well-defined responsibilities
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
<maven.compiler.target>17</maven.compiler.target>
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<hibernate.version>7.1.7.Final</hibernate.version>
|
<hibernate.version>7.1.7.Final</hibernate.version>
|
||||||
<postgresql.version>42.7.7</postgresql.version>
|
<postgresql.version>42.7.5</postgresql.version>
|
||||||
<hikaricp.version>5.1.0</hikaricp.version>
|
<hikaricp.version>5.1.0</hikaricp.version>
|
||||||
<log4j.version>2.23.1</log4j.version>
|
<log4j.version>2.23.1</log4j.version>
|
||||||
<junit.version>5.10.2</junit.version>
|
<junit.version>5.10.2</junit.version>
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.logging.log4j</groupId>
|
<groupId>org.apache.logging.log4j</groupId>
|
||||||
<artifactId>log4j-slf4j-impl</artifactId>
|
<artifactId>log4j-slf4j2-impl</artifactId>
|
||||||
<version>${log4j.version}</version>
|
<version>${log4j.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
@@ -78,27 +78,9 @@
|
|||||||
<version>${junit.version}</version>
|
<version>${junit.version}</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>com.google.protobuf</groupId>
|
|
||||||
<artifactId>protobuf-java</artifactId>
|
|
||||||
<version>4.32.0</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.xerial</groupId>
|
|
||||||
<artifactId>sqlite-jdbc</artifactId>
|
|
||||||
<version>3.44.1.0</version>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<extensions>
|
|
||||||
<extension>
|
|
||||||
<groupId>kr.motd.maven</groupId>
|
|
||||||
<artifactId>os-maven-plugin</artifactId>
|
|
||||||
<version>1.7.1</version>
|
|
||||||
</extension>
|
|
||||||
</extensions>
|
|
||||||
|
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
@@ -109,75 +91,11 @@
|
|||||||
<target>17</target>
|
<target>17</target>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
<version>3.2.5</version>
|
<version>3.2.5</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
<!-- Protocol Buffers Plugin -->
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.xolstice.maven.plugins</groupId>
|
|
||||||
<artifactId>protobuf-maven-plugin</artifactId>
|
|
||||||
<version>0.6.1</version>
|
|
||||||
<configuration>
|
|
||||||
<protocExecutable>/usr/bin/protoc</protocExecutable>
|
|
||||||
<protoSourceRoot>${project.basedir}/src/main/proto</protoSourceRoot>
|
|
||||||
<outputDirectory>${project.build.directory}/generated-sources/protobuf/java</outputDirectory>
|
|
||||||
<clearOutputDirectory>false</clearOutputDirectory>
|
|
||||||
</configuration>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<goals>
|
|
||||||
<goal>compile</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
|
|
||||||
<!-- Build Helper Plugin - Adiciona generated sources ao classpath -->
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.codehaus.mojo</groupId>
|
|
||||||
<artifactId>build-helper-maven-plugin</artifactId>
|
|
||||||
<version>3.5.0</version>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<id>add-source</id>
|
|
||||||
<phase>generate-sources</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>add-source</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<sources>
|
|
||||||
<source>${project.build.directory}/generated-sources/protobuf/java</source>
|
|
||||||
</sources>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</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>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
|||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
####################################################################################################################
|
||||||
|
# WARNING: Do not store sensitive information in this file, as its contents will be included in the Qodana report. #
|
||||||
|
####################################################################################################################
|
||||||
|
|
||||||
|
version: "1.0"
|
||||||
|
# Use canonical linter name to avoid image warnings and CLI flag issues
|
||||||
|
linter: qodana-jvm
|
||||||
|
profile:
|
||||||
|
name: qodana.recommended
|
||||||
|
bootstrap: |
|
||||||
|
# Disabled bootstrap to avoid shell parsing issues in CI. If you need code generation,
|
||||||
|
# we can re-enable a robust script later. For now, do nothing and let Qodana run.
|
||||||
|
:
|
||||||
|
include:
|
||||||
|
- name: CheckDependencyLicenses
|
||||||
|
exclude:
|
||||||
|
# Exclude generated sources from inspections to reduce noise
|
||||||
|
- name: All
|
||||||
|
paths:
|
||||||
|
- target/protoc-dependencies/**
|
||||||
|
# Keep generated sources included for indexing/resolution; excluding them can cause unresolved imports
|
||||||
@@ -1,8 +1,5 @@
|
|||||||
package com.mediamanager;
|
package com.mediamanager;
|
||||||
|
|
||||||
import com.mediamanager.service.database.DatabaseManager;
|
|
||||||
import com.mediamanager.service.delegate.DelegateActionManager;
|
|
||||||
import com.mediamanager.service.ipc.IPCManager;
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
@@ -10,25 +7,9 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
import com.mediamanager.service.database.SqliteDatabaseManager;
|
|
||||||
|
|
||||||
public class MediaManagerApplication {
|
public class MediaManagerApplication {
|
||||||
private static final Logger logger = LogManager.getLogger(MediaManagerApplication.class);
|
private static final Logger logger = LogManager.getLogger(MediaManagerApplication.class);
|
||||||
private static Properties config;
|
private static Properties config;
|
||||||
private static DatabaseManager databaseManager;
|
|
||||||
private static DelegateActionManager actionManager;
|
|
||||||
private static IPCManager ipcManager;
|
|
||||||
|
|
||||||
public enum ApplicationMode {
|
|
||||||
LOCAL("local"),
|
|
||||||
SERVER("server");
|
|
||||||
|
|
||||||
private final String value;
|
|
||||||
|
|
||||||
ApplicationMode(String value) {
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
logger.info("Starting MediaManager Core Application...");
|
logger.info("Starting MediaManager Core Application...");
|
||||||
@@ -36,85 +17,19 @@ public class MediaManagerApplication {
|
|||||||
try {
|
try {
|
||||||
// Load configuration
|
// Load configuration
|
||||||
loadConfiguration();
|
loadConfiguration();
|
||||||
String runTypeString = config.getProperty("runtype","local");
|
|
||||||
ApplicationMode mode = null;
|
|
||||||
|
|
||||||
for (ApplicationMode am : ApplicationMode.values()) {
|
|
||||||
if (am.value.equalsIgnoreCase(runTypeString)) {
|
|
||||||
mode = am;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (mode == null) {
|
|
||||||
logger.error("Invalid run type: {}", runTypeString);
|
|
||||||
throw new Exception("Invalid run type: " + runTypeString);
|
|
||||||
}
|
|
||||||
logger.info("Run type: {}", mode);
|
|
||||||
switch (mode) {
|
|
||||||
case LOCAL:
|
|
||||||
logger.info("Starting local database...");
|
|
||||||
databaseManager = new SqliteDatabaseManager(config);
|
|
||||||
break;
|
|
||||||
case SERVER:
|
|
||||||
throw new Exception("Server mode not yet implemented");
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
databaseManager.init();
|
|
||||||
actionManager = new DelegateActionManager();
|
|
||||||
actionManager.start();
|
|
||||||
ipcManager = new IPCManager(config,actionManager);
|
|
||||||
ipcManager.init();
|
|
||||||
|
|
||||||
|
// TODO: Initialize database connection
|
||||||
|
// TODO: Initialize IPC server with named pipes
|
||||||
// TODO: Start application services
|
// TODO: Start application services
|
||||||
|
|
||||||
logger.info("MediaManager Core started successfully");
|
logger.info("MediaManager Core started successfully");
|
||||||
logger.info("IPC Socket: {}", ipcManager.getSocketPath().toAbsolutePath().toString());
|
logger.info("IPC Pipe: {}", config.getProperty("ipc.pipe.path") + "/" + config.getProperty("ipc.pipe.name"));
|
||||||
|
|
||||||
// Keep application running
|
// Keep application running
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||||
|
|
||||||
logger.info("Shutting down MediaManager Core...");
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (actionManager != null) {
|
|
||||||
actionManager.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
} catch (Exception e) {
|
||||||
logger.error("Failed to start MediaManager Core", e);
|
logger.error("Failed to start MediaManager Core", e);
|
||||||
@@ -124,9 +39,10 @@ public class MediaManagerApplication {
|
|||||||
|
|
||||||
private static void loadConfiguration() throws IOException {
|
private static void loadConfiguration() throws IOException {
|
||||||
config = new Properties();
|
config = new Properties();
|
||||||
try (InputStream input = MediaManagerApplication.class.getClassLoader().getResourceAsStream("config.properties")) {
|
try (InputStream input = MediaManagerApplication.class.getClassLoader()
|
||||||
|
.getResourceAsStream("application.properties")) {
|
||||||
if (input == null) {
|
if (input == null) {
|
||||||
throw new IOException("Unable to find config.properties");
|
throw new IOException("Unable to find application.properties");
|
||||||
}
|
}
|
||||||
config.load(input);
|
config.load(input);
|
||||||
logger.info("Configuration loaded successfully");
|
logger.info("Configuration loaded successfully");
|
||||||
@@ -136,4 +52,4 @@ public class MediaManagerApplication {
|
|||||||
public static Properties getConfig() {
|
public static Properties getConfig() {
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
package com.mediamanager.service.database;
|
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
|
|
||||||
import java.sql.Connection;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.sql.Statement;
|
|
||||||
import java.util.Properties;
|
|
||||||
|
|
||||||
public abstract class DatabaseManager {
|
|
||||||
protected final Properties configuration;
|
|
||||||
protected Connection connection;
|
|
||||||
protected static final Logger logger = LogManager.getLogger(DatabaseManager.class);
|
|
||||||
|
|
||||||
public DatabaseManager(Properties config) {
|
|
||||||
this.configuration = config;
|
|
||||||
logger.debug("DatabaseManager created with configuration:");
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void init() throws Exception;
|
|
||||||
|
|
||||||
protected abstract Connection createConnection() throws Exception;
|
|
||||||
|
|
||||||
protected 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
protected boolean testConnection() {
|
|
||||||
try (Statement stmt = connection.createStatement()) {
|
|
||||||
stmt.execute("SELECT 1");
|
|
||||||
return true;
|
|
||||||
} catch (SQLException e) {
|
|
||||||
logger.error("Connection test failed", e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
package com.mediamanager.service.database;
|
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.sql.Connection;
|
|
||||||
import java.sql.DriverManager;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.sql.Statement;
|
|
||||||
import java.util.Properties;
|
|
||||||
|
|
||||||
public class SqliteDatabaseManager extends DatabaseManager {
|
|
||||||
private static final Logger logger = LogManager.getLogger(SqliteDatabaseManager.class);
|
|
||||||
|
|
||||||
|
|
||||||
private String connectionUrl;
|
|
||||||
|
|
||||||
public SqliteDatabaseManager(Properties config) {
|
|
||||||
super(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init() throws Exception {
|
|
||||||
|
|
||||||
logger.info("Initializing SQLite database...");
|
|
||||||
String dataDir = configuration.getProperty("database.dir",
|
|
||||||
System.getProperty("user.home") + "/.mediamanager/db");
|
|
||||||
String dbFilename = configuration.getProperty("database.filename", "mediamanager.db");
|
|
||||||
String driverClassName = configuration.getProperty("database.driver", "org.sqlite.JDBC");
|
|
||||||
try {
|
|
||||||
Class.forName(driverClassName);
|
|
||||||
}catch(ClassNotFoundException e) {
|
|
||||||
logger.error("Failed to load SQLite driver", e);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
Path dataPath = Paths.get(dataDir);
|
|
||||||
if (!Files.exists(dataPath)) {
|
|
||||||
Files.createDirectories(dataPath);
|
|
||||||
logger.debug("Created database directory: {}", dataDir);
|
|
||||||
}
|
|
||||||
Path dbFile = dataPath.resolve(dbFilename);
|
|
||||||
this.connectionUrl = "jdbc:sqlite:" + dbFile.toAbsolutePath().toString();
|
|
||||||
logger.info("Database file path: {}", dbFile);
|
|
||||||
logger.info("Connection URL: {}", this.connectionUrl);
|
|
||||||
this.connection = createConnection();
|
|
||||||
configurePerformancePragmas();
|
|
||||||
performSanityChecks();
|
|
||||||
ensureSchemaExists();
|
|
||||||
logger.info("SQLite database initialized successfully");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Connection createConnection() throws Exception {
|
|
||||||
try {
|
|
||||||
// O driver org.xerial.sqlite-jdbc é carregado automaticamente aqui
|
|
||||||
Connection conn = DriverManager.getConnection(this.connectionUrl);
|
|
||||||
logger.debug("Got connection to SQLite file");
|
|
||||||
return conn;
|
|
||||||
} catch (SQLException e) {
|
|
||||||
logger.error("Failed to create SQLite connection", e);
|
|
||||||
throw new Exception("SQLite connection failed: " + e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void configurePerformancePragmas() throws SQLException {
|
|
||||||
try (Statement stmt = connection.createStatement()) {
|
|
||||||
|
|
||||||
stmt.execute("PRAGMA journal_mode=WAL;");
|
|
||||||
|
|
||||||
|
|
||||||
stmt.execute("PRAGMA foreign_keys=ON;");
|
|
||||||
|
|
||||||
|
|
||||||
stmt.execute("PRAGMA synchronous=NORMAL;");
|
|
||||||
|
|
||||||
stmt.execute("PRAGMA busy_timeout=5000;");
|
|
||||||
|
|
||||||
logger.debug("SQLite performance PRAGMAs applied (WAL, Synchronous, ForeignKeys).");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ensureSchemaExists() throws SQLException {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
|
|
||||||
super.close();
|
|
||||||
logger.info("SQLite resources released.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package com.mediamanager.service.delegate;
|
|
||||||
|
|
||||||
import com.google.protobuf.ByteString;
|
|
||||||
import com.google.protobuf.InvalidProtocolBufferException;
|
|
||||||
import com.mediamanager.protocol.TransportProtocol;
|
|
||||||
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface ActionHandler {
|
|
||||||
TransportProtocol.Response.Builder handle(ByteString requestPayload) throws InvalidProtocolBufferException;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
package com.mediamanager.service.delegate;
|
|
||||||
|
|
||||||
import com.google.protobuf.ByteString;
|
|
||||||
import com.mediamanager.protocol.TransportProtocol;
|
|
||||||
import com.mediamanager.service.delegate.handler.CloseHandler;
|
|
||||||
import com.mediamanager.service.delegate.handler.EchoHandler;
|
|
||||||
import com.mediamanager.service.delegate.handler.HeartbeatHandler;
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class DelegateActionManager {
|
|
||||||
private static final Logger logger = LogManager.getLogger(DelegateActionManager.class);
|
|
||||||
|
|
||||||
private final Map<String, ActionHandler> handlerRegistry;
|
|
||||||
|
|
||||||
public DelegateActionManager() {
|
|
||||||
|
|
||||||
logger.debug("DelegateActionManager created");
|
|
||||||
this.handlerRegistry = new HashMap<>();
|
|
||||||
registerHandlers();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void registerHandlers() {
|
|
||||||
handlerRegistry.put("echo",new EchoHandler());
|
|
||||||
handlerRegistry.put("heartbeat",new HeartbeatHandler());
|
|
||||||
handlerRegistry.put("close", new CloseHandler());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start(){
|
|
||||||
logger.info("DelegateActionManager started");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stop(){
|
|
||||||
logger.info("DelegateActionManager stopped");
|
|
||||||
}
|
|
||||||
|
|
||||||
public TransportProtocol.Response ProcessedRequest(TransportProtocol.Request request){
|
|
||||||
String requestId = request.getRequestId();
|
|
||||||
logger.info("Processing request: {}", requestId);
|
|
||||||
String action = request.getHeadersMap().getOrDefault("action", "unknown");
|
|
||||||
ActionHandler handler = handlerRegistry.get(action);
|
|
||||||
TransportProtocol.Response.Builder responseBuilder;
|
|
||||||
if (handler == null) {
|
|
||||||
logger.warn("No handler found for action: {}", action);
|
|
||||||
responseBuilder = TransportProtocol.Response.newBuilder()
|
|
||||||
.setStatusCode(404) // 404 Not Found
|
|
||||||
.setPayload(ByteString.copyFromUtf8("Error: Action '" + action + "' not found."));
|
|
||||||
} else{
|
|
||||||
try {
|
|
||||||
logger.debug("Delegating action '{}' to handler...", action);
|
|
||||||
responseBuilder = handler.handle(request.getPayload());
|
|
||||||
}catch (Exception e) {
|
|
||||||
logger.error("Handler for action '{}' threw an exception:", action, e);
|
|
||||||
responseBuilder = TransportProtocol.Response.newBuilder()
|
|
||||||
.setStatusCode(500) // 500 Internal Server Error
|
|
||||||
.setPayload(ByteString.copyFromUtf8("Error: " + e.getMessage()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return responseBuilder.setRequestId(requestId).build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package com.mediamanager.service.delegate.handler;
|
|
||||||
|
|
||||||
import com.google.protobuf.ByteString;
|
|
||||||
import com.google.protobuf.InvalidProtocolBufferException;
|
|
||||||
import com.mediamanager.protocol.TestProtocol.CloseCommand;
|
|
||||||
import com.mediamanager.protocol.TestProtocol.CloseResponse;
|
|
||||||
import com.mediamanager.protocol.TransportProtocol;
|
|
||||||
import com.mediamanager.service.delegate.ActionHandler;
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
|
|
||||||
public class CloseHandler implements ActionHandler {
|
|
||||||
private static final Logger logger = LogManager.getLogger(CloseHandler.class);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TransportProtocol.Response.Builder handle(ByteString requestPayload)
|
|
||||||
throws InvalidProtocolBufferException {
|
|
||||||
|
|
||||||
CloseCommand.parseFrom(requestPayload); // Valida
|
|
||||||
|
|
||||||
logger.info("Close command received - connection will close");
|
|
||||||
|
|
||||||
CloseResponse response = CloseResponse.newBuilder()
|
|
||||||
.setMessage("Connection closing. Goodbye!")
|
|
||||||
.build();
|
|
||||||
|
|
||||||
return TransportProtocol.Response.newBuilder()
|
|
||||||
.setPayload(ByteString.copyFrom(response.toByteArray()))
|
|
||||||
.setStatusCode(200)
|
|
||||||
.putHeaders("Connection", "close"); // ← Marca para fechar
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
package com.mediamanager.service.delegate.handler;
|
|
||||||
|
|
||||||
import com.google.protobuf.ByteString;
|
|
||||||
import com.google.protobuf.InvalidProtocolBufferException;
|
|
||||||
import com.mediamanager.protocol.TestProtocol.EchoCommand; // ← Import
|
|
||||||
import com.mediamanager.protocol.TestProtocol.EchoResponse; // ← Import
|
|
||||||
import com.mediamanager.protocol.TransportProtocol;
|
|
||||||
import com.mediamanager.service.delegate.ActionHandler;
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
|
|
||||||
public class EchoHandler implements ActionHandler {
|
|
||||||
private static final Logger logger = LogManager.getLogger(EchoHandler.class);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TransportProtocol.Response.Builder handle(ByteString requestPayload)
|
|
||||||
throws InvalidProtocolBufferException { // ← Pode lançar exceção
|
|
||||||
|
|
||||||
// 1. Parse Protobuf bytes → EchoCommand
|
|
||||||
EchoCommand command = EchoCommand.parseFrom(requestPayload);
|
|
||||||
|
|
||||||
logger.debug("Echo received: {}", command.getMessage());
|
|
||||||
|
|
||||||
// 2. Cria EchoResponse (Protobuf)
|
|
||||||
EchoResponse echoResponse = EchoResponse.newBuilder()
|
|
||||||
.setMessage(command.getMessage())
|
|
||||||
.setServerTimestamp(System.currentTimeMillis())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
// 3. Serializa EchoResponse → bytes
|
|
||||||
ByteString responsePayload = ByteString.copyFrom(echoResponse.toByteArray());
|
|
||||||
|
|
||||||
// 4. Retorna Response
|
|
||||||
return TransportProtocol.Response.newBuilder()
|
|
||||||
.setPayload(responsePayload)
|
|
||||||
.setStatusCode(200);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
package com.mediamanager.service.delegate.handler;
|
|
||||||
|
|
||||||
import com.google.protobuf.ByteString;
|
|
||||||
import com.google.protobuf.InvalidProtocolBufferException;
|
|
||||||
import com.mediamanager.protocol.TestProtocol.HeartbeatCommand;
|
|
||||||
import com.mediamanager.protocol.TestProtocol.HeartbeatResponse;
|
|
||||||
import com.mediamanager.protocol.TransportProtocol;
|
|
||||||
import com.mediamanager.service.delegate.ActionHandler;
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
|
|
||||||
public class HeartbeatHandler implements ActionHandler {
|
|
||||||
private static final Logger logger = LogManager.getLogger(HeartbeatHandler.class);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TransportProtocol.Response.Builder handle(ByteString requestPayload)
|
|
||||||
throws InvalidProtocolBufferException {
|
|
||||||
|
|
||||||
HeartbeatCommand command = HeartbeatCommand.parseFrom(requestPayload);
|
|
||||||
|
|
||||||
long serverTime = System.currentTimeMillis();
|
|
||||||
|
|
||||||
logger.debug("Heartbeat received. Client T1={}, Server T2={}",
|
|
||||||
command.getClientTimestamp(), serverTime);
|
|
||||||
|
|
||||||
HeartbeatResponse response = HeartbeatResponse.newBuilder()
|
|
||||||
.setClientTimestamp(command.getClientTimestamp()) // Echo T1
|
|
||||||
.setServerTimestamp(serverTime) // T2
|
|
||||||
.build();
|
|
||||||
|
|
||||||
return TransportProtocol.Response.newBuilder()
|
|
||||||
.setPayload(ByteString.copyFrom(response.toByteArray()))
|
|
||||||
.setStatusCode(200);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,337 +0,0 @@
|
|||||||
package com.mediamanager.service.ipc;
|
|
||||||
|
|
||||||
import com.mediamanager.protocol.TransportProtocol;
|
|
||||||
import com.mediamanager.service.delegate.DelegateActionManager;
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
|
|
||||||
|
|
||||||
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 final DelegateActionManager actionManager;
|
|
||||||
|
|
||||||
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, DelegateActionManager actionManager){
|
|
||||||
configuration = config;
|
|
||||||
this.actionManager = actionManager;
|
|
||||||
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(1); // 1 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 {
|
|
||||||
// LOOP: processa múltiplas requests na mesma conexão
|
|
||||||
while (channel.isOpen()) {
|
|
||||||
TransportProtocol.Request request = readRequest(channel);
|
|
||||||
|
|
||||||
if (request == null) {
|
|
||||||
// Cliente desconectou gracefully
|
|
||||||
logger.info("Client {} disconnected (end of stream)", clientId);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info("Client {} sent request {}", clientId, request.getRequestId());
|
|
||||||
|
|
||||||
// Processa usando o DelegateActionManager
|
|
||||||
TransportProtocol.Response response = actionManager.ProcessedRequest(request);
|
|
||||||
|
|
||||||
// Envia resposta de volta
|
|
||||||
writeResponse(channel, response);
|
|
||||||
logger.info("Client {} response sent", clientId);
|
|
||||||
|
|
||||||
// Verifica se é comando CLOSE
|
|
||||||
String connectionHeader = response.getHeadersOrDefault("Connection", "");
|
|
||||||
if ("close".equals(connectionHeader)) {
|
|
||||||
logger.info("Client {} requested connection close", clientId);
|
|
||||||
break; // Sai do loop e fecha
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (channel.isOpen()) {
|
|
||||||
logger.error("IO error handling client {}", clientId, e);
|
|
||||||
} else {
|
|
||||||
logger.debug("Client {} connection closed by peer", clientId);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("Unexpected error handling client {}", clientId, e);
|
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
if (channel.isOpen()) {
|
|
||||||
channel.close();
|
|
||||||
}
|
|
||||||
logger.debug("Client {} channel closed", clientId);
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("Error closing client {} channel", clientId, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private TransportProtocol.Request readRequest(SocketChannel channel) throws IOException {
|
|
||||||
// Primeiro, lê o tamanho da mensagem (4 bytes = int32)
|
|
||||||
java.nio.ByteBuffer sizeBuffer = java.nio.ByteBuffer.allocate(4);
|
|
||||||
int bytesRead = 0;
|
|
||||||
|
|
||||||
while (bytesRead < 4) {
|
|
||||||
int read = channel.read(sizeBuffer);
|
|
||||||
if (read == -1) {
|
|
||||||
logger.debug("Client disconnected before sending size");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
bytesRead += read;
|
|
||||||
}
|
|
||||||
|
|
||||||
sizeBuffer.flip();
|
|
||||||
int messageSize = sizeBuffer.getInt();
|
|
||||||
logger.debug("Expecting message of {} bytes", messageSize);
|
|
||||||
|
|
||||||
// Validação básica de segurança
|
|
||||||
if (messageSize <= 0 || messageSize > 1024 * 1024) { // Max 1MB
|
|
||||||
throw new IOException("Invalid message size: " + messageSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Agora lê a mensagem completa
|
|
||||||
java.nio.ByteBuffer messageBuffer = java.nio.ByteBuffer.allocate(messageSize);
|
|
||||||
bytesRead = 0;
|
|
||||||
|
|
||||||
while (bytesRead < messageSize) {
|
|
||||||
int read = channel.read(messageBuffer);
|
|
||||||
if (read == -1) {
|
|
||||||
throw new IOException("Client disconnected while reading message");
|
|
||||||
}
|
|
||||||
bytesRead += read;
|
|
||||||
}
|
|
||||||
|
|
||||||
messageBuffer.flip();
|
|
||||||
|
|
||||||
// Deserializa o Protocol Buffers
|
|
||||||
byte[] messageBytes = new byte[messageSize];
|
|
||||||
messageBuffer.get(messageBytes);
|
|
||||||
|
|
||||||
return TransportProtocol.Request.parseFrom(messageBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeResponse(SocketChannel channel, TransportProtocol.Response response) throws IOException {
|
|
||||||
byte[] messageBytes = response.toByteArray();
|
|
||||||
int messageSize = messageBytes.length;
|
|
||||||
|
|
||||||
logger.debug("Writing response of {} bytes", messageSize);
|
|
||||||
|
|
||||||
java.nio.ByteBuffer buffer = java.nio.ByteBuffer.allocate(4 + messageSize);
|
|
||||||
buffer.putInt(messageSize);
|
|
||||||
buffer.put(messageBytes);
|
|
||||||
buffer.flip();
|
|
||||||
|
|
||||||
while (buffer.hasRemaining()) {
|
|
||||||
channel.write(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug("Response written successfully");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
option java_package = "com.mediamanager.protocol";
|
|
||||||
option java_outer_classname = "TransportProtocol";
|
|
||||||
|
|
||||||
package mediamanager;
|
|
||||||
|
|
||||||
message Request {
|
|
||||||
string request_id = 1;
|
|
||||||
bytes payload = 2;
|
|
||||||
map<string, string> headers = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Response {
|
|
||||||
string request_id = 1;
|
|
||||||
int32 status_code = 2;
|
|
||||||
bytes payload = 3;
|
|
||||||
map<string, string> headers = 4;
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
option java_package = "com.mediamanager.protocol";
|
|
||||||
option java_outer_classname = "TestProtocol";
|
|
||||||
|
|
||||||
package mediamanager.test;
|
|
||||||
|
|
||||||
message EchoCommand {
|
|
||||||
string message = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message EchoResponse {
|
|
||||||
string message = 1;
|
|
||||||
int64 server_timestamp = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message HeartbeatCommand {
|
|
||||||
int64 client_timestamp = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message HeartbeatResponse {
|
|
||||||
int64 client_timestamp = 1;
|
|
||||||
int64 server_timestamp = 2;
|
|
||||||
}
|
|
||||||
message CloseCommand {
|
|
||||||
// Vazio - apenas sinaliza fechamento
|
|
||||||
}
|
|
||||||
message CloseResponse {
|
|
||||||
string message = 1;
|
|
||||||
}
|
|
||||||
-5
@@ -21,8 +21,3 @@ hibernate.format_sql=true
|
|||||||
ipc.pipe.name=mediamanager-pipe
|
ipc.pipe.name=mediamanager-pipe
|
||||||
ipc.pipe.path=/tmp/mediamanager
|
ipc.pipe.path=/tmp/mediamanager
|
||||||
ipc.buffer.size=8192
|
ipc.buffer.size=8192
|
||||||
runtype=local
|
|
||||||
ipc.socket.path=/tmp/mediamanager
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Configuration status="WARN" shutdownHook="disable">
|
<Configuration status="WARN">
|
||||||
<Appenders>
|
<Appenders>
|
||||||
<Console name="Console" target="SYSTEM_OUT" immediateFlush="true">
|
<Console name="Console" target="SYSTEM_OUT">
|
||||||
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n"/>
|
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n"/>
|
||||||
</Console>
|
</Console>
|
||||||
|
|
||||||
<RollingFile name="FileAppender" fileName="logs/mediamanager.log"
|
<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"/>
|
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n"/>
|
||||||
<Policies>
|
<Policies>
|
||||||
<TimeBasedTriggeringPolicy interval="1"/>
|
<TimeBasedTriggeringPolicy interval="1"/>
|
||||||
@@ -18,17 +17,17 @@
|
|||||||
</Appenders>
|
</Appenders>
|
||||||
|
|
||||||
<Loggers>
|
<Loggers>
|
||||||
<Logger name="com.mediamanager" level="INFO" additivity="false">
|
<Logger name="com.mediamanager" level="debug" additivity="false">
|
||||||
<AppenderRef ref="Console"/>
|
<AppenderRef ref="Console"/>
|
||||||
<AppenderRef ref="FileAppender"/>
|
<AppenderRef ref="FileAppender"/>
|
||||||
</Logger>
|
</Logger>
|
||||||
|
|
||||||
<Logger name="org.hibernate" level="INFO" additivity="false">
|
<Logger name="org.hibernate" level="info" additivity="false">
|
||||||
<AppenderRef ref="Console"/>
|
<AppenderRef ref="Console"/>
|
||||||
<AppenderRef ref="FileAppender"/>
|
<AppenderRef ref="FileAppender"/>
|
||||||
</Logger>
|
</Logger>
|
||||||
|
|
||||||
<Root level="INFO">
|
<Root level="info">
|
||||||
<AppenderRef ref="Console"/>
|
<AppenderRef ref="Console"/>
|
||||||
<AppenderRef ref="FileAppender"/>
|
<AppenderRef ref="FileAppender"/>
|
||||||
</Root>
|
</Root>
|
||||||
|
|||||||
Reference in New Issue
Block a user