291 lines
8.9 KiB
Rust
291 lines
8.9 KiB
Rust
use crate::types::{MinimapTileRecord, NewMinimapTile};
|
|
use crate::image_processor::{ImageProcessor, ImageProcessingError};
|
|
use diesel::prelude::*;
|
|
use diesel::sqlite::SqliteConnection;
|
|
use std::path::{Path, PathBuf};
|
|
use std::fs;
|
|
use thiserror::Error;
|
|
|
|
#[derive(Debug, Error)]
|
|
pub enum MinimapDatabaseError {
|
|
#[error("Database error: {0}")]
|
|
DatabaseError(#[from] diesel::result::Error),
|
|
|
|
#[error("Image processing error: {0}")]
|
|
ImageError(#[from] ImageProcessingError),
|
|
|
|
#[error("IO error: {0}")]
|
|
IoError(#[from] std::io::Error),
|
|
|
|
#[error("Invalid filename format: {0}")]
|
|
InvalidFilename(String),
|
|
|
|
#[error("Connection pool error: {0}")]
|
|
ConnectionError(String),
|
|
}
|
|
|
|
/// Database for managing minimap tiles with actual SQLite storage
|
|
pub struct MinimapDatabase {
|
|
database_url: String,
|
|
image_processor: ImageProcessor,
|
|
}
|
|
|
|
impl MinimapDatabase {
|
|
/// Create new database connection
|
|
pub fn new(database_url: String) -> Self {
|
|
Self {
|
|
database_url,
|
|
image_processor: ImageProcessor::default(),
|
|
}
|
|
}
|
|
|
|
/// Create with custom WebP quality
|
|
pub fn with_quality(database_url: String, quality: f32) -> Self {
|
|
Self {
|
|
database_url,
|
|
image_processor: ImageProcessor::new(quality),
|
|
}
|
|
}
|
|
|
|
/// Establish database connection
|
|
fn establish_connection(&self) -> Result<SqliteConnection, MinimapDatabaseError> {
|
|
SqliteConnection::establish(&self.database_url)
|
|
.map_err(|e| MinimapDatabaseError::ConnectionError(e.to_string()))
|
|
}
|
|
|
|
/// Load all PNG files from directory and process them into database
|
|
pub fn load_from_directory<P: AsRef<Path>>(
|
|
&self,
|
|
minimap_dir: P,
|
|
) -> Result<usize, MinimapDatabaseError> {
|
|
use crate::schema::minimap_tiles;
|
|
|
|
let mut conn = self.establish_connection()?;
|
|
let mut count = 0;
|
|
|
|
// Find all PNG files
|
|
let png_files = self.find_minimap_pngs(&minimap_dir)?;
|
|
|
|
for png_path in png_files {
|
|
// Parse coordinates from filename
|
|
let (x, y) = self.parse_coordinates(&png_path)?;
|
|
|
|
// Process image
|
|
let processed = self.image_processor.process_minimap_png(&png_path)?;
|
|
|
|
// Get original file size
|
|
let original_size = fs::metadata(&png_path)?.len() as i32;
|
|
|
|
// Extract WebP blobs for each size
|
|
let webp_512 = processed.get(512).expect("512px resolution missing");
|
|
let webp_256 = processed.get(256).expect("256px resolution missing");
|
|
let webp_128 = processed.get(128).expect("128px resolution missing");
|
|
let webp_64 = processed.get(64).expect("64px resolution missing");
|
|
|
|
// Create insertable record
|
|
let new_tile = NewMinimapTile {
|
|
x,
|
|
y,
|
|
original_width: 512,
|
|
original_height: 512,
|
|
original_file_size: Some(original_size),
|
|
webp_512,
|
|
webp_256,
|
|
webp_128,
|
|
webp_64,
|
|
webp_512_size: webp_512.len() as i32,
|
|
webp_256_size: webp_256.len() as i32,
|
|
webp_128_size: webp_128.len() as i32,
|
|
webp_64_size: webp_64.len() as i32,
|
|
source_path: png_path.to_str().unwrap_or(""),
|
|
};
|
|
|
|
// Insert into database
|
|
diesel::insert_into(minimap_tiles::table)
|
|
.values(&new_tile)
|
|
.execute(&mut conn)?;
|
|
|
|
count += 1;
|
|
}
|
|
|
|
Ok(count)
|
|
}
|
|
|
|
/// Find all minimap PNG files in directory
|
|
fn find_minimap_pngs<P: AsRef<Path>>(
|
|
&self,
|
|
dir: P,
|
|
) -> Result<Vec<PathBuf>, MinimapDatabaseError> {
|
|
let mut png_files = Vec::new();
|
|
|
|
for entry in fs::read_dir(dir)? {
|
|
let entry = entry?;
|
|
let path = entry.path();
|
|
|
|
if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some("png") {
|
|
// Check if filename matches x_y.png pattern
|
|
if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {
|
|
if stem.contains('_') && stem.chars().all(|c| c.is_numeric() || c == '_' || c == '-') {
|
|
png_files.push(path);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(png_files)
|
|
}
|
|
|
|
/// Parse x,y coordinates from filename (e.g., "0_0.png" -> (0, 0))
|
|
fn parse_coordinates<P: AsRef<Path>>(
|
|
&self,
|
|
path: P,
|
|
) -> Result<(i32, i32), MinimapDatabaseError> {
|
|
let filename = path
|
|
.as_ref()
|
|
.file_stem()
|
|
.and_then(|s| s.to_str())
|
|
.ok_or_else(|| {
|
|
MinimapDatabaseError::InvalidFilename(path.as_ref().display().to_string())
|
|
})?;
|
|
|
|
let parts: Vec<&str> = filename.split('_').collect();
|
|
if parts.len() != 2 {
|
|
return Err(MinimapDatabaseError::InvalidFilename(
|
|
filename.to_string(),
|
|
));
|
|
}
|
|
|
|
let x = parts[0].parse::<i32>().map_err(|_| {
|
|
MinimapDatabaseError::InvalidFilename(filename.to_string())
|
|
})?;
|
|
let y = parts[1].parse::<i32>().map_err(|_| {
|
|
MinimapDatabaseError::InvalidFilename(filename.to_string())
|
|
})?;
|
|
|
|
Ok((x, y))
|
|
}
|
|
|
|
/// Get tile by coordinates
|
|
pub fn get_tile(
|
|
&self,
|
|
x: i32,
|
|
y: i32,
|
|
) -> Result<Option<MinimapTileRecord>, MinimapDatabaseError> {
|
|
use crate::schema::minimap_tiles::dsl;
|
|
|
|
let mut conn = self.establish_connection()?;
|
|
|
|
let tile = dsl::minimap_tiles
|
|
.filter(dsl::x.eq(x))
|
|
.filter(dsl::y.eq(y))
|
|
.first::<MinimapTileRecord>(&mut conn)
|
|
.optional()?;
|
|
|
|
Ok(tile)
|
|
}
|
|
|
|
/// Get tile WebP blob at specific size
|
|
pub fn get_tile_webp(
|
|
&self,
|
|
x: i32,
|
|
y: i32,
|
|
size: u32,
|
|
) -> Result<Option<Vec<u8>>, MinimapDatabaseError> {
|
|
let tile = self.get_tile(x, y)?;
|
|
|
|
Ok(tile.map(|t| match size {
|
|
512 => t.webp_512,
|
|
256 => t.webp_256,
|
|
128 => t.webp_128,
|
|
64 => t.webp_64,
|
|
_ => t.webp_512, // Default to 512
|
|
}))
|
|
}
|
|
|
|
/// Get all tiles
|
|
pub fn get_all_tiles(&self) -> Result<Vec<MinimapTileRecord>, MinimapDatabaseError> {
|
|
use crate::schema::minimap_tiles::dsl::*;
|
|
|
|
let mut conn = self.establish_connection()?;
|
|
|
|
let tiles = minimap_tiles.load::<MinimapTileRecord>(&mut conn)?;
|
|
|
|
Ok(tiles)
|
|
}
|
|
|
|
/// Get map bounds (min/max x and y)
|
|
pub fn get_map_bounds(
|
|
&self,
|
|
) -> Result<((i32, i32), (i32, i32)), MinimapDatabaseError> {
|
|
use crate::schema::minimap_tiles::dsl::*;
|
|
use diesel::dsl::{max, min};
|
|
|
|
let mut conn = self.establish_connection()?;
|
|
|
|
let (min_x, max_x): (Option<i32>, Option<i32>) =
|
|
minimap_tiles.select((min(x), max(x))).first(&mut conn)?;
|
|
|
|
let (min_y, max_y): (Option<i32>, Option<i32>) =
|
|
minimap_tiles.select((min(y), max(y))).first(&mut conn)?;
|
|
|
|
Ok((
|
|
(min_x.unwrap_or(0), min_y.unwrap_or(0)),
|
|
(max_x.unwrap_or(0), max_y.unwrap_or(0)),
|
|
))
|
|
}
|
|
|
|
/// Get count of processed tiles
|
|
pub fn count(&self) -> Result<i64, MinimapDatabaseError> {
|
|
use crate::schema::minimap_tiles::dsl::*;
|
|
use diesel::dsl::count_star;
|
|
|
|
let mut conn = self.establish_connection()?;
|
|
let total = minimap_tiles.select(count_star()).first(&mut conn)?;
|
|
|
|
Ok(total)
|
|
}
|
|
|
|
/// Get total storage size statistics
|
|
pub fn get_storage_stats(&self) -> Result<StorageStats, MinimapDatabaseError> {
|
|
let mut conn = self.establish_connection()?;
|
|
|
|
use crate::schema::minimap_tiles::dsl::*;
|
|
let tiles = minimap_tiles.load::<MinimapTileRecord>(&mut conn)?;
|
|
|
|
let mut stats = StorageStats::default();
|
|
for tile in tiles {
|
|
stats.total_original_size += tile.original_file_size.unwrap_or(0) as i64;
|
|
stats.total_webp_512 += tile.webp_512_size as i64;
|
|
stats.total_webp_256 += tile.webp_256_size as i64;
|
|
stats.total_webp_128 += tile.webp_128_size as i64;
|
|
stats.total_webp_64 += tile.webp_64_size as i64;
|
|
stats.tile_count += 1;
|
|
}
|
|
|
|
Ok(stats)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Default)]
|
|
pub struct StorageStats {
|
|
pub tile_count: i64,
|
|
pub total_original_size: i64,
|
|
pub total_webp_512: i64,
|
|
pub total_webp_256: i64,
|
|
pub total_webp_128: i64,
|
|
pub total_webp_64: i64,
|
|
}
|
|
|
|
impl StorageStats {
|
|
pub fn total_webp_size(&self) -> i64 {
|
|
self.total_webp_512 + self.total_webp_256 + self.total_webp_128 + self.total_webp_64
|
|
}
|
|
|
|
pub fn compression_ratio(&self) -> f64 {
|
|
if self.total_original_size == 0 {
|
|
return 0.0;
|
|
}
|
|
(self.total_webp_size() as f64 / self.total_original_size as f64) * 100.0
|
|
}
|
|
}
|