def test_lyric_filename_search_special_characters(self): """test '<' and/or '>' in name (not parsed (transparent to test))""" with self.lyric_filename_test_setup(no_config=True) as ts: path_variants = ['<oldskool>'] \ if is_windows() else [r'\<artist\>', r'\<artist>', r'<artist\>'] for path_variant in path_variants: ts['artist'] = path_variant + " SpongeBob SquarePants" parts = [ ts.root, ts["artist"] + " - " + ts["title"] + ".lyric" ] rpf = RootPathFile(ts.root, os.path.sep.join(parts)) if not rpf.valid: rpf = RootPathFile(rpf.root, rpf.pathfile_escaped) self.assertTrue(rpf.valid, "even escaped target file is not valid") with io.open(rpf.pathfile, "w", encoding='utf-8') as f: f.write(u"") search = ts.lyric_filename os.remove(rpf.pathfile) fp = rpf.pathfile if is_windows(): # account for 'os.path.normcase' santisatation fp = fp.lower() search = search.lower() # compensate for the above self.assertEqual(search, fp)
def test_lyric_filename_search_special_characters_across_path(self): """test '<' and/or '>' in name across path separator (not parsed (transparent to test))""" with self.lyric_filename_test_setup(no_config=True) as ts: # test '<' and '>' in name across path # (not parsed (transparent to test)) ts['artist'] = "a < b" ts['title'] = "b > a" parts = [ts.root, ts["artist"], ts["title"] + ".lyric"] rpf = RootPathFile(ts.root, os.path.sep.join(parts)) rootp = ts.root rmdirs = [] # ensure valid dir existence for p in rpf.end.split(os.path.sep)[:-1]: rootp = os.path.sep.join([ts.root, p]) if not RootPathFile(ts.root, rootp).valid: rootp = os.path.sep.join([ts.root, escape_filename(p)]) self.assertTrue( RootPathFile(ts.root, rootp).valid, "even escaped target dir part is not valid!") if not os.path.exists(rootp): mkdir(rootp) rmdirs.append(rootp) if not rpf.valid: rpf = RootPathFile(rpf.root, rpf.pathfile_escaped) with io.open(rpf.pathfile, "w", encoding='utf-8') as f: f.write(u"") # search for lyric file search = ts.lyric_filename # clean up test lyric file / path os.remove(rpf.pathfile) for p in rmdirs: os.rmdir(p) # test whether the 'found' file is the test lyric file fp = rpf.pathfile if is_windows(): fp = fp.lower() # account for 'os.path.normcase' santisatation search = search.lower() # compensate for the above self.assertEqual(search, fp)
def expand_pathfile(rpf): """Return the expanded RootPathFile""" expanded = [] root = expanduser(rpf.root) pathfile = expanduser(rpf.pathfile) if rx_params.search(pathfile): root = expand_patterns(root).format(self) pathfile = expand_patterns(pathfile).format(self) rpf = RootPathFile(root, pathfile) expanded.append(rpf) if not os.path.exists(pathfile) and is_windows(): # prioritise a special character encoded version # # most 'alien' chars are supported for 'nix fs paths, and we # only pass the proposed path through 'escape_filename' (which # apparently doesn't respect case) if we don't care about case! # # FIX: assumes 'nix build used on a case-sensitive fs, nt case # insensitive. clearly this is not biting anyone though (yet!) pathfile = os.path.sep.join([rpf.root, rpf.end_escaped]) rpf = RootPathFile(rpf.root, pathfile) expanded.insert(len(expanded) - 1, rpf) return expanded
def lyric_filename(self): """Returns the validated, or default, lyrics filename for this file. User defined '[memory] lyric_rootpaths' and '[memory] lyric_filenames' matches take precedence""" from quodlibet.pattern \ import ArbitraryExtensionFileFromPattern as expand_patterns rx_params = re.compile(r'[^\\]<[^' + re.escape(os.sep) + r']*[^\\]>') def expand_pathfile(rpf): """Return the expanded RootPathFile""" expanded = [] root = expanduser(rpf.root) pathfile = expanduser(rpf.pathfile) if rx_params.search(pathfile): root = expand_patterns(root).format(self) pathfile = expand_patterns(pathfile).format(self) rpf = RootPathFile(root, pathfile) expanded.append(rpf) if not os.path.exists(pathfile) and is_windows(): # prioritise a special character encoded version # # most 'alien' chars are supported for 'nix fs paths, and we # only pass the proposed path through 'escape_filename' (which # apparently doesn't respect case) if we don't care about case! # # FIX: assumes 'nix build used on a case-sensitive fs, nt case # insensitive. clearly this is not biting anyone though (yet!) pathfile = os.path.sep.join([rpf.root, rpf.end_escaped]) rpf = RootPathFile(rpf.root, pathfile) expanded.insert(len(expanded) - 1, rpf) return expanded def sanitise(sep, parts): """Return a santisied version of a path's parts""" return sep.join( part.replace(os.path.sep, u'')[:128] for part in parts) # setup defaults (user-defined take precedence) # root search paths lyric_paths = \ config.getstringlist("memory", "lyric_rootpaths", []) # ensure default paths lyric_paths.append(os.path.join(get_home_dir(), ".lyrics")) lyric_paths.append( os.path.join(os.path.dirname(self.comma('~filename')))) # search pathfile names lyric_filenames = \ config.getstringlist("memory", "lyric_filenames", []) # ensure some default pathfile names lyric_filenames.append( sanitise(os.sep, [(self.comma("lyricist") or self.comma("artist")), self.comma("title")]) + u'.lyric') lyric_filenames.append( sanitise(' - ', [(self.comma("lyricist") or self.comma("artist")), self.comma("title")]) + u'.lyric') # generate all potential paths (unresolved/unexpanded) pathfiles = OrderedDict() for r in lyric_paths: for f in lyric_filenames: pathfile = os.path.join(r, os.path.dirname(f), fsnative(os.path.basename(f))) rpf = RootPathFile(r, pathfile) if not pathfile in pathfiles: pathfiles[pathfile] = rpf #print_d("searching for lyrics in:\n%s" % '\n'.join(pathfiles.keys())) # expand each raw pathfile in turn and test for existence match_ = "" pathfiles_expanded = OrderedDict() for pf, rpf in pathfiles.items(): for rpf in expand_pathfile(rpf): # resolved as late as possible pathfile = rpf.pathfile pathfiles_expanded[pathfile] = rpf if os.path.exists(pathfile): match_ = pathfile break if match_ != "": break if not match_: # search even harder! lyric_extensions = ['lyric', 'lyrics', '', 'txt'] #print_d("extending search to extensions: %s" % lyric_extensions) def generate_mod_ext_paths(pathfile): # separate pathfile's extension (if any) ext = os.path.splitext(pathfile)[1][1:] path = pathfile[:-1 * len(ext)].strip('.') if ext else pathfile # skip the proposed lyric extension if it is the same as # the original for a given search pathfile stub - it has # already been tested without success! extra_extensions = [x for x in lyric_extensions if x != ext] # join valid new extensions to pathfile stub and return return [ '.'.join([path, ext]) if ext else path for ext in extra_extensions ] # look for a match by modifying the extension for each of the # (now fully resolved) 'pathfiles_expanded' search items for pathfile in pathfiles_expanded.keys(): # get alternatives for existence testing paths_mod_ext = generate_mod_ext_paths(pathfile) for path_ext in paths_mod_ext: if os.path.exists(path_ext): # persistence has paid off! #print_d("extended search match!") match_ = path_ext break if match_: break if not match_: # default match_ = list(pathfiles_expanded.keys())[0] return match_