udiskie: New module for automatic mounting of removable media

Signed-off-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
James Valleroy 2018-05-26 19:46:19 -04:00
parent c511dab7ff
commit 08eeace30f
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
9 changed files with 398 additions and 1 deletions

60
actions/udiskie Executable file
View File

@ -0,0 +1,60 @@
#!/usr/bin/python3
# -*- mode: python -*-
#
# This file is part of FreedomBox.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
Configuration helper for udiskie.
"""
import argparse
from plinth import action_utils
def parse_arguments():
"""Return parsed command line arguments as dictionary."""
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
subparsers.add_parser('enable', help='Enable udiskie')
subparsers.add_parser('disable', help='Disable udiskie')
subparsers.required = True
return parser.parse_args()
def subcommand_enable(_):
"""Enable web configuration and reload."""
action_utils.service_enable('freedombox-udiskie')
def subcommand_disable(_):
"""Disable web configuration and reload."""
action_utils.service_disable('freedombox-udiskie')
def main():
"""Parse arguments and perform all duties."""
arguments = parse_arguments()
subcommand = arguments.subcommand.replace('-', '_')
subcommand_method = globals()['subcommand_' + subcommand]
subcommand_method(arguments)
if __name__ == '__main__':
main()

View File

@ -0,0 +1 @@
plinth.modules.udiskie

View File

@ -0,0 +1,26 @@
#
# This file is part of FreedomBox.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
[Unit]
Description=handle automounting
Documentation=man:udiskie(1)
[Service]
ExecStart=/usr/bin/udiskie
[Install]
WantedBy=multi-user.target

2
debian/control vendored
View File

@ -25,6 +25,7 @@ Build-Depends: debhelper (>= 11~)
, python3-cherrypy3
, python3-configobj
, python3-coverage
, python3-dbus
, python3-django (>= 1.11)
, python3-django-axes (>= 3.0.3)
, python3-django-captcha
@ -69,6 +70,7 @@ Depends: ${python3:Depends}
, python3-bootstrapform
, python3-cherrypy3
, python3-configobj
, python3-dbus
, python3-django (>= 1.11)
, python3-django-axes (>= 3.0.3)
, python3-django-captcha

View File

@ -0,0 +1,147 @@
#
# This file is part of FreedomBox.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
FreedomBox app for udiskie.
"""
import dbus
from django.utils.translation import ugettext_lazy as _
from plinth import service as service_module
from plinth import action_utils, actions
from plinth.menu import main_menu
from plinth.modules.storage import format_bytes
version = 1
managed_services = ['freedombox-udiskie']
managed_packages = ['udiskie']
name = _('udiskie')
short_description = _('Removable Media')
description = [
_('udiskie allows automatic mounting of removable media, such as flash '
'drives.'),
]
service = None
def init():
"""Intialize the module."""
menu = main_menu.get('system')
menu.add_urlname(name, 'glyphicon-floppy-disk', 'udiskie:index',
short_description)
global service
setup_helper = globals()['setup_helper']
if setup_helper.get_state() != 'needs-setup':
service = service_module.Service(
managed_services[0], name, ports=[], is_external=False,
is_enabled=is_enabled, enable=enable, disable=disable,
is_running=is_running)
def setup(helper, old_version=None):
"""Install and configure the module."""
helper.install(managed_packages, skip_recommends=True)
helper.call('post', actions.superuser_run, 'udiskie', ['enable'])
global service
if service is None:
service = service_module.Service(
managed_services[0], name, ports=[], is_external=True,
is_enabled=is_enabled, enable=enable, disable=disable,
is_running=is_running)
helper.call('post', service.notify_enabled, None, True)
def is_running():
"""Return whether the service is running."""
return action_utils.service_is_running('freedombox-udiskie')
def is_enabled():
"""Return whether the module is enabled."""
return action_utils.service_is_enabled('freedombox-udiskie')
def enable():
"""Enable the module."""
actions.superuser_run('udiskie', ['enable'])
def disable():
"""Disable the module."""
actions.superuser_run('udiskie', ['disable'])
def list_devices():
UDISKS2 = 'org.freedesktop.UDisks2'
UDISKS2_PATH = '/org/freedesktop/UDisks2'
BLOCK = UDISKS2 + '.Block'
PROPERTIES = 'org.freedesktop.DBus.Properties'
devices = []
bus = dbus.SystemBus()
udisks_obj = bus.get_object(UDISKS2, UDISKS2_PATH)
manager = dbus.Interface(udisks_obj, 'org.freedesktop.DBus.ObjectManager')
for k, v in manager.GetManagedObjects().items():
drive_info = v.get(BLOCK, {})
if drive_info.get('IdUsage') == "filesystem" \
and not drive_info.get('HintSystem') \
and not drive_info.get('ReadOnly'):
device_name = drive_info.get('Device')
if device_name:
device_name = bytearray(device_name).replace(
b'\x00', b'').decode('utf-8')
short_name = device_name.replace('/dev', '', 1)
bd = bus.get_object(
UDISKS2, UDISKS2_PATH + '/block_devices%s' % short_name)
drive_name = bd.Get(BLOCK, 'Drive', dbus_interface=PROPERTIES)
drive = bus.get_object(UDISKS2, drive_name)
ejectable = drive.Get(UDISKS2 + '.Drive', 'Ejectable',
dbus_interface=PROPERTIES)
if ejectable:
label = bd.Get(BLOCK, 'IdLabel', dbus_interface=PROPERTIES)
size = bd.Get(BLOCK, 'Size', dbus_interface=PROPERTIES)
file_system = bd.Get(BLOCK, 'IdType',
dbus_interface=PROPERTIES)
try:
mount_points = bd.Get(UDISKS2 + '.Filesystem',
'MountPoints',
dbus_interface=PROPERTIES)
mount_point = mount_points[0]
except:
mount_point = None
devices.append({
'device':
device_name,
'label':
str(label),
'size':
format_bytes(size),
'file_system':
str(file_system),
'mount_point':
''.join([chr(ch) for ch in mount_point]),
})
return devices

