#
Snapshots
Session persistence and state management in Sonora v1.2.8.
#
Table of Contents
Overview Creating Snapshots Loading Snapshots Snapshot Management Auto-Save Backup & Recovery Security Examples
#
Overview
Snapshots capture the complete state of your music bot at any point in time, allowing you to:
- Save sessions for later restoration
- Backup configurations before major changes
- Recover from crashes or unexpected shutdowns
- Share setups between different instances
- Version control your bot's state
#
What Gets Saved
Snapshots include:
- Current track and playback position
- Queue contents (upcoming and history)
- Player settings (volume, filters, loop mode)
- Autoplay configuration and state
- Plugin states and configurations
- Guild-specific settings
#
Creating Snapshots
#
Manual Snapshots
from sonora import SonoraClient
client = SonoraClient(...)
player = await client.get_player(guild_id)
# Create snapshot
snapshot = await player.create_snapshot()
# Save to file
await snapshot.save_to_file("my_session.json")
# Or get as dictionary
snapshot_data = snapshot.to_dict()
#
CLI Snapshots
# Save current session
sonoractl snapshot save --name "party_session"
# Save with custom path
sonoractl snapshot save --output "/backups/session_$(date +%Y%m%d).json"
#
Automatic Snapshots
# Enable auto-save
player.snapshots.auto_save = True
player.snapshots.auto_save_interval = 300 # Every 5 minutes
# Custom save location
player.snapshots.save_path = "./snapshots/"
#
Loading Snapshots
#
From File
# Load from file
snapshot = await Snapshot.load_from_file("my_session.json")
# Restore to player
await player.load_snapshot(snapshot)
# Or restore specific parts
await player.load_queue_snapshot(snapshot)
await player.load_filters_snapshot(snapshot)
#
CLI Restore
# Restore session
sonoractl snapshot restore "party_session"
# Restore to specific guild
sonoractl snapshot restore "party_session" --guild-id 123456789
# Preview before restoring
sonoractl snapshot info "party_session"
#
Selective Restore
# Load only queue state
await player.load_snapshot(snapshot, components=["queue"])
# Load multiple components
await player.load_snapshot(snapshot, components=[
"queue",
"filters",
"autoplay"
])
# Available components:
# - "queue": Upcoming tracks and history
# - "current": Current track and position
# - "filters": Audio filters and settings
# - "autoplay": Autoplay configuration
# - "settings": Player settings (volume, loop mode)
#
Snapshot Management
#
Listing Snapshots
# Get all snapshots
snapshots = await player.snapshots.list_snapshots()
# Filter by criteria
recent_snapshots = await player.snapshots.list_snapshots(
since=datetime.now() - timedelta(hours=24)
)
# Sort by different criteria
snapshots_by_size = sorted(snapshots, key=lambda s: s.size)
snapshots_by_date = sorted(snapshots, key=lambda s: s.created_at, reverse=True)
#
CLI Management
# List all snapshots
sonoractl snapshot list
# Show snapshot details
sonoractl snapshot info "party_session"
# Delete old snapshots
sonoractl snapshot delete "old_session"
# Clean up snapshots older than 7 days
sonoractl snapshot cleanup --older-than 7d
#
Snapshot Metadata
# Access snapshot information
print(f"Created: {snapshot.created_at}")
print(f"Size: {snapshot.size} bytes")
print(f"Version: {snapshot.version}")
print(f"Guild ID: {snapshot.guild_id}")
# Custom metadata
snapshot.metadata = {
"description": "End of year party",
"tags": ["party", "christmas", "2024"],
"quality": "high"
}
#
Auto-Save
#
Configuration
# Enable auto-save
player.snapshots.auto_save_enabled = True
# Save interval (seconds)
player.snapshots.auto_save_interval = 600 # 10 minutes
# Maximum snapshots to keep
player.snapshots.max_snapshots = 50
# Save path
player.snapshots.save_directory = "./auto_snapshots/"
#
Auto-Save Events
from sonora import event_manager, EventType
@event_manager.on(EventType.SNAPSHOT_AUTO_SAVED)
async def on_auto_save(event):
snapshot = event.data['snapshot']
print(f"Auto-saved snapshot: {snapshot.name}")
@event_manager.on(EventType.SNAPSHOT_AUTO_CLEANUP)
async def on_cleanup(event):
deleted_count = event.data['deleted_count']
print(f"Cleaned up {deleted_count} old snapshots")
#
Conditional Auto-Save
# Only save when queue has tracks
player.snapshots.auto_save_condition = lambda: len(player.queue.upcoming) > 0
# Save only during active playback
player.snapshots.auto_save_condition = lambda: player.current is not None
# Custom condition
def should_save():
return (len(player.queue.upcoming) > 5 and
player.current is not None and
not player.is_paused)
player.snapshots.auto_save_condition = should_save
#
Backup & Recovery
#
Full System Backup
# Backup entire bot state
backup = await client.create_full_backup()
# Save backup
await backup.save_to_file("full_backup.json")
# Restore from backup
restored_backup = await Backup.load_from_file("full_backup.json")
await client.restore_from_backup(restored_backup)
#
Incremental Backups
# Create incremental backup
incremental = await player.create_incremental_backup(
since_last_backup=True
)
# Differential backup
differential = await player.create_differential_backup(
base_snapshot=previous_snapshot
)
#
Disaster Recovery
# Emergency restore from any available snapshot
async def emergency_restore(player, preferred_snapshot=None):
try:
if preferred_snapshot:
await player.load_snapshot(preferred_snapshot)
return True
except Exception:
pass
# Try to find any recent snapshot
snapshots = await player.snapshots.list_snapshots(
limit=5,
sort_by="created_at",
reverse=True
)
for snapshot in snapshots:
try:
await player.load_snapshot(snapshot)
print(f"Restored from backup: {snapshot.name}")
return True
except Exception as e:
print(f"Failed to restore {snapshot.name}: {e}")
continue
# Last resort: reset to clean state
await player.reset_to_defaults()
print("Reset to clean state")
return False
#
Security
#
Encrypted Snapshots
# Create encrypted snapshot
encrypted_snapshot = await player.create_encrypted_snapshot(
encryption_key="your-secure-key"
)
# Save encrypted
await encrypted_snapshot.save_encrypted("secure_snapshot.enc")
# Load and decrypt
decrypted_snapshot = await Snapshot.load_encrypted(
"secure_snapshot.enc",
decryption_key="your-secure-key"
)
#
Access Control
# Restrict snapshot access
player.snapshots.require_permission = True
player.snapshots.allowed_users = ["admin_user_id"]
# Audit snapshot operations
@event_manager.on(EventType.SNAPSHOT_CREATED)
async def audit_snapshot(event):
snapshot = event.data['snapshot']
user = event.data['user']
await log_audit_event(
action="snapshot_created",
resource=snapshot.name,
user=user,
details={"size": snapshot.size}
)
#
Integrity Verification
# Enable integrity checking
player.snapshots.enable_integrity_check = True
# Verify snapshot integrity
is_valid = await snapshot.verify_integrity()
if not is_valid:
raise SecurityError("Snapshot integrity check failed")
# Automatic integrity monitoring
@event_manager.on(EventType.SNAPSHOT_CORRUPTION_DETECTED)
async def on_corruption(event):
snapshot = event.data['snapshot']
await send_alert(f"Snapshot corruption detected: {snapshot.name}")
await snapshot.repair() # Attempt automatic repair
#
Examples
#
Session Management Bot
import discord
from discord.ext import commands
from sonora import SonoraClient
class SessionManager(commands.Cog):
def __init__(self, bot, sonora):
self.bot = bot
self.sonora = sonora
@commands.command()
async def save_session(self, ctx, name: str):
"""Save current session"""
player = await self.sonora.get_player(ctx.guild.id)
snapshot = await player.create_snapshot()
snapshot.name = name
snapshot.description = f"Saved by {ctx.author.name}"
await snapshot.save_to_file(f"sessions/{name}.json")
embed = discord.Embed(
title="š¾ Session Saved",
description=f"Session '{name}' has been saved",
color=0x00ff00
)
embed.add_field(name="Tracks in Queue", value=len(player.queue.upcoming))
embed.add_field(name="Current Track", value=player.current.title if player.current else "None")
await ctx.send(embed=embed)
@commands.command()
async def load_session(self, ctx, name: str):
"""Load a saved session"""
player = await self.sonora.get_player(ctx.guild.id)
try:
snapshot = await Snapshot.load_from_file(f"sessions/{name}.json")
await player.load_snapshot(snapshot)
embed = discord.Embed(
title="š Session Loaded",
description=f"Session '{name}' has been restored",
color=0x00ff00
)
embed.add_field(name="Tracks Restored", value=len(player.queue.upcoming))
embed.add_field(name="Current Track", value=player.current.title if player.current else "None")
await ctx.send(embed=embed)
except FileNotFoundError:
await ctx.send(f"ā Session '{name}' not found")
except Exception as e:
await ctx.send(f"ā Failed to load session: {e}")
@commands.command()
async def list_sessions(self, ctx):
"""List saved sessions"""
import os
sessions_dir = "sessions"
if not os.path.exists(sessions_dir):
await ctx.send("No saved sessions")
return
sessions = [f for f in os.listdir(sessions_dir) if f.endswith('.json')]
if not sessions:
await ctx.send("No saved sessions")
return
embed = discord.Embed(
title="š Saved Sessions",
description="\n".join(f"⢠{s[:-5]}" for s in sessions), # Remove .json
color=0x0099ff
)
await ctx.send(embed=embed)
# Add cog to bot
bot.add_cog(SessionManager(bot, sonora))
#
Auto-Backup System
class AutoBackupManager:
def __init__(self, client):
self.client = client
self.backup_interval = 1800 # 30 minutes
self.max_backups = 24 # Keep 12 hours worth
self.backup_task = None
async def start_auto_backup(self):
"""Start automatic backup system"""
if self.backup_task:
return
self.backup_task = asyncio.create_task(self._backup_loop())
async def stop_auto_backup(self):
"""Stop automatic backup system"""
if self.backup_task:
self.backup_task.cancel()
try:
await self.backup_task
except asyncio.CancelledError:
pass
self.backup_task = None
async def _backup_loop(self):
"""Main backup loop"""
while True:
try:
await asyncio.sleep(self.backup_interval)
await self._perform_backup()
await self._cleanup_old_backups()
except Exception as e:
print(f"Backup failed: {e}")
async def _perform_backup(self):
"""Perform a backup of all active players"""
timestamp = int(time.time())
for guild_id, player in self.client.players.items():
try:
# Only backup if there's activity
if player.current or player.queue.upcoming:
snapshot = await player.create_snapshot()
filename = f"backups/guild_{guild_id}_{timestamp}.json"
await snapshot.save_to_file(filename)
print(f"Backed up guild {guild_id}")
except Exception as e:
print(f"Failed to backup guild {guild_id}: {e}")
async def _cleanup_old_backups(self):
"""Clean up old backup files"""
import os
from pathlib import Path
backup_dir = Path("backups")
if not backup_dir.exists():
return
# Get all backup files
backup_files = list(backup_dir.glob("*.json"))
backup_files.sort(key=lambda x: x.stat().st_mtime, reverse=True)
# Remove old backups
if len(backup_files) > self.max_backups:
for old_file in backup_files[self.max_backups:]:
old_file.unlink()
print(f"Removed old backup: {old_file.name}")
async def restore_latest_backup(self, guild_id):
"""Restore the latest backup for a guild"""
import os
from pathlib import Path
backup_dir = Path("backups")
if not backup_dir.exists():
return False
# Find latest backup for this guild
pattern = f"guild_{guild_id}_*.json"
backups = list(backup_dir.glob(pattern))
if not backups:
return False
# Get most recent
latest_backup = max(backups, key=lambda x: x.stat().st_mtime)
try:
snapshot = await Snapshot.load_from_file(str(latest_backup))
player = await self.client.get_player(guild_id)
await player.load_snapshot(snapshot)
print(f"Restored backup for guild {guild_id}")
return True
except Exception as e:
print(f"Failed to restore backup: {e}")
return False
# Usage
backup_manager = AutoBackupManager(client)
await backup_manager.start_auto_backup()
# Later, to restore
success = await backup_manager.restore_latest_backup(guild_id)
#
Snapshot Analytics
class SnapshotAnalytics:
def __init__(self, player):
self.player = player
self.analytics = {}
async def analyze_snapshots(self):
"""Analyze snapshot patterns and usage"""
snapshots = await self.player.snapshots.list_snapshots()
# Analyze creation patterns
creation_times = [s.created_at for s in snapshots]
hourly_pattern = self.analyze_hourly_pattern(creation_times)
# Analyze content changes
content_changes = []
for i in range(1, len(snapshots)):
changes = self.compare_snapshots(snapshots[i-1], snapshots[i])
content_changes.append(changes)
# Generate insights
insights = {
"total_snapshots": len(snapshots),
"average_interval": self.calculate_average_interval(creation_times),
"peak_hours": hourly_pattern.most_common(3),
"most_changed_component": max(content_changes, key=lambda x: x['total_changes']) if content_changes else None,
"storage_used": sum(s.size for s in snapshots)
}
self.analytics = insights
return insights
def analyze_hourly_pattern(self, timestamps):
"""Analyze when snapshots are typically created"""
from collections import Counter
hours = [ts.hour for ts in timestamps]
return Counter(hours)
def calculate_average_interval(self, timestamps):
"""Calculate average time between snapshots"""
if len(timestamps) < 2:
return 0
intervals = []
for i in range(1, len(timestamps)):
interval = (timestamps[i] - timestamps[i-1]).total_seconds()
intervals.append(interval)
return sum(intervals) / len(intervals)
def compare_snapshots(self, snap1, snap2):
"""Compare two snapshots for changes"""
changes = {
"queue_changes": len(snap2.queue) - len(snap1.queue),
"filter_changes": len(snap2.filters) - len(snap1.filters),
"setting_changes": self.count_setting_changes(snap1.settings, snap2.settings)
}
changes["total_changes"] = sum(abs(v) for v in changes.values())
return changes
def count_setting_changes(self, settings1, settings2):
"""Count changes in settings"""
changes = 0
all_keys = set(settings1.keys()) | set(settings2.keys())
for key in all_keys:
val1 = settings1.get(key)
val2 = settings2.get(key)
if val1 != val2:
changes += 1
return changes
async def generate_report(self):
"""Generate a comprehensive analytics report"""
insights = await self.analyze_snapshots()
report = f"""
š Snapshot Analytics Report
===============================
Total Snapshots: {insights['total_snapshots']}
Storage Used: {insights['storage_used'] / 1024:.1f} KB
Average Interval: {insights['average_interval'] / 3600:.1f} hours
š Creation Patterns:
"""
for hour, count in insights['peak_hours']:
report += f" {hour:02d}:00 - {count} snapshots\n"
if insights['most_changed_component']:
report += f"\nš Most Volatile Component:\n"
changes = insights['most_changed_component']
report += f" Queue: {changes['queue_changes']} changes\n"
report += f" Filters: {changes['filter_changes']} changes\n"
report += f" Settings: {changes['setting_changes']} changes\n"
return report
# Usage
analytics = SnapshotAnalytics(player)
report = await analytics.generate_report()
print(report)
This comprehensive snapshot system provides robust state management and recovery capabilities for Sonora v1.2.8.