From c620c0d9ac7380aeb1d7ff7a8f5d4b11ec69072b Mon Sep 17 00:00:00 2001 From: yfprojects <62463991+real-yfprojects@users.noreply.github.com> Date: Fri, 20 Jan 2023 12:51:53 +0000 Subject: [PATCH] Handle empty path in `FileTreeModel.addItem`. (#1552) --- src/vorta/views/partials/treemodel.py | 31 ++++++++++++++++--- .../borg_json_output/list_archive_stdout.json | 1 + tests/test_treemodel.py | 26 ++++++++++++++++ 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/src/vorta/views/partials/treemodel.py b/src/vorta/views/partials/treemodel.py index 3dff5c67..b95357a6 100644 --- a/src/vorta/views/partials/treemodel.py +++ b/src/vorta/views/partials/treemodel.py @@ -162,15 +162,22 @@ class FileSystemItem(Generic[T]): """ if isinstance(child_subpath_index, FileSystemItem): child = child_subpath_index + if not child.subpath: + raise ValueError("Child without subpath") + i = bisect.bisect_left(self.children, child) if i < len(self.children) and self.children[i] == child: del self.children[i] + else: + raise ValueError("Child not found") elif isinstance(child_subpath_index, str): subpath = child_subpath_index i = bisect.bisect_left(self.children, subpath) if i < len(self.children) and self.children[i].subpath == subpath: del self.children[i] + else: + raise ValueError("Child not found") elif isinstance(child_subpath_index, int): i = child_subpath_index @@ -239,7 +246,7 @@ class FileSystemItem(Generic[T]): i, item = res return item - fsi = reduce(walk, path, self) + fsi = reduce(walk, path, self) # handles empty path -> returns self return fsi def __repr__(self): @@ -351,14 +358,17 @@ class FileTreeModel(QAbstractItemModel, Generic[T]): item : FileSystemItemLike The item. """ - self.beginResetModel() - path = item[0] data = item[1] if isinstance(path, PurePath): path = path.parts + if not path: + return # empty path (e.g. `.`) can't be added + + self.beginResetModel() + def child(tup, subpath): fsi, i = tup i += 1 @@ -594,7 +604,7 @@ class FileTreeModel(QAbstractItemModel, Generic[T]): if isinstance(path, PurePath): path = path.parts - return self.root.get_path(path) + return self.root.get_path(path) # handels empty path def data(self, index: QModelIndex, role: int = Qt.ItemDataRole.DisplayRole): """ @@ -689,6 +699,9 @@ class FileTreeModel(QAbstractItemModel, Generic[T]): if isinstance(path, PurePath): path = path.parts + if not path: + return QModelIndex() # empty path won't ever be in the model + # flat mode if self.mode == self.DisplayMode.FLAT: i = bisect.bisect_left(self._flattened, path) @@ -889,7 +902,15 @@ class FileTreeSortProxyModel(QSortFilterProxyModel): super().__init__(parent) self.folders_on_top = False - def keepFoldersOnTop(self, value: bool = None) -> bool: + @overload + def keepFoldersOnTop(self) -> bool: + ... + + @overload + def keepFoldersOnTop(self, value: bool) -> bool: + ... + + def keepFoldersOnTop(self, value=None) -> bool: """ Set or get whether folders are kept on top when sorting. diff --git a/tests/borg_json_output/list_archive_stdout.json b/tests/borg_json_output/list_archive_stdout.json index eb32515b..30de718e 100644 --- a/tests/borg_json_output/list_archive_stdout.json +++ b/tests/borg_json_output/list_archive_stdout.json @@ -1,3 +1,4 @@ +{"type": "d", "mode": "drwxrwxr-x", "user": "theuser", "group": "theuser", "uid": 1000, "gid": 1000, "path": ".", "healthy": true, "source": "", "linktarget": "", "flags": null, "mtime": "2022-05-13T14:33:57.305797", "size": 0} {"type": "d", "mode": "drwxrwxr-x", "user": "theuser", "group": "theuser", "uid": 1000, "gid": 1000, "path": "home/theuser/vorta", "healthy": true, "source": "", "linktarget": "", "flags": null, "mtime": "2022-05-13T14:33:57.305797", "size": 0} {"type": "d", "mode": "drwx------", "user": "theuser", "group": "theuser", "uid": 1000, "gid": 1000, "path": "home/theuser/vorta/.pytest_cache", "healthy": true, "source": "", "linktarget": "", "flags": null, "mtime": "2022-04-19T17:45:37.456679", "size": 0} {"type": "d", "mode": "drwx------", "user": "theuser", "group": "theuser", "uid": 1000, "gid": 1000, "path": "home/theuser/vorta/.pytest_cache/v", "healthy": true, "source": "", "linktarget": "", "flags": null, "mtime": "2022-04-19T17:45:37.456679", "size": 0} diff --git a/tests/test_treemodel.py b/tests/test_treemodel.py index 40dffe78..31d249f2 100644 --- a/tests/test_treemodel.py +++ b/tests/test_treemodel.py @@ -41,6 +41,11 @@ class TestFileSystemItem: # may not add a child with the same subpath item.add(child) + # not implemented due to performance reasons: + # child = FileSystemItem((), 8) + # with pytest.raises(ValueError): + # item.add(child) # may not add a child with an empty path + def test_remove(self): item = FileSystemItem(PurePath('test').parts, 0) child1 = FileSystemItem(PurePath('test/a').parts, 4) @@ -63,6 +68,14 @@ class TestFileSystemItem: assert len(item.children) == 1 assert child3 not in item.children + # test remove item not present + child = FileSystemItem(PurePath('notpresent').parts, 2) + with pytest.raises(ValueError): + item.remove(child) + child = FileSystemItem((), 2) + with pytest.raises(ValueError): + item.remove(child) + def test_get(self): item = FileSystemItem(PurePath('test').parts, 0) child1 = FileSystemItem(PurePath('test/a').parts, 4) @@ -105,6 +118,9 @@ class TestFileSystemItem: assert item.get_path(PurePath('a/aa').parts) is child11 assert item.get_path(('b',)) is child2 + # test get empty path + assert item.get_path(()) is item + class TestFileTreeModel: def test_basic_setup(self): @@ -170,6 +186,16 @@ class TestFileTreeModel: item3 = model.getItem(PurePath('/hello')) assert len(item3.children) == 0 + def test_empty_path(self): + model = TreeModelImp() + assert model.rowCount() == 0 + + model.addItem(((), None)) + assert model.rowCount() == 0 + + model.addItem((PurePath('.'), None)) + assert model.rowCount() == 0 + def test_root(self): model = TreeModelImp() assert model.getItem(PurePath()) == model.root