Aniworld/src/server/services/config_service.py
2025-10-12 18:05:31 +02:00

981 lines
38 KiB
Python

"""
User Preferences and Settings Persistence Manager
This module provides user preferences management, settings persistence,
and customization options for the AniWorld web interface.
"""
import json
import os
from typing import Dict, Any, Optional
from datetime import datetime
from flask import Blueprint, request, jsonify, session
class UserPreferencesManager:
"""Manages user preferences and settings persistence."""
def __init__(self, app=None):
self.app = app
self.preferences_file = 'data/user_preferences.json'
self.preferences = {} # Initialize preferences attribute
self.default_preferences = {
'ui': {
'theme': 'auto', # 'light', 'dark', 'auto'
'density': 'comfortable', # 'compact', 'comfortable', 'spacious'
'language': 'en',
'animations_enabled': True,
'sidebar_collapsed': False,
'grid_view': True,
'items_per_page': 20
},
'downloads': {
'auto_download': False,
'download_quality': 'best',
'concurrent_downloads': 3,
'retry_failed': True,
'notification_sound': True,
'auto_organize': True
},
'notifications': {
'browser_notifications': True,
'email_notifications': False,
'webhook_notifications': False,
'notification_types': {
'download_complete': True,
'download_error': True,
'series_updated': False,
'system_alerts': True
}
},
'keyboard_shortcuts': {
'enabled': True,
'shortcuts': {
'search': 'ctrl+f',
'download': 'ctrl+d',
'refresh': 'f5',
'select_all': 'ctrl+a',
'help': 'f1',
'settings': 'ctrl+comma'
}
},
'advanced': {
'debug_mode': False,
'performance_mode': False,
'cache_enabled': True,
'auto_backup': True,
'log_level': 'info'
}
}
# Initialize with defaults if no app provided
if app is None:
self.preferences = self.default_preferences.copy()
else:
self.init_app(app)
def init_app(self, app):
"""Initialize with Flask app."""
self.app = app
self.preferences_file = os.path.join(app.instance_path, 'data/user_preferences.json')
# Ensure instance path exists
os.makedirs(app.instance_path, exist_ok=True)
# Load or create preferences file
self.load_preferences()
def load_preferences(self) -> Dict[str, Any]:
"""Load preferences from file."""
try:
if os.path.exists(self.preferences_file):
with open(self.preferences_file, 'r', encoding='utf-8') as f:
loaded_prefs = json.load(f)
# Merge with defaults to ensure all keys exist
self.preferences = self.merge_preferences(self.default_preferences, loaded_prefs)
else:
self.preferences = self.default_preferences.copy()
self.save_preferences()
except Exception as e:
print(f"Error loading preferences: {e}")
self.preferences = self.default_preferences.copy()
return self.preferences
def save_preferences(self) -> bool:
"""Save preferences to file."""
try:
with open(self.preferences_file, 'w', encoding='utf-8') as f:
json.dump(self.preferences, f, indent=2, ensure_ascii=False)
return True
except Exception as e:
print(f"Error saving preferences: {e}")
return False
def merge_preferences(self, defaults: Dict, user_prefs: Dict) -> Dict:
"""Recursively merge user preferences with defaults."""
result = defaults.copy()
for key, value in user_prefs.items():
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
result[key] = self.merge_preferences(result[key], value)
else:
result[key] = value
return result
def get_preference(self, key: str, default: Any = None) -> Any:
"""Get a specific preference using dot notation (e.g., 'ui.theme')."""
keys = key.split('.')
value = self.preferences
try:
for k in keys:
value = value[k]
return value
except (KeyError, TypeError):
return default
def set_preference(self, key: str, value: Any) -> bool:
"""Set a specific preference using dot notation."""
keys = key.split('.')
pref_dict = self.preferences
try:
# Navigate to parent dictionary
for k in keys[:-1]:
if k not in pref_dict:
pref_dict[k] = {}
pref_dict = pref_dict[k]
# Set the value
pref_dict[keys[-1]] = value
# Save to file
return self.save_preferences()
except Exception as e:
print(f"Error setting preference {key}: {e}")
return False
def reset_preferences(self) -> bool:
"""Reset all preferences to defaults."""
self.preferences = self.default_preferences.copy()
return self.save_preferences()
def export_preferences(self) -> str:
"""Export preferences as JSON string."""
try:
return json.dumps(self.preferences, indent=2, ensure_ascii=False)
except Exception as e:
print(f"Error exporting preferences: {e}")
return "{}"
def import_preferences(self, json_data: str) -> bool:
"""Import preferences from JSON string."""
try:
imported_prefs = json.loads(json_data)
self.preferences = self.merge_preferences(self.default_preferences, imported_prefs)
return self.save_preferences()
except Exception as e:
print(f"Error importing preferences: {e}")
return False
def get_user_session_preferences(self) -> Dict[str, Any]:
"""Get preferences for current user session."""
# For now, return global preferences
# In the future, could be user-specific
return self.preferences.copy()
def get_preferences_js(self):
"""Generate JavaScript code for preferences management."""
return f"""
// AniWorld User Preferences Manager
class UserPreferencesManager {{
constructor() {{
this.preferences = {json.dumps(self.preferences)};
this.defaultPreferences = {json.dumps(self.default_preferences)};
this.changeListeners = new Map();
this.init();
}}
init() {{
this.loadFromServer();
this.applyPreferences();
this.setupPreferencesUI();
this.setupAutoSave();
}}
async loadFromServer() {{
try {{
const response = await fetch('/api/preferences');
if (response.ok) {{
this.preferences = await response.json();
this.applyPreferences();
}}
}} catch (error) {{
console.error('Error loading preferences:', error);
}}
}}
async saveToServer() {{
try {{
const response = await fetch('/api/preferences', {{
method: 'PUT',
headers: {{
'Content-Type': 'application/json'
}},
body: JSON.stringify(this.preferences)
}});
if (!response.ok) {{
console.error('Error saving preferences to server');
}}
}} catch (error) {{
console.error('Error saving preferences:', error);
}}
}}
get(key, defaultValue = null) {{
const keys = key.split('.');
let value = this.preferences;
try {{
for (const k of keys) {{
value = value[k];
}}
return value !== undefined ? value : defaultValue;
}} catch (error) {{
return defaultValue;
}}
}}
set(key, value, save = true) {{
const keys = key.split('.');
let obj = this.preferences;
// Navigate to parent object
for (let i = 0; i < keys.length - 1; i++) {{
const k = keys[i];
if (!obj[k] || typeof obj[k] !== 'object') {{
obj[k] = {{}};
}}
obj = obj[k];
}}
// Set the value
const lastKey = keys[keys.length - 1];
const oldValue = obj[lastKey];
obj[lastKey] = value;
// Apply the change immediately
this.applyPreference(key, value);
// Notify listeners
this.notifyChangeListeners(key, value, oldValue);
// Save to server
if (save) {{
this.saveToServer();
}}
// Store in localStorage as backup
localStorage.setItem('aniworld_preferences', JSON.stringify(this.preferences));
}}
applyPreferences() {{
// Apply all preferences
this.applyTheme();
this.applyUIPreferences();
this.applyKeyboardShortcuts();
this.applyNotificationSettings();
}}
applyPreference(key, value) {{
// Apply individual preference change
if (key.startsWith('ui.theme')) {{
this.applyTheme();
}} else if (key.startsWith('ui.')) {{
this.applyUIPreferences();
}} else if (key.startsWith('keyboard_shortcuts.')) {{
this.applyKeyboardShortcuts();
}} else if (key.startsWith('notifications.')) {{
this.applyNotificationSettings();
}}
}}
applyTheme() {{
const theme = this.get('ui.theme', 'auto');
const html = document.documentElement;
html.classList.remove('theme-light', 'theme-dark');
if (theme === 'auto') {{
// Use system preference
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
html.classList.add(prefersDark ? 'theme-dark' : 'theme-light');
}} else {{
html.classList.add(`theme-${{theme}}`);
}}
// Update Bootstrap theme
html.setAttribute('data-bs-theme', theme === 'dark' || (theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches) ? 'dark' : 'light');
}}
applyUIPreferences() {{
const density = this.get('ui.density', 'comfortable');
const animations = this.get('ui.animations_enabled', true);
const gridView = this.get('ui.grid_view', true);
// Apply UI density
document.body.className = document.body.className.replace(/density-\\w+/g, '');
document.body.classList.add(`density-${{density}}`);
// Apply animations
if (!animations) {{
document.body.classList.add('no-animations');
}} else {{
document.body.classList.remove('no-animations');
}}
// Apply view mode
const viewToggle = document.querySelector('.view-toggle');
if (viewToggle) {{
viewToggle.classList.toggle('grid-view', gridView);
viewToggle.classList.toggle('list-view', !gridView);
}}
}}
applyKeyboardShortcuts() {{
const enabled = this.get('keyboard_shortcuts.enabled', true);
const shortcuts = this.get('keyboard_shortcuts.shortcuts', {{}});
if (window.keyboardManager) {{
window.keyboardManager.setEnabled(enabled);
window.keyboardManager.updateShortcuts(shortcuts);
}}
}}
applyNotificationSettings() {{
const browserNotifications = this.get('notifications.browser_notifications', true);
// Request notification permission if needed
if (browserNotifications && 'Notification' in window && Notification.permission === 'default') {{
Notification.requestPermission();
}}
}}
setupPreferencesUI() {{
this.createSettingsModal();
this.bindSettingsEvents();
}}
createSettingsModal() {{
const existingModal = document.getElementById('preferences-modal');
if (existingModal) return;
const modal = document.createElement('div');
modal.id = 'preferences-modal';
modal.className = 'modal fade';
modal.innerHTML = `
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Preferences</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<ul class="nav nav-tabs mb-3">
<li class="nav-item">
<a class="nav-link active" data-bs-toggle="tab" href="#ui-tab">Interface</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#downloads-tab">Downloads</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#notifications-tab">Notifications</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#shortcuts-tab">Shortcuts</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#advanced-tab">Advanced</a>
</li>
</ul>
<div class="tab-content">
${{this.createUITab()}}
${{this.createDownloadsTab()}}
${{this.createNotificationsTab()}}
${{this.createShortcutsTab()}}
${{this.createAdvancedTab()}}
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-outline-danger" id="reset-preferences">Reset to Defaults</button>
<button type="button" class="btn btn-outline-primary" id="export-preferences">Export</button>
<button type="button" class="btn btn-outline-primary" id="import-preferences">Import</button>
<button type="button" class="btn btn-primary" id="save-preferences">Save</button>
</div>
</div>
</div>
`;
document.body.appendChild(modal);
}}
createUITab() {{
return `
<div class="tab-pane fade show active" id="ui-tab">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Theme</label>
<select class="form-select" id="pref-theme">
<option value="auto">Auto (System)</option>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">UI Density</label>
<select class="form-select" id="pref-density">
<option value="compact">Compact</option>
<option value="comfortable">Comfortable</option>
<option value="spacious">Spacious</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Language</label>
<select class="form-select" id="pref-language">
<option value="en">English</option>
<option value="de">German</option>
<option value="ja">Japanese</option>
</select>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Items per page</label>
<select class="form-select" id="pref-items-per-page">
<option value="10">10</option>
<option value="20">20</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="pref-animations">
<label class="form-check-label" for="pref-animations">
Enable animations
</label>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="pref-grid-view">
<label class="form-check-label" for="pref-grid-view">
Default to grid view
</label>
</div>
</div>
</div>
</div>
`;
}}
createDownloadsTab() {{
return `
<div class="tab-pane fade" id="downloads-tab">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Download Quality</label>
<select class="form-select" id="pref-download-quality">
<option value="best">Best Available</option>
<option value="1080p">1080p</option>
<option value="720p">720p</option>
<option value="480p">480p</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Concurrent Downloads</label>
<input type="number" class="form-control" id="pref-concurrent-downloads" min="1" max="10">
</div>
</div>
<div class="col-md-6">
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="pref-auto-download">
<label class="form-check-label" for="pref-auto-download">
Auto-download new episodes
</label>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="pref-retry-failed">
<label class="form-check-label" for="pref-retry-failed">
Retry failed downloads
</label>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="pref-auto-organize">
<label class="form-check-label" for="pref-auto-organize">
Auto-organize downloads
</label>
</div>
</div>
</div>
</div>
`;
}}
createNotificationsTab() {{
return `
<div class="tab-pane fade" id="notifications-tab">
<div class="row">
<div class="col-md-6">
<h6>General</h6>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="pref-browser-notifications">
<label class="form-check-label" for="pref-browser-notifications">
Browser notifications
</label>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="pref-notification-sound">
<label class="form-check-label" for="pref-notification-sound">
Notification sound
</label>
</div>
</div>
<div class="col-md-6">
<h6>Notification Types</h6>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="pref-notify-download-complete">
<label class="form-check-label" for="pref-notify-download-complete">
Download complete
</label>
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="pref-notify-download-error">
<label class="form-check-label" for="pref-notify-download-error">
Download errors
</label>
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="pref-notify-series-updated">
<label class="form-check-label" for="pref-notify-series-updated">
Series updates
</label>
</div>
</div>
</div>
</div>
`;
}}
createShortcutsTab() {{
return `
<div class="tab-pane fade" id="shortcuts-tab">
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="pref-shortcuts-enabled">
<label class="form-check-label" for="pref-shortcuts-enabled">
Enable keyboard shortcuts
</label>
</div>
<div id="shortcuts-list">
<!-- Shortcuts will be populated dynamically -->
</div>
</div>
`;
}}
createAdvancedTab() {{
return `
<div class="tab-pane fade" id="advanced-tab">
<div class="row">
<div class="col-md-6">
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="pref-debug-mode">
<label class="form-check-label" for="pref-debug-mode">
Debug mode
</label>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="pref-performance-mode">
<label class="form-check-label" for="pref-performance-mode">
Performance mode
</label>
</div>
</div>
<div class="col-md-6">
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="pref-cache-enabled">
<label class="form-check-label" for="pref-cache-enabled">
Enable caching
</label>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="pref-auto-backup">
<label class="form-check-label" for="pref-auto-backup">
Auto backup settings
</label>
</div>
</div>
</div>
</div>
`;
}}
bindSettingsEvents() {{
// Theme system preference listener
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {{
if (this.get('ui.theme') === 'auto') {{
this.applyTheme();
}}
}});
// Settings modal events will be bound when modal is shown
document.addEventListener('show.bs.modal', (e) => {{
if (e.target.id === 'preferences-modal') {{
this.populateSettingsForm();
}}
}});
}}
populateSettingsForm() {{
// Populate form fields with current preferences
const fields = [
{{ id: 'pref-theme', key: 'ui.theme' }},
{{ id: 'pref-density', key: 'ui.density' }},
{{ id: 'pref-language', key: 'ui.language' }},
{{ id: 'pref-items-per-page', key: 'ui.items_per_page' }},
{{ id: 'pref-animations', key: 'ui.animations_enabled' }},
{{ id: 'pref-grid-view', key: 'ui.grid_view' }},
{{ id: 'pref-download-quality', key: 'downloads.download_quality' }},
{{ id: 'pref-concurrent-downloads', key: 'downloads.concurrent_downloads' }},
{{ id: 'pref-auto-download', key: 'downloads.auto_download' }},
{{ id: 'pref-retry-failed', key: 'downloads.retry_failed' }},
{{ id: 'pref-auto-organize', key: 'downloads.auto_organize' }},
{{ id: 'pref-browser-notifications', key: 'notifications.browser_notifications' }},
{{ id: 'pref-notification-sound', key: 'downloads.notification_sound' }},
{{ id: 'pref-shortcuts-enabled', key: 'keyboard_shortcuts.enabled' }},
{{ id: 'pref-debug-mode', key: 'advanced.debug_mode' }},
{{ id: 'pref-performance-mode', key: 'advanced.performance_mode' }},
{{ id: 'pref-cache-enabled', key: 'advanced.cache_enabled' }},
{{ id: 'pref-auto-backup', key: 'advanced.auto_backup' }}
];
fields.forEach(field => {{
const element = document.getElementById(field.id);
if (element) {{
const value = this.get(field.key);
if (element.type === 'checkbox') {{
element.checked = value;
}} else {{
element.value = value;
}}
}}
}});
}}
setupAutoSave() {{
// Auto-save preferences on change
document.addEventListener('change', (e) => {{
if (e.target.id && e.target.id.startsWith('pref-')) {{
this.saveFormValue(e.target);
}}
}});
}}
saveFormValue(element) {{
const keyMap = {{
'pref-theme': 'ui.theme',
'pref-density': 'ui.density',
'pref-language': 'ui.language',
'pref-items-per-page': 'ui.items_per_page',
'pref-animations': 'ui.animations_enabled',
'pref-grid-view': 'ui.grid_view',
'pref-download-quality': 'downloads.download_quality',
'pref-concurrent-downloads': 'downloads.concurrent_downloads',
'pref-auto-download': 'downloads.auto_download',
'pref-retry-failed': 'downloads.retry_failed',
'pref-auto-organize': 'downloads.auto_organize',
'pref-browser-notifications': 'notifications.browser_notifications',
'pref-notification-sound': 'downloads.notification_sound',
'pref-shortcuts-enabled': 'keyboard_shortcuts.enabled',
'pref-debug-mode': 'advanced.debug_mode',
'pref-performance-mode': 'advanced.performance_mode',
'pref-cache-enabled': 'advanced.cache_enabled',
'pref-auto-backup': 'advanced.auto_backup'
}};
const key = keyMap[element.id];
if (key) {{
let value = element.type === 'checkbox' ? element.checked : element.value;
if (element.type === 'number') {{
value = parseInt(value, 10);
}}
this.set(key, value);
}}
}}
showPreferences() {{
const modal = document.getElementById('preferences-modal');
if (modal) {{
const bsModal = new bootstrap.Modal(modal);
bsModal.show();
}}
}}
onPreferenceChange(key, callback) {{
if (!this.changeListeners.has(key)) {{
this.changeListeners.set(key, []);
}}
this.changeListeners.get(key).push(callback);
}}
notifyChangeListeners(key, newValue, oldValue) {{
const listeners = this.changeListeners.get(key) || [];
listeners.forEach(callback => {{
try {{
callback(newValue, oldValue, key);
}} catch (error) {{
console.error('Error in preference change listener:', error);
}}
}});
}}
reset() {{
this.preferences = JSON.parse(JSON.stringify(this.defaultPreferences));
this.applyPreferences();
this.saveToServer();
localStorage.removeItem('aniworld_preferences');
}}
export() {{
const data = JSON.stringify(this.preferences, null, 2);
const blob = new Blob([data], {{ type: 'application/json' }});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'aniworld_preferences.json';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}}
import(file) {{
return new Promise((resolve, reject) => {{
const reader = new FileReader();
reader.onload = (e) => {{
try {{
const imported = JSON.parse(e.target.result);
this.preferences = this.mergePreferences(this.defaultPreferences, imported);
this.applyPreferences();
this.saveToServer();
resolve(true);
}} catch (error) {{
reject(error);
}}
}};
reader.onerror = reject;
reader.readAsText(file);
}});
}}
mergePreferences(defaults, userPrefs) {{
const result = {{ ...defaults }};
for (const [key, value] of Object.entries(userPrefs)) {{
if (key in result && typeof result[key] === 'object' && typeof value === 'object') {{
result[key] = this.mergePreferences(result[key], value);
}} else {{
result[key] = value;
}}
}}
return result;
}}
}}
// Initialize preferences when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {{
window.preferencesManager = new UserPreferencesManager();
}});
"""
def get_css(self):
"""Generate CSS for user preferences."""
return """
/* User Preferences Styles */
.density-compact {
--spacing: 0.5rem;
--font-size: 0.875rem;
}
.density-comfortable {
--spacing: 1rem;
--font-size: 1rem;
}
.density-spacious {
--spacing: 1.5rem;
--font-size: 1.125rem;
}
.no-animations * {
animation-duration: 0s !important;
transition-duration: 0s !important;
}
.theme-light {
--bs-body-bg: #ffffff;
--bs-body-color: #212529;
--bs-primary: #0d6efd;
}
.theme-dark {
--bs-body-bg: #121212;
--bs-body-color: #e9ecef;
--bs-primary: #0d6efd;
}
#preferences-modal .nav-tabs {
border-bottom: 1px solid var(--bs-border-color);
}
#preferences-modal .tab-pane {
min-height: 300px;
}
.preference-group {
margin-bottom: 2rem;
}
.preference-group h6 {
color: var(--bs-secondary);
margin-bottom: 1rem;
}
/* Responsive preferences modal */
@media (max-width: 768px) {
#preferences-modal .modal-dialog {
max-width: 95vw;
margin: 0.5rem;
}
#preferences-modal .nav-tabs {
flex-wrap: wrap;
}
#preferences-modal .nav-link {
font-size: 0.875rem;
padding: 0.5rem;
}
}
"""
# Create the preferences API blueprint
preferences_bp = Blueprint('preferences', __name__, url_prefix='/api')
# Global preferences manager instance
preferences_manager = UserPreferencesManager()
@preferences_bp.route('/preferences', methods=['GET'])
def get_preferences():
"""Get user preferences."""
try:
return jsonify(preferences_manager.get_user_session_preferences())
except Exception as e:
return jsonify({'error': str(e)}), 500
@preferences_bp.route('/preferences', methods=['PUT'])
def update_preferences():
"""Update user preferences."""
try:
data = request.get_json()
preferences_manager.preferences = preferences_manager.merge_preferences(
preferences_manager.default_preferences,
data
)
if preferences_manager.save_preferences():
return jsonify({'success': True, 'message': 'Preferences updated'})
else:
return jsonify({'error': 'Failed to save preferences'}), 500
except Exception as e:
return jsonify({'error': str(e)}), 500
@preferences_bp.route('/preferences/<key>', methods=['GET'])
def get_preference(key):
"""Get a specific preference."""
try:
value = preferences_manager.get_preference(key)
return jsonify({'key': key, 'value': value})
except Exception as e:
return jsonify({'error': str(e)}), 500
@preferences_bp.route('/preferences/<key>', methods=['PUT'])
def set_preference(key):
"""Set a specific preference."""
try:
data = request.get_json()
value = data.get('value')
if preferences_manager.set_preference(key, value):
return jsonify({'success': True, 'key': key, 'value': value})
else:
return jsonify({'error': 'Failed to set preference'}), 500
except Exception as e:
return jsonify({'error': str(e)}), 500
@preferences_bp.route('/preferences/reset', methods=['POST'])
def reset_preferences():
"""Reset preferences to defaults."""
try:
if preferences_manager.reset_preferences():
return jsonify({'success': True, 'message': 'Preferences reset to defaults'})
else:
return jsonify({'error': 'Failed to reset preferences'}), 500
except Exception as e:
return jsonify({'error': str(e)}), 500
@preferences_bp.route('/preferences/export', methods=['GET'])
def export_preferences():
"""Export preferences as JSON file."""
try:
from flask import Response
json_data = preferences_manager.export_preferences()
return Response(
json_data,
mimetype='application/json',
headers={'Content-Disposition': 'attachment; filename=aniworld_preferences.json'}
)
except Exception as e:
return jsonify({'error': str(e)}), 500
@preferences_bp.route('/preferences/import', methods=['POST'])
def import_preferences():
"""Import preferences from JSON file."""
try:
if 'file' not in request.files:
return jsonify({'error': 'No file provided'}), 400
file = request.files['file']
if file.filename == '':
return jsonify({'error': 'No file selected'}), 400
json_data = file.read().decode('utf-8')
if preferences_manager.import_preferences(json_data):
return jsonify({'success': True, 'message': 'Preferences imported successfully'})
else:
return jsonify({'error': 'Failed to import preferences'}), 500
except Exception as e:
return jsonify({'error': str(e)}), 500