Automated formatting pass across 30 files to establish consistent code style enforced by CI. No logic changes.
448 lines
16 KiB
Python
448 lines
16 KiB
Python
"""Diagnostic utilities for troubleshooting the PromptManager gallery system.
|
|
|
|
This module provides comprehensive diagnostic tools to help identify and resolve
|
|
issues with the PromptManager system, particularly the gallery and image monitoring
|
|
functionality. It performs systematic checks of all system components and provides
|
|
clear feedback on potential problems.
|
|
|
|
The diagnostic system checks:
|
|
- Database connectivity and schema integrity
|
|
- Image table structure and data
|
|
- File system permissions and access
|
|
- ComfyUI output directory detection
|
|
- Required Python dependencies
|
|
- System integration points
|
|
|
|
Typical usage:
|
|
from utils.diagnostics import GalleryDiagnostics, run_diagnostics
|
|
|
|
# Run full diagnostic suite
|
|
results = run_diagnostics()
|
|
|
|
# Or create custom diagnostic instance
|
|
diagnostics = GalleryDiagnostics("custom_db.db")
|
|
database_status = diagnostics.check_database()
|
|
|
|
The diagnostics provide:
|
|
- Clear pass/fail status for each component
|
|
- Detailed error messages and remediation suggestions
|
|
- Statistics and system information
|
|
- Test utilities for verifying functionality
|
|
"""
|
|
|
|
import os
|
|
import sqlite3
|
|
from pathlib import Path
|
|
from typing import Dict, Any, List
|
|
|
|
from .logging_config import get_logger
|
|
|
|
|
|
class GalleryDiagnostics:
|
|
"""Diagnostics for the gallery system.
|
|
|
|
This class provides comprehensive diagnostic capabilities for the PromptManager
|
|
gallery system. It systematically checks all components and dependencies to
|
|
identify potential issues and provide actionable feedback.
|
|
|
|
The diagnostics cover:
|
|
- Database structure and connectivity
|
|
- Image tracking table integrity
|
|
- File system access and permissions
|
|
- ComfyUI integration points
|
|
- Python dependency availability
|
|
|
|
Each diagnostic method returns a standardized result dictionary with:
|
|
- status: 'ok', 'warning', or 'error'
|
|
- message: Descriptive message (for warnings/errors)
|
|
- Additional data specific to the diagnostic
|
|
"""
|
|
|
|
def __init__(self, db_path: str = "prompts.db"):
|
|
"""Initialize the diagnostics system.
|
|
|
|
Args:
|
|
db_path: Path to the SQLite database file to diagnose
|
|
"""
|
|
self.db_path = db_path
|
|
self.logger = get_logger("prompt_manager.diagnostics")
|
|
|
|
def run_full_diagnostic(self) -> Dict[str, Any]:
|
|
"""Run a complete diagnostic check.
|
|
|
|
Executes all diagnostic checks in sequence and provides a comprehensive
|
|
report of system status. Logs detailed information during the process.
|
|
|
|
Returns:
|
|
Dictionary mapping diagnostic categories to their results:
|
|
- database: Database connectivity and structure check
|
|
- images_table: Image tracking table specific check
|
|
- file_system: File system access and permissions check
|
|
- comfyui_output: ComfyUI output directory detection
|
|
- dependencies: Python dependency availability check
|
|
"""
|
|
self.logger.info("\n" + "=" * 60)
|
|
self.logger.info("[DIAG] PROMPTMANAGER GALLERY DIAGNOSTICS")
|
|
self.logger.info("=" * 60)
|
|
|
|
results = {
|
|
"database": self.check_database(),
|
|
"images_table": self.check_images_table(),
|
|
"file_system": self.check_file_system(),
|
|
"comfyui_output": self.check_comfyui_output(),
|
|
"dependencies": self.check_dependencies(),
|
|
}
|
|
|
|
self.logger.info("\n" + "=" * 60)
|
|
self.logger.info("[SUMMARY] DIAGNOSTIC SUMMARY")
|
|
self.logger.info("=" * 60)
|
|
|
|
for category, result in results.items():
|
|
status = "[PASS] PASS" if result["status"] == "ok" else "[FAIL] FAIL"
|
|
self.logger.info(f"{category.upper():<20} {status}")
|
|
if result["status"] != "ok":
|
|
self.logger.warning(f" Issue: {result['message']}")
|
|
|
|
self.logger.info("\n" + "=" * 60)
|
|
return results
|
|
|
|
def check_database(self) -> Dict[str, Any]:
|
|
"""Check database connection and structure.
|
|
|
|
Verifies that the database file exists, is accessible, and contains
|
|
the expected prompt table structure.
|
|
|
|
Returns:
|
|
Dictionary containing:
|
|
- status: 'ok' or 'error'
|
|
- message: Error description (if status is 'error')
|
|
- prompt_count: Number of prompts in database (if successful)
|
|
- has_images_table: Whether generated_images table exists (if successful)
|
|
"""
|
|
self.logger.info("\n[DB] Checking Database...")
|
|
|
|
try:
|
|
if not os.path.exists(self.db_path):
|
|
return {
|
|
"status": "error",
|
|
"message": f"Database file not found: {self.db_path}",
|
|
}
|
|
|
|
with sqlite3.connect(self.db_path) as conn:
|
|
conn.row_factory = sqlite3.Row
|
|
|
|
# Check prompts table
|
|
cursor = conn.execute("SELECT COUNT(*) as count FROM prompts")
|
|
prompt_count = cursor.fetchone()["count"]
|
|
self.logger.info(f" [NOTE] Prompts in database: {prompt_count}")
|
|
|
|
# Check if generated_images table exists
|
|
cursor = conn.execute("""
|
|
SELECT name FROM sqlite_master
|
|
WHERE type='table' AND name='generated_images'
|
|
""")
|
|
|
|
has_images_table = cursor.fetchone() is not None
|
|
self.logger.info(f" [IMG] Images table exists: {has_images_table}")
|
|
|
|
return {
|
|
"status": "ok",
|
|
"prompt_count": prompt_count,
|
|
"has_images_table": has_images_table,
|
|
}
|
|
|
|
except Exception as e:
|
|
return {"status": "error", "message": f"Database error: {str(e)}"}
|
|
|
|
def check_images_table(self) -> Dict[str, Any]:
|
|
"""Check the generated_images table specifically.
|
|
|
|
Examines the generated_images table structure and content to verify
|
|
the gallery system can function properly.
|
|
|
|
Returns:
|
|
Dictionary containing:
|
|
- status: 'ok' or 'error'
|
|
- message: Error description (if table missing or inaccessible)
|
|
- image_count: Number of images in table (if successful)
|
|
- recent_images: List of recent image records (if successful)
|
|
"""
|
|
self.logger.info("\n[IMG] Checking Images Table...")
|
|
|
|
try:
|
|
with sqlite3.connect(self.db_path) as conn:
|
|
conn.row_factory = sqlite3.Row
|
|
|
|
# Check if table exists
|
|
cursor = conn.execute("""
|
|
SELECT name FROM sqlite_master
|
|
WHERE type='table' AND name='generated_images'
|
|
""")
|
|
|
|
if not cursor.fetchone():
|
|
return {
|
|
"status": "error",
|
|
"message": "generated_images table does not exist - run the updated code to create it",
|
|
}
|
|
|
|
# Check image records
|
|
cursor = conn.execute("SELECT COUNT(*) as count FROM generated_images")
|
|
image_count = cursor.fetchone()["count"]
|
|
self.logger.info(f" [STATS] Images in database: {image_count}")
|
|
|
|
# Get recent images
|
|
cursor = conn.execute("""
|
|
SELECT gi.*, p.text
|
|
FROM generated_images gi
|
|
LEFT JOIN prompts p ON gi.prompt_id = p.id
|
|
ORDER BY gi.generation_time DESC
|
|
LIMIT 5
|
|
""")
|
|
recent_images = [dict(row) for row in cursor.fetchall()]
|
|
|
|
self.logger.info(f" [TIME] Recent images: {len(recent_images)}")
|
|
for img in recent_images:
|
|
self.logger.info(
|
|
f" - {img['filename']} -> Prompt {img['prompt_id']}"
|
|
)
|
|
|
|
return {
|
|
"status": "ok",
|
|
"image_count": image_count,
|
|
"recent_images": recent_images,
|
|
}
|
|
|
|
except Exception as e:
|
|
return {"status": "error", "message": f"Images table error: {str(e)}"}
|
|
|
|
def check_file_system(self) -> Dict[str, Any]:
|
|
"""Check file system and permissions.
|
|
|
|
Verifies that the application has appropriate file system access
|
|
for reading images and writing database files.
|
|
|
|
Returns:
|
|
Dictionary containing:
|
|
- status: 'ok' or 'error'
|
|
- message: Error description (if issues detected)
|
|
- current_dir: Current working directory path
|
|
- can_write: Whether write access is available
|
|
"""
|
|
self.logger.info("\n[DIR] Checking File System...")
|
|
|
|
try:
|
|
# Check current directory
|
|
current_dir = os.getcwd()
|
|
self.logger.info(f" [FOLDER] Current directory: {current_dir}")
|
|
|
|
# Check if we can write to current directory
|
|
test_file = "test_write.tmp"
|
|
try:
|
|
with open(test_file, "w") as f:
|
|
f.write("test")
|
|
os.remove(test_file)
|
|
can_write = True
|
|
except (IOError, OSError):
|
|
can_write = False
|
|
|
|
self.logger.info(f" [EDIT] Can write to directory: {can_write}")
|
|
|
|
return {"status": "ok", "current_dir": current_dir, "can_write": can_write}
|
|
|
|
except Exception as e:
|
|
return {"status": "error", "message": f"File system error: {str(e)}"}
|
|
|
|
def check_comfyui_output(self) -> Dict[str, Any]:
|
|
"""Check ComfyUI output directories.
|
|
|
|
Attempts to locate ComfyUI output directories where generated images
|
|
would be stored. Checks both common locations and ComfyUI's configured paths.
|
|
|
|
Returns:
|
|
Dictionary containing:
|
|
- status: 'ok' if directories found, 'warning' if none found
|
|
- message: Warning message (if no directories found)
|
|
- output_dirs: List of detected output directory paths
|
|
"""
|
|
self.logger.info("\n[STYLE] Checking ComfyUI Output...")
|
|
|
|
output_dirs = []
|
|
|
|
# Try to detect ComfyUI output directories
|
|
potential_dirs = [
|
|
"output",
|
|
"../output",
|
|
"../../output",
|
|
"ComfyUI/output",
|
|
"../ComfyUI/output",
|
|
"../../ComfyUI/output",
|
|
]
|
|
|
|
for dir_path in potential_dirs:
|
|
abs_path = os.path.abspath(dir_path)
|
|
if os.path.exists(abs_path):
|
|
output_dirs.append(abs_path)
|
|
self.logger.info(f" [DIR] Found output dir: {abs_path}")
|
|
|
|
# Count images in this directory
|
|
try:
|
|
image_files = []
|
|
for ext in [".png", ".jpg", ".jpeg", ".webp"]:
|
|
image_files.extend(Path(abs_path).rglob(f"*{ext}"))
|
|
self.logger.info(f" [IMG] Images found: {len(image_files)}")
|
|
except Exception as e:
|
|
self.logger.error(f" [FAIL] Error scanning: {e}")
|
|
|
|
# Try ComfyUI's folder_paths
|
|
try:
|
|
import folder_paths
|
|
|
|
comfyui_output = folder_paths.get_output_directory()
|
|
if comfyui_output and comfyui_output not in output_dirs:
|
|
output_dirs.append(comfyui_output)
|
|
self.logger.info(f" [DIR] ComfyUI output dir: {comfyui_output}")
|
|
except ImportError:
|
|
self.logger.warning(" [WARN] ComfyUI folder_paths not available")
|
|
|
|
return {
|
|
"status": "ok" if output_dirs else "warning",
|
|
"message": "No output directories found" if not output_dirs else None,
|
|
"output_dirs": output_dirs,
|
|
}
|
|
|
|
def check_dependencies(self) -> Dict[str, Any]:
|
|
"""Check required dependencies.
|
|
|
|
Verifies that all required Python packages are available for the
|
|
PromptManager system to function properly.
|
|
|
|
Returns:
|
|
Dictionary containing:
|
|
- status: 'ok' if all dependencies available, 'error' if any missing
|
|
- message: Error description (if dependencies missing)
|
|
- dependencies: Dictionary mapping package names to availability status
|
|
"""
|
|
self.logger.info("\n[PKG] Checking Dependencies...")
|
|
|
|
dependencies = {"watchdog": False, "PIL": False, "sqlite3": False}
|
|
|
|
# Check watchdog
|
|
try:
|
|
import watchdog
|
|
|
|
dependencies["watchdog"] = True
|
|
self.logger.info(f" [PASS] watchdog: {watchdog.__version__}")
|
|
except ImportError:
|
|
self.logger.error(" [FAIL] watchdog: NOT INSTALLED")
|
|
|
|
# Check PIL
|
|
try:
|
|
from PIL import Image
|
|
|
|
dependencies["PIL"] = True
|
|
self.logger.info(f" [PASS] PIL (Pillow): Available")
|
|
except ImportError:
|
|
self.logger.error(" [FAIL] PIL (Pillow): NOT AVAILABLE")
|
|
|
|
# Check sqlite3
|
|
try:
|
|
import sqlite3
|
|
|
|
dependencies["sqlite3"] = True
|
|
self.logger.info(f" [PASS] sqlite3: {sqlite3.sqlite_version}")
|
|
except ImportError:
|
|
self.logger.error(" [FAIL] sqlite3: NOT AVAILABLE")
|
|
|
|
all_deps_ok = all(dependencies.values())
|
|
|
|
return {
|
|
"status": "ok" if all_deps_ok else "error",
|
|
"message": "Missing dependencies" if not all_deps_ok else None,
|
|
"dependencies": dependencies,
|
|
}
|
|
|
|
def create_test_image_link(
|
|
self, prompt_id: int, test_image_path: str = None
|
|
) -> Dict[str, Any]:
|
|
"""Create a test image link to verify the system works.
|
|
|
|
Creates a test entry in the generated_images table to verify that
|
|
the image linking functionality is working correctly.
|
|
|
|
Args:
|
|
prompt_id: ID of an existing prompt to link the test image to
|
|
test_image_path: Optional path for the test image (uses fake path if None)
|
|
|
|
Returns:
|
|
Dictionary containing:
|
|
- status: 'ok' if test link created successfully, 'error' otherwise
|
|
- message: Success or error message
|
|
- image_id: ID of created test image record (if successful)
|
|
"""
|
|
self.logger.info(f"\n[TEST] Creating test image link for prompt {prompt_id}...")
|
|
|
|
try:
|
|
# Import database operations
|
|
import sys
|
|
import os
|
|
|
|
sys.path.insert(
|
|
0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
)
|
|
from database.operations import PromptDatabase
|
|
|
|
db = PromptDatabase()
|
|
|
|
# Create a fake image path if none provided
|
|
if not test_image_path:
|
|
test_image_path = "/fake/test/image.png"
|
|
|
|
# Create test metadata
|
|
test_metadata = {
|
|
"file_info": {
|
|
"size": 1024000,
|
|
"dimensions": [512, 512],
|
|
"format": "PNG",
|
|
},
|
|
"workflow": {"test": True},
|
|
"prompt": {"test_prompt": "This is a test"},
|
|
}
|
|
|
|
# Link the test image
|
|
image_id = db.link_image_to_prompt(
|
|
prompt_id=str(prompt_id),
|
|
image_path=test_image_path,
|
|
metadata=test_metadata,
|
|
)
|
|
|
|
self.logger.info(f" [PASS] Test image linked with ID: {image_id}")
|
|
|
|
return {
|
|
"status": "ok",
|
|
"image_id": image_id,
|
|
"message": f"Test image linked successfully with ID {image_id}",
|
|
}
|
|
|
|
except Exception as e:
|
|
return {
|
|
"status": "error",
|
|
"message": f"Failed to create test link: {str(e)}",
|
|
}
|
|
|
|
|
|
def run_diagnostics() -> Dict[str, Any]:
|
|
"""Run diagnostics from command line.
|
|
|
|
Convenience function to create a GalleryDiagnostics instance and run
|
|
the full diagnostic suite with default settings.
|
|
|
|
Returns:
|
|
Complete diagnostic results dictionary
|
|
"""
|
|
diagnostics = GalleryDiagnostics()
|
|
return diagnostics.run_full_diagnostic()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
run_diagnostics()
|