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)
Beispiel #3
0
 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
Beispiel #4
0
    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_