From fdccb2ca4dfa26beeadf1c984c26a0d185ad1c17 Mon Sep 17 00:00:00 2001 From: Vlad <427departament@gmail.com> Date: Thu, 25 Dec 2025 17:33:53 +0300 Subject: [PATCH 1/6] Add functions to check mounted devices and file locations on USB; implement unmount confirmation dialog in gcodes.py --- panels/gcodes.py | 159 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 150 insertions(+), 9 deletions(-) diff --git a/panels/gcodes.py b/panels/gcodes.py index c735e28dc..32f98f28d 100644 --- a/panels/gcodes.py +++ b/panels/gcodes.py @@ -1,5 +1,6 @@ import logging import os +import subprocess import gi @@ -20,6 +21,56 @@ def format_label(widget): label.set_lines(3) +def is_mounted_device(path): + """Проверяет, является ли путь примонтированным устройством""" + gcodes_base = "/home/pi/printer_data/gcodes" + + # Убираем префикс 'gcodes/' если он есть + if path.startswith("gcodes/"): + path = path[7:] + + # Если путь пустой, это не устройство + if not path or path == '': + return False + + full_path = os.path.join(gcodes_base, path) + + # Проверяем, что путь существует и является точкой монтирования + if os.path.exists(full_path) and os.path.ismount(full_path): + return True + return False + + +def is_file_on_usb(filepath): + """Проверяет, находится ли файл на USB устройстве (флешке)""" + gcodes_base = "/home/pi/printer_data/gcodes" + + # Убираем префикс 'gcodes/' если он есть + if filepath.startswith("gcodes/"): + filepath = filepath[7:] + + # Получаем директорию файла + dir_path = os.path.dirname(filepath) + + # Если файл в корне gcodes, он не на USB + if dir_path == '' or dir_path == '.': + return False + + # Проверяем, является ли родительская директория примонтированным устройством + full_dir_path = os.path.join(gcodes_base, dir_path) + + # Проверяем все родительские директории до gcodes + current_path = full_dir_path + while current_path != gcodes_base and current_path != os.path.dirname(gcodes_base): + if os.path.ismount(current_path): + return True + current_path = os.path.dirname(current_path) + if not current_path or current_path == '/': + break + + return False + + class Panel(ScreenPanel): def __init__(self, screen, title): title = title or (_("Print") if self._printer.extrudercount > 0 else _("Gcodes")) @@ -156,9 +207,6 @@ def create_item(self, item): rename = Gtk.Button(hexpand=False, vexpand=False, can_focus=False, always_show_image=True) rename.get_style_context().add_class("color2") rename.set_image(self._gtk.Image("files", self.list_button_size, self.list_button_size)) - move = Gtk.Button(hexpand=False, vexpand=False, can_focus=False, always_show_image=True) - move.get_style_context().add_class("color3") - move.set_image(self._gtk.Image("sd", self.list_button_size, self.list_button_size)) itemname = Gtk.Label(hexpand=True, halign=Gtk.Align.START, ellipsize=Pango.EllipsizeMode.END) itemname.get_style_context().add_class("print-filename") itemname.set_markup(f"{basename}") @@ -169,14 +217,26 @@ def create_item(self, item): row.attach(icon, 0, 0, 1, 2) row.attach(itemname, 1, 0, 3, 1) row.attach(info, 1, 1, 1, 1) - row.attach(rename, 2, 1, 1, 1) - row.attach(delete, 3, 1, 1, 1) + + # Определяем позиции кнопок в зависимости от типа элемента + button_col = 2 if 'filename' in item: icon.connect("clicked", self.confirm_print, path) image_args = (path, icon, self.thumbsize / 2, True, "file") delete.connect("clicked", self.confirm_delete_file, f"gcodes/{path}") - move.connect("clicked", self.confirm_move_file, f"gcodes/{path}") rename.connect("clicked", self.show_rename, f"gcodes/{path}") + # Показываем кнопку переноса только если файл находится на USB + if is_file_on_usb(f"gcodes/{path}"): + move = Gtk.Button(hexpand=False, vexpand=False, can_focus=False, always_show_image=True) + move.get_style_context().add_class("color3") + move.set_image(self._gtk.Image("sd", self.list_button_size, self.list_button_size)) + move.connect("clicked", self.confirm_move_file, f"gcodes/{path}") + row.attach(move, button_col, 1, 1, 1) + button_col += 1 + row.attach(rename, button_col, 1, 1, 1) + button_col += 1 + row.attach(delete, button_col, 1, 1, 1) + button_col += 1 action_icon = "printer" if self._printer.extrudercount > 0 else "load" action = self._gtk.Button(action_icon, style="color3") action.connect("clicked", self.confirm_print, path) @@ -184,21 +244,33 @@ def create_item(self, item): action.set_vexpand(False) action.set_halign(Gtk.Align.END) if self._screen.width >= 400: - row.attach(action, 4, 0, 1, 2) + row.attach(action, button_col, 0, 1, 2) else: icon.get_style_context().add_class("color3") - row.attach(icon, 4, 0, 1, 2) + row.attach(icon, button_col, 0, 1, 2) elif 'dirname' in item: icon.connect("clicked", self.change_dir, path) image_args = (None, icon, self.thumbsize / 2, True, "folder") delete.connect("clicked", self.confirm_delete_directory, path) rename.connect("clicked", self.show_rename, path) + # Проверяем, является ли папка примонтированным устройством + if is_mounted_device(path): + unmount = Gtk.Button(hexpand=False, vexpand=False, can_focus=False, always_show_image=True) + unmount.get_style_context().add_class("color4") + unmount.set_image(self._gtk.Image("sd", self.list_button_size, self.list_button_size)) + unmount.connect("clicked", self.confirm_unmount, path) + row.attach(unmount, button_col, 1, 1, 1) + button_col += 1 + row.attach(rename, button_col, 1, 1, 1) + button_col += 1 + row.attach(delete, button_col, 1, 1, 1) + button_col += 1 action = self._gtk.Button("load", style="color3") action.connect("clicked", self.change_dir, path) action.set_hexpand(False) action.set_vexpand(False) action.set_halign(Gtk.Align.END) - row.attach(action, 4, 0, 1, 2) + row.attach(action, button_col, 0, 1, 2) else: return fbchild.add(row) @@ -278,6 +350,75 @@ def confirm_delete_directory(self, widget, dirpath): params ) + def confirm_unmount(self, widget, dirpath): + """Отмонтирует примонтированное устройство""" + logging.debug(f"Requesting unmount confirmation for {dirpath}") + + if not is_mounted_device(dirpath): + logging.warning(f"Path {dirpath} is not a mounted device") + self._screen.show_popup_message(_("This directory is not a mounted device")) + return + + # Используем диалог подтверждения + buttons = [ + {"name": _("Unmount"), "response": Gtk.ResponseType.OK, "style": 'dialog-primary'}, + {"name": _("Cancel"), "response": Gtk.ResponseType.CANCEL, "style": 'dialog-secondary'} + ] + + label = Gtk.Label( + hexpand=True, vexpand=True, + wrap=True, wrap_mode=Pango.WrapMode.WORD_CHAR, + ellipsize=Pango.EllipsizeMode.END + ) + label.set_markup(_("Unmount device?") + f"\n\n{dirpath}") + + self._gtk.Dialog( + _("Unmount Device"), + buttons, + label, + self.confirm_unmount_response, + dirpath + ) + + def confirm_unmount_response(self, dialog, response_id, dirpath): + """Обработчик ответа на диалог отмонтирования""" + self._gtk.remove_dialog(dialog) + if response_id != Gtk.ResponseType.OK: + return + + logging.debug(f"Unmounting device {dirpath}") + gcodes_base = "/home/pi/printer_data/gcodes" + + # Убираем префикс 'gcodes/' если он есть + clean_path = dirpath + if clean_path.startswith("gcodes/"): + clean_path = clean_path[7:] + + full_path = os.path.join(gcodes_base, clean_path) + + # Выполняем отмонтирование через subprocess + try: + result = subprocess.run( + ["sudo", "umount", full_path], + capture_output=True, + text=True, + timeout=10 + ) + if result.returncode == 0: + logging.info(f"Successfully unmounted {full_path}") + # Обновляем список файлов после отмонтирования + self._refresh_files() + self._screen.show_popup_message(_("Device unmounted successfully")) + else: + logging.error(f"Failed to unmount {full_path}: {result.stderr}") + self._screen.show_popup_message(_("Failed to unmount device") + f": {result.stderr}") + except subprocess.TimeoutExpired: + logging.error(f"Timeout while unmounting {full_path}") + self._screen.show_popup_message(_("Timeout while unmounting device")) + except Exception as e: + logging.error(f"Error unmounting {full_path}: {e}") + self._screen.show_popup_message(_("Error unmounting device") + f": {str(e)}") + def back(self): if self.showing_rename: self.hide_rename() From 6c25a7ce4a2b880f7594736c8e4f414944f861e9 Mon Sep 17 00:00:00 2001 From: Vlad <427departament@gmail.com> Date: Thu, 25 Dec 2025 17:42:55 +0300 Subject: [PATCH 2/6] Rename "Resave" button to "Move" in USB dialog; update file existence check to use is_file_on_usb function for improved clarity and functionality. --- panels/gcodes.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/panels/gcodes.py b/panels/gcodes.py index 32f98f28d..d2d353e06 100644 --- a/panels/gcodes.py +++ b/panels/gcodes.py @@ -497,7 +497,7 @@ def confirm_print(self, widget, filename): buttons_usb = [ {"name": _("Delete"), "response": Gtk.ResponseType.REJECT, "style": 'dialog-error'}, - {"name": _("Resave"), "response": Gtk.ResponseType.APPLY, "style": 'dialog-secondary'}, + {"name": _("Move"), "response": Gtk.ResponseType.APPLY, "style": 'dialog-secondary'}, {"name": action, "response": Gtk.ResponseType.OK, "style": 'dialog-primary'}, {"name": _("Cancel"), "response": Gtk.ResponseType.CANCEL, "style": 'dialog-secondary'} ] @@ -537,13 +537,12 @@ def confirm_print(self, widget, filename): inside_box.pack_start(info_box, True, True, 0) main_box.pack_start(inside_box, True, True, 0) - # self._gtk.Dialog(f'{action} {filename}', buttons, main_box, self.confirm_print_response, filename) - - dir_path = "/home/pi/printer_data/gcodes/" - if os.path.isfile(f"{dir_path} + {filename}"): - self._gtk.Dialog(f'{action} {filename}', buttons, main_box, self.confirm_print_response, filename) - else: + + # Проверяем, находится ли файл на USB устройстве + if is_file_on_usb(f"gcodes/{filename}"): self._gtk.Dialog(f'{action} {filename}', buttons_usb, main_box, self.confirm_print_response, filename) + else: + self._gtk.Dialog(f'{action} {filename}', buttons, main_box, self.confirm_print_response, filename) def confirm_print_response(self, dialog, response_id, filename): self._gtk.remove_dialog(dialog) @@ -554,7 +553,7 @@ def confirm_print_response(self, dialog, response_id, filename): self._screen._ws.klippy.print_start(filename) elif response_id == Gtk.ResponseType.APPLY: logging.info(f"Move file {filename} to internal storage") - self.confirm_move_file(self, f"gcodes/{filename}") + self.confirm_move_file(None, f"gcodes/{filename}") elif response_id == Gtk.ResponseType.REJECT: self.confirm_delete_file(None, f"gcodes/{filename}") From 6a2ff6e83f5b83ae73709ba7377882763958c219 Mon Sep 17 00:00:00 2001 From: Vlad <427departament@gmail.com> Date: Thu, 25 Dec 2025 17:44:44 +0300 Subject: [PATCH 3/6] Add directory deletion after unmounting in gcodes.py; rename "Move" button to "Resave" in USB dialog for clarity. --- panels/gcodes.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/panels/gcodes.py b/panels/gcodes.py index d2d353e06..48cd40af1 100644 --- a/panels/gcodes.py +++ b/panels/gcodes.py @@ -406,6 +406,20 @@ def confirm_unmount_response(self, dialog, response_id, dirpath): ) if result.returncode == 0: logging.info(f"Successfully unmounted {full_path}") + # Удаляем папку устройства после отмонтирования + try: + if os.path.exists(full_path): + # Используем API для удаления директории + params = {"path": clean_path, "force": True} + self._screen._send_action( + None, + "server.files.delete_directory", + params + ) + logging.info(f"Deleted directory {full_path} after unmounting") + except Exception as e: + logging.error(f"Error deleting directory {full_path}: {e}") + # Продолжаем выполнение даже если удаление не удалось # Обновляем список файлов после отмонтирования self._refresh_files() self._screen.show_popup_message(_("Device unmounted successfully")) @@ -497,7 +511,7 @@ def confirm_print(self, widget, filename): buttons_usb = [ {"name": _("Delete"), "response": Gtk.ResponseType.REJECT, "style": 'dialog-error'}, - {"name": _("Move"), "response": Gtk.ResponseType.APPLY, "style": 'dialog-secondary'}, + {"name": _("Resave"), "response": Gtk.ResponseType.APPLY, "style": 'dialog-secondary'}, {"name": action, "response": Gtk.ResponseType.OK, "style": 'dialog-primary'}, {"name": _("Cancel"), "response": Gtk.ResponseType.CANCEL, "style": 'dialog-secondary'} ] From 27c3ffbf072cb035c78aa300b91d75668807d56f Mon Sep 17 00:00:00 2001 From: Vlad <427departament@gmail.com> Date: Thu, 25 Dec 2025 17:48:56 +0300 Subject: [PATCH 4/6] Update popup message level for device unmount confirmation in gcodes.py --- panels/gcodes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panels/gcodes.py b/panels/gcodes.py index 48cd40af1..6b31202a4 100644 --- a/panels/gcodes.py +++ b/panels/gcodes.py @@ -422,7 +422,7 @@ def confirm_unmount_response(self, dialog, response_id, dirpath): # Продолжаем выполнение даже если удаление не удалось # Обновляем список файлов после отмонтирования self._refresh_files() - self._screen.show_popup_message(_("Device unmounted successfully")) + self._screen.show_popup_message(_("Device unmounted successfully"), level=1) else: logging.error(f"Failed to unmount {full_path}: {result.stderr}") self._screen.show_popup_message(_("Failed to unmount device") + f": {result.stderr}") From e249ebc9ff22db9f9ef34b8ff01acdf2cd8884c4 Mon Sep 17 00:00:00 2001 From: Vlad <427departament@gmail.com> Date: Thu, 25 Dec 2025 17:55:39 +0300 Subject: [PATCH 5/6] Refactor file transfer confirmation in gcodes.py to clarify internal storage transfer; update Russian localization for improved accuracy and new messages. --- .../locales/ru/LC_MESSAGES/KlipperScreen.mo | Bin 28703 -> 29148 bytes .../locales/ru/LC_MESSAGES/KlipperScreen.po | 16 +++++++++-- panels/gcodes.py | 26 +++++------------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/ks_includes/locales/ru/LC_MESSAGES/KlipperScreen.mo b/ks_includes/locales/ru/LC_MESSAGES/KlipperScreen.mo index f8531785d42178a26f2cc97d8a7265c158a0f967..956fd6003d35a80d1c046bd609eed194ab922a65 100644 GIT binary patch delta 8350 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 diff --git a/ks_includes/locales/ru/LC_MESSAGES/KlipperScreen.po b/ks_includes/locales/ru/LC_MESSAGES/KlipperScreen.po index 6a6527b53..99e4a7161 100644 --- a/ks_includes/locales/ru/LC_MESSAGES/KlipperScreen.po +++ b/ks_includes/locales/ru/LC_MESSAGES/KlipperScreen.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: KlipperScreen\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-11-26 12:31-0300\n" -"PO-Revision-Date: 2025-06-23 15:16+0300\n" +"PO-Revision-Date: 2025-12-25 17:55+0300\n" "Last-Translator: gfbdrgng \n" "Language-Team: Russian \n" "Language: ru\n" @@ -448,7 +448,7 @@ msgid "Job Status" msgstr "Статус работы" msgid "Klipper Restart" -msgstr "Klipper перезагружается" +msgstr "Перезагрузить Klipper" msgid "Klipper has disconnected" msgstr "Нет связи с Klipper" @@ -1135,3 +1135,15 @@ msgstr "После подтверждения принтер перейдет в msgid "Emergency Homing" msgstr "Аварийная парковка" + +msgid "After confirmation, the file will be transferred to internal storage." +msgstr "После подтверждения файл будет перенесен на внутреннее хранилище." + +msgid "Transfer file?" +msgstr "Перенести файл?" + +msgid "Unmount" +msgstr "Отмонтировать" + +msgid "Unmount device?" +msgstr "Отмонтировать устройство?" diff --git a/panels/gcodes.py b/panels/gcodes.py index 6b31202a4..353ea127e 100644 --- a/panels/gcodes.py +++ b/panels/gcodes.py @@ -320,25 +320,13 @@ def confirm_move_file(self, widget, filepath): dest = "gcodes/" + filename params = {"source": f"{filepath}", "dest": f"{dest}"} - gcodes_path = "/home/pi/printer_data/" - check_file = os.path.exists(gcodes_path + dest) - if check_file: - self._screen._confirm_send_action( - None, - _("A file with this name already exists") + "\n\n" + - _("You may be trying to move a file that is already in the main directory") + - "\n\n" + _("Replace it?") + "\n\n" + filepath, - "server.files.move", - params - ) - else: - self._screen._confirm_send_action( - None, - _("This function is designed to move a file to the main directory") + - "\n\n" + _("Move file to main directory?") + "\n\n" + filepath, - "server.files.move", - params - ) + self._screen._confirm_send_action( + None, + _("After confirmation, the file will be transferred to internal storage.") + + "\n\n" + _("Transfer file?") + "\n\n" + filepath, + "server.files.move", + params + ) def confirm_delete_directory(self, widget, dirpath): logging.debug(f"Sending delete_directory {dirpath}") From a1fbafb0e4b6a5711afb737b40bb424799e2d514 Mon Sep 17 00:00:00 2001 From: Vlad <427departament@gmail.com> Date: Thu, 25 Dec 2025 18:00:40 +0300 Subject: [PATCH 6/6] Update Russian localization in KlipperScreen.po with new unmounting messages and adjust PO-Revision-Date for accuracy. --- .../locales/ru/LC_MESSAGES/KlipperScreen.mo | Bin 29148 -> 29624 bytes .../locales/ru/LC_MESSAGES/KlipperScreen.po | 14 +++++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/ks_includes/locales/ru/LC_MESSAGES/KlipperScreen.mo b/ks_includes/locales/ru/LC_MESSAGES/KlipperScreen.mo index 956fd6003d35a80d1c046bd609eed194ab922a65..ddcc9e6e6d1fe27c85b1bf9abc710a640604cdfe 100644 GIT binary patch delta 8419 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 delta 8029 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 diff --git a/ks_includes/locales/ru/LC_MESSAGES/KlipperScreen.po b/ks_includes/locales/ru/LC_MESSAGES/KlipperScreen.po index 99e4a7161..0c8b77ad9 100644 --- a/ks_includes/locales/ru/LC_MESSAGES/KlipperScreen.po +++ b/ks_includes/locales/ru/LC_MESSAGES/KlipperScreen.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: KlipperScreen\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-11-26 12:31-0300\n" -"PO-Revision-Date: 2025-12-25 17:55+0300\n" +"PO-Revision-Date: 2025-12-25 18:00+0300\n" "Last-Translator: gfbdrgng \n" "Language-Team: Russian \n" "Language: ru\n" @@ -1147,3 +1147,15 @@ msgstr "Отмонтировать" msgid "Unmount device?" msgstr "Отмонтировать устройство?" + +msgid "Device unmounted successfully" +msgstr "Устройство успешно отмонтировано" + +msgid "Failed to unmount device" +msgstr "Не удалось отмонтировать устройство" + +msgid "Timeout while unmounting device" +msgstr "Истекло время ожидания при отмонтировании устройства" + +msgid "Error unmounting device" +msgstr "Ошибка при отмонтировании устройства"