def iter_filtered_dir_entry(dir_entries, match_patterns, on_skip): """ Filter a list of DirEntryPath instances with the given pattern :param dir_entries: list of DirEntryPath instances :param match_patterns: used with Path.match() e.g.: "__pycache__/*", "*.tmp", "*.cache" :param on_skip: function that will be called if 'match_patterns' hits. e.g.: def on_skip(entry, pattern): log.error("Skip pattern %r hit: %s" % (pattern, entry.path)) :return: yields None or DirEntryPath instances """ def match(dir_entry_path, match_patterns, on_skip): for match_pattern in match_patterns: if dir_entry_path.path_instance.match(match_pattern): on_skip(dir_entry_path, match_pattern) return True return False for entry in dir_entries: try: dir_entry_path = DirEntryPath(entry) except FileNotFoundError as err: # e.g.: A file was deleted after the first filesystem scan # Will be obsolete if we use shadow-copy / snapshot function from filesystem # see: https://github.com/jedie/PyHardLinkBackup/issues/6 log.error("Can't make DirEntryPath() instance: %s" % err) continue if match(dir_entry_path, match_patterns, on_skip): yield None else: yield dir_entry_path
def test_symlink(self): src_file = Path2("source_file.txt") src_file.touch() dst_file = Path2("destination.txt") dst_file.symlink_to(src_file) scan_result = list(Path2(".").scandir()) scan_result.sort(key=lambda x: x.path) self.assertEqual([f.path for f in scan_result], ['./destination.txt', './source_file.txt']) for dir_entry in scan_result: dir_entry_path = DirEntryPath(dir_entry) info = dir_entry_path.pformat() # print(info) if dir_entry_path.path == "./source_file.txt": self.assertFalse(dir_entry_path.is_symlink) self.assertTrue(dir_entry_path.is_file) elif dir_entry_path.path == "./destination.txt": self.assertTrue(dir_entry_path.is_symlink) self.assertFalse(dir_entry_path.is_file) else: self.fail() self.assertEqual(dir_entry_path.resolved_path, Path2(self.temp_root_path, "source_file.txt")) # Create a broken symlink, by deleting the source file: src_file.unlink() scan_result = list(Path2(".").scandir()) self.assertEqual([f.path for f in scan_result], ['./destination.txt']) dir_entry_path = DirEntryPath(scan_result[0]) info = dir_entry_path.pformat() print(info) self.assertEqual(dir_entry_path.path, "./destination.txt") self.assertEqual(dir_entry_path.resolved_path, None) # <- broken, so can't be resolve self.assertIsInstance(dir_entry_path.resolve_error, FileNotFoundError) # <- the error instance self.assertTrue(dir_entry_path.is_symlink) self.assertFalse(dir_entry_path.is_file)
def test_normal_file(self): f = Path2("normal_file.txt") f.touch() self.assertTrue(f.is_file()) p = Path2(self.temp_root_path) dir_entries = tuple(p.scandir()) print(dir_entries) self.assertEqual(len(dir_entries), 1) dir_entry = dir_entries[0] dir_entry_path = DirEntryPath(dir_entry) print(dir_entry_path.pformat()) self.assertEqual(dir_entry_path.is_symlink, False) self.assertEqual(dir_entry_path.different_path, False) self.assertEqual( dir_entry_path.resolved_path, Path2(Path2(p, f).extended_path) ) self.assertEqual(dir_entry_path.resolve_error, None)
def test_directory_junction(self): os.mkdir("dir1") dir1 = Path2("dir1").resolve() dir2 = Path2(self.temp_root_path, "dir2") print(dir1.path) print(dir2.path) # mklink /d /j <destination> <source> # Strange that first is destination and second is the source path ! txt = self.mklink("/d", "/j", "dir2", "dir1") print(txt) self.assertIn("dir2 <<===>> dir1", txt) p = Path2(self.temp_root_path) dir_entries = list(p.scandir()) dir_entries.sort(key=lambda x: x.name) print(dir_entries) self.assertEqual(repr(dir_entries), "[<DirEntry 'dir1'>, <DirEntry 'dir2'>]") dir_entry1, dir_entry2 = dir_entries self.assertEqual(dir_entry1.name, "dir1") self.assertEqual(dir_entry2.name, "dir2") dir_entry_path1 = DirEntryPath(dir_entry1) print(dir_entry_path1.pformat()) self.assertEqual(dir_entry_path1.is_symlink, False) self.assertEqual(dir_entry_path1.different_path, False) self.assertEqual( dir_entry_path1.resolved_path, Path2(Path2(self.temp_root_path, "dir1").extended_path) ) self.assertEqual(dir_entry_path1.resolve_error, None) dir_entry_path2 = DirEntryPath(dir_entry2) print(dir_entry_path2.pformat()) self.assertEqual(dir_entry_path2.is_symlink, False) self.assertEqual(dir_entry_path2.different_path, True) # <<--- because of junction self.assertEqual( # pointed to dir1 ! dir_entry_path2.resolved_path, Path2(Path2(self.temp_root_path, "dir1").extended_path) ) self.assertEqual(dir_entry_path2.resolve_error, None) # remove junction source and try again # dir1.unlink() # Will not work: PermissionError: [WinError 5] Zugriff verweigert dir1.rename("new_name") # Will also break the junction ;) # check again: dir_entry_path2 = DirEntryPath( dir_entry2, onerror=print # will be called, because resolve can't be done. ) print(dir_entry_path2.pformat()) self.assertEqual(dir_entry_path2.is_symlink, False) self.assertEqual(dir_entry_path2.different_path, True) # <<--- because of junction # can't be resole, because source was renamed: self.assertEqual(dir_entry_path2.resolved_path, None) self.assertIsInstance(dir_entry_path2.resolve_error, FileNotFoundError)
def test_normal_file(tmp_path): """ Test DirEntryPath() on all platforms """ f = Path2(tmp_path, "normal_file.txt") f.touch() assert f.is_file() is True p = Path2(tmp_path) dir_entries = tuple(p.scandir()) print(dir_entries) assert len(dir_entries) == 1 dir_entry = dir_entries[0] dir_entry_path = DirEntryPath(dir_entry) print(dir_entry_path.pformat()) assert dir_entry_path.is_symlink is False assert dir_entry_path.different_path is False assert dir_entry_path.resolved_path == Path2(Path2(p, f).extended_path) assert dir_entry_path.resolve_error is None
def test_symlink(self, tmp_path): os.chdir(tmp_path) src_file = Path2("source_file.txt") src_file.touch() dst_file = Path2("destination.txt") dst_file.symlink_to(src_file) scan_result = list(Path2(".").scandir()) scan_result.sort(key=lambda x: x.path) self.assertEqual([f.path for f in scan_result], ['./destination.txt', './source_file.txt']) for dir_entry in scan_result: dir_entry_path = DirEntryPath(dir_entry) info = dir_entry_path.pformat() # print(info) if dir_entry_path.path == "./source_file.txt": self.assertFalse(dir_entry_path.is_symlink) self.assertTrue(dir_entry_path.is_file) elif dir_entry_path.path == "./destination.txt": self.assertTrue(dir_entry_path.is_symlink) self.assertFalse(dir_entry_path.is_file) else: self.fail() self.assertEqual(dir_entry_path.resolved_path, Path2(self.temp_root_path, "source_file.txt")) # Create a broken symlink, by deleting the source file: src_file.unlink() scan_result = list(Path2(".").scandir()) self.assertEqual([f.path for f in scan_result], ['./destination.txt']) dir_entry_path = DirEntryPath(scan_result[0]) info = dir_entry_path.pformat() print(info) self.assertEqual(dir_entry_path.path, "./destination.txt") self.assertEqual(dir_entry_path.resolved_path, None) # <- broken, so can't be resolve self.assertIsInstance(dir_entry_path.resolve_error, FileNotFoundError) # <- the error instance self.assertTrue(dir_entry_path.is_symlink) self.assertFalse(dir_entry_path.is_file)
def test_directory_junction(self, tmp_path): os.chdir(tmp_path) os.mkdir("dir1") dir1 = Path2("dir1").resolve() dir2 = Path2(self.temp_root_path, "dir2") print(dir1.path) print(dir2.path) # mklink /d /j <destination> <source> # Strange that first is destination and second is the source path ! txt = self.mklink("/d", "/j", "dir2", "dir1") print(txt) self.assertIn("dir2 <<===>> dir1", txt) p = Path2(self.temp_root_path) dir_entries = list(p.scandir()) dir_entries.sort(key=lambda x: x.name) print(dir_entries) self.assertEqual(repr(dir_entries), "[<DirEntry 'dir1'>, <DirEntry 'dir2'>]") dir_entry1, dir_entry2 = dir_entries self.assertEqual(dir_entry1.name, "dir1") self.assertEqual(dir_entry2.name, "dir2") dir_entry_path1 = DirEntryPath(dir_entry1) print(dir_entry_path1.pformat()) self.assertEqual(dir_entry_path1.is_symlink, False) self.assertEqual(dir_entry_path1.different_path, False) self.assertEqual( dir_entry_path1.resolved_path, Path2(Path2(self.temp_root_path, "dir1").extended_path)) self.assertEqual(dir_entry_path1.resolve_error, None) dir_entry_path2 = DirEntryPath(dir_entry2) print(dir_entry_path2.pformat()) self.assertEqual(dir_entry_path2.is_symlink, False) self.assertEqual(dir_entry_path2.different_path, True) # <<--- because of junction self.assertEqual( # pointed to dir1 ! dir_entry_path2.resolved_path, Path2(Path2(self.temp_root_path, "dir1").extended_path)) self.assertEqual(dir_entry_path2.resolve_error, None) # remove junction source and try again # dir1.unlink() # Will not work: PermissionError: [WinError 5] Zugriff verweigert dir1.rename("new_name") # Will also break the junction ;) # check again: dir_entry_path2 = DirEntryPath( dir_entry2, onerror=print # will be called, because resolve can't be done. ) print(dir_entry_path2.pformat()) self.assertEqual(dir_entry_path2.is_symlink, False) self.assertEqual(dir_entry_path2.different_path, True) # <<--- because of junction # can't be resole, because source was renamed: self.assertEqual(dir_entry_path2.resolved_path, None) self.assertIsInstance(dir_entry_path2.resolve_error, FileNotFoundError)