Skip to content
GitLab
Explore
Sign in
Register
Primary navigation
Search or go to…
Project
mat2
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Model registry
Operate
Environments
Monitor
Incidents
Service Desk
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
tails
mat2
Commits
e9200835
Commit
e9200835
authored
6 years ago
by
Julien (jvoisin) Voisin
Browse files
Options
Downloads
Patches
Plain Diff
The Nautilus extension is now working
parent
71b1ced8
No related branches found
Branches containing commit
No related tags found
Tags containing commit
No related merge requests found
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
nautilus/nautilus_mat2.py
+144
-48
144 additions, 48 deletions
nautilus/nautilus_mat2.py
with
144 additions
and
48 deletions
nautilus/nautilus_mat2.py
+
144
−
48
View file @
e9200835
#!/usr/bin/env python3
# TODO:
# - Test with a large amount of files.
# - Show a progression bar when the removal takes time.
# - Improve the MessageDialog list for failed items.
# pylint: disable=unused-argument,len-as-condition,arguments-differ
"""
Because writing GUI is non-trivial (cf. https://0xacab.org/jvoisin/mat2/issues/3),
we decided to write a Nautilus extensions instead
(cf. https://0xacab.org/jvoisin/mat2/issues/2).
The code is a little bit convoluted because Gtk isn
'
t thread-safe,
so we
'
re not allowed to call anything Gtk-related outside of the main
thread, so we
'
ll have to resort to using a `queue` to pass
"
messages
"
around.
"""
import
os
import
queue
import
threading
from
urllib.parse
import
unquote
import
gi
gi
.
require_version
(
'
Nautilus
'
,
'
3.0
'
)
gi
.
require_version
(
'
Gtk
'
,
'
3.0
'
)
from
gi.repository
import
Nautilus
,
GObject
,
Gtk
,
Gio
from
gi.repository
import
Nautilus
,
GObject
,
Gtk
,
Gio
,
GLib
from
libmat2
import
parser_factory
class
Mat2Wrapper
():
def
__init__
(
self
,
filepath
):
self
.
__filepath
=
filepath
def
remove_metadata
(
self
):
parser
,
mtype
=
parser_factory
.
get_parser
(
self
.
__filepath
)
if
parser
is
None
:
return
False
,
mtype
return
parser
.
remove_all
(),
mtype
def
_remove_metadata
(
fpath
):
"""
This is a simple wrapper around libmat2, because it
'
s
easier and cleaner this way.
"""
parser
,
mtype
=
parser_factory
.
get_parser
(
fpath
)
if
parser
is
None
:
return
False
,
mtype
return
parser
.
remove_all
(),
mtype
class
ColumnExtension
(
GObject
.
GObject
,
Nautilus
.
MenuProvider
,
Nautilus
.
LocationWidgetProvider
):
def
notify
(
self
):
self
.
infobar_msg
.
set_text
(
"
Failed to clean some items
"
)
self
.
infobar
.
show_all
()
"""
This class adds an item to the right-clic menu in Nautilus.
"""
def
__init__
(
self
):
super
().
__init__
()
self
.
infobar_hbox
=
None
self
.
infobar
=
None
self
.
failed_items
=
list
()
def
get_widget
(
self
,
uri
,
window
):
self
.
infobar
=
Gtk
.
InfoBar
()
self
.
infobar
.
set_message_type
(
Gtk
.
MessageType
.
ERROR
)
def
__infobar_failure
(
self
):
"""
Add an hbox to the `infobar` warning about the fact that we didn
'
t
manage to remove the metadata from every single file.
"""
self
.
infobar
.
set_show_close_button
(
True
)
self
.
infobar
.
connect
(
"
response
"
,
self
.
__cb_infobar_response
)
hbox
=
Gtk
.
Box
(
orientation
=
Gtk
.
Orientation
.
HORIZONTAL
)
self
.
infobar
.
get_content_area
().
pack_start
(
hbox
,
False
,
False
,
0
)
self
.
infobar_hbox
=
Gtk
.
Box
(
orientation
=
Gtk
.
Orientation
.
HORIZONTAL
)
btn
=
Gtk
.
Button
(
"
Show
"
)
btn
.
connect
(
"
clicked
"
,
self
.
__cb_show_failed
)
self
.
infobar
.
get_content_area
()
.
pack_end
(
btn
,
False
,
False
,
0
)
self
.
infobar
_hbox
.
pack_end
(
btn
,
False
,
False
,
0
)
self
.
infobar_msg
=
Gtk
.
Label
()
hbox
.
pack_start
(
self
.
infobar_msg
,
False
,
False
,
0
)
infobar_msg
=
Gtk
.
Label
(
"
Failed to clean some items
"
)
self
.
infobar_hbox
.
pack_start
(
infobar_msg
,
False
,
False
,
0
)
self
.
infobar
.
get_content_area
().
pack_start
(
self
.
infobar_hbox
,
True
,
True
,
0
)
self
.
infobar
.
show_all
()
def
get_widget
(
self
,
uri
,
window
):
"""
This is the method that we have to implement (because we
'
re
a LocationWidgetProvider) in order to show our infobar.
"""
self
.
infobar
=
Gtk
.
InfoBar
()
self
.
infobar
.
set_message_type
(
Gtk
.
MessageType
.
ERROR
)
self
.
infobar
.
connect
(
"
response
"
,
self
.
__cb_infobar_response
)
return
self
.
infobar
def
__cb_infobar_response
(
self
,
infobar
,
response
):
"""
Callback for the infobar close button.
"""
if
response
==
Gtk
.
ResponseType
.
CLOSE
:
self
.
infobar_hbox
.
destroy
()
self
.
infobar
.
hide
()
def
__cb_show_failed
(
self
,
button
):
"""
Callback to show a popup containing a list of files
that we didn
'
t manage to clean.
"""
# FIXME this should be done only once the window is destroyed
self
.
infobar_hbox
.
destroy
()
self
.
infobar
.
hide
()
window
=
Gtk
.
Window
()
h
b
=
Gtk
.
HeaderBar
()
window
.
set_titlebar
(
h
b
)
h
b
.
props
.
title
=
"
Metadata removal failed
"
h
eaderbar
=
Gtk
.
HeaderBar
()
window
.
set_titlebar
(
h
eaderbar
)
h
eaderbar
.
props
.
title
=
"
Metadata removal failed
"
exit_buton
=
Gtk
.
Button
(
"
Exit
"
)
exit_buton
.
connect
(
"
clicked
"
,
lambda
_
:
window
.
close
())
h
b
.
pack_end
(
exit_buton
)
h
eaderbar
.
pack_end
(
exit_buton
)
box
=
Gtk
.
Box
(
orientation
=
Gtk
.
Orientation
.
VERTICAL
)
window
.
add
(
box
)
...
...
@@ -71,7 +100,7 @@ class ColumnExtension(GObject.GObject, Nautilus.MenuProvider, Nautilus.LocationW
listbox
.
set_selection_mode
(
Gtk
.
SelectionMode
.
NONE
)
box
.
pack_start
(
listbox
,
True
,
True
,
0
)
for
i
,
mtype
in
self
.
failed_items
:
for
fname
,
mtype
in
self
.
failed_items
:
row
=
Gtk
.
ListBoxRow
()
hbox
=
Gtk
.
Box
(
orientation
=
Gtk
.
Orientation
.
HORIZONTAL
)
row
.
add
(
hbox
)
...
...
@@ -80,7 +109,7 @@ class ColumnExtension(GObject.GObject, Nautilus.MenuProvider, Nautilus.LocationW
select_image
=
Gtk
.
Image
.
new_from_gicon
(
icon
,
Gtk
.
IconSize
.
BUTTON
)
hbox
.
pack_start
(
select_image
,
False
,
False
,
0
)
label
=
Gtk
.
Label
(
os
.
path
.
basename
(
i
))
label
=
Gtk
.
Label
(
os
.
path
.
basename
(
fname
))
hbox
.
pack_start
(
label
,
True
,
False
,
0
)
listbox
.
add
(
row
)
...
...
@@ -90,37 +119,104 @@ class ColumnExtension(GObject.GObject, Nautilus.MenuProvider, Nautilus.LocationW
@staticmethod
def
__validate
(
f
):
if
f
.
get_uri_scheme
()
!=
"
file
"
or
f
.
is_directory
():
def
__validate
(
fileinfo
):
"""
Validate if a given file FileInfo `fileinfo` can be processed.
"""
if
fileinfo
.
get_uri_scheme
()
!=
"
file
"
or
fileinfo
.
is_directory
():
return
False
elif
not
f
.
can_write
():
elif
not
f
ileinfo
.
can_write
():
return
False
return
True
def
__create_progressbar
(
self
):
"""
Create the progressbar used to notify that files are currently
being processed.
"""
self
.
infobar
.
set_show_close_button
(
False
)
self
.
infobar
.
set_message_type
(
Gtk
.
MessageType
.
INFO
)
self
.
infobar_hbox
=
Gtk
.
Box
(
orientation
=
Gtk
.
Orientation
.
HORIZONTAL
)
progressbar
=
Gtk
.
ProgressBar
()
self
.
infobar_hbox
.
pack_start
(
progressbar
,
True
,
True
,
0
)
progressbar
.
set_show_text
(
True
)
self
.
infobar
.
get_content_area
().
pack_start
(
self
.
infobar_hbox
,
True
,
True
,
0
)
self
.
infobar
.
show_all
()
return
progressbar
def
__update_progressbar
(
self
,
processing_queue
,
progressbar
):
"""
This method is run via `Glib.add_idle` to update the progressbar.
"""
try
:
fname
=
processing_queue
.
get
(
block
=
False
)
except
queue
.
Empty
:
return
True
# `None` is the marker put in the queue to signal that every selected
# file was processed.
if
fname
is
None
:
self
.
infobar_hbox
.
destroy
()
self
.
infobar
.
hide
()
if
len
(
self
.
failed_items
):
self
.
__infobar_failure
()
if
not
processing_queue
.
empty
():
print
(
"
Something went wrong, the queue isn
'
t empty :/
"
)
return
False
progressbar
.
pulse
()
progressbar
.
set_text
(
"
Cleaning %s
"
%
fname
)
progressbar
.
show_all
()
self
.
infobar_hbox
.
show_all
()
self
.
infobar
.
show_all
()
return
True
def
__clean_files
(
self
,
files
,
processing_queue
):
"""
This method is threaded in order to avoid blocking the GUI
while cleaning up the files.
"""
for
fileinfo
in
files
:
fname
=
fileinfo
.
get_name
()
processing_queue
.
put
(
fname
)
if
not
self
.
__validate
(
fileinfo
):
self
.
failed_items
.
append
((
fname
,
None
))
continue
fpath
=
unquote
(
fileinfo
.
get_uri
()[
7
:])
# `len('file://') = 7`
success
,
mtype
=
_remove_metadata
(
fpath
)
if
not
success
:
self
.
failed_items
.
append
((
fname
,
mtype
))
processing_queue
.
put
(
None
)
# signal that we processed all the files
return
True
def
__cb_menu_activate
(
self
,
menu
,
files
):
"""
This method is called when the user clicked the
"
clean metadata
"
menu item.
"""
self
.
failed_items
=
list
()
for
f
in
files
:
if
not
self
.
__validate
(
f
):
self
.
failed_items
.
append
((
f
.
get_name
(),
None
))
continue
progressbar
=
self
.
__create_progressbar
()
progressbar
.
set_pulse_step
=
1.0
/
len
(
files
)
self
.
infobar
.
show_all
()
processing_queue
=
queue
.
Queue
()
GLib
.
idle_add
(
self
.
__update_progressbar
,
processing_queue
,
progressbar
)
fname
=
unquote
(
f
.
get_uri
()[
7
:])
ret
,
mtype
=
Mat2Wrapper
(
fname
).
remove_metadata
()
if
not
ret
:
self
.
failed_items
.
append
((
f
.
get_name
(),
mtype
))
thread
=
threading
.
Thread
(
target
=
self
.
__clean_files
,
args
=
(
files
,
processing_queue
))
thread
.
daemon
=
True
thread
.
start
()
if
len
(
self
.
failed_items
):
self
.
notify
()
def
get_background_items
(
self
,
window
,
file
):
"""
https://bugzilla.gnome.org/show_bug.cgi?id=784278
"""
return
None
def
get_file_items
(
self
,
window
,
files
):
"""
This method is the one allowing us to create a menu item.
"""
# Do not show the menu item if not a single file has a chance to be
# processed by mat2.
if
not
any
(
map
(
self
.
__validate
,
files
)):
return
return
None
item
=
Nautilus
.
MenuItem
(
name
=
"
MAT2::Remove_metadata
"
,
...
...
@@ -129,4 +225,4 @@ class ColumnExtension(GObject.GObject, Nautilus.MenuProvider, Nautilus.LocationW
)
item
.
connect
(
'
activate
'
,
self
.
__cb_menu_activate
,
files
)
return
[
item
]
return
[
item
,
]
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment