toggle logging added (--debug-on) to enable logging

minor bug fixes.
known stutter bug in database/meta systems
appears to be the delay in connecting to the database.

future work:
1. intergrate firefly.dll as server module.
2. ensure full database funcctionality.
3. add toggle to use database or not. (by default we check for firefly if we don't find we default to python systems. if found we automatically use firefly.(so maybe on toggles))
4. investigate and fix the stutter bug.
This commit is contained in:
Stan44 2025-04-11 23:59:50 -05:00
parent fcc0b906e5
commit a69391b1d8
6 changed files with 281 additions and 86 deletions

View File

@ -1,11 +1,13 @@
"""File browser implementation with tools and audio playback. """File browser implementation with tools and audio playback.
this includes a midi player""" this includes a midi player"""
# flake8: noqa: E501 # flake8: noqa: E501
# This is the main file this file is the one to run. # This is the main file this file is the one to run.
import sys import sys
import os import os
import warnings import warnings
import argparse
from PyQt6.QtWidgets import ( from PyQt6.QtWidgets import (
QApplication, QApplication,
QMainWindow, QMainWindow,
@ -38,10 +40,21 @@ from ScanOrg101 import (
ArchiveExtractor, ArchiveExtractor,
) )
import mutagen import mutagen
from logging_setup import setup_logging
warnings.filterwarnings("ignore", category=DeprecationWarning) warnings.filterwarnings("ignore", category=DeprecationWarning)
# Parse command line arguments
def parse_arguments():
"""Parse command line arguments"""
parser = argparse.ArgumentParser(description="File Browser Application")
parser.add_argument(
"--debug-on", action="store_true", help="Enable debug logging mode"
)
return parser.parse_args()
class MetadataTooltip(QFrame): class MetadataTooltip(QFrame):
"""Custom styled tooltip for displaying audio metadata""" """Custom styled tooltip for displaying audio metadata"""
@ -110,8 +123,10 @@ class MetadataTooltip(QFrame):
class Fbrowser(QMainWindow): class Fbrowser(QMainWindow):
"""Fbrowser main class""" """Fbrowser main class"""
def __init__(self): def __init__(self, logger):
super().__init__() super().__init__()
self.logger = logger
self.logger.info("Initializing Fbrowser application")
self.midplay = None self.midplay = None
self.current_path = QDir.homePath() self.current_path = QDir.homePath()
self.organizer = Organizer() self.organizer = Organizer()
@ -129,9 +144,11 @@ class Fbrowser(QMainWindow):
self.hover_position = None self.hover_position = None
self.hover_path = None self.hover_path = None
self.metadata_cache = {} self.metadata_cache = {}
self.logger.debug("Fbrowser initialization complete")
def init_ui(self): def init_ui(self):
"""initilize UI""" """initilize UI"""
self.logger.debug("Initializing UI components")
self.setWindowTitle("File Browser") self.setWindowTitle("File Browser")
central_widget = QWidget() central_widget = QWidget()
layout = QVBoxLayout(central_widget) layout = QVBoxLayout(central_widget)
@ -266,13 +283,16 @@ class Fbrowser(QMainWindow):
self.folder_contents_view.setMouseTracking(True) self.folder_contents_view.setMouseTracking(True)
self.folder_contents_view.viewport().setMouseTracking(True) # type: ignore self.folder_contents_view.viewport().setMouseTracking(True) # type: ignore
self.folder_contents_view.viewport().installEventFilter(self) # type: ignore self.folder_contents_view.viewport().installEventFilter(self) # type: ignore
self.logger.debug("UI initialization complete")
def set_volume(self, volume): def set_volume(self, volume):
"""Set Volume""" """Set Volume"""
self.audio_output.setVolume(volume / 100.0) self.audio_output.setVolume(volume / 100.0)
self.logger.debug(f"Volume set to {volume}%")
def show_exit_popup(self): def show_exit_popup(self):
"""Exit Prompt""" """Exit Prompt"""
self.logger.debug("Exit prompt displayed")
reply = QMessageBox.question( reply = QMessageBox.question(
self, self,
"Exit", "Exit",
@ -281,20 +301,24 @@ class Fbrowser(QMainWindow):
QMessageBox.StandardButton.No, QMessageBox.StandardButton.No,
) )
if reply == QMessageBox.StandardButton.Yes: if reply == QMessageBox.StandardButton.Yes:
self.logger.info("User confirmed exit, shutting down application")
sys.exit() sys.exit()
def navigate_to_address(self): def navigate_to_address(self):
"""Navigation Via Address Bar""" """Navigation Via Address Bar"""
new_path = self.address_bar.text() new_path = self.address_bar.text()
self.logger.debug(f"Navigating to address: {new_path}")
if os.path.isdir(new_path): if os.path.isdir(new_path):
self.add_to_history(self.current_path) self.add_to_history(self.current_path)
self.current_path = new_path self.current_path = new_path
self.update_file_views(new_path) self.update_file_views(new_path)
else: else:
self.logger.warning(f"Invalid directory path: {new_path}")
self.address_bar.setText(self.current_path) self.address_bar.setText(self.current_path)
def add_to_history(self, path): def add_to_history(self, path):
"""History""" """History"""
self.logger.debug(f"Adding path to history: {path}")
# If we're not at the end of the history, truncate it # If we're not at the end of the history, truncate it
if self.history_position < len(self.history) - 1: if self.history_position < len(self.history) - 1:
self.history = self.history[: self.history_position + 1] self.history = self.history[: self.history_position + 1]
@ -307,12 +331,14 @@ class Fbrowser(QMainWindow):
index = self.directory_model.mapToSource(index) index = self.directory_model.mapToSource(index)
try: try:
file_path = self.tree_model.filePath(index) file_path = self.tree_model.filePath(index)
self.logger.debug(f"Changing directory to: {file_path}")
if os.path.isdir(file_path): if os.path.isdir(file_path):
self.add_to_history(self.current_path) self.add_to_history(self.current_path)
self.current_path = file_path self.current_path = file_path
self.update_file_views(file_path) self.update_file_views(file_path)
except (OSError, IOError) as e: except (OSError, IOError) as e:
print(f"Error Changing Dirs.: {e}") self.logger.error(f"Error changing directory: {e}")
logger.debug(f"Error Changing Dirs.: {e}")
def update_file_views(self, path): def update_file_views(self, path):
"""Updates the File trees""" """Updates the File trees"""
@ -726,7 +752,7 @@ class Fbrowser(QMainWindow):
self.show_metadata_tooltip(metadata) self.show_metadata_tooltip(metadata)
except Exception as e: except Exception as e:
print(f"Error extracting metadata for hover: {e}") logger.debug(f"Error extracting metadata for hover: {e}")
def _get_tag(self, audio, tag_name, default_value): def _get_tag(self, audio, tag_name, default_value):
"""Helper method to safely extract tags from audio files""" """Helper method to safely extract tags from audio files"""
@ -762,6 +788,7 @@ class Fbrowser(QMainWindow):
def closeEvent(self, event): def closeEvent(self, event):
"""Handle application close event""" """Handle application close event"""
self.logger.info("Application closing")
# Close database connection # Close database connection
if hasattr(self, "organizer") and self.organizer: if hasattr(self, "organizer") and self.organizer:
self.organizer.close() self.organizer.close()
@ -774,7 +801,7 @@ class Fbrowser(QMainWindow):
if not self.should_auto_scan(path): if not self.should_auto_scan(path):
return return
print(f"Auto-scanning directory: {path}") self.logger.info(f"Auto-scanning directory: {path}")
# Initialize organizer with database connection if not already done # Initialize organizer with database connection if not already done
if not hasattr(self.organizer, "db") or self.organizer.db is None: if not hasattr(self.organizer, "db") or self.organizer.db is None:
@ -797,13 +824,13 @@ class Fbrowser(QMainWindow):
files_to_process.append(file_path) files_to_process.append(file_path)
if files_to_process: if files_to_process:
print( self.logger.debug(
f"Found {len(files_to_process)} files without metadata, extracting..." f"Found {len(files_to_process)} files without metadata, extracting..."
) )
self.organizer.file_list = files_to_process self.organizer.file_list = files_to_process
self.organizer.extract_metadata() self.organizer.extract_metadata()
else: else:
print( self.logger.debug(
"All files already have metadata in database, skipping extraction" "All files already have metadata in database, skipping extraction"
) )
self.progress_bar.setValue(100) self.progress_bar.setValue(100)
@ -817,16 +844,16 @@ class Fbrowser(QMainWindow):
def auto_extract_metadata(self): def auto_extract_metadata(self):
"""Automatically extract metadata after scanning""" """Automatically extract metadata after scanning"""
if not self.organizer.file_list: if not self.organizer.file_list:
print("No audio files found to extract metadata from") logger.debug("No audio files found to extract metadata from")
return return
print( self.logger.debug(
f"Auto-extracting metadata for {len(self.organizer.file_list)} files" f"Auto-extracting metadata for {len(self.organizer.file_list)} files"
) )
# Connect signals # Connect signals
self.organizer.on_progress_update = self.update_progress # type: ignore self.organizer.on_progress_update = self.update_progress # type: ignore
self.organizer.on_metadata_complete = lambda: print("Metadata extraction complete") # type: ignore self.organizer.on_metadata_complete = lambda: logger.debug("Metadata extraction complete") # type: ignore
# Start extraction # Start extraction
self.organizer.extract_metadata() self.organizer.extract_metadata()
@ -859,8 +886,22 @@ class Fbrowser(QMainWindow):
return True return True
if __name__ == "__main__": def main():
# Parse command line arguments
args = parse_arguments()
# Setup logging
logger = setup_logging(args)
logger.info("Starting Fbrowser application")
app = QApplication(sys.argv) app = QApplication(sys.argv)
ex = Fbrowser() ex = Fbrowser(logger)
ex.show() ex.show()
logger.info("Application UI displayed")
sys.exit(app.exec()) sys.exit(app.exec())
if __name__ == "__main__":
main()

View File

@ -5,6 +5,7 @@ ScanOrg101.py - Enhanced file scanning and organization module
# flake8: noqa: E501 # flake8: noqa: E501
import os import os
import logging
import concurrent.futures import concurrent.futures
from collections import deque from collections import deque
import time import time
@ -13,6 +14,9 @@ from dbman import FireflyDB
from metaextract import MetadataExtractor, mutagen from metaextract import MetadataExtractor, mutagen
from archiver import ArchiveExtractor from archiver import ArchiveExtractor
# Get the logger
logger = logging.getLogger("fbroswer")
# Directory Filter Proxy Model # Directory Filter Proxy Model
class DirectoryFilterProxyModel(QSortFilterProxyModel): class DirectoryFilterProxyModel(QSortFilterProxyModel):
@ -20,6 +24,7 @@ class DirectoryFilterProxyModel(QSortFilterProxyModel):
super().__init__() super().__init__()
self.setFilterCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive) self.setFilterCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive)
self.setFilterKeyColumn(0) self.setFilterKeyColumn(0)
logger.debug("DirectoryFilterProxyModel initialized")
def filterAcceptsRow(self, source_row, source_parent): def filterAcceptsRow(self, source_row, source_parent):
source_model = self.sourceModel() source_model = self.sourceModel()
@ -57,6 +62,9 @@ class FileFilterProxyModel(QSortFilterProxyModel):
".7z", ".7z",
".rar", ".rar",
] ]
logger.debug(
"FileFilterProxyModel initialized with allowed extensions"
)
def filterAcceptsRow(self, source_row, source_parent): def filterAcceptsRow(self, source_row, source_parent):
source_model = self.sourceModel() source_model = self.sourceModel()
@ -221,7 +229,9 @@ class FileScanner(QThread):
self.progress_update.emit(progress) self.progress_update.emit(progress)
last_update_time = current_time last_update_time = current_time
except Exception as e: except Exception as e:
print(f"Error scanning directory {dir_path}: {e}") logger.debug(
f"Error scanning directory {dir_path}: {e}"
)
# Emit any remaining items in the final batch # Emit any remaining items in the final batch
if batch and not self.stop_requested: if batch and not self.stop_requested:
@ -268,10 +278,10 @@ class FileScanner(QThread):
self.progress_update.emit(100) # Show complete for this directory self.progress_update.emit(100) # Show complete for this directory
return items return items
except PermissionError: except PermissionError:
print(f"Permission denied: {path}") logger.debug(f"Permission denied: {path}")
return [] return []
except OSError as e: except OSError as e:
print(f"Error accessing {path}: {e}") logger.debug(f"Error accessing {path}: {e}")
return [] return []
def scan_single_directory_helper(self, path): def scan_single_directory_helper(self, path):
@ -293,7 +303,7 @@ class FileScanner(QThread):
): ):
items.append((entry.path, False)) items.append((entry.path, False))
except (PermissionError, OSError) as e: except (PermissionError, OSError) as e:
print(f"Error accessing {path}: {e}") logger.debug(f"Error accessing {path}: {e}")
return items, subdirs return items, subdirs
@ -372,7 +382,7 @@ class Organizer:
def scan_finished(self): def scan_finished(self):
"""Handle scan completion""" """Handle scan completion"""
print( logger.debug(
f"Scan complete. Found {len(self.dir_list)} directories\ f"Scan complete. Found {len(self.dir_list)} directories\
and {len(self.file_list)} files." and {len(self.file_list)} files."
) )
@ -388,13 +398,13 @@ class Organizer:
def extract_metadata(self): def extract_metadata(self):
"""Extract metadata from audio files using MetadataExtractor from metaextract.py""" """Extract metadata from audio files using MetadataExtractor from metaextract.py"""
if not self.file_list: if not self.file_list:
print("No files to extract metadata from") logger.debug("No files to extract metadata from")
return return
# Verify database connection if enabled # Verify database connection if enabled
if self.use_db and self.db: if self.use_db and self.db:
if not self.db_manager.verify_database_connection(): if not self.db_manager.verify_database_connection():
print( logger.debug(
"Warning: Database verification failed, continuing without database" "Warning: Database verification failed, continuing without database"
) )
self.use_db = False self.use_db = False
@ -451,7 +461,7 @@ class Organizer:
def metadata_extraction_complete(self): def metadata_extraction_complete(self):
"""Handle metadata extraction completion""" """Handle metadata extraction completion"""
print( logger.debug(
f"Metadata extraction complete. Artists: {len(getattr(self, 'artists', []))}, " f"Metadata extraction complete. Artists: {len(getattr(self, 'artists', []))}, "
f"Albums: {len(getattr(self, 'albums', []))}, Genres: {len(getattr(self, 'genres', []))}, " f"Albums: {len(getattr(self, 'albums', []))}, Genres: {len(getattr(self, 'genres', []))}, "
f"Years: {len(getattr(self, 'years', []))}" f"Years: {len(getattr(self, 'years', []))}"
@ -460,16 +470,16 @@ class Organizer:
def extract_archive(self, archive_path, extraction_dir): def extract_archive(self, archive_path, extraction_dir):
"""Extract an archive to specified directory using ArchiveExtractor from archiver.py""" """Extract an archive to specified directory using ArchiveExtractor from archiver.py"""
if not os.path.isfile(archive_path): if not os.path.isfile(archive_path):
print(f"Error: Archive file {archive_path} does not exist") logger.debug(f"Error: Archive file {archive_path} does not exist")
return False return False
if not os.path.isdir(extraction_dir): if not os.path.isdir(extraction_dir):
print( logger.debug(
f"Error: Extraction directory {extraction_dir} does not exist" f"Error: Extraction directory {extraction_dir} does not exist"
) )
return False return False
print(f"Extracting archive {archive_path} to {extraction_dir}") logger.debug(f"Extracting archive {archive_path} to {extraction_dir}")
# Create an ArchiveExtractor instance from archiver.py # Create an ArchiveExtractor instance from archiver.py
self.archive_extractor = ArchiveExtractor(archive_path, extraction_dir) self.archive_extractor = ArchiveExtractor(archive_path, extraction_dir)
@ -482,7 +492,7 @@ class Organizer:
# Define completion handler # Define completion handler
def on_extraction_complete(extracted_files): def on_extraction_complete(extracted_files):
print( logger.debug(
f"Archive extraction complete. Extracted {len(extracted_files)} files." f"Archive extraction complete. Extracted {len(extracted_files)} files."
) )
# Add extracted files to our file list if they match our criteria # Add extracted files to our file list if they match our criteria
@ -520,7 +530,7 @@ class Organizer:
] ]
if audio_files and self.use_db: if audio_files and self.use_db:
print( logger.debug(
f"Found {len(audio_files)} audio files in archive, extracting metadata..." f"Found {len(audio_files)} audio files in archive, extracting metadata..."
) )
temp_extractor = MetadataExtractor(audio_files) temp_extractor = MetadataExtractor(audio_files)
@ -536,7 +546,7 @@ class Organizer:
# Define error handler # Define error handler
def on_extraction_error(error_message): def on_extraction_error(error_message):
print(f"Archive extraction error: {error_message}") logger.debug(f"Archive extraction error: {error_message}")
# Connect error signal # Connect error signal
self.archive_extractor.extraction_error.connect(on_extraction_error) self.archive_extractor.extraction_error.connect(on_extraction_error)
@ -550,7 +560,7 @@ class Organizer:
): ):
"""Extract an archive to a specified directory or to a subdirectory in the same location""" """Extract an archive to a specified directory or to a subdirectory in the same location"""
if not os.path.isfile(archive_path): if not os.path.isfile(archive_path):
print(f"Error: Archive file {archive_path} does not exist") logger.debug(f"Error: Archive file {archive_path} does not exist")
return False return False
# If no target directory is specified, create one based on the archive name # If no target directory is specified, create one based on the archive name
@ -564,9 +574,11 @@ class Organizer:
if not os.path.exists(target_directory): if not os.path.exists(target_directory):
try: try:
os.makedirs(target_directory) os.makedirs(target_directory)
print(f"Created directory {target_directory}") logger.debug(f"Created directory {target_directory}")
except OSError as e: except OSError as e:
print(f"Error creating directory {target_directory}: {e}") logger.debug(
f"Error creating directory {target_directory}: {e}"
)
return False return False
return self.extract_archive(archive_path, target_directory) return self.extract_archive(archive_path, target_directory)
@ -580,7 +592,7 @@ class Organizer:
# Delegate to the db_manager # Delegate to the db_manager
return self.db_manager.has_metadata_in_db(file_path) return self.db_manager.has_metadata_in_db(file_path)
except Exception as e: except Exception as e:
print(f"Error checking metadata in database: {e}") logger.debug(f"Error checking metadata in database: {e}")
return False return False
def get_metadata_from_db(self, file_path): def get_metadata_from_db(self, file_path):
@ -592,20 +604,20 @@ class Organizer:
# Delegate to the db_manager # Delegate to the db_manager
return self.db_manager.get_metadata_from_db(file_path) return self.db_manager.get_metadata_from_db(file_path)
except Exception as e: except Exception as e:
print(f"Error retrieving metadata from database: {e}") logger.debug(f"Error retrieving metadata from database: {e}")
return None return None
def store_metadata(self, metadata): def store_metadata(self, metadata):
"""Store audio file metadata in the database""" """Store audio file metadata in the database"""
if not self.use_db or not self.db: if not self.use_db or not self.db:
print("Database usage is disabled, not storing metadata") logger.debug("Database usage is disabled, not storing metadata")
return False return False
try: try:
# Delegate to the db_manager # Delegate to the db_manager
return self.db_manager.store_metadata(metadata) return self.db_manager.store_metadata(metadata)
except Exception as e: except Exception as e:
print(f"Error storing metadata in database: {e}") logger.debug(f"Error storing metadata in database: {e}")
import traceback import traceback
traceback.print_exc() traceback.print_exc()

