mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-03-25 09:21:10 +00:00
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:
parent
db690cb581
commit
d8942eec6f
@ -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')
|
||||
|
||||
@ -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 %}
|
||||
|
||||
@ -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
|
||||
@ -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)
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user