udiskie: Finish merging udiskie into storage

Fixes #1370

Signed-off-by: Joseph Nuthalapati <njoseph@thoughtworks.com>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Joseph Nuthalapati 2018-12-04 15:23:14 +05:30 committed by James Valleroy
parent db690cb581
commit d8942eec6f
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
4 changed files with 94 additions and 182 deletions

View File

@ -27,7 +27,7 @@ from plinth import service as service_module
from plinth import action_utils, actions, cfg
from plinth.errors import PlinthError
from plinth.menu import main_menu
from plinth.utils import format_lazy, is_user_admin
from plinth.utils import format_lazy, import_from_gi, is_user_admin
from .manifest import backup
@ -75,27 +75,12 @@ def get_disks():
disk_from_df.update(disk_from_lsblk)
combined_list.append(disk_from_df)
for disk in combined_list:
disk['is_root_device'] = is_root_device(disk)
return combined_list
def get_disk_info(mountpoint, request):
"""Get information about the free space of a drive"""
if not is_user_admin(request, cached=True):
raise PermissionError
disks = get_disks()
list_root = [disk for disk in disks if disk['mountpoint'] == mountpoint]
if not list_root:
raise PlinthError
percent_used = list_root[0]['percent_used']
free_bytes = list_root[0]['free']
free_gib = free_bytes / (1024**3)
return {
"percent_used": percent_used,
"free_bytes": free_bytes,
"free_gib": free_gib
}
def _get_disks_from_df():
"""Return the list of disks and free space available using 'df'."""
command = [
@ -130,7 +115,9 @@ def _get_disks_from_df():
def _get_disks_from_lsblk():
"""Return the list of disks and other information from 'lsblk'."""
command = ['lsblk', '--json', '--output', 'kname,pkname,mountpoint,type']
command = [
'lsblk', '--json', '--output', 'label,kname,pkname,mountpoint,type'
]
try:
process = subprocess.run(command, stdout=subprocess.PIPE, check=True)
except subprocess.CalledProcessError as exception:
@ -145,16 +132,35 @@ def _get_disks_from_lsblk():
return disks
def get_disk_info(mountpoint, request):
"""Get information about the free space of a drive"""
if not is_user_admin(request, cached=True):
raise PermissionError
disks = get_disks()
list_root = [disk for disk in disks if disk['mountpoint'] == mountpoint]
if not list_root:
raise PlinthError
percent_used = list_root[0]['percent_used']
free_bytes = list_root[0]['free']
free_gib = free_bytes / (1024**3)
return {
"percent_used": percent_used,
"free_bytes": free_bytes,
"free_gib": free_gib
}
def get_root_device(disks):
"""Return the root partition's device from list of partitions."""
devices = [
disk['dev_kname'] for disk in disks
if disk['mountpoint'] == '/' and disk['type'] == 'part'
]
try:
return devices[0]
except IndexError:
return None
for disk in disks:
if is_root_device(disk):
return disk['dev_kname']
return None
def is_root_device(disk):
"""Return whether a given disk is a root device or not."""
return disk['mountpoint'] == '/' and disk['type'] == 'part'
def is_expandable(device):
@ -200,6 +206,49 @@ def format_bytes(size):
return _('{disk_size:.1f} TiB').format(disk_size=size)
def get_error_message(error):
"""Return an error message given an exception."""
udisks = import_from_gi('UDisks', '2.0')
if error.matches(udisks.Error.quark(), udisks.Error.FAILED):
message = _('The operation failed.')
elif error.matches(udisks.Error.quark(), udisks.Error.CANCELLED):
message = _('The operation was cancelled.')
elif error.matches(udisks.Error.quark(), udisks.Error.ALREADY_UNMOUNTING):
message = _('The device is already unmounting.')
elif error.matches(udisks.Error.quark(), udisks.Error.NOT_SUPPORTED):
message = _('The operation is not supported due to '
'missing driver/tool support.')
elif error.matches(udisks.Error.quark(), udisks.Error.TIMED_OUT):
message = _('The operation timed out.')
elif error.matches(udisks.Error.quark(), udisks.Error.WOULD_WAKEUP):
message = _('The operation would wake up a disk that is '
'in a deep-sleep state.')
elif error.matches(udisks.Error.quark(), udisks.Error.DEVICE_BUSY):
message = _('Attempting to unmount a device that is busy.')
elif error.matches(udisks.Error.quark(), udisks.Error.ALREADY_CANCELLED):
message = _('The operation has already been cancelled.')
elif error.matches(udisks.Error.quark(), udisks.Error.NOT_AUTHORIZED) or \
error.matches(udisks.Error.quark(),
udisks.Error.NOT_AUTHORIZED_CAN_OBTAIN) or \
error.matches(udisks.Error.quark(),
udisks.Error.NOT_AUTHORIZED_DISMISSED):
message = _('Not authorized to perform the requested operation.')
elif error.matches(udisks.Error.quark(), udisks.Error.ALREADY_MOUNTED):
message = _('The device is already mounted.')
elif error.matches(udisks.Error.quark(), udisks.Error.NOT_MOUNTED):
message = _('The device is not mounted.')
elif error.matches(udisks.Error.quark(),
udisks.Error.OPTION_NOT_PERMITTED):
message = _('Not permitted to use the requested option.')
elif error.matches(udisks.Error.quark(),
udisks.Error.MOUNTED_BY_OTHER_USER):
message = _('The device is mounted by another user.')
else:
message = error.message
return message
def is_running():
"""Return whether the service is running."""
return action_utils.service_is_running('freedombox-udiskie')

View File

@ -56,15 +56,18 @@
<thead>
<tr>
<th>{% trans "Device" %}</th>
<th>{% trans "Label" %}</th>
<th>{% trans "Mount Point" %}</th>
<th>{% trans "Type" %}</th>
<th>{% trans "Used" %}</th>
<th>{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
{% for disk in disks %}
<tr>
<td>{{ disk.dev_kname }}</td>
<td>{{ disk.label|default_if_none:"" }}</td>
<td>{{ disk.mount_point }}</td>
<td>{{ disk.file_system_type }}</td>
<td>
@ -84,6 +87,18 @@
</div>
<div>{{ disk.used_str }} / {{ disk.size_str }}</div>
</td>
<td>
{% if not disk.is_root_device %}
<form class="form" method="post"
action="{% url 'storage:eject' disk.dev_kname|urlencode:'' %}">
{% csrf_token %}
<button type="submit"
class="btn btn-sm btn-default glyphicon glyphicon-eject">
</button>
</form>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
@ -110,51 +125,5 @@
{{ block.super }}
<h3>{% trans "Removable Devices" %}</h3>
{% if not devices %}
<p>
{% blocktrans trimmed %}
There are no additional storage devices attached.
{% endblocktrans %}
</p>
{% else %}
<table class="table table-bordered table-condensed table-striped">
<thead>
<tr>
<th>{% trans "Device" %}</th>
<th>{% trans "Label" %}</th>
<th>{% trans "Mount Point" %}</th>
<th>{% trans "Type" %}</th>
<th>{% trans "Size" %}</th>
<th>{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
{% for device in devices %}
<tr>
<td>{{ device.device }}</td>
<td>{{ device.label }}</td>
<td>{{ device.mount_points|join:', ' }}</td>
<td>{{ device.filesystem_type }}</td>
<td>{{ device.size }}</td>
<td>
{% if device.mount_points %}
<form class="form" method="post"
action="{% url 'storage:eject' device.device|urlencode:'' %}">
{% csrf_token %}
<button type="submit"
class="btn btn-sm btn-default glyphicon glyphicon-eject">
</button>
</form>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% endblock %}
{% endblock %}

View File

@ -1,105 +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 <http://www.gnu.org/licenses/>.
#
"""
Library for interacting with udisks2 D-Bus service.
"""
from django.utils.translation import ugettext as _
from plinth import utils
from plinth.modules.storage import format_bytes
def list_devices():
"""List devices that can be ejected."""
udisks = utils.import_from_gi('UDisks', '2.0')
client = udisks.Client.new_sync()
object_manager = client.get_object_manager()
block = None
devices = []
for obj in object_manager.get_objects():
if not obj.get_block():
continue
block = obj.get_block()
if block.props.id_usage != 'filesystem' or \
block.props.hint_system:
continue
device_name = block.props.device
if not device_name:
continue
device = {
'device': block.props.device,
'label': block.props.id_label,
'size': format_bytes(block.props.size),
'filesystem_type': block.props.id_type
}
try:
device['mount_points'] = obj.get_filesystem().props.mount_points
except Exception:
pass
devices.append(device)
return devices
def get_error_message(error):
"""Return an error message given an exception."""
udisks = utils.import_from_gi('UDisks', '2.0')
if error.matches(udisks.Error.quark(), udisks.Error.FAILED):
message = _('The operation failed.')
elif error.matches(udisks.Error.quark(), udisks.Error.CANCELLED):
message = _('The operation was cancelled.')
elif error.matches(udisks.Error.quark(), udisks.Error.ALREADY_UNMOUNTING):
message = _('The device is already unmounting.')
elif error.matches(udisks.Error.quark(), udisks.Error.NOT_SUPPORTED):
message = _('The operation is not supported due to '
'missing driver/tool support.')
elif error.matches(udisks.Error.quark(), udisks.Error.TIMED_OUT):
message = _('The operation timed out.')
elif error.matches(udisks.Error.quark(), udisks.Error.WOULD_WAKEUP):
message = _('The operation would wake up a disk that is '
'in a deep-sleep state.')
elif error.matches(udisks.Error.quark(), udisks.Error.DEVICE_BUSY):
message = _('Attempting to unmount a device that is busy.')
elif error.matches(udisks.Error.quark(), udisks.Error.ALREADY_CANCELLED):
message = _('The operation has already been cancelled.')
elif error.matches(udisks.Error.quark(), udisks.Error.NOT_AUTHORIZED) or \
error.matches(udisks.Error.quark(),
udisks.Error.NOT_AUTHORIZED_CAN_OBTAIN) or \
error.matches(udisks.Error.quark(),
udisks.Error.NOT_AUTHORIZED_DISMISSED):
message = _('Not authorized to perform the requested operation.')
elif error.matches(udisks.Error.quark(), udisks.Error.ALREADY_MOUNTED):
message = _('The device is already mounted.')
elif error.matches(udisks.Error.quark(), udisks.Error.NOT_MOUNTED):
message = _('The device is not mounted.')
elif error.matches(udisks.Error.quark(),
udisks.Error.OPTION_NOT_PERMITTED):
message = _('Not permitted to use the requested option.')
elif error.matches(udisks.Error.quark(),
udisks.Error.MOUNTED_BY_OTHER_USER):
message = _('The device is mounted by another user.')
else:
message = error.message
return message

View File

@ -34,7 +34,7 @@ from plinth.errors import PlinthError
from plinth.modules import storage
from plinth.utils import format_lazy
from . import udisks2, get_disk_info
from . import get_disk_info, get_error_message
logger = logging.getLogger(__name__)
@ -53,7 +53,6 @@ def index(request):
'title': _('Storage'),
'description': storage.description,
'disks': disks,
'devices': udisks2.list_devices(),
'manual_page': storage.manual_page,
'expandable_root_size': expandable_root_size
})
@ -133,7 +132,7 @@ def eject(request, device_path):
messages.success(request, _('Device can be safely unplugged.'))
except Exception as exception:
try:
message = udisks2.get_error_message(exception)
message = get_error_message(exception)
except AttributeError:
message = str(exception)