MC / mod_manager.py
smodusermc's picture
Rename mod_manger.py to mod_manager.py
605a0dc verified
Raw History Blame Contribute Delete
8.01 kB
import os
import shutil
import hashlib
import json
import zipfile
from pathlib import Path
from datetime import datetime
from typing import List, Dict, Optional
from werkzeug.utils import secure_filename
from config import config
import humanize
class ModManager:
def __init__(self):
self.mods_dir = config.MODS_DIR
self.mods_data_file = config.SERVER_DIR / "mods_data.json"
self.mods_data = self._load_data()
self.allowed_extensions = {'.jar'}
# Ensure mods directory exists
self.mods_dir.mkdir(parents=True, exist_ok=True)
def _load_data(self) -> Dict:
"""Load mods metadata"""
if self.mods_data_file.exists():
try:
with open(self.mods_data_file, 'r') as f:
return json.load(f)
except:
pass
return {'mods': {}}
def _save_data(self):
"""Save mods metadata"""
with open(self.mods_data_file, 'w') as f:
json.dump(self.mods_data, f, indent=2)
def _get_file_hash(self, filepath: Path) -> str:
"""Get MD5 hash of file"""
hash_md5 = hashlib.md5()
with open(filepath, 'rb') as f:
for chunk in iter(lambda: f.read(4096), b''):
hash_md5.update(chunk)
return hash_md5.hexdigest()
def get_installed_mods(self) -> List[Dict]:
"""Get list of installed mods"""
mods = []
for file in self.mods_dir.iterdir():
if file.suffix.lower() == '.jar':
mod_info = self._get_mod_info(file)
mods.append(mod_info)
return sorted(mods, key=lambda x: x['name'].lower())
def _get_mod_info(self, filepath: Path) -> Dict:
"""Extract mod information"""
stat = filepath.stat()
file_hash = self._get_file_hash(filepath)
# Check if we have cached metadata
cached = self.mods_data['mods'].get(file_hash, {})
# Try to read mod info from jar
mod_name = filepath.stem
mod_version = "Unknown"
mod_description = ""
mod_authors = []
try:
with zipfile.ZipFile(filepath, 'r') as jar:
# Try fabric.mod.json
if 'fabric.mod.json' in jar.namelist():
with jar.open('fabric.mod.json') as f:
mod_meta = json.load(f)
mod_name = mod_meta.get('name', mod_name)
mod_version = mod_meta.get('version', mod_version)
mod_description = mod_meta.get('description', '')
mod_authors = mod_meta.get('authors', [])
if isinstance(mod_authors, list):
mod_authors = [a if isinstance(a, str) else a.get('name', '') for a in mod_authors]
except:
pass
# Check if mod is disabled
is_disabled = filepath.name.endswith('.disabled')
return {
'filename': filepath.name,
'name': mod_name,
'version': mod_version,
'description': mod_description,
'authors': mod_authors,
'size': humanize.naturalsize(stat.st_size),
'size_bytes': stat.st_size,
'modified': datetime.fromtimestamp(stat.st_mtime).isoformat(),
'hash': file_hash,
'enabled': not is_disabled,
'notes': cached.get('notes', '')
}
def upload_mod(self, file) -> Dict:
"""Upload a new mod"""
if not file or not file.filename:
return {'success': False, 'message': 'No file provided'}
filename = secure_filename(file.filename)
if not filename.endswith('.jar'):
return {'success': False, 'message': 'Only .jar files are allowed'}
filepath = self.mods_dir / filename
# Check if file already exists
if filepath.exists():
return {'success': False, 'message': f'Mod {filename} already exists'}
try:
file.save(str(filepath))
mod_info = self._get_mod_info(filepath)
# Store metadata
self.mods_data['mods'][mod_info['hash']] = {
'uploaded': datetime.now().isoformat(),
'original_name': filename,
'notes': ''
}
self._save_data()
return {
'success': True,
'message': f'Mod {mod_info["name"]} uploaded successfully',
'mod': mod_info
}
except Exception as e:
return {'success': False, 'message': str(e)}
def delete_mod(self, filename: str) -> Dict:
"""Delete a mod"""
filepath = self.mods_dir / secure_filename(filename)
if not filepath.exists():
return {'success': False, 'message': 'Mod not found'}
try:
# Get hash before deleting
file_hash = self._get_file_hash(filepath)
filepath.unlink()
# Remove from metadata
if file_hash in self.mods_data['mods']:
del self.mods_data['mods'][file_hash]
self._save_data()
return {'success': True, 'message': f'Mod {filename} deleted'}
except Exception as e:
return {'success': False, 'message': str(e)}
def toggle_mod(self, filename: str) -> Dict:
"""Enable/disable a mod"""
filename = secure_filename(filename)
if filename.endswith('.disabled'):
# Enable
disabled_path = self.mods_dir / filename
enabled_path = self.mods_dir / filename[:-9] # Remove .disabled
if not disabled_path.exists():
return {'success': False, 'message': 'Mod not found'}
disabled_path.rename(enabled_path)
return {'success': True, 'message': f'Mod enabled: {enabled_path.name}', 'enabled': True}
else:
# Disable
enabled_path = self.mods_dir / filename
disabled_path = self.mods_dir / (filename + '.disabled')
if not enabled_path.exists():
return {'success': False, 'message': 'Mod not found'}
enabled_path.rename(disabled_path)
return {'success': True, 'message': f'Mod disabled: {filename}', 'enabled': False}
def get_mod_details(self, filename: str) -> Optional[Dict]:
"""Get detailed mod information"""
filepath = self.mods_dir / secure_filename(filename)
if filepath.exists():
return self._get_mod_info(filepath)
return None
def update_notes(self, filename: str, notes: str) -> Dict:
"""Update mod notes"""
filepath = self.mods_dir / secure_filename(filename)
if not filepath.exists():
return {'success': False, 'message': 'Mod not found'}
file_hash = self._get_file_hash(filepath)
if file_hash not in self.mods_data['mods']:
self.mods_data['mods'][file_hash] = {}
self.mods_data['mods'][file_hash]['notes'] = notes
self._save_data()
return {'success': True, 'message': 'Notes updated'}
def get_total_size(self) -> str:
"""Get total size of all mods"""
total = sum(f.stat().st_size for f in self.mods_dir.iterdir() if f.is_file())
return humanize.naturalsize(total)
def export_mod_list(self) -> List[Dict]:
"""Export mod list for backup"""
mods = self.get_installed_mods()
return [{
'name': m['name'],
'version': m['version'],
'filename': m['filename'],
'hash': m['hash']
} for m in mods]