1
0
Fork 0
mirror of https://github.com/borgbase/vorta synced 2024-12-21 23:33:13 +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:
Henry Spanka 2023-04-01 21:10:56 +02:00 committed by real-yfprojects
parent f407032a76
commit e3451ed49e
No known key found for this signature in database
GPG key ID: 00F630DFDEE25747
2 changed files with 217 additions and 14 deletions

View file

@ -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

View file

@ -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),
),
),