Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
jvoisin
mat2
Commits
6479d869
Commit
6479d869
authored
Dec 05, 2022
by
jvoisin
Browse files
Remove the Nautilus extension
parent
29057d6c
Pipeline
#121401
passed with stages
in 2 minutes and 53 seconds
Changes
5
Pipelines
4
Hide whitespace changes
Inline
Side-by-side
.gitlab-ci.yml
View file @
6479d869
...
...
@@ -18,7 +18,6 @@ linting:bandit:
stage
:
linting
script
:
# TODO: remove B405 and B314
-
bandit ./mat2 --format txt --skip B101
-
bandit -r ./nautilus/ --format txt --skip B101
-
bandit -r ./libmat2 --format txt --skip B101,B404,B603,B405,B314,B108,B311
linting:codespell:
...
...
@@ -35,14 +34,12 @@ linting:pylint:
stage
:
linting
script
:
-
pylint --disable=no-else-return,no-else-raise,no-else-continue,unnecessary-comprehension,raise-missing-from,unsubscriptable-object,use-dict-literal,unspecified-encoding,consider-using-f-string,use-list-literal,too-many-statements --extension-pkg-whitelist=cairo,gi ./libmat2 ./mat2
# Once nautilus-python is in Debian, decomment it form the line below
-
pylint --disable=no-else-return,no-else-raise,no-else-continue,unnecessary-comprehension,raise-missing-from,unsubscriptable-object,use-list-literal --extension-pkg-whitelist=Nautilus,GObject,Gtk,Gio,GLib,gi ./nautilus/mat2.py
linting:mypy:
image
:
$CONTAINER_REGISTRY:linting
stage
:
linting
script
:
-
mypy --ignore-missing-imports mat2 libmat2/*.py
./nautilus/mat2.py
-
mypy --ignore-missing-imports mat2 libmat2/*.py
tests:archlinux:
image
:
$CONTAINER_REGISTRY:archlinux
...
...
INSTALL.md
View file @
6479d869
...
...
@@ -34,20 +34,16 @@ apt install mat2
Thanks to
[
atenart
](
https://ack.tf/
)
, there is a package available on
[
Fedora's copr
](
https://copr.fedorainfracloud.org/coprs/atenart/mat2/
)
.
We use copr (cool other packages repo) as the mat2 Nautilus plugin depends on
python3-nautilus, which isn't available yet in Fedora (but is distributed
through this copr).
First you need to enable mat2's copr:
```
dnf -y copr enable atenart/mat2
```
Then you can install
both the mat2 command and Nautilus extension
:
Then you can install
mat2
:
```
dnf -y install mat2
mat2-nautilus
dnf -y install mat2
```
## Gentoo
...
...
README.md
View file @
6479d869
...
...
@@ -22,9 +22,14 @@ Maybe you don't want to disclose those information.
This is precisely the job of mat2: getting rid, as much as possible, of
metadata.
mat2 provides a command line tool, and graphical user interfaces via a service
menu for Dolphin, the default file manager of KDE, and an extension for
Nautilus, the default file manager of GNOME.
mat2 provides:
-
a library called
`libmat2`
;
-
a command line tool called
`mat2`
,
-
a service menu for Dolphin, KDE's default file manager
If you prefer a regular graphical user interface, you might be interested in
[
Metadata Cleaner
](
https://metadatacleaner.romainvigier.fr/
)
, which is using
`mat2`
under the hood.
# Requirements
...
...
nautilus/README.md
deleted
100644 → 0
View file @
29057d6c
# mat2's Nautilus extension
# Dependencies
-
Nautilus (now known as
[
Files
](
https://wiki.gnome.org/action/show/Apps/Files
)
)
-
[
nautilus-python
](
https://gitlab.gnome.org/GNOME/nautilus-python
)
>= 2.10
# Installation
Simply copy the
`mat2.py`
file to
`~/.local/share/nautilus-python/extensions`
,
and launch Nautilus; you should now have a "Remove metadata" item in the
right-click menu on supported files.
Please note: This is not needed if using a distribution provided package. It
only applies if installing from source.
nautilus/mat2.py
deleted
100644 → 0
View file @
29057d6c
#!/usr/bin/env python3
"""
Because writing GUI is non-trivial (cf. https://0xacab.org/jvoisin/mat2/issues/3),
we decided to write a Nautilus extension 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.
"""
# pylint: disable=no-name-in-module,unused-argument,import-error
import
queue
import
threading
from
typing
import
Tuple
,
Optional
,
List
from
urllib.parse
import
unquote
import
gettext
import
gi
gi
.
require_version
(
'Nautilus'
,
'3.0'
)
gi
.
require_version
(
'Gtk'
,
'3.0'
)
gi
.
require_version
(
'GdkPixbuf'
,
'2.0'
)
from
gi.repository
import
Nautilus
,
GObject
,
Gtk
,
Gio
,
GLib
,
GdkPixbuf
from
libmat2
import
parser_factory
_
=
gettext
.
gettext
def
_remove_metadata
(
fpath
)
->
Tuple
[
bool
,
Optional
[
str
]]:
""" 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
Mat2Extension
(
GObject
.
GObject
,
Nautilus
.
MenuProvider
,
Nautilus
.
LocationWidgetProvider
):
""" This class adds an item to the right-click menu in Nautilus. """
def
__init__
(
self
):
super
().
__init__
()
self
.
infobar_hbox
=
None
self
.
infobar
=
None
self
.
failed_items
=
list
()
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_hbox
=
Gtk
.
Box
(
orientation
=
Gtk
.
Orientation
.
HORIZONTAL
)
btn
=
Gtk
.
Button
(
_
(
"Show"
))
btn
.
connect
(
"clicked"
,
self
.
__cb_show_failed
)
self
.
infobar_hbox
.
pack_end
(
btn
,
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
)
->
Gtk
.
Widget
:
""" 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
()
headerbar
=
Gtk
.
HeaderBar
()
window
.
set_titlebar
(
headerbar
)
headerbar
.
props
.
title
=
_
(
"Metadata removal failed"
)
close_buton
=
Gtk
.
Button
(
_
(
"Close"
))
close_buton
.
connect
(
"clicked"
,
lambda
_
:
window
.
close
())
headerbar
.
pack_end
(
close_buton
)
box
=
Gtk
.
Box
(
orientation
=
Gtk
.
Orientation
.
VERTICAL
)
window
.
add
(
box
)
box
.
add
(
self
.
__create_treeview
())
window
.
show_all
()
@
staticmethod
def
__validate
(
fileinfo
)
->
Tuple
[
bool
,
str
]:
""" Validate if a given file FileInfo `fileinfo` can be processed.
Returns a boolean, and a textreason why"""
if
fileinfo
.
get_uri_scheme
()
!=
"file"
or
fileinfo
.
is_directory
():
return
False
,
_
(
"Not a file"
)
elif
not
fileinfo
.
can_write
():
return
False
,
_
(
"Not writeable"
)
return
True
,
""
def
__create_treeview
(
self
)
->
Gtk
.
TreeView
:
liststore
=
Gtk
.
ListStore
(
GdkPixbuf
.
Pixbuf
,
str
,
str
)
treeview
=
Gtk
.
TreeView
(
model
=
liststore
)
renderer_pixbuf
=
Gtk
.
CellRendererPixbuf
()
column_pixbuf
=
Gtk
.
TreeViewColumn
(
"Icon"
,
renderer_pixbuf
,
pixbuf
=
0
)
treeview
.
append_column
(
column_pixbuf
)
for
idx
,
name
in
enumerate
([
_
(
'File'
),
_
(
'Reason'
)]):
renderer_text
=
Gtk
.
CellRendererText
()
column_text
=
Gtk
.
TreeViewColumn
(
name
,
renderer_text
,
text
=
idx
+
1
)
treeview
.
append_column
(
column_text
)
for
(
fname
,
mtype
,
reason
)
in
self
.
failed_items
:
# This part is all about adding mimetype icons to the liststore
icon
=
Gio
.
content_type_get_icon
(
'text/plain'
if
not
mtype
else
mtype
)
# in case we don't have the corresponding icon,
# we're adding `text/plain`, because we have this one for sure™
names
=
icon
.
get_names
()
+
[
'text/plain'
,
]
icon_theme
=
Gtk
.
IconTheme
.
get_default
()
for
name
in
names
:
try
:
img
=
icon_theme
.
load_icon
(
name
,
Gtk
.
IconSize
.
BUTTON
,
0
)
break
except
GLib
.
GError
:
pass
liststore
.
append
([
img
,
fname
,
reason
])
treeview
.
show_all
()
return
treeview
def
__create_progressbar
(
self
)
->
Gtk
.
ProgressBar
:
""" 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
)
->
bool
:
""" 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
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
:
list
,
processing_queue
:
queue
.
Queue
)
->
bool
:
""" 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
)
valid
,
reason
=
self
.
__validate
(
fileinfo
)
if
not
valid
:
self
.
failed_items
.
append
((
fname
,
None
,
reason
))
continue
fpath
=
unquote
(
fileinfo
.
get_uri
()[
7
:])
# `len('file://') = 7`
success
,
mtype
=
_remove_metadata
(
fpath
)
if
not
success
:
self
.
failed_items
.
append
((
fname
,
mtype
,
_
(
'Unsupported/invalid'
)))
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
()
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
)
thread
=
threading
.
Thread
(
target
=
self
.
__clean_files
,
args
=
(
files
,
processing_queue
))
thread
.
daemon
=
True
thread
.
start
()
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
)
->
Optional
[
List
[
Nautilus
.
MenuItem
]]:
""" 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
((
is_valid
for
(
is_valid
,
_
)
in
map
(
self
.
__validate
,
files
))):
return
None
item
=
Nautilus
.
MenuItem
(
name
=
"mat2::Remove_metadata"
,
label
=
_
(
"Remove metadata"
),
tip
=
_
(
"Remove metadata"
)
)
item
.
connect
(
'activate'
,
self
.
__cb_menu_activate
,
files
)
return
[
item
,
]
Write
Preview
Supports
Markdown
0%
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!
Cancel
Please
register
or
sign in
to comment