diff --git a/src/onedrive_gui/main_window.py b/src/onedrive_gui/main_window.py
index 02058bb..65b296c 100644
--- a/src/onedrive_gui/main_window.py
+++ b/src/onedrive_gui/main_window.py
@@ -62,6 +62,10 @@ except ImportError:
window.hide()
+# Label of a failed file operation in the file operation list.
+FAILED_OPERATION_LABELS = {"Uploading": "Upload", "Downloading": "Download", "Moving": "Move to Recycle Bin"}
+
+
class MainWindow(QMainWindow, Ui_MainWindow):
def __init__(self):
# Expose this instance as main_window.main_window_instance so other modules
@@ -1066,7 +1070,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
item_widget.set_icon(file_path)
item_widget.hide_progress_bar(transfer_complete or transfer_failed)
- if file_operation == "Deleting":
+ if file_operation == "Deleting" and not transfer_failed:
item_widget.set_label_1(f"Deleted from {parent_dir}")
item_widget.set_custom_icon(self.trash_icon)
item_widget.set_label_2(f"")
@@ -1078,7 +1082,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
else:
item_widget.set_timestamp("")
- elif file_operation == "Moving":
+ elif file_operation == "Moving" and not transfer_failed:
item_widget.set_label_1(f"Trashed from {parent_dir}")
item_widget.set_label_2(f"")
# Store timestamp and display for moved files
@@ -1097,7 +1101,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# is deliberately left empty so repeated attempts of the
# same file update this row rather than adding duplicates,
# and a later successful upload flips it back to "Uploaded".
- failed_operation = "Upload" if file_operation == "Uploading" else "Download"
+ failed_operation = FAILED_OPERATION_LABELS.get(file_operation, "Download")
shortened_path = shorten_path(relative_path_display, 32)
item_widget.set_label_1(f"{failed_operation} failed - {shortened_path}")
item_widget.set_label_2(f"")
@@ -1181,7 +1185,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
myQCustomQWidget.set_icon(file_path)
myQCustomQWidget.hide_progress_bar(transfer_complete or transfer_failed)
- if file_operation == "Deleting":
+ if file_operation == "Deleting" and not transfer_failed:
myQCustomQWidget.set_label_1(f"Deleted from {parent_dir}")
myQCustomQWidget.set_custom_icon(self.trash_icon)
myQCustomQWidget.set_label_2(f"")
@@ -1193,7 +1197,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
else:
myQCustomQWidget.set_timestamp("")
- elif file_operation == "Moving":
+ elif file_operation == "Moving" and not transfer_failed:
myQCustomQWidget.set_label_1(f"Trashed from {parent_dir}")
myQCustomQWidget.set_label_2(f"")
# Store timestamp and display for moved files
@@ -1207,7 +1211,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
elif transfer_failed:
# The transfer failed before an item existed - create it in the failed
# state so the failure is visible in the file operation list.
- failed_operation = "Upload" if file_operation == "Uploading" else "Download"
+ failed_operation = FAILED_OPERATION_LABELS.get(file_operation, "Download")
shortened_path = shorten_path(relative_path_display, 32)
myQCustomQWidget.set_label_1(f"{failed_operation} failed - {shortened_path}")
myQCustomQWidget.set_label_2(f"")
diff --git a/src/onedrive_gui/workers.py b/src/onedrive_gui/workers.py
index b6f8d4a..2abf181 100644
--- a/src/onedrive_gui/workers.py
+++ b/src/onedrive_gui/workers.py
@@ -315,7 +315,7 @@ class WorkerThread(QThread):
self.pending_error_path = None
# Check if we were collecting failed files and this line is not a "Failed to" line
- if self.collecting_failed_files and "Failed to upload:" not in stdout and "Failed to download:" not in stdout:
+ if self.collecting_failed_files and not any(m in stdout for m in ("Failed to upload:", "Failed to download:", "Failed to move to the configured Recycle Bin:")):
# We're done collecting, emit the status
self._emit_failed_files_status()
self.collecting_failed_files = False
@@ -647,6 +647,16 @@ class WorkerThread(QThread):
if path_value and path_value != "(not available)":
self.pending_error_path = path_value
+ elif "to the configured Recycle Bin failed for " in stdout:
+ # Client v2.5.12+: 'ERROR: Move of local file to the configured Recycle Bin
+ # failed for : ' - single line, no 'Path:' continuation.
+ error_text = stdout.split("ERROR:", 1)[-1].strip()
+ match = re.search(r"failed for (.+?): ([^:]+)$", error_text)
+ if match:
+ self.file_errors[_path_key(match.group(1).strip())] = error_text
+ logging.error(f"[{self.profile_name}] {error_text}")
+ self._emit_error_status(error_text)
+
elif "ERROR:" in stdout:
# Extract error message after "ERROR:" prefix
error_parts = stdout.split("ERROR:", 1)
@@ -673,6 +683,30 @@ class WorkerThread(QThread):
# Start collecting failed file lines (they come after this summary line)
self.collecting_failed_files = True
+ elif stdout.startswith("Failed local items to move to the configured Recycle Bin:"):
+ # Client v2.5.12+: online deletions that could not be moved to the
+ # local Recycle Bin are summarised after the transfer failures.
+ match = re.search(r": (\d+)$", stdout)
+ if match:
+ self.failed_files_count += int(match.group(1))
+ self.collecting_failed_files = True
+
+ elif stdout.startswith("Failed to move to the configured Recycle Bin:"):
+ file_path = stdout.split(":", 1)[1].strip()
+ if len(self.failed_files) < 25:
+ self.failed_files.append(f"Failed to move to Recycle Bin: {file_path}")
+ transfer_progress_new = {
+ "file_operation": "Moving",
+ "file_path": file_path,
+ "progress": "0",
+ "transfer_complete": False,
+ "transfer_failed": True,
+ "error_message": self.file_errors.get(_path_key(file_path)),
+ "timestamp": None,
+ }
+ logging.debug(transfer_progress_new)
+ self.update_progress_new.emit(transfer_progress_new, self.profile_name)
+
elif "Failed to upload:" in stdout or "Failed to download:" in stdout:
# Extract the operation and file path
match = re.search(r"Failed to (upload|download): (.+)$", stdout)