View File

@ -1,4 +1,5 @@
import os import os
import logging
import zipfile import zipfile
import py7zr import py7zr
import rarfile # typed: ignore import rarfile # typed: ignore
@ -6,6 +7,10 @@ import rarfile # typed: ignore
from PyQt6.QtCore import QThread, pyqtSignal from PyQt6.QtCore import QThread, pyqtSignal
# Get the logger
logger = logging.getLogger("fbroswer")
# Archive Extractor not fully tested or implemented # Archive Extractor not fully tested or implemented
class ArchiveExtractor(QThread): class ArchiveExtractor(QThread):
extraction_progress = pyqtSignal(int) extraction_progress = pyqtSignal(int)
@ -116,7 +121,7 @@ class ArchiveExtractor(QThread):
def extract_archives(self): def extract_archives(self):
"""Extract archives""" """Extract archives"""
if not self.file_list: if not self.file_list:
print("No files to extract archives from") logger.debug("No files to extract archives from")
return return
self.archive_extractor = ArchiveExtractor( self.archive_extractor = ArchiveExtractor(
@ -136,7 +141,7 @@ class ArchiveExtractor(QThread):
def archive_extraction_complete(self): def archive_extraction_complete(self):
"""Handle archive extraction completion""" """Handle archive extraction completion"""
print("Archive extraction complete.") logger.debug("Archive extraction complete.")
if self.on_progress_update: if self.on_progress_update:
self.on_progress_update(100) self.on_progress_update(100)

121
dbman.py
View File

@ -1,6 +1,10 @@
import logging
from datetime import datetime from datetime import datetime
from ifireflylib import IFireflyClient as FireflyDatabase from ifireflylib import IFireflyClient as FireflyDatabase
# Get the logger
logger = logging.getLogger("fbroswer")
class FireflyDB: class FireflyDB:
def __init__(self): def __init__(self):
@ -11,12 +15,14 @@ class FireflyDB:
self.albums = set() self.albums = set()
self.genres = set() self.genres = set()
self.years = set() self.years = set()
logger.debug("FireflyDB instance initialized")
def connect_to( def connect_to(
self, use_db=False, db_host="localhost", db_port=6379, db_password=None self, use_db=False, db_host="localhost", db_port=6379, db_password=None
): ):
# Set the use_db flag # Set the use_db flag
self.use_db = use_db self.use_db = use_db
logger.info(f"Database usage set to: {use_db}")
# Metadata organization # Metadata organization
self.artists = set() self.artists = set()
@ -26,26 +32,30 @@ class FireflyDB:
if use_db: if use_db:
try: try:
print(f"Connecting to FireflyDB at {db_host}:{db_port}") logger.info(f"Connecting to FireflyDB at {db_host}:{db_port}")
self.db = FireflyDatabase( self.db = FireflyDatabase(
host=db_host, port=db_port, password=db_password host=db_host, port=db_port, password=db_password
) )
# Test connection # Test connection
if not self.db.ping(): if not self.db.ping():
print( logger.warning(
"Warning: Could not connect to FireflyDB. Continuing without database." "Could not connect to FireflyDB. Continuing without database."
) )
self.use_db = False self.use_db = False
self.db = None self.db = None
else: else:
print("Successfully connected to FireflyDB") logger.info("Successfully connected to FireflyDB")
# Store connection timestamp # Store connection timestamp
timestamp = str(datetime.now()) timestamp = str(datetime.now())
print(f"Setting last_connection timestamp: {timestamp}") logger.debug(
f"Setting last_connection timestamp: {timestamp}"
)
self.db.string_ops.string_set("last_connection", timestamp) self.db.string_ops.string_set("last_connection", timestamp)
print("Connection timestamp stored successfully") logger.debug("Connection timestamp stored successfully")
except Exception as e: except Exception as e:
print(f"Error connecting to FireflyDB: {e}") logger.error(
f"Error connecting to FireflyDB: {e}", exc_info=True
)
import traceback import traceback
traceback.print_exc() traceback.print_exc()
@ -55,6 +65,7 @@ class FireflyDB:
def close(self): def close(self):
"""Close database connection when done""" """Close database connection when done"""
if self.db: if self.db:
logger.info("Closing database connection")
self.db.close() self.db.close()
self.db = None self.db = None
@ -65,17 +76,20 @@ class FireflyDB:
try: try:
key = f"audio:{file_path}" key = f"audio:{file_path}"
# Use hash_exists to check if the key exists in the database # Use hash_field_exists to check if the key exists in the database
exists = self.db.hash_ops.hash_exists(key, "title") exists = self.db.hash_ops.hash_field_exists(key, "title")
logger.debug(
f"Metadata check for {file_path}: {'exists' if exists else 'not found'}"
)
return exists return exists
except Exception as e: except Exception as e:
print(f"Error checking metadata in database: {e}") logger.error(f"Error checking metadata in database: {e}")
return False return False
def store_metadata(self, metadata): def store_metadata(self, metadata):
"""Store audio file metadata in the database""" """Store audio file metadata in the database"""
if not self.use_db or not self.db: if not self.use_db or not self.db:
print("Database usage is disabled, not storing metadata") logger.info("Database usage is disabled, not storing metadata")
return False return False
try: try:
@ -84,42 +98,42 @@ class FireflyDB:
key = f"audio:{file_path}" key = f"audio:{file_path}"
# Log the storage attempt # Log the storage attempt
print(f"Storing metadata for {file_path} in FireflyDB") logger.info(f"Storing metadata for {file_path} in FireflyDB")
print(f"Metadata fields: {list(metadata.keys())}") logger.debug(f"Metadata fields: {list(metadata.keys())}")
# Store as a hash with all metadata fields # Store as a hash with all metadata fields
success = True success = True
for field, value in metadata.items(): for field, value in metadata.items():
if field != "file_path": # Skip using file_path as a field if field != "file_path": # Skip using file_path as a field
print(f" Setting field {field}={value}") logger.debug(f" Setting field {field}={value}")
# Use the direct hash_set method instead of hash_ops # Use the direct hash_set method instead of hash_ops
result = self.db.hash_ops.hash_set(key, field, value) result = self.db.hash_ops.hash_set(key, field, value)
if not result: if not result:
print( logger.warning(
f" Failed to store field {field} for {file_path}" f" Failed to store field {field} for {file_path}"
) )
success = False success = False
else: else:
print(f" Successfully stored field {field}") logger.debug(f" Successfully stored field {field}")
# Add to index lists for quick lookup # Add to index lists for quick lookup
if "artist" in metadata and metadata["artist"]: if "artist" in metadata and metadata["artist"]:
artist_key = f"index:artist:{metadata['artist']}" artist_key = f"index:artist:{metadata['artist']}"
print(f" Adding to artist index: {artist_key}") logger.debug(f" Adding to artist index: {artist_key}")
self.db.list_ops.list_right_push(artist_key, file_path) self.db.list_ops.list_right_push(artist_key, file_path)
if "album" in metadata and metadata["album"]: if "album" in metadata and metadata["album"]:
album_key = f"index:album:{metadata['album']}" album_key = f"index:album:{metadata['album']}"
print(f" Adding to album index: {album_key}") logger.debug(f" Adding to album index: {album_key}")
self.db.list_ops.list_right_push(album_key, file_path) self.db.list_ops.list_right_push(album_key, file_path)
if "genre" in metadata and metadata["genre"]: if "genre" in metadata and metadata["genre"]:
genre_key = f"index:genre:{metadata['genre']}" genre_key = f"index:genre:{metadata['genre']}"
print(f" Adding to genre index: {genre_key}") logger.debug(f" Adding to genre index: {genre_key}")
self.db.list_ops.list_right_push(genre_key, file_path) self.db.list_ops.list_right_push(genre_key, file_path)
# Add to a master list of all audio files for easy retrieval # Add to a master list of all audio files for easy retrieval
print(" Adding to master audio files list") logger.debug(" Adding to master audio files list")
self.db.list_ops.list_right_push("all_audio_files", file_path) self.db.list_ops.list_right_push("all_audio_files", file_path)
# Store timestamp of when metadata was added # Store timestamp of when metadata was added
@ -128,19 +142,38 @@ class FireflyDB:
# Verify storage by retrieving one field # Verify storage by retrieving one field
if "title" in metadata: if "title" in metadata:
retrieved_title = self.db.hash_ops.hash_get(key, "title") retrieved_title = self.db.hash_ops.hash_get(key, "title")
print(f" Verification - Retrieved title: {retrieved_title}") logger.debug(
f" Verification - Retrieved title: {retrieved_title}"
)
# Strip quotes if present in the retrieved value
if retrieved_title and isinstance(retrieved_title, str):
if retrieved_title.startswith(
'"'
) and retrieved_title.endswith('"'):
retrieved_title = retrieved_title[1:-1]
# Compare the cleaned value
if retrieved_title != metadata["title"]: if retrieved_title != metadata["title"]:
print( logger.warning(
f" Verification failed: expected '{metadata['title']}', got '{retrieved_title}'" f" Verification failed: expected '{metadata['title']}', got '{retrieved_title}'"
) )
success = False # Don't mark as failure if it's just a quoting issue
if retrieved_title.strip('"') == metadata["title"]:
logger.info(
" Verification passed after stripping quotes"
)
else:
success = False
print( logger.info(
f"Metadata storage {'successful' if success else 'partially failed'} for {file_path}" f"Metadata storage {'successful' if success else 'partially failed'} for {file_path}"
) )
return success return success
except Exception as e: except Exception as e:
print(f"Error storing metadata in database: {e}") logger.error(
f"Error storing metadata in database: {e}", exc_info=True
)
import traceback import traceback
traceback.print_exc() traceback.print_exc()
@ -158,10 +191,14 @@ class FireflyDB:
if metadata: if metadata:
# Add the file path to the metadata # Add the file path to the metadata
metadata["file_path"] = file_path metadata["file_path"] = file_path
logger.debug(
f"Retrieved metadata for {file_path} from database"
)
return metadata return metadata
logger.debug(f"No metadata found for {file_path} in database")
return None return None
except Exception as e: except Exception as e:
print(f"Error retrieving metadata from database: {e}") logger.error(f"Error retrieving metadata from database: {e}")
return None return None
def search_by_artist(self, artist): def search_by_artist(self, artist):
@ -171,9 +208,10 @@ class FireflyDB:
try: try:
key = f"index:artist:{artist}" key = f"index:artist:{artist}"
return self.db.list_range(key, 0, -1) logger.debug(f"Searching for files by artist: {artist}")
return self.db.list_ops.list_range(key, 0, -1)
except Exception as e: except Exception as e:
print(f"Error searching by artist: {e}") logger.error(f"Error searching by artist: {e}")
return [] return []
# Similar methods for album and genre searches # Similar methods for album and genre searches
@ -191,27 +229,31 @@ class FireflyDB:
# Store in database if enabled # Store in database if enabled
if self.use_db and self.db: if self.use_db and self.db:
logger.debug(
f"Storing metadata for {metadata.get('file_path', 'unknown file')} in database"
)
db_success = self.store_metadata(metadata) db_success = self.store_metadata(metadata)
if db_success: if db_success:
print( logger.info(
f"Successfully stored metadata for {metadata.get('file_path', 'unknown file')} in database" f"Successfully stored metadata for {metadata.get('file_path', 'unknown file')} in database"
) )
else: else:
print( logger.warning(
f"Failed to store metadata for {metadata.get('file_path', 'unknown file')} in database" f"Failed to store metadata for {metadata.get('file_path', 'unknown file')} in database"
) )
def verify_database_connection(self): def verify_database_connection(self):
"""Verify that the database connection is working properly""" """Verify that the database connection is working properly"""
if not self.use_db or not self.db: if not self.use_db or not self.db:
print("Database usage is disabled") logger.info("Database usage is disabled")
return False return False
try: try:
# Test ping # Test ping
logger.debug("Testing database connection with ping")
ping_result = self.db.ping() ping_result = self.db.ping()
if not ping_result: if not ping_result:
print("Database ping failed") logger.warning("Database ping failed")
return False return False
# Test basic operations # Test basic operations
@ -219,14 +261,17 @@ class FireflyDB:
test_value = "connection_test" test_value = "connection_test"
# Test string operations # Test string operations
string_set_result = self.db.string_set(test_key, test_value) logger.debug("Testing string operations")
string_set_result = self.db.string_ops.string_set(
test_key, test_value
)
if not string_set_result: if not string_set_result:
print("Failed to set test string in database") logger.warning("Failed to set test string in database")
return False return False
string_get_result = self.db.string_get(test_key) string_get_result = self.db.string_get(test_key)
if string_get_result != test_value: if string_get_result != test_value:
print( logger.warning(
f"String get test failed. Expected '{test_value}', got '{string_get_result}'" f"String get test failed. Expected '{test_value}', got '{string_get_result}'"
) )
return False return False
@ -234,8 +279,10 @@ class FireflyDB:
# Clean up # Clean up
self.db.delete(test_key) self.db.delete(test_key)
print("Database connection verified successfully") logger.info("Database connection verified successfully")
return True return True
except Exception as e: except Exception as e:
print(f"Database verification failed with error: {e}") logger.error(
f"Database verification failed with error: {e}", exc_info=True
)
return False return False

44
logging_setup.py Normal file
View File

@ -0,0 +1,44 @@
import logging
import argparse
def setup_logging(args=None):
"""
Configure logging based on command line arguments
Args:
args: The parsed command line arguments
Returns:
A configured logger instance
"""
# Create logger
logger = logging.getLogger("fbroswer")
# Set level based on debug flag
if args and args.debug_on:
logger.setLevel(logging.DEBUG)
# Create more verbose formatter for debug mode
formatter = logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s"
)
else:
logger.setLevel(logging.CRITICAL)
# Create simpler formatter for normal operation
formatter = logging.Formatter(
"%(asctime)s - %(levelname)s - %(message)s"
)
# Create console handler
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
# Add handler to logger
logger.addHandler(console_handler)
# Optionally add file handler for persistent logs
file_handler = logging.FileHandler("fbroswer.log")
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
return logger

View File

@ -1,10 +1,15 @@
import os import os
import logging
import concurrent.futures import concurrent.futures
import mutagen import mutagen
from datetime import datetime from datetime import datetime
from PyQt6.QtCore import QThread, pyqtSignal from PyQt6.QtCore import QThread, pyqtSignal
# Get the logger
logger = logging.getLogger("fbroswer")
# Metadata Extractor # Metadata Extractor
class MetadataExtractor(QThread): class MetadataExtractor(QThread):
metadata_extracted = pyqtSignal(dict) metadata_extracted = pyqtSignal(dict)
@ -24,16 +29,24 @@ class MetadataExtractor(QThread):
self.genres = set() self.genres = set()
self.years = set() self.years = set()
logger.debug(
f"MetadataExtractor initialized with {len(file_list)} files"
)
def run(self): def run(self):
total_files = len(self.file_list) total_files = len(self.file_list)
processed_files = 0 processed_files = 0
logger.info(f"Starting metadata extraction for {total_files} files")
with concurrent.futures.ThreadPoolExecutor() as executor: with concurrent.futures.ThreadPoolExecutor() as executor:
futures = [] futures = []
for file_path in self.file_list: for file_path in self.file_list:
if self.stop_requested: if self.stop_requested:
logger.debug("Metadata extraction stopped by user request")
break break
if file_path in self.metadata_cache: if file_path in self.metadata_cache:
logger.debug(f"Using cached metadata for {file_path}")
self.metadata_extracted.emit( self.metadata_extracted.emit(
self.metadata_cache[file_path] self.metadata_cache[file_path]
) )
@ -48,36 +61,68 @@ class MetadataExtractor(QThread):
for future in concurrent.futures.as_completed(futures): for future in concurrent.futures.as_completed(futures):
if self.stop_requested: if self.stop_requested:
logger.debug(
"Metadata extraction stopped during processing"
)
break break
try: try:
metadata = future.result() metadata = future.result()
if metadata: if metadata:
self.metadata_extracted.emit(metadata) self.metadata_extracted.emit(metadata)
except Exception as e: except Exception as e:
print(f"Error extracting metadata: {e}") logger.error(
f"Error extracting metadata: {e}", exc_info=True
)
processed_files += 1 processed_files += 1
self.progress_update.emit( progress = int(processed_files / total_files * 100)
int(processed_files / total_files * 100) logger.debug(f"Metadata extraction progress: {progress}%")
) self.progress_update.emit(progress)
logger.info("Metadata extraction complete")
self.extraction_complete.emit() self.extraction_complete.emit()
def extract_metadata(self, file_path): def extract_metadata(self, file_path):
try: try:
if not os.path.isfile(file_path): if not os.path.isfile(file_path):
logger.warning(f"File does not exist: {file_path}")
return None return None
# Skip non-audio files # Skip non-audio files
if not file_path.lower().endswith( if not file_path.lower().endswith(
(".mp3", ".wav", ".flac", ".m4a", ".wma", ".mid", ".midi") (".mp3", ".wav", ".flac", ".m4a", ".wma", ".mid", ".midi")
): ):
logger.debug(f"Skipping non-audio file: {file_path}")
return None return None
print(f"Extracting metadata for {file_path}") logger.info(f"Extracting metadata for {file_path}")
audio = mutagen.File(file_path) # type: ignore
# Add additional error handling for mutagen
try:
audio = mutagen.File(file_path) # type: ignore
except (OSError, IOError) as e:
logger.error(f"Mutagen error reading file {file_path}: {e}")
# Return basic metadata without audio tags
file_size_mb = os.path.getsize(file_path) / (1024 * 1024)
return {
"file_path": file_path,
"artist": "Unknown Artist",
"album": "Unknown Album",
"title": os.path.basename(file_path),
"genre": "Unknown Genre",
"year": "Unknown Year",
"size_mb": f"{file_size_mb:.2f}",
"filename": os.path.basename(file_path),
"extension": os.path.splitext(file_path)[1].lower(),
"last_modified": str(
datetime.fromtimestamp(os.path.getmtime(file_path))
),
"extracted_at": str(datetime.now()),
"error": str(e),
}
if not audio: if not audio:
print(f"No metadata found for {file_path}") logger.warning(f"No metadata found for {file_path}")
return None return None
# Get file size in MB # Get file size in MB
@ -101,14 +146,14 @@ class MetadataExtractor(QThread):
"extracted_at": str(datetime.now()), "extracted_at": str(datetime.now()),
} }
print(f"Extracted metadata: {metadata}") logger.debug(f"Extracted metadata: {metadata}")
# Cache the result # Cache the result
self.metadata_cache[file_path] = metadata self.metadata_cache[file_path] = metadata
return metadata return metadata
except Exception as e: except Exception as e:
print(f"Error processing {file_path}: {e}") logger.error(f"Error processing {file_path}: {e}", exc_info=True)
import traceback import traceback
traceback.print_exc() traceback.print_exc()
@ -122,19 +167,20 @@ class MetadataExtractor(QThread):
if isinstance(value, list) and len(value) > 0: if isinstance(value, list) and len(value) > 0:
return str(value[0]) return str(value[0])
return str(value) return str(value)
except Exception: except Exception as e:
pass logger.debug(f"Error extracting tag {tag_name}: {e}")
return default_value return default_value
def stop(self): def stop(self):
logger.info("Stopping metadata extraction")
self.stop_requested = True self.stop_requested = True
def metadata_extraction_complete(self): def metadata_extraction_complete(self):
"""Handle metadata extraction completion""" """Handle metadata extraction completion"""
print( logger.info(
f"Metadata extraction complete. Artists: {len(self.artists)},\ f"Metadata extraction complete. Artists: {len(self.artists)}, "
Albums: {len(self.albums)}, Genres: {len(self.genres)},\ f"Albums: {len(self.albums)}, Genres: {len(self.genres)}, "
Years: {len(self.years)}" f"Years: {len(self.years)}"
) )
if self.on_metadata_complete: if self.on_metadata_complete:
self.on_metadata_complete() self.on_metadata_complete()