Skip to content

Commit

Permalink
fix: Open symlinked folders in fileview
Browse files Browse the repository at this point in the history
Symlink folders in fileview now can be opened via double click in all circumstances.

Fix #1476
  • Loading branch information
buhtz committed Mar 28, 2024
1 parent e818aeb commit d33f35e
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 34 deletions.
3 changes: 2 additions & 1 deletion CHANGES
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
Back In Time

Version 1.4.4-dev (development of upcoming release)
* Fix bug: Open symlinked folders in file view (#1476)
* Fix bug: Respect dark mode using color roles (#1601)
* Fix: "Highly recommended" exclusion pattern in "Manage Profile" dialog's "Exclude" tab show missings only (#1620)
* Build: Activate PyLint error E0401 (import-error)
* Fix: Respect dark mode using color roles (#1601)
* Dependency: Migration to PyQt6
* Build: PyLint unit test is skipped if PyLint isn't installed, but will always run on TravisCI (#1634)
* Feature: Support rsync '--one-file-system' in Expert Options (#1598)
Expand Down
6 changes: 5 additions & 1 deletion common/snapshots.py
Original file line number Diff line number Diff line change
Expand Up @@ -2625,7 +2625,7 @@ def exists(self):
"""
return os.path.isdir(self.path()) and os.path.isdir(self.pathBackup())

def canOpenPath(self, path):
def isExistingPathInsideSnapshotFolder(self, path):
"""
``True`` if path is a file inside this snapshot
Expand All @@ -2636,13 +2636,17 @@ def canOpenPath(self, path):
bool: ``True`` if file exists
"""
fullPath = self.pathBackup(path)

if not os.path.exists(fullPath):
return False

if not os.path.islink(fullPath):
return True

basePath = self.pathBackup()
target = os.readlink(fullPath)
target = os.path.join(os.path.abspath(os.path.dirname(fullPath)), target)

return target.startswith(basePath)

@property
Expand Down
14 changes: 7 additions & 7 deletions common/test/test_sid.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,40 +185,40 @@ def test_exists(self):
'backup'))
self.assertTrue(sid.exists())

def test_canOpenPath(self):
def test_isExistingPathInsideSnapshotFolder(self):
sid = snapshots.SID('20151219-010324-123', self.cfg)
backupPath = os.path.join(self.snapshotPath,
'20151219-010324-123',
'backup')
os.makedirs(os.path.join(backupPath, 'foo'))

#test existing file and non existing file
self.assertTrue(sid.canOpenPath('/foo'))
self.assertFalse(sid.canOpenPath('/tmp'))
self.assertTrue(sid.isExistingPathInsideSnapshotFolder('/foo'))
self.assertFalse(sid.isExistingPathInsideSnapshotFolder('/tmp'))

#test valid absolute symlink inside snapshot
os.symlink(os.path.join(backupPath, 'foo'),
os.path.join(backupPath, 'bar'))
self.assertIsLink(backupPath, 'bar')
self.assertTrue(sid.canOpenPath('/bar'))
self.assertTrue(sid.isExistingPathInsideSnapshotFolder('/bar'))

#test valid relative symlink inside snapshot
os.symlink('./foo',
os.path.join(backupPath, 'baz'))
self.assertIsLink(backupPath, 'baz')
self.assertTrue(sid.canOpenPath('/baz'))
self.assertTrue(sid.isExistingPathInsideSnapshotFolder('/baz'))

#test invalid symlink
os.symlink(os.path.join(backupPath, 'asdf'),
os.path.join(backupPath, 'qwer'))
self.assertIsLink(backupPath, 'qwer')
self.assertFalse(sid.canOpenPath('/qwer'))
self.assertFalse(sid.isExistingPathInsideSnapshotFolder('/qwer'))

#test valid symlink outside snapshot
os.symlink('/tmp',
os.path.join(backupPath, 'bla'))
self.assertIsLink(backupPath, 'bla')
self.assertFalse(sid.canOpenPath('/bla'))
self.assertFalse(sid.isExistingPathInsideSnapshotFolder('/bla'))

def test_name(self):
sid = snapshots.SID('20151219-010324-123', self.cfg)
Expand Down
32 changes: 16 additions & 16 deletions common/test/test_takeSnapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,9 @@ def test_takeSnapshot(self, sleep):
)

self.assertTrue(sid1.exists())
self.assertTrue(sid1.canOpenPath(os.path.join(self.include.name, 'foo', 'bar', 'baz')))
self.assertTrue(sid1.canOpenPath(os.path.join(self.include.name, 'test')))
self.assertTrue(sid1.canOpenPath(os.path.join(self.include.name, 'file with spaces')))
self.assertTrue(sid1.isExistingPathInsideSnapshotFolder(os.path.join(self.include.name, 'foo', 'bar', 'baz')))
self.assertTrue(sid1.isExistingPathInsideSnapshotFolder(os.path.join(self.include.name, 'test')))
self.assertTrue(sid1.isExistingPathInsideSnapshotFolder(os.path.join(self.include.name, 'file with spaces')))
self.assertExists(self.cfg.anacronSpoolFile())
for f in ('config',
'fileinfo.bz2',
Expand Down Expand Up @@ -100,7 +100,7 @@ def test_takeSnapshot(self, sleep):

self.assertListEqual([True, False], self.sn.takeSnapshot(sid3, now, [(self.include.name, 0),]))
self.assertTrue(sid3.exists())
self.assertTrue(sid3.canOpenPath(os.path.join(self.include.name, 'lalala')))
self.assertTrue(sid3.isExistingPathInsideSnapshotFolder(os.path.join(self.include.name, 'lalala')))
inode1 = self.getInode(sid1)
inode3 = self.getInode(sid3)
self.assertEqual(inode1, inode3)
Expand All @@ -113,8 +113,8 @@ def test_takeSnapshot(self, sleep):

self.assertListEqual([True, False], self.sn.takeSnapshot(sid4, now, [(self.include.name, 0),]))
self.assertTrue(sid4.exists())
self.assertTrue(sid4.canOpenPath(os.path.join(self.include.name, 'foo', 'bar', 'baz')))
self.assertTrue(sid4.canOpenPath(os.path.join(self.include.name, 'test')))
self.assertTrue(sid4.isExistingPathInsideSnapshotFolder(os.path.join(self.include.name, 'foo', 'bar', 'baz')))
self.assertTrue(sid4.isExistingPathInsideSnapshotFolder(os.path.join(self.include.name, 'test')))

@patch('time.sleep') # speed up unittest
def test_takeSnapshot_with_spaces_in_include(self, sleep):
Expand All @@ -125,8 +125,8 @@ def test_takeSnapshot_with_spaces_in_include(self, sleep):

self.assertListEqual([True, False], self.sn.takeSnapshot(sid1, now, [(include, 0),]))
self.assertTrue(sid1.exists())
self.assertTrue(sid1.canOpenPath(os.path.join(include, 'foo', 'bar', 'baz')))
self.assertTrue(sid1.canOpenPath(os.path.join(include, 'test')))
self.assertTrue(sid1.isExistingPathInsideSnapshotFolder(os.path.join(include, 'foo', 'bar', 'baz')))
self.assertTrue(sid1.isExistingPathInsideSnapshotFolder(os.path.join(include, 'test')))
for f in ('config',
'fileinfo.bz2',
'info',
Expand All @@ -145,9 +145,9 @@ def test_takeSnapshot_exclude(self, sleep):

self.assertListEqual([True, False], self.sn.takeSnapshot(sid1, now, [(self.include.name, 0),]))
self.assertTrue(sid1.exists())
self.assertTrue(sid1.canOpenPath(os.path.join(self.include.name, 'foo', 'bar')))
self.assertFalse(sid1.canOpenPath(os.path.join(self.include.name, 'foo', 'bar', 'baz')))
self.assertTrue(sid1.canOpenPath(os.path.join(self.include.name, 'test')))
self.assertTrue(sid1.isExistingPathInsideSnapshotFolder(os.path.join(self.include.name, 'foo', 'bar')))
self.assertFalse(sid1.isExistingPathInsideSnapshotFolder(os.path.join(self.include.name, 'foo', 'bar', 'baz')))
self.assertTrue(sid1.isExistingPathInsideSnapshotFolder(os.path.join(self.include.name, 'test')))
for f in ('config',
'fileinfo.bz2',
'info',
Expand All @@ -168,9 +168,9 @@ def test_takeSnapshot_with_spaces_in_exclude(self, sleep):

self.assertListEqual([True, False], self.sn.takeSnapshot(sid1, now, [(self.include.name, 0),]))
self.assertTrue(sid1.exists())
self.assertTrue(sid1.canOpenPath(os.path.join(self.include.name, 'foo', 'bar', 'baz')))
self.assertTrue(sid1.canOpenPath(os.path.join(self.include.name, 'test')))
self.assertFalse(sid1.canOpenPath(exclude))
self.assertTrue(sid1.isExistingPathInsideSnapshotFolder(os.path.join(self.include.name, 'foo', 'bar', 'baz')))
self.assertTrue(sid1.isExistingPathInsideSnapshotFolder(os.path.join(self.include.name, 'test')))
self.assertFalse(sid1.isExistingPathInsideSnapshotFolder(exclude))
for f in ('config',
'fileinfo.bz2',
'info',
Expand All @@ -189,8 +189,8 @@ def test_takeSnapshot_error(self, sleep):

self.assertListEqual([True, True], self.sn.takeSnapshot(sid1, now, [(self.include.name, 0),]))
self.assertTrue(sid1.exists())
self.assertTrue(sid1.canOpenPath(os.path.join(self.include.name, 'foo', 'bar', 'baz')))
self.assertFalse(sid1.canOpenPath(os.path.join(self.include.name, 'test')))
self.assertTrue(sid1.isExistingPathInsideSnapshotFolder(os.path.join(self.include.name, 'foo', 'bar', 'baz')))
self.assertFalse(sid1.isExistingPathInsideSnapshotFolder(os.path.join(self.include.name, 'test')))
for f in ('config',
'fileinfo.bz2',
'info',
Expand Down
20 changes: 11 additions & 9 deletions qt/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -1554,18 +1554,16 @@ def btnFolderUpClicked(self):
self.updateFilesView(0)

def btnFolderHistoryPreviousClicked(self):
path = self.path_history.previous()
full_path = self.sid.pathBackup(path)

if os.path.isdir(full_path) and self.sid.canOpenPath(path):
self.path = path
self.updateFilesView(0)
self._folderHistoryClicked(self.path_history.previous())

def btnFolderHistoryNextClicked(self):
path = self.path_history.next()
self._folderHistoryClicked(self.path_history.next())

def _folderHistoryClicked(self, path):
full_path = self.sid.pathBackup(path)

if os.path.isdir(full_path) and self.sid.canOpenPath(path):
if (os.path.isdir(full_path)
and self.sid.isExistingPathInsideSnapshotFolder(path)):
self.path = path
self.updateFilesView(0)

Expand Down Expand Up @@ -1646,7 +1644,11 @@ def openPath(self, rel_path):
rel_path = os.path.join(self.path, rel_path)
full_path = self.sid.pathBackup(rel_path)

if os.path.exists(full_path) and self.sid.canOpenPath(rel_path):
# The class "GenericNonSnapshot" indicates that "Now" is selected
# in the snapshots timeline widget.
if (os.path.exists(full_path)
and (isinstance(self.sid, snapshots.GenericNonSnapshot) # "Now"
or self.sid.isExistingPathInsideSnapshotFolder(rel_path))):

if os.path.isdir(full_path):
self.path = rel_path
Expand Down

0 comments on commit d33f35e

Please sign in to comment.