diff options
| author | jvoisin | 2018-07-19 00:11:30 +0200 |
|---|---|---|
| committer | jvoisin | 2018-07-19 00:11:30 +0200 |
| commit | 052a356750541d5f985cfa7f995afc4083f42a13 (patch) | |
| tree | 7b079fc51f3acd8035132e400025703dd5ae3c6a | |
| parent | 2f670651cf9307a1d8ae7b4cb83a8f2d03ad0ef7 (diff) | |
Implement a much better Nautilus extension thanks to @atenart
Co-authored-by: Antoine Tenart <antoine.tenart@ack.tf>
Co-authored-by: jvoisin <julien.voisin@dustri.org>
| -rw-r--r-- | nautilus/nautilus_mat2.py | 157 |
1 files changed, 70 insertions, 87 deletions
diff --git a/nautilus/nautilus_mat2.py b/nautilus/nautilus_mat2.py index 48f2261..9e8d4db 100644 --- a/nautilus/nautilus_mat2.py +++ b/nautilus/nautilus_mat2.py | |||
| @@ -1,5 +1,10 @@ | |||
| 1 | #!/usr/bin/env python3 | 1 | #!/usr/bin/env python3 |
| 2 | 2 | ||
| 3 | # TODO: | ||
| 4 | # - Test with a large amount of files. | ||
| 5 | # - Show a progression bar when the removal takes time. | ||
| 6 | # - Improve the MessageDialog list for failed items. | ||
| 7 | |||
| 3 | import os | 8 | import os |
| 4 | from urllib.parse import unquote | 9 | from urllib.parse import unquote |
| 5 | 10 | ||
| @@ -8,46 +13,65 @@ gi.require_version('Nautilus', '3.0') | |||
| 8 | gi.require_version('Gtk', '3.0') | 13 | gi.require_version('Gtk', '3.0') |
| 9 | from gi.repository import Nautilus, GObject, Gtk, Gio | 14 | from gi.repository import Nautilus, GObject, Gtk, Gio |
| 10 | 15 | ||
| 11 | |||
| 12 | from libmat2 import parser_factory | 16 | from libmat2 import parser_factory |
| 13 | 17 | ||
| 14 | class Mat2Wrapper(): | 18 | class Mat2Wrapper(): |
| 15 | def __init__(self, filepath): | 19 | def __init__(self, filepath): |
| 16 | self.filepath = filepath | 20 | self.__filepath = filepath |
| 17 | 21 | ||
| 18 | class StatusWindow(Gtk.Window): | 22 | def remove_metadata(self): |
| 19 | def __init__(self, items): | 23 | parser, mtype = parser_factory.get_parser(self.__filepath) |
| 20 | self.window = Gtk.Window() | 24 | if parser is None: |
| 21 | self.window.set_border_width(10) | 25 | return False, mtype |
| 26 | return parser.remove_all(), mtype | ||
| 22 | 27 | ||
| 23 | self.items = items | 28 | class ColumnExtension(GObject.GObject, Nautilus.MenuProvider, Nautilus.LocationWidgetProvider): |
| 24 | self.confirm_window() | 29 | def notify(self): |
| 30 | self.infobar_msg.set_text("Failed to clean some items") | ||
| 31 | self.infobar.show_all() | ||
| 25 | 32 | ||
| 26 | def confirm_window(self): | 33 | def get_widget(self, uri, window): |
| 27 | # Header bar | 34 | self.infobar = Gtk.InfoBar() |
| 28 | hb = Gtk.HeaderBar() | 35 | self.infobar.set_message_type(Gtk.MessageType.ERROR) |
| 29 | self.window.set_titlebar(hb) | 36 | self.infobar.set_show_close_button(True) |
| 30 | hb.props.title = "Remove metadata" | 37 | self.infobar.connect("response", self.__cb_infobar_response) |
| 38 | |||
| 39 | hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) | ||
| 40 | self.infobar.get_content_area().pack_start(hbox, False, False, 0) | ||
| 41 | |||
| 42 | btn = Gtk.Button("Show") | ||
| 43 | btn.connect("clicked", self.__cb_show_failed) | ||
| 44 | self.infobar.get_content_area().pack_end(btn, False, False, 0) | ||
| 45 | |||
| 46 | self.infobar_msg = Gtk.Label() | ||
| 47 | hbox.pack_start(self.infobar_msg, False, False, 0) | ||
| 48 | |||
| 49 | return self.infobar | ||
| 50 | |||
| 51 | def __cb_infobar_response(self, infobar, response): | ||
| 52 | if response == Gtk.ResponseType.CLOSE: | ||
| 53 | self.infobar.hide() | ||
| 31 | 54 | ||
| 32 | cancel = Gtk.Button("Cancel") | 55 | def __cb_show_failed(self, button): |
| 33 | cancel.connect("clicked", self.cancel_btn) | 56 | self.infobar.hide() |
| 34 | hb.pack_start(cancel) | ||
| 35 | 57 | ||
| 36 | self.remove = Gtk.Button("Remove") | 58 | window = Gtk.Window() |
| 37 | self.remove.get_style_context().add_class(Gtk.STYLE_CLASS_SUGGESTED_ACTION) | 59 | hb = Gtk.HeaderBar() |
| 38 | self.remove.connect("clicked", self.remove_btn) | 60 | window.set_titlebar(hb) |
| 39 | hb.pack_end(self.remove) | 61 | hb.props.title = "Metadata removal failed" |
| 62 | |||
| 63 | exit_buton = Gtk.Button("Exit") | ||
| 64 | exit_buton.connect("clicked", lambda _: window.close()) | ||
| 65 | hb.pack_end(exit_buton) | ||
| 40 | 66 | ||
| 41 | self.main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) | 67 | box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) |
| 42 | self.window.add(self.main_box) | 68 | window.add(box) |
| 43 | 69 | ||
| 44 | # List of files to clean | ||
| 45 | listbox = Gtk.ListBox() | 70 | listbox = Gtk.ListBox() |
| 46 | self.main_box.pack_start(listbox, True, True, 0) | ||
| 47 | listbox.set_selection_mode(Gtk.SelectionMode.NONE) | 71 | listbox.set_selection_mode(Gtk.SelectionMode.NONE) |
| 48 | for i in self.items: | 72 | box.pack_start(listbox, True, True, 0) |
| 49 | p, mtype = parser_factory.get_parser(i) | ||
| 50 | 73 | ||
| 74 | for i, mtype in self.failed_items: | ||
| 51 | row = Gtk.ListBoxRow() | 75 | row = Gtk.ListBoxRow() |
| 52 | hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) | 76 | hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) |
| 53 | row.add(hbox) | 77 | row.add(hbox) |
| @@ -56,78 +80,37 @@ class StatusWindow(Gtk.Window): | |||
| 56 | select_image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.BUTTON) | 80 | select_image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.BUTTON) |
| 57 | hbox.pack_start(select_image, False, False, 0) | 81 | hbox.pack_start(select_image, False, False, 0) |
| 58 | 82 | ||
| 59 | image = Gtk.Image() | ||
| 60 | image.set_from_stock(Gtk.STOCK_NO if not p else Gtk.STOCK_YES, Gtk.IconSize.BUTTON) | ||
| 61 | hbox.pack_start(image, False, False, 0) | ||
| 62 | |||
| 63 | label = Gtk.Label(os.path.basename(i)) | 83 | label = Gtk.Label(os.path.basename(i)) |
| 64 | hbox.pack_start(label, True, False, 0) | 84 | hbox.pack_start(label, True, False, 0) |
| 65 | 85 | ||
| 66 | listbox.add(row) | 86 | listbox.add(row) |
| 67 | listbox.show_all() | ||
| 68 | |||
| 69 | # Options | ||
| 70 | separator = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL) | ||
| 71 | self.main_box.pack_start(separator, True, True, 5) | ||
| 72 | |||
| 73 | hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) | ||
| 74 | self.main_box.pack_start(hbox, True, True, 0) | ||
| 75 | label = Gtk.Label(xalign=0) | ||
| 76 | label.set_markup("Lightweight mode (only remove <i>some</i> metadata)") | ||
| 77 | hbox.pack_start(label, False, True, 0) | ||
| 78 | hbox.pack_start(Gtk.Switch(), False, True, 0) | ||
| 79 | 87 | ||
| 80 | self.window.show_all() | ||
| 81 | |||
| 82 | def error_window(self, items): | ||
| 83 | self.window.remove(self.main_box) | ||
| 84 | |||
| 85 | box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) | ||
| 86 | self.window.add(box) | ||
| 87 | |||
| 88 | # Disclaimer | ||
| 89 | box.pack_start(Gtk.Label("Could not remove metadata from the following items:", | ||
| 90 | xalign=0), True, True, 0) | ||
| 91 | |||
| 92 | # List of failed files | ||
| 93 | listbox = Gtk.ListBox() | ||
| 94 | box.pack_start(listbox, True, True, 0) | ||
| 95 | listbox.set_selection_mode(Gtk.SelectionMode.NONE) | ||
| 96 | for i in items: | ||
| 97 | listbox.add(Gtk.Label(os.path.basename(i), xalign=0)) | ||
| 98 | listbox.show_all() | 88 | listbox.show_all() |
| 89 | window.show_all() | ||
| 99 | 90 | ||
| 100 | self.window.show_all() | ||
| 101 | self.remove.hide() | ||
| 102 | 91 | ||
| 103 | def cancel_btn(self, button): | 92 | @staticmethod |
| 104 | self.window.close() | 93 | def __validate(f): |
| 105 | 94 | if f.get_uri_scheme() != "file" or f.is_directory(): | |
| 106 | def remove_btn(self, button): | ||
| 107 | failed = [] | ||
| 108 | for i in self.items: | ||
| 109 | p, _ = parser_factory.get_parser(i) | ||
| 110 | if p is not None and p.remove_all(): | ||
| 111 | continue | ||
| 112 | failed.append(i) | ||
| 113 | |||
| 114 | # Everything went the right way, exit | ||
| 115 | if not len(failed): | ||
| 116 | self.window.close() | ||
| 117 | |||
| 118 | self.error_window(failed) | ||
| 119 | |||
| 120 | class ColumnExtension(GObject.GObject, Nautilus.MenuProvider): | ||
| 121 | def __validate(self, file): | ||
| 122 | if file.get_uri_scheme() != "file" or file.is_directory(): | ||
| 123 | return False | 95 | return False |
| 124 | if not file.can_write(): | 96 | elif not f.can_write(): |
| 125 | return False | 97 | return False |
| 126 | return True | 98 | return True |
| 127 | 99 | ||
| 128 | def menu_activate_cb(self, menu, files): | 100 | def __cb_menu_activate(self, menu, files): |
| 129 | items = list(map(lambda x: unquote(x.get_uri()[7:]), files)) | 101 | self.failed_items = list() |
| 130 | StatusWindow(items) | 102 | for f in files: |
| 103 | if not self.__validate(f): | ||
| 104 | self.failed_items.append((f.get_name(), None)) | ||
| 105 | continue | ||
| 106 | |||
| 107 | fname = unquote(f.get_uri()[7:]) | ||
| 108 | ret, mtype = Mat2Wrapper(fname).remove_metadata() | ||
| 109 | if not ret: | ||
| 110 | self.failed_items.append((f.get_name(), mtype)) | ||
| 111 | |||
| 112 | if len(self.failed_items): | ||
| 113 | self.notify() | ||
| 131 | 114 | ||
| 132 | def get_background_items(self, window, file): | 115 | def get_background_items(self, window, file): |
| 133 | """ https://bugzilla.gnome.org/show_bug.cgi?id=784278 """ | 116 | """ https://bugzilla.gnome.org/show_bug.cgi?id=784278 """ |
| @@ -144,6 +127,6 @@ class ColumnExtension(GObject.GObject, Nautilus.MenuProvider): | |||
| 144 | label="Remove metadata", | 127 | label="Remove metadata", |
| 145 | tip="Remove metadata" | 128 | tip="Remove metadata" |
| 146 | ) | 129 | ) |
| 147 | item.connect('activate', self.menu_activate_cb, files) | 130 | item.connect('activate', self.__cb_menu_activate, files) |
| 148 | 131 | ||
| 149 | return [item] | 132 | return [item] |
