From a307476634565bb2155de1bd2faa1607e1f9a84f Mon Sep 17 00:00:00 2001 From: Joseph Nuthalapati Date: Mon, 24 Sep 2018 11:39:43 +0530 Subject: [PATCH] udiskie: Merge into storage module udiskie is now an essential module that will be installed along with storage. Signed-off-by: Joseph Nuthalapati Reviewed-by: James Valleroy --- data/etc/plinth/modules-enabled/udiskie | 1 - plinth/modules/backups/__init__.py | 6 +- plinth/modules/storage/__init__.py | 54 +++++++++-- plinth/modules/storage/templates/storage.html | 77 +++++++++++++--- .../modules/{udiskie => storage}/udisks2.py | 0 plinth/modules/storage/urls.py | 4 +- plinth/modules/storage/views.py | 47 +++++++++- plinth/modules/udiskie/__init__.py | 90 ------------------- plinth/modules/udiskie/templates/udiskie.html | 75 ---------------- plinth/modules/udiskie/urls.py | 34 ------- plinth/modules/udiskie/views.py | 79 ---------------- 11 files changed, 163 insertions(+), 304 deletions(-) delete mode 100644 data/etc/plinth/modules-enabled/udiskie rename plinth/modules/{udiskie => storage}/udisks2.py (100%) delete mode 100644 plinth/modules/udiskie/__init__.py delete mode 100644 plinth/modules/udiskie/templates/udiskie.html delete mode 100644 plinth/modules/udiskie/urls.py delete mode 100644 plinth/modules/udiskie/views.py diff --git a/data/etc/plinth/modules-enabled/udiskie b/data/etc/plinth/modules-enabled/udiskie deleted file mode 100644 index bb489cbd2..000000000 --- a/data/etc/plinth/modules-enabled/udiskie +++ /dev/null @@ -1 +0,0 @@ -plinth.modules.udiskie diff --git a/plinth/modules/backups/__init__.py b/plinth/modules/backups/__init__.py index 9be12676e..b461213b1 100644 --- a/plinth/modules/backups/__init__.py +++ b/plinth/modules/backups/__init__.py @@ -27,7 +27,7 @@ from django.utils.translation import ugettext_lazy as _ from plinth import actions from plinth.menu import main_menu -from plinth.modules import udiskie +from plinth.modules import storage from .backups import backup_apps, restore_apps @@ -118,8 +118,8 @@ def export_archive(name, location): def get_export_locations(): """Return a list of storage locations for exported backup archives.""" locations = [('/var/lib/freedombox/', _('Root Filesystem'))] - if udiskie.is_running(): - devices = udiskie.udisks2.list_devices() + if storage.is_running(): + devices = storage.udisks2.list_devices() for device in devices: if 'mount_points' in device and len(device['mount_points']) > 0: name = device['label'] or device['device'] diff --git a/plinth/modules/storage/__init__.py b/plinth/modules/storage/__init__.py index 569190d09..7a97aa9e6 100644 --- a/plinth/modules/storage/__init__.py +++ b/plinth/modules/storage/__init__.py @@ -17,21 +17,32 @@ """ FreedomBox app to manage storage. """ - import json import logging import subprocess from django.utils.translation import ugettext_lazy as _ -from plinth import actions +from plinth import service as service_module +from plinth import action_utils, actions, cfg from plinth.menu import main_menu +from plinth.utils import format_lazy -version = 2 +version = 3 name = _('Storage') -description = [] +managed_services = ['freedombox-udiskie'] + +managed_packages = ['udiskie', 'gir1.2-udisks-2.0'] + +description = [ + format_lazy( + _('This module allows you to manage storage media attached to your ' + '{box_name}. You can view the storage media currently in use, mount ' + 'and unmount removable media, expand the root partition etc.'), + box_name=_(cfg.box_name)) +] service = None @@ -81,8 +92,8 @@ def _get_disks_from_df(): disks = [] for line in output.splitlines()[1:]: parts = line.split(maxsplit=6) - keys = ('device', 'file_system_type', 'size', 'used', - 'free', 'percent_used', 'mount_point') + keys = ('device', 'file_system_type', 'size', 'used', 'free', + 'percent_used', 'mount_point') disk = dict(zip(keys, parts)) disk['percent_used'] = int(disk['percent_used'].rstrip('%')) disk['size'] = int(disk['size']) @@ -168,8 +179,37 @@ def format_bytes(size): return _('{disk_size:.1f} TiB').format(disk_size=size) +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 setup(helper, old_version=None): - """Expand root parition on first setup.""" + """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) disks = get_disks() root_device = get_root_device(disks) if is_expandable(root_device): diff --git a/plinth/modules/storage/templates/storage.html b/plinth/modules/storage/templates/storage.html index 69b06b2da..fa9e3710d 100644 --- a/plinth/modules/storage/templates/storage.html +++ b/plinth/modules/storage/templates/storage.html @@ -36,6 +36,12 @@

{{ title }}

{% endblock %} + {% block description %} + {% for paragraph in description %} +

{{ paragraph|safe }}

+ {% endfor %} + {% endblock %} + {% if manual_page %}

@@ -44,7 +50,7 @@

{% endif %} -

{% trans "The following disks are in use:" %}

+

{% trans "The following storage devices are in use:" %}

@@ -66,17 +72,17 @@ {% if disk.percent_used < 75 %}
- {{ disk.percent_used }}% -
- -
{{ disk.used_str }} / {{ disk.size_str }}
+ role="progressbar" aria-valuenow="disk.percent_used" + aria-valuemin="0" aria-valuemax="100" + style="width: {{ disk.percent_used }}%;"> + {{ disk.percent_used }}% + + +
{{ disk.used_str }} / {{ disk.size_str }}
{% endfor %} @@ -100,4 +106,55 @@

{% endif %} + {% block status %} + + {{ block.super }} + +

{% trans "Removable Devices" %}

+ + {% if not devices %} +

+ {% blocktrans trimmed %} + There are no additional storage devices attached. + {% endblocktrans %} +

+ {% else %} +
+ + + + + + + + + + + + {% for device in devices %} + + + + + + + + + {% endfor %} + +
{% trans "Device" %}{% trans "Label" %}{% trans "Mount Point" %}{% trans "Type" %}{% trans "Size" %}{% trans "Actions" %}
{{ device.device }}{{ device.label }}{{ device.mount_points|join:', ' }}{{ device.filesystem_type }}{{ device.size }} + {% if device.mount_points %} +
+ {% csrf_token %} + + +
+ {% endif %} +
+ {% endif %} + + {% endblock %} {% endblock %} diff --git a/plinth/modules/udiskie/udisks2.py b/plinth/modules/storage/udisks2.py similarity index 100% rename from plinth/modules/udiskie/udisks2.py rename to plinth/modules/storage/udisks2.py diff --git a/plinth/modules/storage/urls.py b/plinth/modules/storage/urls.py index 6f005e079..1eda3efa0 100644 --- a/plinth/modules/storage/urls.py +++ b/plinth/modules/storage/urls.py @@ -14,7 +14,6 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # - """ URLs for the disks module. """ @@ -23,8 +22,9 @@ from django.conf.urls import url from . import views - urlpatterns = [ url(r'^sys/storage/$', views.index, name='index'), url(r'^sys/storage/expand$', views.expand, name='expand'), + url(r'^sys/storage/eject/(?P[\w%]+)/$', views.eject, + name='eject') ] diff --git a/plinth/modules/storage/views.py b/plinth/modules/storage/views.py index b3738cbf5..80db7f7f0 100644 --- a/plinth/modules/storage/views.py +++ b/plinth/modules/storage/views.py @@ -19,16 +19,20 @@ Views for storage module. """ import logging +import urllib.parse from django.contrib import messages from django.shortcuts import redirect from django.template.response import TemplateResponse from django.urls import reverse from django.utils.translation import ugettext as _ +from django.views.decorators.http import require_POST from plinth.modules import storage from plinth.utils import format_lazy, is_user_admin +from . import udisks2 + logger = logging.getLogger(__name__) @@ -44,7 +48,9 @@ def index(request): return TemplateResponse( request, 'storage.html', { 'title': _('Storage'), + 'description': storage.description, 'disks': disks, + 'devices': udisks2.list_devices(), 'manual_page': storage.manual_page, 'expandable_root_size': expandable_root_size }) @@ -73,9 +79,10 @@ def expand_partition(request, device): try: storage.expand_partition(device) except Exception as exception: - messages.error(request, - _('Error expanding partition: {exception}') - .format(exception=exception)) + messages.error( + request, + _('Error expanding partition: {exception}') + .format(exception=exception)) else: messages.success(request, _('Partition expanded successfully.')) @@ -106,3 +113,37 @@ def warn_about_low_disk_space(request): messages.error(request, message) elif percent_used > 75 or free_gib < 2: messages.warning(request, message) + + +@require_POST +def eject(request, device_path): + """Eject a device, given its path. + + Device path is quoted with slashes written as %2F. + + """ + device_path = urllib.parse.unquote(device_path) + + try: + drive = udisks2.eject_drive_of_device(device_path) + if drive: + messages.success( + request, + _('{drive_vendor} {drive_model} can be safely unplugged.') + .format(drive_vendor=drive['vendor'], + drive_model=drive['model'])) + else: + messages.success(request, _('Device can be safely unplugged.')) + except Exception as exception: + try: + message = udisks2.get_error_message(exception) + except AttributeError: + message = str(exception) + + logger.exception('Error ejecting device - %s', message) + messages.error( + request, + _('Error ejecting device: {error_message}').format( + error_message=message)) + + return redirect(reverse('storage:index')) diff --git a/plinth/modules/udiskie/__init__.py b/plinth/modules/udiskie/__init__.py deleted file mode 100644 index 1c8d531cd..000000000 --- a/plinth/modules/udiskie/__init__.py +++ /dev/null @@ -1,90 +0,0 @@ -# -# 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 . -# -""" -FreedomBox app for udiskie. -""" - -from django.utils.translation import ugettext_lazy as _ - -from plinth import action_utils, actions -from plinth import service as service_module -from plinth.menu import main_menu - -version = 1 - -managed_services = ['freedombox-udiskie'] - -managed_packages = ['udiskie', 'gir1.2-udisks-2.0'] - -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=True, - 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']) diff --git a/plinth/modules/udiskie/templates/udiskie.html b/plinth/modules/udiskie/templates/udiskie.html deleted file mode 100644 index d30ae9126..000000000 --- a/plinth/modules/udiskie/templates/udiskie.html +++ /dev/null @@ -1,75 +0,0 @@ -{% extends "service.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 . -# -{% endcomment %} - -{% load bootstrap %} -{% load i18n %} -{% load static %} - -{% block status %} - - {{ block.super }} - -

