def replace_path(self, config_path, path): if config_path in self.replacements: replacements = self.replacements[config_path] else: replacements = self.replacements[config_path] = get_replacements(config_path) return util.sanitize_path(path, replacements)
def test_sanitize_windows_replaces_illegal_chars(self): p = util.sanitize_path(':*?"<>|', ntpath) self.assertFalse(":" in p) self.assertFalse("*" in p) self.assertFalse("?" in p) self.assertFalse('"' in p) self.assertFalse("<" in p) self.assertFalse(">" in p) self.assertFalse("|" in p)
def test_sanitize_windows_replaces_illegal_chars(self): p = util.sanitize_path(u':*?"<>|', ntpath) self.assertFalse(':' in p) self.assertFalse('*' in p) self.assertFalse('?' in p) self.assertFalse('"' in p) self.assertFalse('<' in p) self.assertFalse('>' in p) self.assertFalse('|' in p)
def test_sanitize_windows_replaces_illegal_chars(self): with _common.platform_windows(): p = util.sanitize_path(u':*?"<>|') self.assertFalse(':' in p) self.assertFalse('*' in p) self.assertFalse('?' in p) self.assertFalse('"' in p) self.assertFalse('<' in p) self.assertFalse('>' in p) self.assertFalse('|' in p)
def test_sanitize_windows_replaces_illegal_chars(self): with _common.platform_windows(): p = util.sanitize_path(u':*?"<>|') self.assertFalse(u':' in p) self.assertFalse(u'*' in p) self.assertFalse(u'?' in p) self.assertFalse(u'"' in p) self.assertFalse(u'<' in p) self.assertFalse(u'>' in p) self.assertFalse(u'|' in p)
def _create_cover_path(self, item): if item.singleton: template = self._singleton_template else: template = self._default_template evaluated_template = item.evaluate_template(template) cover_name = self._get_cover_name(item) path = os.path.join(evaluated_template, cover_name) return os.path.join(self._library_path, beetsutil.sanitize_path(path))
def sub_path(self, path, pattern, repl=''): if pattern in self.patterns: pattern = self.patterns[pattern] else: pattern = self.patterns[pattern] = re.compile(pattern) try: return util.sanitize_path(path, [(pattern, repl)]) except re.error as exc: raise UserError(u'%sub_path: error compiling regex `{0}`: {1}'.format(pattern, str(exc)))
def replace(self, field, path): if field in self.replacements: replacements = self.replacements[field] else: try: replacements = self.replacements[field] = get_replacements( self.config[field]) except confuse.ConfigError as exc: raise UserError( f"Configuration error in `{self.name}.{field}`: {exc}") return util.sanitize_path(path, replacements)
def update_playlists(self, lib): self._log.info("Updating {0} smart playlists...", len(self._matched_playlists)) playlist_dir = self.config['playlist_dir'].as_filename() playlist_dir = bytestring_path(playlist_dir) relative_to = self.config['relative_to'].get() if relative_to: relative_to = normpath(relative_to) # Maps playlist filenames to lists of track filenames. m3us = {} for playlist in self._matched_playlists: name, (query, q_sort), (album_query, a_q_sort) = playlist self._log.debug("Creating playlist {0}", name) items = [] if query: items.extend(lib.items(query, q_sort)) if album_query: for album in lib.albums(album_query, a_q_sort): items.extend(album.items()) # As we allow tags in the m3u names, we'll need to iterate through # the items and generate the correct m3u file names. for item in items: m3u_name = item.evaluate_template(name, True) m3u_name = sanitize_path(m3u_name, lib.replacements) if m3u_name not in m3us: m3us[m3u_name] = [] item_path = item.path if relative_to: item_path = os.path.relpath(item.path, relative_to) if item_path not in m3us[m3u_name]: m3us[m3u_name].append(item_path) prefix = bytestring_path(self.config['prefix'].as_str()) # Write all of the accumulated track lists to files. for m3u in m3us: m3u_path = normpath( os.path.join(playlist_dir, bytestring_path(m3u))) mkdirall(m3u_path) with open(syspath(m3u_path), 'wb') as f: for path in m3us[m3u]: if self.config['forward_slash'].get(): path = path_as_posix(path) if self.config['urlencode']: path = bytestring_path(pathname2url(path)) f.write(prefix + path + b'\n') self._log.info("{0} playlists updated", len(self._matched_playlists))
def directory_name(release): ''' Returns the proper directory name for a Release. ''' artist = textwrap.shorten(release.album_artist, width=50, placeholder='_') album = textwrap.shorten(release.title, width=40, placeholder='_') year = release.year format_info = release.format if release.medium != 'CD': format_info = release.medium + ' ' + format_info path = ALBUM_TEMPLATE.substitute(**locals()) if release.catalog_number: path += ' {' + release.catalog_number + '}' path = path.replace('/', '_').replace('\\', '_') path = sanitize_path(path) return path
def update_playlists(self, lib): self._log.info(u"Updating {0} smart playlists...", len(self._matched_playlists)) playlist_dir = self.config['playlist_dir'].as_filename() playlist_dir = bytestring_path(playlist_dir) relative_to = self.config['relative_to'].get() if relative_to: relative_to = normpath(relative_to) # Maps playlist filenames to lists of track filenames. m3us = {} for playlist in self._matched_playlists: name, (query, q_sort), (album_query, a_q_sort) = playlist self._log.debug(u"Creating playlist {0}", name) items = [] if query: items.extend(lib.items(query, q_sort)) if album_query: for album in lib.albums(album_query, a_q_sort): items.extend(album.items()) # As we allow tags in the m3u names, we'll need to iterate through # the items and generate the correct m3u file names. for item in items: m3u_name = item.evaluate_template(name, True) m3u_name = sanitize_path(m3u_name, lib.replacements) if m3u_name not in m3us: m3us[m3u_name] = [] item_path = item.path if relative_to: item_path = os.path.relpath(item.path, relative_to) if item_path not in m3us[m3u_name]: m3us[m3u_name].append(item_path) # Write all of the accumulated track lists to files. for m3u in m3us: m3u_path = normpath(os.path.join(playlist_dir, bytestring_path(m3u))) mkdirall(m3u_path) with open(syspath(m3u_path), 'wb') as f: for path in m3us[m3u]: f.write(path + b'\n') self._log.info(u"{0} playlists updated", len(self._matched_playlists))
def update_playlists(self, lib): self._log.info(u"Updating {0} smart playlists...", len(self._matched_playlists)) playlist_dir = self.config['playlist_dir'].as_filename() playlist_dir = bytestring_path(playlist_dir) relative_to = self.config['relative_to'].get() if relative_to: relative_to = normpath(relative_to) for playlist in self._matched_playlists: name, (query, q_sort), (album_query, a_q_sort) = playlist self._log.debug(u"Creating playlist {0}", name) items = [] if query: items.extend(lib.items(query, q_sort)) if album_query: for album in lib.albums(album_query, a_q_sort): items.extend(album.items()) m3us = {} # As we allow tags in the m3u names, we'll need to iterate through # the items and generate the correct m3u file names. for item in items: m3u_name = item.evaluate_template(name, True) m3u_name = sanitize_path(m3u_name, lib.replacements) if m3u_name not in m3us: m3us[m3u_name] = [] item_path = item.path if relative_to: item_path = os.path.relpath(item.path, relative_to) if item_path not in m3us[m3u_name]: m3us[m3u_name].append(item_path) # Now iterate through the m3us that we need to generate for m3u in m3us: m3u_path = normpath( os.path.join(playlist_dir, bytestring_path(m3u))) mkdirall(m3u_path) with open(syspath(m3u_path), 'wb') as f: for path in m3us[m3u]: f.write(path + b'\n') self._log.info(u"{0} playlists updated", len(self._matched_playlists))
def art_destination(self, image, item_dir=None): """Returns a path to the destination for the album art image for the album. `image` is the path of the image that will be moved there (used for its extension). The path construction uses the existing path of the album's items, so the album must contain at least one item or item_dir must be provided. """ image = bytestring_path(image) item_dir = item_dir or self.item_dir() filename_tmpl = Template(beets.config['art_filename'].get(unicode)) subpath = self.evaluate_template(filename_tmpl, True) subpath = util.sanitize_path(subpath, replacements=self._db.replacements) subpath = bytestring_path(subpath) _, ext = os.path.splitext(image) dest = os.path.join(item_dir, subpath + ext) return bytestring_path(dest)
def test_sanitize_with_custom_replace_overrides_built_in_sub(self): with _common.platform_posix(): p = util.sanitize_path(u'a/.?/b', [ (re.compile(r'foo'), u'bar'), ]) self.assertEqual(p, u'a/.?/b')
def test_sanitize_windows_replaces_trailing_space(self): with _common.platform_windows(): p = util.sanitize_path(u'one/two /three') self.assertFalse(u' ' in p)
def test_sanitize_unix_replaces_leading_dot(self): with _common.platform_posix(): p = util.sanitize_path(u'one/.two/three') self.assertFalse(u'.' in p)
def test_sanitize_with_custom_replace_adds_replacements(self): p = util.sanitize_path("foo/bar", posixpath, [(re.compile(r"foo"), "bar")]) self.assertEqual(p, "bar/bar")
def test_sanitize_with_custom_replace_adds_replacements(self): p = util.sanitize_path('foo/bar', posixpath, [ (re.compile(r'foo'), 'bar'), ]) self.assertEqual(p, 'bar/bar')
def test_sanitize_path_returns_unicode(self): path = u'b\xe1r?' new_path = util.sanitize_path(path) self.assert_(isinstance(new_path, unicode))
def destination(self, item, pathmod=None, in_album=False, fragment=False, basedir=None): """Returns the path in the library directory designated for item item (i.e., where the file ought to be). in_album forces the item to be treated as part of an album. fragment makes this method return just the path fragment underneath the root library directory; the path is also returned as Unicode instead of encoded as a bytestring. basedir can override the library's base directory for the destination. """ pathmod = pathmod or os.path # Use a path format based on a query, falling back on the # default. for query, path_format in self.path_formats: if query == PF_KEY_DEFAULT: continue query = AndQuery.from_string(query) if in_album: # If we're treating this item as a member of the item, # hack the query so that singleton queries always # observe the item to be non-singleton. for i, subquery in enumerate(query): if isinstance(subquery, SingletonQuery): query[i] = FalseQuery() if subquery.sense \ else TrueQuery() if query.match(item): # The query matches the item! Use the corresponding path # format. break else: # No query matched; fall back to default. for query, path_format in self.path_formats: if query == PF_KEY_DEFAULT: break else: assert False, "no default path format" subpath_tmpl = Template(path_format) # Get the item's Album if it has one. album = self.get_album(item) # Build the mapping for substitution in the path template, # beginning with the values from the database. mapping = {} for key in ITEM_KEYS_META: # Get the values from either the item or its album. if key in ALBUM_KEYS_ITEM and album is not None: # From album. value = getattr(album, key) else: # From Item. value = getattr(item, key) mapping[key] = util.sanitize_for_path(value, pathmod, key) # Use the album artist if the track artist is not set and # vice-versa. if not mapping['artist']: mapping['artist'] = mapping['albumartist'] if not mapping['albumartist']: mapping['albumartist'] = mapping['artist'] # Perform substitution. mapping.update(plugins.template_values(item)) funcs = dict(TEMPLATE_FUNCTIONS) funcs.update(plugins.template_funcs()) subpath = subpath_tmpl.substitute(mapping, funcs) # Encode for the filesystem, dropping unencodable characters. if isinstance(subpath, unicode) and not fragment: encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() subpath = subpath.encode(encoding, 'replace') # Truncate components and remove forbidden characters. subpath = util.sanitize_path(subpath, pathmod, self.replacements) # Preserve extension. _, extension = pathmod.splitext(item.path) subpath += extension if fragment: return subpath else: basedir = basedir or self.directory return normpath(os.path.join(basedir, subpath))
def test_sanitize_path_returns_bytestring(self): path = 'b\xe1r?' new_path = util.sanitize_path(path) self.assert_(isinstance(new_path, str))
def test_sanitize_with_custom_replace_overrides_built_in_sub(self): p = util.sanitize_path('a/.?/b', posixpath, [ (re.compile(r'foo'), 'bar'), ]) self.assertEqual(p, 'a/.?/b')
def test_sanitize_replaces_colon_with_dash(self): p = util.sanitize_path(u':', posixpath) self.assertEqual(p, u'-')
def test_sanitize_windows_replaces_trailing_dot(self): p = util.sanitize_path('one/two./three', ntpath) self.assertFalse('.' in p)
def destination(self, item, pathmod=None, in_album=False, fragment=False, basedir=None): """Returns the path in the library directory designated for item item (i.e., where the file ought to be). in_album forces the item to be treated as part of an album. fragment makes this method return just the path fragment underneath the root library directory; the path is also returned as Unicode instead of encoded as a bytestring. basedir can override the library's base directory for the destination. """ pathmod = pathmod or os.path # Use a path format based on the album type, if available. if not item.album_id and not in_album: # Singleton track. Never use the "album" formats. if 'singleton' in self.path_formats: path_format = self.path_formats['singleton'] else: path_format = self.path_formats['default'] elif item.albumtype and item.albumtype in self.path_formats: path_format = self.path_formats[item.albumtype] elif item.comp and 'comp' in self.path_formats: path_format = self.path_formats['comp'] else: path_format = self.path_formats['default'] subpath_tmpl = Template(path_format) # Get the item's Album if it has one. album = self.get_album(item) # Build the mapping for substitution in the path template, # beginning with the values from the database. mapping = {} for key in ITEM_KEYS_META: # Get the values from either the item or its album. if key in ALBUM_KEYS_ITEM and album is not None: # From album. value = getattr(album, key) else: # From Item. value = getattr(item, key) mapping[key] = util.sanitize_for_path(value, pathmod, key) # Use the album artist if the track artist is not set and # vice-versa. if not mapping['artist']: mapping['artist'] = mapping['albumartist'] if not mapping['albumartist']: mapping['albumartist'] = mapping['artist'] # Perform substitution. subpath = subpath_tmpl.substitute(mapping) # Encode for the filesystem, dropping unencodable characters. if isinstance(subpath, unicode) and not fragment: encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() subpath = subpath.encode(encoding, 'replace') # Truncate components and remove forbidden characters. subpath = util.sanitize_path(subpath) # Preserve extension. _, extension = pathmod.splitext(item.path) subpath += extension if fragment: return subpath else: basedir = basedir or self.directory return normpath(os.path.join(basedir, subpath))
def test_sanitize_empty_component(self): with _common.platform_posix(): p = util.sanitize_path(u'foo//bar', [ (re.compile(r'^$'), u'_'), ]) self.assertEqual(p, u'foo/_/bar')
def test_sanitize_path_returns_unicode(self): path = u'b\xe1r?' new_path = util.sanitize_path(path) self.assertTrue(isinstance(new_path, six.text_type))
def test_sanitize_path_works_on_empty_string(self): p = util.sanitize_path(u'', posixpath) self.assertEqual(p, u'')
def destination(self, fragment=False, basedir=None, platform=None, path_formats=None): """Returns the path in the library directory designated for the item (i.e., where the file ought to be). fragment makes this method return just the path fragment underneath the root library directory; the path is also returned as Unicode instead of encoded as a bytestring. basedir can override the library's base directory for the destination. """ self._check_db() platform = platform or sys.platform basedir = basedir or self._db.directory path_formats = path_formats or self._db.path_formats # Use a path format based on a query, falling back on the # default. for query, path_format in path_formats: if query == PF_KEY_DEFAULT: continue query = get_query(query, type(self)) if query.match(self): # The query matches the item! Use the corresponding path # format. break else: # No query matched; fall back to default. for query, path_format in path_formats: if query == PF_KEY_DEFAULT: break else: assert False, "no default path format" if isinstance(path_format, Template): subpath_tmpl = path_format else: subpath_tmpl = Template(path_format) # Evaluate the selected template. subpath = self.evaluate_template(subpath_tmpl, True) # Prepare path for output: normalize Unicode characters. if platform == 'darwin': subpath = unicodedata.normalize('NFD', subpath) else: subpath = unicodedata.normalize('NFC', subpath) # Truncate components and remove forbidden characters. subpath = util.sanitize_path(subpath, self._db.replacements) # Encode for the filesystem. if not fragment: subpath = bytestring_path(subpath) # Preserve extension. _, extension = os.path.splitext(self.path) if fragment: # Outputting Unicode. extension = extension.decode('utf8', 'ignore') subpath += extension.lower() # Truncate too-long components. maxlen = beets.config['max_filename_length'].get(int) if not maxlen: # When zero, try to determine from filesystem. maxlen = util.max_filename_length(self._db.directory) subpath = util.truncate_path(subpath, maxlen) if fragment: return subpath else: return normpath(os.path.join(basedir, subpath))
def test_sanitize_unix_replaces_leading_dot(self): p = util.sanitize_path(u'one/.two/three', posixpath) self.assertFalse('.' in p)
def rename_files(host_filter, torrent_filter, btn_api_key, orpheus_api_key, redacted_api_key): hosts = DELUGE_API() apis = [] if (btn_api_key): logger.trace('Testing BTN API configuration') btn_api = BTN_API(btn_api_key) btn_index = btn_api.get_index() logger.success(f'Connected to BTN as {btn_index.username}') apis.append(btn_api) if (orpheus_api_key): logger.trace('Testing Orpheus API configuration') orp_api = ORP_API(orpheus_api_key) orp_index = orp_api.get_index() logger.success(f'Connected to Orpheus as {orp_index.username}') apis.append(orp_api) if (redacted_api_key): logger.trace('Testing Redacted API configuration') red_api = RED_API(redacted_api_key) red_index = red_api.get_index() logger.success(f'Connected to Redacted as {red_index.username}') apis.append(red_api) host_filter = compile(host_filter) if not isinstance( host_filter, Pattern) else host_filter torrent_filter = compile(torrent_filter) if not isinstance( torrent_filter, Pattern) else torrent_filter filtered_hosts = [host for host in hosts if host_filter.search(str(host))] logger.info(f'{hosts} ({len(filtered_hosts)} filtered):') total_torrents = 0 total_files = 0 renamed_torrents = 0 renamed_files = 0 failed_torrents = 0 failed_files = 0 for host in filtered_hosts: logger.info(f' {host}:') filtered_torrents = [ torrent for torrent in host.torrents if torrent_filter.search(str(torrent)) ] logger.info( f' {host.torrents} ({len(filtered_torrents)} filtered):') for torrent in filtered_torrents: total_torrents += 1 logger.info(f' {torrent}:') logger.info(f' {torrent.trackers}:') for tracker in torrent.trackers: logger.info(f' {tracker}') logger.info(f' {torrent.files}:') changed = False failed = False prompt = False for api in apis: if torrent.trackers.tracker in api.supported_trackers: logger.success(f'Renaming files with {type(api)}') for file in torrent.files: total_files += 1 logger.info(f' {file}') result = api.rename_torrent_file( torrent.hash, file.path) if result is None: failed_files += 1 failed = True continue path = sanitize_path( asciify_path(unescape(result), '_')).replace( '_', '').replace(' (VBR)]', ']') if file.path != path and (not prompt or confirm( f'Rename {file.path} to {path}?', default=True)): renamed_files += 1 file.path = path changed = True if changed: renamed_torrents += 1 torrent.force_recheck() if failed: failed_torrents += 1 echo(f'Checked {total_files} files in {total_torrents} torrents', nl=False) if renamed_files > 0: echo(', ' + style( f'renamed {renamed_files} files in {renamed_torrents} torrents', fg='green'), nl=False) if failed_files > 0: echo( ', ' + style(f'failed {failed_files} files in {failed_torrents} torrents', fg='red'), nl=False) echo('.')
def test_sanitize_windows_replaces_trailing_space(self): p = util.sanitize_path(u'one/two /three', ntpath) self.assertFalse(' ' in p)
def test_sanitize_with_custom_replace_overrides_built_in_sub(self): p = util.sanitize_path("a/.?/b", posixpath, [(re.compile(r"foo"), "bar")]) self.assertEqual(p, "a/.?/b")
def test_sanitize_path_works_on_empty_string(self): with _common.platform_posix(): p = util.sanitize_path(u'') self.assertEqual(p, u'')
def test_sanitize_with_custom_replace_adds_replacements(self): with _common.platform_posix(): p = util.sanitize_path(u'foo/bar', [ (re.compile(r'foo'), u'bar'), ]) self.assertEqual(p, u'bar/bar')
def test_sanitize_unix_replaces_leading_dot(self): with _common.platform_posix(): p = util.sanitize_path(u'one/.two/three') self.assertFalse('.' in p)
def test_sanitize_path_with_special_chars(self): path = u'b\xe1r?' new_path = util.sanitize_path(path) self.assertTrue(new_path.startswith(u'b\xe1r'))
def test_sanitize_path_with_special_chars(self): path = "b\xe1r?" new_path = util.sanitize_path(path) self.assert_(new_path.startswith("b\xe1r"))
def test_sanitize_windows_replaces_trailing_space(self): with _common.platform_windows(): p = util.sanitize_path(u'one/two /three') self.assertFalse(' ' in p)
def test_sanitize_path_with_special_chars(self): path = u'b\xe1r?' new_path = util.sanitize_path(path) self.assert_(new_path.startswith(u'b\xe1r'))
def test_sanitize_with_custom_replace_overrides_built_in_sub(self): with _common.platform_posix(): p = util.sanitize_path(u'a/.?/b', [ (re.compile(ur'foo'), u'bar'), ]) self.assertEqual(p, u'a/.?/b')
def test_sanitize_with_custom_replace_adds_replacements(self): with _common.platform_posix(): p = util.sanitize_path(u'foo/bar', [ (re.compile(ur'foo'), u'bar'), ]) self.assertEqual(p, u'bar/bar')