mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-05-20 10:34:30 +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 import action_utils, actions, cfg
|
||||||
from plinth.errors import PlinthError
|
from plinth.errors import PlinthError
|
||||||
from plinth.menu import main_menu
|
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
|
from .manifest import backup
|
||||||
|
|
||||||
@ -75,27 +75,12 @@ def get_disks():
|
|||||||
disk_from_df.update(disk_from_lsblk)
|
disk_from_df.update(disk_from_lsblk)
|
||||||
combined_list.append(disk_from_df)
|
combined_list.append(disk_from_df)
|
||||||
|
|
||||||
|
for disk in combined_list:
|
||||||
|
disk['is_root_device'] = is_root_device(disk)
|
||||||
|
|
||||||
return combined_list
|
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():
|
def _get_disks_from_df():
|
||||||
"""Return the list of disks and free space available using 'df'."""
|
"""Return the list of disks and free space available using 'df'."""
|
||||||
command = [
|
command = [
|
||||||
@ -130,7 +115,9 @@ def _get_disks_from_df():
|
|||||||
|
|
||||||
def _get_disks_from_lsblk():
|
def _get_disks_from_lsblk():
|
||||||
"""Return the list of disks and other information 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:
|
try:
|
||||||
process = subprocess.run(command, stdout=subprocess.PIPE, check=True)
|
process = subprocess.run(command, stdout=subprocess.PIPE, check=True)
|
||||||
except subprocess.CalledProcessError as exception:
|
except subprocess.CalledProcessError as exception:
|
||||||
@ -145,16 +132,35 @@ def _get_disks_from_lsblk():
|
|||||||
return disks
|
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):
|
def get_root_device(disks):
|
||||||
"""Return the root partition's device from list of partitions."""
|
"""Return the root partition's device from list of partitions."""
|
||||||
devices = [
|
for disk in disks:
|
||||||
disk['dev_kname'] for disk in disks
|
if is_root_device(disk):
|
||||||
if disk['mountpoint'] == '/' and disk['type'] == 'part'
|
return disk['dev_kname']
|
||||||
]
|
return None
|
||||||
try:
|
|
||||||
return devices[0]
|
|
||||||
except IndexError:
|
def is_root_device(disk):
|
||||||
return None
|
"""Return whether a given disk is a root device or not."""
|
||||||
|
return disk['mountpoint'] == '/' and disk['type'] == 'part'
|
||||||
|
|
||||||
|
|
||||||
def is_expandable(device):
|
def is_expandable(device):
|
||||||
@ -200,6 +206,49 @@ def format_bytes(size):
|
|||||||
return _('{disk_size:.1f} TiB').format(disk_size=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():
|
def is_running():
|
||||||
"""Return whether the service is running."""
|
"""Return whether the service is running."""
|
||||||
return action_utils.service_is_running('freedombox-udiskie')
|
return action_utils.service_is_running('freedombox-udiskie')
|
||||||
|
|||||||
@ -56,15 +56,18 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans "Device" %}</th>
|
<th>{% trans "Device" %}</th>
|
||||||
|
<th>{% trans "Label" %}</th>
|
||||||
<th>{% trans "Mount Point" %}</th>
|
<th>{% trans "Mount Point" %}</th>
|
||||||
<th>{% trans "Type" %}</th>
|
<th>{% trans "Type" %}</th>
|
||||||
<th>{% trans "Used" %}</th>
|
<th>{% trans "Used" %}</th>
|
||||||
|
<th>{% trans "Actions" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for disk in disks %}
|
{% for disk in disks %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ disk.dev_kname }}</td>
|
<td>{{ disk.dev_kname }}</td>
|
||||||
|
<td>{{ disk.label|default_if_none:"" }}</td>
|
||||||
<td>{{ disk.mount_point }}</td>
|
<td>{{ disk.mount_point }}</td>
|
||||||
<td>{{ disk.file_system_type }}</td>
|
<td>{{ disk.file_system_type }}</td>
|
||||||
<td>
|
<td>
|
||||||
@ -84,6 +87,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>{{ disk.used_str }} / {{ disk.size_str }}</div>
|
<div>{{ disk.used_str }} / {{ disk.size_str }}</div>
|
||||||
</td>
|
</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>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -110,51 +125,5 @@
|
|||||||
|
|
||||||
{{ block.super }}
|
{{ 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 %}
|
||||||
{% 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.modules import storage
|
||||||
from plinth.utils import format_lazy
|
from plinth.utils import format_lazy
|
||||||
|
|
||||||
from . import udisks2, get_disk_info
|
from . import get_disk_info, get_error_message
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -53,7 +53,6 @@ def index(request):
|
|||||||
'title': _('Storage'),
|
'title': _('Storage'),
|
||||||
'description': storage.description,
|
'description': storage.description,
|
||||||
'disks': disks,
|
'disks': disks,
|
||||||
'devices': udisks2.list_devices(),
|
|
||||||
'manual_page': storage.manual_page,
|
'manual_page': storage.manual_page,
|
||||||
'expandable_root_size': expandable_root_size
|
'expandable_root_size': expandable_root_size
|
||||||
})
|
})
|
||||||
@ -133,7 +132,7 @@ def eject(request, device_path):
|
|||||||
messages.success(request, _('Device can be safely unplugged.'))
|
messages.success(request, _('Device can be safely unplugged.'))
|
||||||
except Exception as exception:
|
except Exception as exception:
|
||||||
try:
|
try:
|
||||||
message = udisks2.get_error_message(exception)
|
message = get_error_message(exception)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
message = str(exception)
|
message = str(exception)
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user