def test_expanduser_simple(): home = _get_userdir() assert expanduser("~") == home assert isinstance(expanduser("~"), fsnative) assert expanduser(os.path.join("~", "a", "b")) == \ os.path.join(home, "a", "b") assert expanduser(senf.sep + "~") == senf.sep + "~" if senf.altsep is not None: assert expanduser("~" + senf.altsep) == home + senf.altsep
def scan(self, paths, exclude=[], cofuncid=None): added = [] exclude = [expanduser(path) for path in exclude if path] def need_yield(last_yield=[0]): current = time.time() if abs(current - last_yield[0]) > 0.015: last_yield[0] = current return True return False def need_added(last_added=[0]): current = time.time() if abs(current - last_added[0]) > 1.0: last_added[0] = current return True return False for fullpath in paths: print_d("Scanning %r." % fullpath, self) desc = _("Scanning %s") % (unexpand(fsn2text(fullpath))) with Task(_("Library"), desc) as task: if cofuncid: task.copool(cofuncid) fullpath = expanduser(fullpath) if filter(fullpath.startswith, exclude): continue for path, dnames, fnames in os.walk(fullpath): for filename in fnames: fullfilename = os.path.join(path, filename) if filter(fullfilename.startswith, exclude): continue if fullfilename not in self._contents: fullfilename = os.path.realpath(fullfilename) # skip unknown file extensions if not formats.filter(fullfilename): continue if filter(fullfilename.startswith, exclude): continue if fullfilename not in self._contents: item = self.add_filename(fullfilename, False) if item is not None: added.append(item) if len(added) > 100 or need_added(): self.add(added) added = [] task.pulse() yield if added and need_yield(): yield if added: self.add(added) added = [] task.pulse() yield True
def get_home_dir(): """Returns the root directory of the user, /home/user or C:\\Users\\user""" if os.name == "nt": return windows.get_profile_dir() else: return expanduser("~")
def test_get_exclude_dirs(self): some_path = os.path.join(get_home_dir(), "foo") if os.name != "nt": some_path = unexpand(some_path) config.set('library', 'exclude', some_path) assert expanduser(some_path) in get_exclude_dirs() assert all([isinstance(p, fsnative) for p in get_exclude_dirs()])
def test_get_scan_dirs(self): some_path = os.path.join(get_home_dir(), "foo") if os.name != "nt": some_path = unexpand(some_path) config.set('settings', 'scan', some_path) assert expanduser(some_path) in get_scan_dirs() assert all([isinstance(p, fsnative) for p in get_scan_dirs()])
def get_scan_dirs(): """Returns a list of paths which should be scanned Returns: list """ joined_paths = bytes2fsn(config.getbytes("settings", "scan"), "utf-8") return [expanduser(p) for p in split_scan_dirs(joined_paths)]
def test_expanduser_user(): home = _get_userdir() user = os.path.basename(home) assert expanduser("~" + user) == home assert expanduser(os.path.join("~" + user, "foo")) == \ os.path.join(home, "foo") if senf.altsep is not None: assert expanduser("~" + senf.altsep + "foo") == \ home + senf.altsep + "foo" assert expanduser("~" + user + senf.altsep + "a" + senf.sep) == \ home + senf.altsep + "a" + senf.sep if os.name == "nt": assert expanduser(os.path.join("~nope", "foo")) == \ os.path.join(os.path.dirname(home), "nope", "foo")
def get_exclude_dirs(): """Returns a list of paths which should be ignored during scanning Returns: list """ paths = split_scan_dirs(bytes2fsn(config.getbytes("library", "exclude"), "utf-8")) return [expanduser(p) for p in paths]
def unexpand(filename, HOME=expanduser("~")): """Replace the user's home directory with ~/, if it appears at the start of the path name.""" sub = (os.name == "nt" and "%USERPROFILE%") or "~" if filename == HOME: return sub elif filename.startswith(HOME + os.path.sep): filename = filename.replace(HOME, sub, 1) return filename
def get_exclude_dirs(): """Returns a list of paths which should be ignored during scanning Returns: list """ paths = split_scan_dirs( bytes2fsn(config.getbytes("library", "exclude"), "utf-8")) return [expanduser(p) for p in paths]
def unexpand(filename): """Replace the user's home directory with ~ or %USERPROFILE%, if it appears at the start of the path name. Args: filename (fsnative): The file path Returns: fsnative: The path with the home directory replaced """ sub = (os.name == "nt" and fsnative(u"%USERPROFILE%")) or fsnative(u"~") home = expanduser("~") if filename == home: return sub elif filename.startswith(home + os.path.sep): filename = filename.replace(home, sub, 1) return filename
def _post(self, value, song, keep_extension=True): if value: assert isinstance(value, str) value = fsnative(value) if keep_extension: fn = song.get("~filename", ".") ext = fn[fn.rfind("."):].lower() val_ext = value[-len(ext):].lower() if not ext == val_ext: value += ext.lower() if os.name == "nt": assert isinstance(value, str) value = strip_win32_incompat_from_path(value) value = expanduser(value) value = limit_path(value) if sep in value and not os.path.isabs(value): raise ValueError("Pattern is not rooted") return value else: return fsnative(value)
def _post(self, value, song, keep_extension=True): if value: assert isinstance(value, text_type) value = fsnative(value) if keep_extension: fn = song.get("~filename", ".") ext = fn[fn.rfind("."):].lower() val_ext = value[-len(ext):].lower() if not ext == val_ext: value += ext.lower() if os.name == "nt": assert isinstance(value, text_type) value = strip_win32_incompat_from_path(value) value = expanduser(value) value = limit_path(value) if sep in value and not os.path.isabs(value): raise ValueError("Pattern is not rooted") return value else: return fsnative(value)
def test_get_scan_dirs(self): some_path = os.path.join(unexpand(get_home_dir()), "foo") config.set('settings', 'scan', some_path) assert expanduser(some_path) in get_scan_dirs() assert all([isinstance(p, fsnative) for p in get_scan_dirs()])
def test_get_exclude_dirs(self): some_path = os.path.join(unexpand(get_home_dir()), "foo") config.set('library', 'exclude', some_path) assert expanduser(some_path) in get_exclude_dirs() assert all([isinstance(p, fsnative) for p in get_exclude_dirs()])
class TWatchedFileLibrary(TLibrary): Fake = FakeSongFile temp_path = Path(normalize_path(expanduser(_TEMP_DIR), True)).resolve() def setUp(self): init_fake_app() config.set("library", "watch", True) super().setUp() # Replace global one with this one librarian = app.library.librarian app.library.destroy() self.library.librarian = librarian app.library = self.library self.library.filename = "watching" librarian.register(self.library, "main") assert self.library.librarian.libraries def test_test_setup(self): assert self.temp_path.is_dir() assert self.temp_path.is_absolute() assert not self.temp_path.is_symlink(), "Symlinks cause trouble in these tests" assert not get_exclude_dirs() def tearDown(self): destroy_fake_app() def Library(self): lib = SongFileLibrary(watch_dirs=[text2fsn(str(self.temp_path))]) # Setup needs copools run_gtk_loop() return lib def test_monitors(self): monitors = self.library._monitors assert monitors, "Not monitoring any dirs" temp_path = Path(self.temp_path) assert temp_path in monitors, f"Not monitoring {temp_path} (but {monitors})" @pytest.mark.flaky(max_runs=3, min_passes=2) def test_watched_adding_removing(self): with temp_filename(dir=self.temp_path, suffix=".mp3", as_path=True) as path: shutil.copy(Path(get_data_path("silence-44-s.mp3")), path) sleep(0.5) run_gtk_loop() assert path.exists() assert str(path) in self.library, f"{path} should be in [{self.fns}] now" assert not path.exists(), "Failed to delete test file" sleep(0.5) # Deletion now run_gtk_loop() assert self.removed, "Nothing was automatically removed" assert self.added, "Nothing was automatically added" assert {Path(af("~filename")) for af in self.added} == {path} assert {Path(af("~filename")) for af in self.removed} == {path} assert str(path) not in self.library, f"{path} shouldn't be in the library now" def test_watched_adding(self): with temp_filename(dir=self.temp_path, suffix=".mp3", as_path=True) as path: shutil.copy(Path(get_data_path("silence-44-s.mp3")), path) assert self.temp_path in path.parents, "Copied test file incorrectly" watch_dirs = self.library._monitors.keys() assert path.parent in watch_dirs, "Not monitoring directory of new file" run_gtk_loop() assert self.library, f"Nothing in library despite watches on {watch_dirs}" assert str(path) in self.library, (f"{path!s} should have been added to " f"library [{self.fns}]") assert str(path) in {af("~filename") for af in self.added} def test_watched_moving_song(self): with temp_filename(dir=self.temp_path, suffix=".flac", as_path=True) as path: shutil.copy(Path(get_data_path("silence-44-s.flac")), path) sleep(0.2) assert path.exists() run_gtk_loop() assert str(path) in self.library, f"New path {path!s} didn't get added" assert len(self.added) == 1 assert self.added[0]("~basename") == path.name self.added.clear() # Now move it... new_path = path.parent / f"moved-{path.name}" path.rename(new_path) sleep(0.2) assert not path.exists(), "test should have removed old file" assert new_path.exists(), "test should have renamed file" print_d(f"New test file at {new_path}") run_gtk_loop() p = normalize_path(str(new_path), True) assert p in self.library, f"New path {new_path} not in library [{self.fns}]" msg = "Inconsistent events: should be (added and removed) or nothing at all" assert not (bool(self.added) ^ bool(self.removed)), msg def test_watched_moving_dir(self): temp_dir = self.temp_path / "old" temp_dir.mkdir(exist_ok=False) sleep(0.2) run_gtk_loop() assert temp_dir in self.library._monitors with temp_filename(dir=temp_dir, suffix=".flac", as_path=True) as path: shutil.copy(Path(get_data_path("silence-44-s.flac")), path) sleep(0.2) assert path.exists() run_gtk_loop() assert str(path) in self.library, f"New path {path!s} didn't get added" assert len(self.added) == 1 self.added.clear() assert self.library # Now move the directory... new_dir = path.parent.parent / "new" temp_dir.rename(new_dir) assert new_dir.is_dir(), "test should have moved to new dir" sleep(0.2) run_gtk_loop() new_path = new_dir / path.name assert new_path.is_file() msg = f"New path {new_path} not in library [{self.fns}]. Did move_root run?" assert str(new_path) in self.library, msg assert not self.removed, "A file was removed" @property def fns(self) -> str: return ", ".join(s("~filename") for s in self.library)
def get_exclude_dirs() -> Iterable[fsnative]: """:return: a list of paths which should be ignored during scanning""" paths = split_scan_dirs( bytes2fsn(config.getbytes("library", "exclude"), "utf-8")) return [expanduser(p) for p in paths] # type: ignore