{% trans "Devices" %}

- - {% if not devices %} -

- {% blocktrans trimmed %} - There are no additional storage devices attached. - {% endblocktrans %} -

- {% else %} - - - - - - - - - - - - - {% for device in devices %} - - - - - - - - - {% endfor %} - -
{% trans "Device" %}{% trans "Label" %}{% trans "Size" %}{% trans "Filesystem" %}{% trans "Mount Point" %}{% trans "Actions" %}
{{ device.device }}{{ device.label }}{{ device.size }}{{ device.filesystem_type }}{{ device.mount_points|join:', ' }} - {% if device.mount_points %} -
- {% csrf_token %} - - -
- {% endif %} -
- {% endif %} - -{% endblock %} diff --git a/plinth/modules/udiskie/urls.py b/plinth/modules/udiskie/urls.py deleted file mode 100644 index 519acb26b..000000000 --- a/plinth/modules/udiskie/urls.py +++ /dev/null @@ -1,34 +0,0 @@ -# -# 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 . -# -""" -URLs for the udiskie module. -""" - -from django.conf.urls import url - -from plinth.modules import udiskie - -from .views import Index, eject - -urlpatterns = [ - url( - r'^sys/udiskie/$', - Index.as_view(service_id=udiskie.managed_services[0], - description=udiskie.description, show_status_block=True), - name='index'), - url(r'^sys/udiskie/eject/(?P[\w%]+)/$', eject, name='eject'), -] diff --git a/plinth/modules/udiskie/views.py b/plinth/modules/udiskie/views.py deleted file mode 100644 index c00cbb689..000000000 --- a/plinth/modules/udiskie/views.py +++ /dev/null @@ -1,79 +0,0 @@ -# -# 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 . -# -""" -Views for udiskie module. -""" - -import urllib.parse -from logging import Logger - -from django.contrib import messages -from django.shortcuts import redirect -from django.urls import reverse -from django.utils.translation import ugettext as _ -from django.views.decorators.http import require_POST - -from plinth.views import ServiceView - -from . import udisks2 - -logger = Logger(__name__) - - -class Index(ServiceView): - """View to show devices.""" - template_name = 'udiskie.html' - - def get_context_data(self, *args, **kwargs): - """Return the context data rendering the template.""" - context = super().get_context_data(**kwargs) - context['devices'] = udisks2.list_devices() - return context - - -@require_POST -def eject(request, device_path): - """Eject a device, given its path. - - Device path is quoted with slashes written as %2F. - - """ - device_path = urllib.parse.unquote(device_path) - - try: - drive = udisks2.eject_drive_of_device(device_path) - if drive: - messages.success( - request, - _('{drive_vendor} {drive_model} can be safely unplugged.') - .format(drive_vendor=drive['vendor'], - drive_model=drive['model'])) - else: - messages.success(request, _('Device can be safely unplugged.')) - except Exception as exception: - try: - message = udisks2.get_error_message(exception) - except AttributeError: - message = str(exception) - - logger.exception('Error ejecting device - %s', message) - messages.error( - request, - _('Error ejecting device: {error_message}').format( - error_message=message)) - - return redirect(reverse('udiskie:index'))