A bunch of changes
This commit is contained in:
@@ -9,7 +9,6 @@ Classes:
|
||||
PropertiesDialog: A QDialog that presents file properties in a tabbed
|
||||
interface.
|
||||
"""
|
||||
import os
|
||||
from PySide6.QtWidgets import (
|
||||
QWidget, QLabel, QVBoxLayout, QMessageBox, QMenu, QInputDialog,
|
||||
QDialog, QTableWidget, QTableWidgetItem, QHeaderView, QTabWidget,
|
||||
@@ -18,14 +17,40 @@ from PySide6.QtWidgets import (
|
||||
from PySide6.QtGui import (
|
||||
QImageReader, QIcon, QColor
|
||||
)
|
||||
from PySide6.QtCore import (
|
||||
Qt, QFileInfo, QLocale
|
||||
)
|
||||
from PySide6.QtCore import (QThread, Signal, Qt, QFileInfo, QLocale)
|
||||
from constants import (
|
||||
RATING_XATTR_NAME, XATTR_NAME, UITexts
|
||||
)
|
||||
from metadatamanager import MetadataManager, HAVE_EXIV2, notify_baloo
|
||||
from utils import preserve_mtime
|
||||
from metadatamanager import MetadataManager, HAVE_EXIV2, XattrManager
|
||||
|
||||
|
||||
class PropertiesLoader(QThread):
|
||||
"""Background thread to load metadata (xattrs and EXIF) asynchronously."""
|
||||
loaded = Signal(dict, dict)
|
||||
|
||||
def __init__(self, path, parent=None):
|
||||
super().__init__(parent)
|
||||
self.path = path
|
||||
self._abort = False
|
||||
|
||||
def stop(self):
|
||||
"""Signals the thread to stop and waits for it."""
|
||||
self._abort = True
|
||||
self.wait()
|
||||
|
||||
def run(self):
|
||||
# Xattrs
|
||||
if self._abort:
|
||||
return
|
||||
xattrs = XattrManager.get_all_attributes(self.path)
|
||||
|
||||
if self._abort:
|
||||
return
|
||||
|
||||
# EXIF
|
||||
exif_data = MetadataManager.read_all_metadata(self.path)
|
||||
if not self._abort:
|
||||
self.loaded.emit(xattrs, exif_data)
|
||||
|
||||
|
||||
class PropertiesDialog(QDialog):
|
||||
@@ -51,6 +76,7 @@ class PropertiesDialog(QDialog):
|
||||
self.setWindowTitle(UITexts.PROPERTIES_TITLE)
|
||||
self._initial_tags = initial_tags if initial_tags is not None else []
|
||||
self._initial_rating = initial_rating
|
||||
self.loader = None
|
||||
self.resize(400, 500)
|
||||
self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
|
||||
|
||||
@@ -128,7 +154,8 @@ class PropertiesDialog(QDialog):
|
||||
self.table.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||
self.table.customContextMenuRequested.connect(self.show_context_menu)
|
||||
|
||||
self.load_metadata()
|
||||
# Initial partial load (synchronous, just passed args)
|
||||
self.update_metadata_table({}, initial_only=True)
|
||||
meta_layout.addWidget(self.table)
|
||||
tabs.addTab(meta_widget, QIcon.fromTheme("document-properties"),
|
||||
UITexts.PROPERTIES_METADATA_TAB)
|
||||
@@ -159,7 +186,8 @@ class PropertiesDialog(QDialog):
|
||||
# This is a disk read.
|
||||
self.exif_table.customContextMenuRequested.connect(self.show_exif_context_menu)
|
||||
|
||||
self.load_exif_data()
|
||||
# Placeholder for EXIF
|
||||
self.update_exif_table(None)
|
||||
|
||||
exif_layout.addWidget(self.exif_table)
|
||||
tabs.addTab(exif_widget, QIcon.fromTheme("view-details"),
|
||||
@@ -173,10 +201,18 @@ class PropertiesDialog(QDialog):
|
||||
btn_box.rejected.connect(self.close)
|
||||
layout.addWidget(btn_box)
|
||||
|
||||
def load_metadata(self):
|
||||
# Start background loading
|
||||
self.reload_metadata()
|
||||
|
||||
def closeEvent(self, event):
|
||||
if self.loader and self.loader.isRunning():
|
||||
self.loader.stop()
|
||||
super().closeEvent(event)
|
||||
|
||||
def update_metadata_table(self, disk_xattrs, initial_only=False):
|
||||
"""
|
||||
Loads metadata from the file's text keys (via QImageReader) and
|
||||
extended attributes (xattrs) into the metadata table.
|
||||
Updates the metadata table with extended attributes.
|
||||
Merges initial tags/rating with loaded xattrs.
|
||||
"""
|
||||
self.table.blockSignals(True)
|
||||
self.table.setRowCount(0)
|
||||
@@ -188,26 +224,11 @@ class PropertiesDialog(QDialog):
|
||||
if self._initial_rating > 0:
|
||||
preloaded_xattrs[RATING_XATTR_NAME] = str(self._initial_rating)
|
||||
|
||||
# Read other xattrs from disk
|
||||
xattrs = {}
|
||||
try:
|
||||
for xkey in os.listxattr(self.path):
|
||||
# Avoid re-reading already known attributes
|
||||
if xkey not in preloaded_xattrs:
|
||||
try:
|
||||
val = os.getxattr(self.path, xkey) # This is a disk read
|
||||
try:
|
||||
val_str = val.decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
val_str = str(val)
|
||||
xattrs[xkey] = val_str
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Combine preloaded and newly read xattrs
|
||||
all_xattrs = {**preloaded_xattrs, **xattrs}
|
||||
all_xattrs = preloaded_xattrs.copy()
|
||||
if not initial_only and disk_xattrs:
|
||||
# Disk data takes precedence or adds to it
|
||||
all_xattrs.update(disk_xattrs)
|
||||
|
||||
self.table.setRowCount(len(all_xattrs))
|
||||
|
||||
@@ -224,11 +245,34 @@ class PropertiesDialog(QDialog):
|
||||
row += 1
|
||||
self.table.blockSignals(False)
|
||||
|
||||
def load_exif_data(self):
|
||||
"""Loads EXIF, XMP, and IPTC metadata using the MetadataManager."""
|
||||
def reload_metadata(self):
|
||||
"""Starts the background thread to load metadata."""
|
||||
if self.loader and self.loader.isRunning():
|
||||
# Already running
|
||||
return
|
||||
self.loader = PropertiesLoader(self.path, self)
|
||||
self.loader.loaded.connect(self.on_data_loaded)
|
||||
self.loader.start()
|
||||
|
||||
def on_data_loaded(self, xattrs, exif_data):
|
||||
"""Slot called when metadata is loaded from the thread."""
|
||||
self.update_metadata_table(xattrs, initial_only=False)
|
||||
self.update_exif_table(exif_data)
|
||||
|
||||
def update_exif_table(self, exif_data):
|
||||
"""Updates the EXIF table with loaded data."""
|
||||
self.exif_table.blockSignals(True)
|
||||
self.exif_table.setRowCount(0)
|
||||
|
||||
if exif_data is None:
|
||||
# Loading state
|
||||
self.exif_table.setRowCount(1)
|
||||
item = QTableWidgetItem("Loading data...")
|
||||
item.setFlags(Qt.ItemIsEnabled)
|
||||
self.exif_table.setItem(0, 0, item)
|
||||
self.exif_table.blockSignals(False)
|
||||
return
|
||||
|
||||
if not HAVE_EXIV2:
|
||||
self.exif_table.setRowCount(1)
|
||||
error_color = QColor("red")
|
||||
@@ -243,8 +287,6 @@ class PropertiesDialog(QDialog):
|
||||
self.exif_table.blockSignals(False)
|
||||
return
|
||||
|
||||
exif_data = MetadataManager.read_all_metadata(self.path)
|
||||
|
||||
if not exif_data:
|
||||
self.exif_table.setRowCount(1)
|
||||
item = QTableWidgetItem(UITexts.INFO)
|
||||
@@ -291,16 +333,11 @@ class PropertiesDialog(QDialog):
|
||||
if item.column() == 1:
|
||||
key = self.table.item(item.row(), 0).text()
|
||||
val = item.text()
|
||||
# Treat empty or whitespace-only values as removal to match previous
|
||||
# behavior
|
||||
val_to_set = val if val.strip() else None
|
||||
try:
|
||||
with preserve_mtime(self.path):
|
||||
if not val.strip():
|
||||
try:
|
||||
os.removexattr(self.path, key)
|
||||
except OSError:
|
||||
pass
|
||||
else:
|
||||
os.setxattr(self.path, key, val.encode('utf-8'))
|
||||
notify_baloo(self.path)
|
||||
XattrManager.set_attribute(self.path, key, val_to_set)
|
||||
except Exception as e:
|
||||
QMessageBox.warning(self, UITexts.ERROR,
|
||||
UITexts.PROPERTIES_ERROR_SET_ATTR.format(e))
|
||||
@@ -361,10 +398,8 @@ class PropertiesDialog(QDialog):
|
||||
key))
|
||||
if ok2:
|
||||
try:
|
||||
with preserve_mtime(self.path):
|
||||
os.setxattr(self.path, key, val.encode('utf-8'))
|
||||
notify_baloo(self.path)
|
||||
self.load_metadata()
|
||||
XattrManager.set_attribute(self.path, key, val)
|
||||
self.reload_metadata()
|
||||
except Exception as e:
|
||||
QMessageBox.warning(self, UITexts.ERROR,
|
||||
UITexts.PROPERTIES_ERROR_ADD_ATTR.format(e))
|
||||
@@ -378,9 +413,7 @@ class PropertiesDialog(QDialog):
|
||||
"""
|
||||
key = self.table.item(row, 0).text()
|
||||
try:
|
||||
with preserve_mtime(self.path):
|
||||
os.removexattr(self.path, key)
|
||||
notify_baloo(self.path)
|
||||
XattrManager.set_attribute(self.path, key, None)
|
||||
self.table.removeRow(row)
|
||||
except Exception as e:
|
||||
QMessageBox.warning(self, UITexts.ERROR,
|
||||
|
||||
Reference in New Issue
Block a user