diff --git a/actions/snapshot b/actions/snapshot
index 538042258..f74513089 100755
--- a/actions/snapshot
+++ b/actions/snapshot
@@ -35,9 +35,6 @@ def parse_arguments():
help='Delete a snapshot by number')
subparser.add_argument('number', help='Number of snapshot to delete')
- subparser = subparsers.add_parser('delete-all',
- help='Delete all the snapshots')
-
subparser = subparsers.add_parser('set-config',
help='Configure automatic snapshots')
subparser.add_argument('config')
@@ -166,25 +163,30 @@ def _add_fstab_entry(mount_point):
aug.save()
-def _get_snapper_list():
- command = ['snapper', 'list']
- process = subprocess.run(command, stdout=subprocess.PIPE, check=True)
- return process.stdout.decode().splitlines()
+def _parse_number(number):
+ """Parse the char following the number and return status of snapshot."""
+ is_default = number[-1] in ('+', '*')
+ is_active = number[-1] in ('-', '*')
+ return number.strip('-+*'), is_default, is_active
def subcommand_list(_):
"""List snapshots."""
- lines = _get_snapper_list()
- keys = ('number', 'type', 'pre_number', 'date', 'user', 'cleanup',
- 'description')
+ process = subprocess.run(['snapper', 'list'], stdout=subprocess.PIPE,
+ check=True)
+ lines = process.stdout.decode().splitlines()
+
+ keys = ('number', 'is_default', 'is_active', 'type', 'pre_number', 'date',
+ 'user', 'cleanup', 'description')
snapshots = []
for line in lines[2:]:
- parts = [part.strip('* ') for part in line.split('|')]
- snapshots.append(dict(zip(keys, parts)))
-
- default = _get_default_snapshot()
- for snapshot in snapshots:
- snapshot['is_default'] = (snapshot['number'] == default)
+ parts = [part.strip() for part in line.split('|')]
+ parts = list(_parse_number(parts[0])) + parts[1:]
+ snapshot = dict(zip(keys, parts))
+ # Snapshot 0 always represents the current system, it need not be
+ # listed and cannot be deleted.
+ if snapshot['number'] != '0':
+ snapshots.append(snapshot)
snapshots.reverse()
print(json.dumps(snapshots))
@@ -231,35 +233,6 @@ def subcommand_delete(arguments):
subprocess.run(command, check=True)
-def subcommand_delete_all(_):
- """Delete all the snapshots (except the active one)."""
- lines = _get_snapper_list()
- snapshot_range = [line.split('|')[0].strip() for line in lines[3:]]
- default_snapshot = _get_default_snapshot()
- if snapshot_range:
- if default_snapshot:
- index = snapshot_range.index(default_snapshot)
- range_before = snapshot_range[:index]
- range_after = snapshot_range[index + 1:]
- to_delete = [range_before, range_after]
- else:
- to_delete = [snapshot_range]
-
- delete_args = filter(None, map(_get_delete_arg, to_delete))
- for arg in delete_args:
- subprocess.run(['snapper', 'delete', arg], check=True)
-
-
-def _get_delete_arg(range_list):
- """Return 'a-b' when given ['a', ..., 'b']."""
- if not range_list:
- return None
- elif len(range_list) == 1:
- return range_list[0]
- else:
- return range_list[0] + '-' + range_list[-1]
-
-
def subcommand_set_config(arguments):
command = ['snapper', 'set-config'] + arguments.config.split()
subprocess.run(command, check=True)
diff --git a/plinth/modules/snapshot/templates/snapshot_delete_selected.html b/plinth/modules/snapshot/templates/snapshot_delete_selected.html
index 2bfbab10d..929f6f4b5 100644
--- a/plinth/modules/snapshot/templates/snapshot_delete_selected.html
+++ b/plinth/modules/snapshot/templates/snapshot_delete_selected.html
@@ -19,13 +19,11 @@
{% for snapshot in snapshots %}
- {% if not snapshot.is_default %}
-
- {{ snapshot.number }}
- {{ snapshot.date }}
- {{ snapshot.description }}
-
- {% endif %}
+
+ {{ snapshot.number }}
+ {{ snapshot.date }}
+ {{ snapshot.description }}
+
{% endfor %}
@@ -33,6 +31,11 @@
diff --git a/plinth/modules/snapshot/templates/snapshot_manage.html b/plinth/modules/snapshot/templates/snapshot_manage.html
index 7244b2c39..e15842e21 100644
--- a/plinth/modules/snapshot/templates/snapshot_manage.html
+++ b/plinth/modules/snapshot/templates/snapshot_manage.html
@@ -19,7 +19,8 @@
+ value="{% trans 'Delete Snapshots' %}"
+ {{ has_deletable_snapshots|yesno:',disabled="disabled"' }}/>
@@ -33,35 +34,38 @@
{% for snapshot in snapshots %}
- {% if snapshot.description != "current" %}
-
-
- {{ snapshot.number }}
- {% if snapshot.is_default %}
-
- {% trans "active" %}
-
- {% endif %}
-
- {{ snapshot.date }}
- {{ snapshot.description }}
-
-
-
-
-
-
- {% if not snapshot.is_default %}
-
- {% endif %}
-
-
- {% endif %}
+
+
+ {{ snapshot.number }}
+ {% if snapshot.is_default %}
+
+ {% trans "will be used at next boot" %}
+
+ {% endif %}
+ {% if snapshot.is_active %}
+
+ {% trans "in use" %}
+
+ {% endif %}
+
+ {{ snapshot.date }}
+ {{ snapshot.description }}
+
+
+
+
+
+
+ {% if not snapshot.is_default and not snapshot.is_active %}
+
+ {% endif %}
+
+
{% endfor %}
diff --git a/plinth/modules/snapshot/views.py b/plinth/modules/snapshot/views.py
index bfe741aa2..03ee06804 100644
--- a/plinth/modules/snapshot/views.py
+++ b/plinth/modules/snapshot/views.py
@@ -4,8 +4,10 @@ Views for snapshot module.
"""
import json
+import urllib.parse
from django.contrib import messages
+from django.http import HttpResponseRedirect
from django.shortcuts import redirect
from django.template.response import TemplateResponse
from django.urls import reverse, reverse_lazy
@@ -78,15 +80,21 @@ def manage(request):
actions.superuser_run('snapshot', ['create'])
messages.success(request, _('Created snapshot.'))
if 'delete_selected' in request.POST:
- if request.POST.getlist('snapshot_list'):
- snapshot_to_delete = request.POST.getlist('snapshot_list')
- request.session['snapshots'] = snapshot_to_delete
- return redirect(reverse('snapshot:delete-selected'))
+ to_delete = request.POST.getlist('snapshot_list')
+ if to_delete:
+ # Send values using GET params instead of session variables so
+ # that snapshots can be deleted even when disk is full.
+ params = [('snapshots', number) for number in to_delete]
+ params = urllib.parse.urlencode(params)
+ url = reverse('snapshot:delete-selected')
+ return HttpResponseRedirect(f'{url}?{params}')
output = actions.superuser_run('snapshot', ['list'])
snapshots = json.loads(output)
- has_deletable_snapshots = any(
- [snapshot for snapshot in snapshots[1:] if not snapshot['is_default']])
+ has_deletable_snapshots = any([
+ snapshot for snapshot in snapshots
+ if not snapshot['is_default'] and not snapshot['is_active']
+ ])
return TemplateResponse(
request, 'snapshot_manage.html', {
@@ -100,6 +108,7 @@ def manage(request):
def update_configuration(request, old_status, new_status):
"""Update configuration of snapshots."""
+
def make_config(args):
key, stamp = args[0], args[1]
if old_status[key] != new_status[key]:
@@ -142,39 +151,44 @@ def update_configuration(request, old_status, new_status):
def delete_selected(request):
+ """View to delete selected snapshots."""
+ if request.method == 'POST':
+ to_delete = set(request.POST.getlist('snapshots'))
+ else:
+ to_delete = set(request.GET.getlist('snapshots'))
+
+ if not to_delete:
+ return redirect(reverse('snapshot:manage'))
+
output = actions.superuser_run('snapshot', ['list'])
snapshots = json.loads(output)
+ snapshots_to_delete = [
+ snapshot for snapshot in snapshots if snapshot['number'] in to_delete
+ and not snapshot['is_active'] and not snapshot['is_default']
+ ]
if request.method == 'POST':
- if 'snapshots' in request.session:
- to_delete = request.session['snapshots']
- try:
- if to_delete == len(snapshots):
- actions.superuser_run('snapshot', ['delete_all'])
- messages.success(request, _('Deleted all snapshots'))
- else:
- for snapshot in to_delete:
- actions.superuser_run('snapshot', ['delete', snapshot])
- messages.success(request, _('Deleted selected snapshots'))
- except ActionError as exception:
- if 'Config is in use.' in exception.args[2]:
- messages.error(
- request,
- _('Snapshot is currently in use. '
- 'Please try again later.'))
- else:
- raise
+ try:
+ for snapshot in snapshots_to_delete:
+ actions.superuser_run('snapshot',
+ ['delete', snapshot['number']])
- return redirect(reverse('snapshot:manage'))
+ messages.success(request, _('Deleted selected snapshots'))
+ except ActionError as exception:
+ if 'Config is in use.' in exception.args[2]:
+ messages.error(
+ request,
+ _('Snapshot is currently in use. '
+ 'Please try again later.'))
+ else:
+ raise
- if 'snapshots' in request.session:
- data = request.session['snapshots']
- to_delete = list(filter(lambda x: x['number'] in data, snapshots))
+ return redirect(reverse('snapshot:manage'))
- return TemplateResponse(request, 'snapshot_delete_selected.html', {
- 'title': _('Delete Snapshots'),
- 'snapshots': to_delete
- })
+ return TemplateResponse(request, 'snapshot_delete_selected.html', {
+ 'title': _('Delete Snapshots'),
+ 'snapshots': snapshots_to_delete
+ })
def rollback(request, number):