mirror of
https://github.com/borgbase/vorta
synced 2024-12-22 07:43:09 +00:00
Handle ctime and mtime diff changes (#1675)
Borg v1.2.4 added new change types called `mtime` and `ctime` for the modification and the creation time of a file. Our diff json parser doesn't support these changes yet. The plain text parser doesn't need to be updated since it is only used for earlier versions of borg. This also extends the tooltip in the diff view to show changes in `ctime` or `mtime` in a localised manner. * src/vorta/views/diff_result.py (ChangeType): Add `CTIME` and `MTIME` linking to `MODIFIED`. * src/vorta/views/diff_result.py (DiffData): Add fields `ctime_change` and `mtime_change`. * src/vorta/views/diff_result.py (parse_diff_json): Parse the new change types. * src/vorta/views/diff_result.py (DiffTree.data): Add time changes to tooltip in a human readable format. * tests/test_diff.py : Update test data to include new change types. Add additional test cases for unittesting the new change types.
This commit is contained in:
parent
f407032a76
commit
e3451ed49e
2 changed files with 217 additions and 14 deletions
|
@ -6,7 +6,7 @@
|
|||
from pathlib import PurePath
|
||||
from typing import List, Optional, Tuple
|
||||
from PyQt5 import uic
|
||||
from PyQt5.QtCore import QMimeData, QModelIndex, QPoint, Qt, QThread, QUrl
|
||||
from PyQt5.QtCore import QDateTime, QLocale, QMimeData, QModelIndex, QPoint, Qt, QThread, QUrl
|
||||
from PyQt5.QtGui import QColor, QKeySequence
|
||||
from PyQt5.QtWidgets import QApplication, QHeaderView, QMenu, QShortcut, QTreeView
|
||||
from vorta.utils import get_asset, pretty_bytes, uses_dark_mode
|
||||
|
@ -206,6 +206,8 @@ def parse_diff_json(diffs: List[dict], model: 'DiffTree'):
|
|||
change_type: ChangeType = None
|
||||
mode_change: Optional[Tuple[str, str]] = None
|
||||
owner_change: Optional[Tuple[str, str, str, str]] = None
|
||||
ctime_change: Optional[Tuple[QDateTime, QDateTime]] = None
|
||||
mtime_change: Optional[Tuple[QDateTime, QDateTime]] = None
|
||||
modified: Optional[Tuple[int, int]] = None
|
||||
|
||||
# added link, removed link, changed link
|
||||
|
@ -213,6 +215,8 @@ def parse_diff_json(diffs: List[dict], model: 'DiffTree'):
|
|||
# added directory, removed directory
|
||||
# owner (old_user, new_user, old_group, new_group))
|
||||
# mode (old_mode, new_mode)
|
||||
# ctime (old_ctime, new_ctime)
|
||||
# mtime (old_mtime, new_mtime)
|
||||
for change in item['changes']:
|
||||
# if more than one type of change has happened for this file/dir/link, then report the most important
|
||||
# (higher priority)
|
||||
|
@ -269,6 +273,22 @@ def parse_diff_json(diffs: List[dict], model: 'DiffTree'):
|
|||
change['new_user'],
|
||||
change['new_group'],
|
||||
)
|
||||
|
||||
elif change['type'] == 'ctime':
|
||||
# ctime change can occur along with previous changes
|
||||
change_type = ChangeType.MODIFIED
|
||||
ctime_change = (
|
||||
QDateTime.fromString(change['old_ctime'], Qt.DateFormat.ISODateWithMs),
|
||||
QDateTime.fromString(change['new_ctime'], Qt.DateFormat.ISODateWithMs),
|
||||
)
|
||||
elif change['type'] == 'mtime':
|
||||
# mtime change can occur along with previous changes
|
||||
change_type = ChangeType.MODIFIED
|
||||
mtime_change = (
|
||||
QDateTime.fromString(change['old_mtime'], Qt.DateFormat.ISODateWithMs),
|
||||
QDateTime.fromString(change['new_mtime'], Qt.DateFormat.ISODateWithMs),
|
||||
)
|
||||
|
||||
else:
|
||||
raise Exception('Unknown change type: {}'.format(change['type']))
|
||||
|
||||
|
@ -282,6 +302,8 @@ def parse_diff_json(diffs: List[dict], model: 'DiffTree'):
|
|||
size=size,
|
||||
mode_change=mode_change,
|
||||
owner_change=owner_change,
|
||||
ctime_change=ctime_change,
|
||||
mtime_change=mtime_change,
|
||||
modified=modified,
|
||||
),
|
||||
)
|
||||
|
@ -492,6 +514,8 @@ class ChangeType(enum.Enum):
|
|||
such as - a file is deleted and replaced with
|
||||
a directory of the same name.
|
||||
owner - user and/or group ownership changed.
|
||||
ctime - creation time changed.
|
||||
mtime - modification time changed.
|
||||
|
||||
size:
|
||||
If type == `added` or `removed`,
|
||||
|
@ -518,6 +542,14 @@ class ChangeType(enum.Enum):
|
|||
See old_user property.
|
||||
new_group:
|
||||
See old_user property.
|
||||
old_ctime:
|
||||
If type == `ctime`, then old_ctime and new_ctime provide creation time changes.
|
||||
new_ctime:
|
||||
See old_ctime property.
|
||||
old_mtime:
|
||||
If type == `mtime`, then old_mtime and new_mtime provide modification time changes.
|
||||
new_mtime:
|
||||
See old_mtime property.
|
||||
"""
|
||||
|
||||
NONE = 0 # no change
|
||||
|
@ -531,6 +563,8 @@ class ChangeType(enum.Enum):
|
|||
CHANGED_LINK = MODIFIED
|
||||
MODE = MODIFIED # changed permissions
|
||||
OWNER = MODIFIED
|
||||
CTIME = MODIFIED
|
||||
MTIME = MODIFIED
|
||||
|
||||
def short(self):
|
||||
"""Get a short identifier for the change type."""
|
||||
|
@ -588,6 +622,8 @@ class DiffData:
|
|||
size: int # size change (disk usage)
|
||||
mode_change: Optional[Tuple[str, str]] = None
|
||||
owner_change: Optional[Tuple[str, str, str, str]] = None
|
||||
ctime_change: Optional[Tuple[QDateTime, QDateTime]] = None
|
||||
mtime_change: Optional[Tuple[QDateTime, QDateTime]] = None
|
||||
modified: Optional[Tuple[int, int]] = None
|
||||
|
||||
|
||||
|
@ -798,6 +834,7 @@ def data(self, index: QModelIndex, role: int = Qt.ItemDataRole.DisplayRole):
|
|||
|
||||
modified_template = self.tr("Added {}, deleted {}")
|
||||
owner_template = "{: <10} -> {: >10}"
|
||||
time_template = "{}: {} -> {}"
|
||||
permission_template = "{} -> {}"
|
||||
|
||||
# format
|
||||
|
@ -844,4 +881,20 @@ def data(self, index: QModelIndex, role: int = Qt.ItemDataRole.DisplayRole):
|
|||
"{}:{}".format(item.data.owner_change[2], item.data.owner_change[3]),
|
||||
)
|
||||
|
||||
if item.data.ctime_change:
|
||||
tooltip += '\n'
|
||||
tooltip += time_template.format(
|
||||
"Creation Time",
|
||||
QLocale.system().toString(item.data.ctime_change[0], QLocale.FormatType.ShortFormat),
|
||||
QLocale.system().toString(item.data.ctime_change[1], QLocale.FormatType.ShortFormat),
|
||||
)
|
||||
|
||||
if item.data.mtime_change:
|
||||
tooltip += '\n'
|
||||
tooltip += time_template.format(
|
||||
"Modification Time",
|
||||
QLocale.system().toString(item.data.mtime_change[0], QLocale.FormatType.ShortFormat),
|
||||
QLocale.system().toString(item.data.mtime_change[1], QLocale.FormatType.ShortFormat),
|
||||
)
|
||||
|
||||
return tooltip
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from pathlib import PurePath
|
||||
import pytest
|
||||
from PyQt5.QtCore import QItemSelectionModel
|
||||
from PyQt5.QtCore import QDateTime, QItemSelectionModel, Qt
|
||||
import vorta.borg
|
||||
import vorta.utils
|
||||
import vorta.views.archive_tab
|
||||
|
@ -56,11 +56,22 @@ def check(feature_name):
|
|||
[
|
||||
(
|
||||
'changed link some/changed/link',
|
||||
('some/changed/link', FileType.LINK, ChangeType.CHANGED_LINK, 0, 0, None, None, None),
|
||||
('some/changed/link', FileType.LINK, ChangeType.CHANGED_LINK, 0, 0, None, None, None, None, None),
|
||||
),
|
||||
(
|
||||
' +77.8 kB -77.8 kB some/changed/file',
|
||||
('some/changed/file', FileType.FILE, ChangeType.MODIFIED, 2 * 77800, 0, None, None, (77800, 77800)),
|
||||
(
|
||||
'some/changed/file',
|
||||
FileType.FILE,
|
||||
ChangeType.MODIFIED,
|
||||
2 * 77800,
|
||||
0,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
(77800, 77800),
|
||||
),
|
||||
),
|
||||
(
|
||||
' +77.8 kB -77.8 kB [-rw-rw-rw- -> -rw-r--r--] some/changed/file',
|
||||
|
@ -72,20 +83,33 @@ def check(feature_name):
|
|||
0,
|
||||
('-rw-rw-rw-', '-rw-r--r--'),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
(77800, 77800),
|
||||
),
|
||||
),
|
||||
(
|
||||
'[-rw-rw-rw- -> -rw-r--r--] some/changed/file',
|
||||
('some/changed/file', FileType.FILE, ChangeType.MODE, 0, 0, ('-rw-rw-rw-', '-rw-r--r--'), None, None),
|
||||
(
|
||||
'some/changed/file',
|
||||
FileType.FILE,
|
||||
ChangeType.MODE,
|
||||
0,
|
||||
0,
|
||||
('-rw-rw-rw-', '-rw-r--r--'),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
),
|
||||
),
|
||||
(
|
||||
'added directory some/changed/dir',
|
||||
('some/changed/dir', FileType.DIRECTORY, ChangeType.ADDED, 0, 0, None, None, None),
|
||||
('some/changed/dir', FileType.DIRECTORY, ChangeType.ADDED, 0, 0, None, None, None, None, None),
|
||||
),
|
||||
(
|
||||
'removed directory some/changed/dir',
|
||||
('some/changed/dir', FileType.DIRECTORY, ChangeType.REMOVED_DIR, 0, 0, None, None, None),
|
||||
('some/changed/dir', FileType.DIRECTORY, ChangeType.REMOVED_DIR, 0, 0, None, None, None, None, None),
|
||||
),
|
||||
# Example from https://github.com/borgbase/vorta/issues/521
|
||||
(
|
||||
|
@ -99,12 +123,25 @@ def check(feature_name):
|
|||
None,
|
||||
('user', 'user', 'nfsnobody', 'nfsnobody'),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
),
|
||||
),
|
||||
# Very short owner change, to check stripping whitespace from file path
|
||||
(
|
||||
'[a:a -> b:b] home/user/arrays/test.txt',
|
||||
('home/user/arrays/test.txt', FileType.FILE, ChangeType.OWNER, 0, 0, None, ('a', 'a', 'b', 'b'), None),
|
||||
(
|
||||
'home/user/arrays/test.txt',
|
||||
FileType.FILE,
|
||||
ChangeType.OWNER,
|
||||
0,
|
||||
0,
|
||||
None,
|
||||
('a', 'a', 'b', 'b'),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
),
|
||||
),
|
||||
# All file-related changes in one test
|
||||
(
|
||||
|
@ -117,6 +154,8 @@ def check(feature_name):
|
|||
77000,
|
||||
('-rw-rw-rw-', '-rw-r--r--'),
|
||||
('user', 'user', 'nfsnobody', 'nfsnobody'),
|
||||
None,
|
||||
None,
|
||||
(77800, 800),
|
||||
),
|
||||
),
|
||||
|
@ -139,11 +178,22 @@ def test_archive_diff_parser(line, expected):
|
|||
[
|
||||
(
|
||||
{'path': 'some/changed/link', 'changes': [{'type': 'changed link'}]},
|
||||
('some/changed/link', FileType.LINK, ChangeType.CHANGED_LINK, 0, 0, None, None, None),
|
||||
('some/changed/link', FileType.LINK, ChangeType.CHANGED_LINK, 0, 0, None, None, None, None, None),
|
||||
),
|
||||
(
|
||||
{'path': 'some/changed/file', 'changes': [{'type': 'modified', 'added': 77800, 'removed': 77800}]},
|
||||
('some/changed/file', FileType.FILE, ChangeType.MODIFIED, 2 * 77800, 0, None, None, (77800, 77800)),
|
||||
(
|
||||
'some/changed/file',
|
||||
FileType.FILE,
|
||||
ChangeType.MODIFIED,
|
||||
2 * 77800,
|
||||
0,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
(77800, 77800),
|
||||
),
|
||||
),
|
||||
(
|
||||
{
|
||||
|
@ -161,6 +211,8 @@ def test_archive_diff_parser(line, expected):
|
|||
77000,
|
||||
('-rw-rw-rw-', '-rw-r--r--'),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
(77800, 800),
|
||||
),
|
||||
),
|
||||
|
@ -169,15 +221,26 @@ def test_archive_diff_parser(line, expected):
|
|||
'path': 'some/changed/file',
|
||||
'changes': [{'type': 'mode', 'old_mode': '-rw-rw-rw-', 'new_mode': '-rw-r--r--'}],
|
||||
},
|
||||
('some/changed/file', FileType.FILE, ChangeType.MODE, 0, 0, ('-rw-rw-rw-', '-rw-r--r--'), None, None),
|
||||
(
|
||||
'some/changed/file',
|
||||
FileType.FILE,
|
||||
ChangeType.MODE,
|
||||
0,
|
||||
0,
|
||||
('-rw-rw-rw-', '-rw-r--r--'),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
),
|
||||
),
|
||||
(
|
||||
{'path': 'some/changed/dir', 'changes': [{'type': 'added directory'}]},
|
||||
('some/changed/dir', FileType.DIRECTORY, ChangeType.ADDED, 0, 0, None, None, None),
|
||||
('some/changed/dir', FileType.DIRECTORY, ChangeType.ADDED, 0, 0, None, None, None, None, None),
|
||||
),
|
||||
(
|
||||
{'path': 'some/changed/dir', 'changes': [{'type': 'removed directory'}]},
|
||||
('some/changed/dir', FileType.DIRECTORY, ChangeType.REMOVED_DIR, 0, 0, None, None, None),
|
||||
('some/changed/dir', FileType.DIRECTORY, ChangeType.REMOVED_DIR, 0, 0, None, None, None, None, None),
|
||||
),
|
||||
# Example from https://github.com/borgbase/vorta/issues/521
|
||||
(
|
||||
|
@ -202,6 +265,8 @@ def test_archive_diff_parser(line, expected):
|
|||
None,
|
||||
('user', 'user', 'nfsnobody', 'nfsnobody'),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
),
|
||||
),
|
||||
# Very short owner change, to check stripping whitespace from file path
|
||||
|
@ -210,7 +275,74 @@ def test_archive_diff_parser(line, expected):
|
|||
'path': 'home/user/arrays/test.txt',
|
||||
'changes': [{'type': 'owner', 'old_user': 'a', 'new_user': 'b', 'old_group': 'a', 'new_group': 'b'}],
|
||||
},
|
||||
('home/user/arrays/test.txt', FileType.FILE, ChangeType.OWNER, 0, 0, None, ('a', 'a', 'b', 'b'), None),
|
||||
(
|
||||
'home/user/arrays/test.txt',
|
||||
FileType.FILE,
|
||||
ChangeType.OWNER,
|
||||
0,
|
||||
0,
|
||||
None,
|
||||
('a', 'a', 'b', 'b'),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
),
|
||||
),
|
||||
# Short ctime change
|
||||
(
|
||||
{
|
||||
'path': 'home/user/arrays',
|
||||
'changes': [
|
||||
{
|
||||
'new_ctime': '2023-04-01T17:23:14.104630',
|
||||
'old_ctime': '2023-03-03T23:40:17.073948',
|
||||
'type': 'ctime',
|
||||
}
|
||||
],
|
||||
},
|
||||
(
|
||||
'home/user/arrays',
|
||||
FileType.FILE,
|
||||
ChangeType.MODIFIED,
|
||||
0,
|
||||
0,
|
||||
None,
|
||||
None,
|
||||
(
|
||||
QDateTime.fromString('2023-03-03T23:40:17.073948', Qt.DateFormat.ISODateWithMs),
|
||||
QDateTime.fromString('2023-04-01T17:23:14.104630', Qt.DateFormat.ISODateWithMs),
|
||||
),
|
||||
None,
|
||||
None,
|
||||
),
|
||||
),
|
||||
# Short mtime change
|
||||
(
|
||||
{
|
||||
'path': 'home/user/arrays',
|
||||
'changes': [
|
||||
{
|
||||
'new_mtime': '2023-04-01T17:23:14.104630',
|
||||
'old_mtime': '2023-03-03T23:40:17.073948',
|
||||
'type': 'mtime',
|
||||
}
|
||||
],
|
||||
},
|
||||
(
|
||||
'home/user/arrays',
|
||||
FileType.FILE,
|
||||
ChangeType.MODIFIED,
|
||||
0,
|
||||
0,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
(
|
||||
QDateTime.fromString('2023-03-03T23:40:17.073948', Qt.DateFormat.ISODateWithMs),
|
||||
QDateTime.fromString('2023-04-01T17:23:14.104630', Qt.DateFormat.ISODateWithMs),
|
||||
),
|
||||
None,
|
||||
),
|
||||
),
|
||||
# All file-related changes in one test
|
||||
(
|
||||
|
@ -226,6 +358,16 @@ def test_archive_diff_parser(line, expected):
|
|||
'old_group': 'user',
|
||||
'new_group': 'nfsnobody',
|
||||
},
|
||||
{
|
||||
'new_ctime': '2023-04-01T17:23:14.104630',
|
||||
'old_ctime': '2023-03-03T23:40:17.073948',
|
||||
'type': 'ctime',
|
||||
},
|
||||
{
|
||||
'new_mtime': '2023-04-01T17:15:50.290565',
|
||||
'old_mtime': '2023-03-05T00:24:00.359045',
|
||||
'type': 'mtime',
|
||||
},
|
||||
],
|
||||
},
|
||||
(
|
||||
|
@ -236,6 +378,14 @@ def test_archive_diff_parser(line, expected):
|
|||
0,
|
||||
('-rw-rw-rw-', '-rw-r--r--'),
|
||||
('user', 'user', 'nfsnobody', 'nfsnobody'),
|
||||
(
|
||||
QDateTime.fromString('2023-03-03T23:40:17.073948', Qt.DateFormat.ISODateWithMs),
|
||||
QDateTime.fromString('2023-04-01T17:23:14.104630', Qt.DateFormat.ISODateWithMs),
|
||||
),
|
||||
(
|
||||
QDateTime.fromString('2023-03-05T00:24:00.359045', Qt.DateFormat.ISODateWithMs),
|
||||
QDateTime.fromString('2023-04-01T17:15:50.290565', Qt.DateFormat.ISODateWithMs),
|
||||
),
|
||||
(77800, 77800),
|
||||
),
|
||||
),
|
||||
|
|
Loading…
Reference in a new issue