View File

@ -0,0 +1,98 @@
{% extends "base.html" %}
{% comment %}
#
# This file is part of FreedomBox.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
{% endcomment %}
{% load bootstrap %}
{% load i18n %}
{% load static %}
{% block content %}
<h2>{{ service.name }}</h2>
{% for paragraph in description %}
<p>{{ paragraph|safe }}</p>
{% endfor %}
{% if manual_page %}
<p class="manual-page">
<a href="{% url 'help:manual-page' manual_page %}">
{% trans 'Learn more...' %}
</a>
</p>
{% endif %}
<h3>{% trans "Devices" %}</h3>
<div class="row">
<div class="col-lg-12">
<table class="table table-bordered table-condensed table-striped">
<thead>
<tr>
<th>{% trans "Device" %}</th>
<th>{% trans "Label" %}</th>
<th>{% trans "Size" %}</th>
<th>{% trans "File System" %}</th>
<th>{% trans "Mount Point" %}</th>
</tr>
</thead>
<tbody>
{% for device in devices %}
<tr>
<td>{{ device.device }}</td>
<td>{{ device.label }}</td>
<td>{{ device.size }}</td>
<td>{{ device.file_system }}</td>
<td>{{ device.mount_point }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% if show_status_block %}
<h3>{% trans "Status" %}</h3>
<p class="running-status-parent">
{% with service_name=service.name %}
{% if service.is_running %}
<span class="running-status active"></span>
{% blocktrans trimmed %}
Service <em>{{ service_name }}</em> is running.
{% endblocktrans %}
{% else %}
<span class="running-status inactive"></span>
{% blocktrans trimmed %}
Service <em>{{ service_name }}</em> is not running.
{% endblocktrans %}
{% endif %}
{% endwith %}
</p>
{% endif %}
<h3>{% trans "Configuration" %}</h3>
<form class="form form-configuration" method="post">
{% csrf_token %}
{{ form|bootstrap }}
<input type="submit" class="btn btn-primary"
value="{% trans "Update setup" %}"/>
</form>
{% endblock %}

View File

@ -0,0 +1,31 @@
#
# This file is part of FreedomBox.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
URLs for the udiskie module.
"""
from django.conf.urls import url
from .views import UdiskieView
from plinth.modules import udiskie
urlpatterns = [
url(r'^sys/udiskie/$',
UdiskieView.as_view(service_id=udiskie.managed_services[0],
description=udiskie.description,
show_status_block=True), name='index'),
]

View File

@ -0,0 +1,32 @@
#
# This file is part of FreedomBox.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
Views for udiskie module.
"""
from plinth.modules import udiskie
from plinth.views import ServiceView
class UdiskieView(ServiceView):
template_name = 'udiskie.html'
def get_context_data(self, **kwargs):
"""Return the context data rendering the template."""
context = super().get_context_data(**kwargs)
context['devices'] = udiskie.list_devices()
return context

View File

@ -241,7 +241,7 @@ setuptools.setup(
['data/etc/NetworkManager/dispatcher.d/10-freedombox-batman']),
('/etc/sudoers.d', ['data/etc/sudoers.d/plinth']),
('/lib/systemd/system',
['data/lib/systemd/system/plinth.service']),
glob.glob('data/lib/systemd/system/*.service')),
('/usr/share/plinth/actions',
glob.glob(os.path.join('actions', '*'))),
('/usr/share/polkit-1/rules.d',