def count_subfolders_and_files(self): if self.dir: self.subdircount = 0 self.subfilescount = 0 fullpath = CherryModel.abspath(self.path) if not os.path.isdir(fullpath): # not a dir, or not even there: fail gracefully. # There are 0 subfolders and 0 files by default. log.error( "MusicEntry does not exist: %r", self.path) return try: directory_listing = os.listdir(fullpath) except OSError as e: log.e(_('Error listing directory %s: %s') % (fullpath, str(e))) directory_listing = [] for idx, filename in enumerate(directory_listing): if idx > MusicEntry.MAX_SUB_FILES_ITER_COUNT: # estimate remaining file count self.subfilescount *= len(directory_listing)/float(idx+1) self.subfilescount = int(self.subfilescount) self.subdircount *= len(directory_listing)/float(idx+1) self.subdircount = int(self.subdircount) self.subfilesestimate = True return subfilefullpath = os.path.join(fullpath, filename) if os.path.isfile(subfilefullpath): if CherryModel.isplayable(subfilefullpath): self.subfilescount += 1 else: self.subdircount += 1
def delete_user(username): userservice = service.get('users') userid = userservice.getIdByName(username) if userid is None: log.e(_('user with the name "%s" does not exist!'), username) return False return userservice.deleteUser(userid)
def addUser(self, username, password, admin): if not (username.strip() or password.strip()): log.d(_('empty username or password!')) return False user = User.create(username, password, admin) try: exists = self.conn.execute( 'SELECT username' ' FROM users WHERE lower(username) = lower(?)', (username, )).fetchone() if (not exists): self.conn.execute( ''' INSERT INTO users (username, admin, password, salt) VALUES (?,?,?,?)''', (user.name, 1 if user.isadmin else 0, user.password, user.salt)) else: raise sqlite3.IntegrityError except sqlite3.IntegrityError: log.e('cannot create user "%s", already exists!' % user.name) return False self.conn.commit() log.i('added user: ' + user.name) return True
def __init__(self, method="amazon", timeout=10): """define the urls of the services and a regex to fetch images """ self.MAX_IMAGE_SIZE_BYTES = 100 * 1024 self.IMAGE_SIZE = 80 # the GET parameter value of the searchterm must be appendable # to the urls defined in "methods". self.methods = { "amazon": { "url": "http://www.amazon.com/s/ref=sr_nr_i_0?rh=k:", "regexes": ['<img src="([^"]*)" class="productImage"', '<img.+?src="([^"]*)" class="productImage"'], }, "bestbuy.com": { "url": "http://www.bestbuy.com/site/searchpage.jsp?_dyncharset=ISO-8859-1&id=pcat17071&type=page&ks=960&sc=Global&cp=1&sp=&qp=crootcategoryid%23%23-1%23%23-1~~q6a616d657320626c616b65206a616d657320626c616b65~~nccat02001%23%230%23%23e&list=y&usc=All+Categories&nrp=15&iht=n&st=", "regexes": ['<img itemprop="image" class="thumb" src="([^"]*)"'], }, "buy.com": { "url": "http://www.buy.com/sr/srajax.aspx?from=2&qu=", "regexes": [' class="productImageLink"><img src="([^"]*)"'], }, } if not method in self.methods: log.e("unknown album art fetch method: %s, using default." % self.method) method = "amazon" self.method = method self.timeout = timeout self.imageMagickAvailable = self.programAvailable("convert")
def create_user(username, password): """ Creates a non-admin user with given username and password """ non_alnum = re.compile('[^a-z0-9]', re.IGNORECASE) if non_alnum.findall(username): log.e(_('usernames may only contain letters and digits')) return False return service.get('users').addUser(username, password, admin=False)
def __init__(self, method='amazon', timeout=10): self.MAX_IMAGE_SIZE_BYTES = 100 * 1024 self.IMAGE_SIZE = 80 self.methods = { 'amazon': { 'url': "http://www.amazon.com/s/ref=sr_nr_i_0?rh=k:", 'regex': '<img src="([^"]*)" class="productImage"' }, 'music.ovi.com': { 'url': 'http://music.ovi.com/gb/en/pc/Search/?display=detail&text=', 'regex': 'class="prod-sm"><img src="([^"]*)"', #improve image quality: 'urltransformer': lambda x: x[:x.rindex('/')] + '/?w=200&q=100', }, 'bestbuy.com': { 'url': 'http://www.bestbuy.com/site/searchpage.jsp?_dyncharset=ISO-8859-1&id=pcat17071&type=page&ks=960&sc=Global&cp=1&sp=&qp=crootcategoryid%23%23-1%23%23-1~~q6a616d657320626c616b65206a616d657320626c616b65~~nccat02001%23%230%23%23e&list=y&usc=All+Categories&nrp=15&iht=n&st=', 'regex': '<img itemprop="image" class="thumb" src="([^"]*)"' }, 'buy.com': { 'url': "http://www.buy.com/sr/srajax.aspx?from=2&qu=", 'regex': ' class="productImageLink"><img src="([^"]*)"' }, } if not method in self.methods: log.e('unknown album art fetch method: %s, using default.' % self.method) method = 'amazon' self.method = method self.timeout = timeout self.imageMagickAvailable = self.programAvailable('convert')
def remove_recursive(self, fileobj, progress=None): '''recursively remove fileobj and all its children from the media db.''' if progress is None: log.i( _('removing dead reference(s): %s "%s"'), 'directory' if fileobj.isdir else 'file', fileobj.relpath, ) factory = None remove = lambda item: self.remove_file(item) else: def factory(new, pnt): if pnt is None: return (new, None, progress) return (new, pnt, pnt[2].spawnchild('[-] ' + new.relpath)) remove = lambda item: (self.remove_file(item[0]), item[2].tick()) deld = 0 try: with self.conn: for item in self.db_recursive_filelister(fileobj, factory): remove(item) deld += 1 except Exception as e: log.e(_('error while removing dead reference(s): %s'), e) log.e(_('rolled back to safe state.')) return 0 else: return deld
def listdir(self, dirpath, filterstr=''): absdirpath = CherryModel.abspath(dirpath) if cherry.config['browser.pure_database_lookup']: allfilesindir = self.cache.listdir(dirpath) # NOT absdirpath! else: in_basedir = (os.path.normpath(absdirpath)+'/').startswith( cherry.config['media.basedir']) if not in_basedir: raise ValueError('dirpath not in basedir: %r' % dirpath) try: allfilesindir = os.listdir(absdirpath) except OSError as e: log.e(_('Error listing directory %s: %s') % (absdirpath, str(e))) allfilesindir = [] #remove all files not inside the filter if filterstr: filterstr = filterstr.lower() allfilesindir = [f for f in allfilesindir if f.lower().startswith(filterstr)] else: allfilesindir = [f for f in allfilesindir if not f.startswith('.')] musicentries = [] maximum_shown_files = cherry.config['browser.maxshowfiles'] compactlisting = len(allfilesindir) > maximum_shown_files if compactlisting: upper_case_files = [x.upper() for x in allfilesindir] filterstr = os.path.commonprefix(upper_case_files) filterlength = len(filterstr)+1 currentletter = '/' # impossible first character # don't care about natural number order in compact listing sortedfiles = self.sortFiles(allfilesindir, number_ordering=False) for dir in sortedfiles: filter_match = dir.upper().startswith(currentletter.upper()) if filter_match and not len(currentletter) < filterlength: continue else: currentletter = dir[:filterlength] #if the filter equals the foldername if len(currentletter) == len(filterstr): subpath = os.path.join(absdirpath, dir) CherryModel.addMusicEntry(subpath, musicentries) else: musicentries.append( MusicEntry(strippath(absdirpath), repr=currentletter, compact=True)) else: # enable natural number ordering for real directories and files sortedfiles = self.sortFiles(allfilesindir, absdirpath, number_ordering=True) for dir in sortedfiles: subpath = os.path.join(absdirpath, dir) CherryModel.addMusicEntry(subpath, musicentries) if cherry.config['media.show_subfolder_count']: for musicentry in musicentries: musicentry.count_subfolders_and_files() return musicentries
def update_db_recursive(self, fullpath, skipfirst=False): '''recursively update the media database for a path in basedir''' from collections import namedtuple Item = namedtuple('Item', 'infs indb parent progress') def factory(fs, db, parent): fileobj = fs if fs is not None else db name = fileobj.relpath or fileobj.fullpath if fileobj else '<path not found in filesystem or database>' if parent is None: progress = ProgressTree(name=name) maxlen = lambda s: util.trim_to_maxlen(50, s) progress.reporter = ProgressReporter(lvl=1, namefmt=maxlen) else: progress = parent.progress.spawnchild(name) return Item(fs, db, parent, progress) log.d(_('recursive update for %s'), fullpath) generator = self.enumerate_fs_with_db(fullpath, itemfactory=factory) skipfirst and generator.send(None) adds_without_commit = 0 add = 0 deld = 0 try: with self.conn: for item in generator: infs, indb, progress = (item.infs, item.indb, item.progress) if infs and indb: if infs.isdir != indb.isdir: progress.name = '[±] ' + progress.name deld += self.remove_recursive(indb, progress) self.register_file_with_db(infs) adds_without_commit = 1 else: infs.uid = indb.uid progress.name = '[=] ' + progress.name elif indb: progress.name = '[-] ' + progress.name deld += self.remove_recursive(indb, progress) adds_without_commit = 0 continue # progress ticked by remove; don't tick again elif infs: self.register_file_with_db(item.infs) adds_without_commit += 1 progress.name = '[+] ' + progress.name else: progress.name = '[?] ' + progress.name if adds_without_commit == AUTOSAVEINTERVAL: self.conn.commit() add += adds_without_commit adds_without_commit = 0 progress.tick() except Exception as exc: log.e(_("error while updating media: %s %s"), exc.__class__.__name__, exc) log.e(_("rollback to previous commit.")) traceback.print_exc() raise exc finally: add += adds_without_commit log.i(_('items added %d, removed %d'), add, deld) self.load_db_to_memory()
def count_subfolders_and_files(self): if self.dir: self.subdircount = 0 self.subfilescount = 0 fullpath = CherryModel.abspath(self.path) if not os.path.isdir(fullpath): # not a dir, or not even there: fail gracefully. # There are 0 subfolders and 0 files by default. log.error("MusicEntry does not exist: %r", self.path) return try: directory_listing = os.listdir(fullpath) except OSError as e: log.e(_('Error listing directory %s: %s') % (fullpath, str(e))) directory_listing = [] for idx, filename in enumerate(directory_listing): if idx > MusicEntry.MAX_SUB_FILES_ITER_COUNT: # estimate remaining file count self.subfilescount *= len(directory_listing) / float(idx + 1) self.subfilescount = int(self.subfilescount) self.subdircount *= len(directory_listing) / float(idx + 1) self.subdircount = int(self.subdircount) self.subfilesestimate = True return subfilefullpath = os.path.join(fullpath, filename) if os.path.isfile(subfilefullpath): if CherryModel.isplayable(subfilefullpath): self.subfilescount += 1 else: self.subdircount += 1
def __init__(self,method='amazon', timeout=10): self.methods = { 'amazon' : { 'url' : "http://www.amazon.com/s/ref=sr_nr_i_0?rh=k:", 'regex' : '<img src="([^"]*)" class="productImage"' }, 'music.ovi.com' : { 'url' : 'http://music.ovi.com/gb/en/pc/Search/?display=detail&text=', 'regex': 'class="prod-sm"><img src="([^"]*)"', #improve image quality: 'urltransformer' : lambda x : x[:x.rindex('/')]+'/?w=200&q=100', }, 'bestbuy.com':{ 'url' : 'http://www.bestbuy.com/site/searchpage.jsp?_dyncharset=ISO-8859-1&id=pcat17071&type=page&ks=960&sc=Global&cp=1&sp=&qp=crootcategoryid%23%23-1%23%23-1~~q6a616d657320626c616b65206a616d657320626c616b65~~nccat02001%23%230%23%23e&list=y&usc=All+Categories&nrp=15&iht=n&st=', 'regex' : '<img itemprop="image" class="thumb" src="([^"]*)"' }, 'buy.com' : { 'url' : "http://www.buy.com/sr/srajax.aspx?from=2&qu=", 'regex' : ' class="productImageLink"><img src="([^"]*)"' }, } if not method in self.methods: log.e('unknown album art fetch method: %s, using default.'%self.method) method = 'amazon' self.method = method self.timeout = timeout
def _unicode_listdir(dirname): for name in os.listdir(dirname): try: yield (name if is_unicode(name) else decode(name, encoding)) except UnicodeError: log.e(_('unable to decode filename %r in %r; skipping.'), name, dirname)
def listdir(self, path): basedir = cherry.config['media.basedir'] targetpath = os.path.join(basedir, path) targetdir = self.db_find_file_by_path(targetpath) if targetdir is None: log.e(_('media cache cannot listdir %r: path not in database'), path) return [] return [f.basename for f in self.fetch_child_files(targetdir)]
def register_file_with_db(self, fileobj): """add data in File object to relevant tables in media database""" try: self.add_to_file_table(fileobj) word_ids = self.add_to_dictionary_table(fileobj.name) self.add_to_search_table(fileobj.uid, word_ids) except UnicodeEncodeError as e: log.e("wrong encoding for filename '%s' (%s)", fileobj.relpath, e.__class__.__name__)
def listdir(self, path): basedir = cherry.config['media.basedir'] targetpath = os.path.join(basedir, path) targetdir = self.db_find_file_by_path(targetpath) if targetdir is None: log.e(_('media cache cannot listdir %r: path not in database'), path) return [] return list(map(lambda f: f.basename, self.fetch_child_files(targetdir)))
def register_file_with_db(self, fileobj): """add data in File object to relevant tables in media database""" try: self.add_to_file_table(fileobj) word_ids = self.add_to_dictionary_table(fileobj.name) self.add_to_search_table(fileobj.uid, word_ids) return fileobj except UnicodeEncodeError as e: log.e(_("wrong encoding for filename '%s' (%s)"), fileobj.relpath, e.__class__.__name__)
def check_for_updates(self): try: url = 'http://fomori.org/cherrymusic/update_check.php?version=' url += cherry.__version__ urlhandler = urllib.request.urlopen(url, timeout=5) jsondata = codecs.decode(urlhandler.read(), 'UTF-8') versioninfo = json.loads(jsondata) return versioninfo except Exception as e: log.e(_('Error fetching version info: %s') % str(e)) return []
def __init__(self, method="amazon", timeout=10): """define the urls of the services and a regex to fetch images """ self.MAX_IMAGE_SIZE_BYTES = 100 * 1024 self.IMAGE_SIZE = 80 # the GET parameter value of the searchterm must be appendable # to the urls defined in "methods". if not method in self.methods: log.e(_(("""unknown album art fetch method: '%(method)s', """ """using default.""")), {"method": method}) method = "google" self.method = method self.timeout = timeout
def full_update(self): '''verify complete media database against the filesystem and make necesary changes.''' log.i(_('running full update...')) try: self.update_db_recursive(cherry.config['media.basedir'], skipfirst=True) except: log.e(_('error during media update. database update incomplete.')) finally: self.update_word_occurrences() log.i(_('media database update complete.'))
def full_update(self): """verify complete media database against the filesystem and make necesary changes.""" log.i("running full update...") try: self.update_db_recursive(cherry.config["media.basedir"], skipfirst=True) except: log.e("error during media update. database update incomplete.") finally: self.update_word_occurrences() log.i("media database update complete.")
def children(self, sort=True, reverse=True): '''If self.isdir and self.exists, return an iterable of fileobjects corresponding to its direct content (non-recursive). Otherwise, log an error and return (). ''' try: content = _unicode_listdir(self.fullpath) if sort: content = sorted(content, reverse=reverse) return (File(name, parent=self) for name in content) except OSError as error: log.e(_('cannot list directory: %s'), error) return ()
def remove_file(self, fileobj): '''removes a file entry from the db, which means removing: - all search references, - all dictionary words which were orphaned by this, - the reference in the files table.''' try: dead_wordids = self.remove_from_search(fileobj.uid) self.remove_all_from_dictionary(dead_wordids) self.remove_from_files(fileobj.uid) except Exception as exception: log.ex(exception) log.e(_('error removing entry for %s'), fileobj.relpath) raise exception
def _runscript(self, version, name, cxn): try: cxn.executescript(self.desc[version][name]) except sqlite3.OperationalError: # update scripts are tested, so the problem's seems to be sqlite # itself log.x(_('Exception while updating database schema.')) log.e(_('Database error. This is probably due to your version of' ' sqlite being too old. Try updating sqlite3 and' ' updating python. If the problem persists, you will need' ' to delete the database at ' + self.db.dblocation)) import sys sys.exit(1)
def inputfilter(cls, files_iter): basedir = cherry.config['media.basedir'] for f in files_iter: if not f.exists: log.e(_('file not found: %s. skipping.' % f.fullpath)) continue if not f.fullpath.startswith(basedir): log.e(_('file not in basedir: %s. skipping.') % f.fullpath) continue if f.islink: rp = os.path.realpath(f.fullpath) if os.path.abspath(basedir).startswith(rp) \ or (os.path.islink(basedir) and os.path.realpath(basedir).startswith(rp)): log.e(_(("Cyclic symlink found: %s creates a circle " "if followed. Skipping.")) % f.relpath) continue if not (f.parent is None or f.parent.parent is None): log.e(_(("Deeply nested symlink found: %s . All links " "must be directly in your basedir (%s). The " "program cannot safely handle them otherwise." " Skipping.")) % (f.relpath, os.path.abspath(basedir))) continue yield f
def __init__(self, method='amazon', timeout=10): """define the urls of the services and a regex to fetch images """ self.MAX_IMAGE_SIZE_BYTES = 100*1024 self.IMAGE_SIZE = 80 # the GET parameter value of the searchterm must be appendable # to the urls defined in "methods". if not method in self.methods: log.e(_('''unknown album art fetch method: '%(method)s', using default.'''), {'method': method}) method = 'amazon' self.method = method self.timeout = timeout self.imageMagickAvailable = self.programAvailable('convert')
def full_update(self): '''verify complete media database against the filesystem and make necesary changes.''' log.i('running full update...') try: self.update_db_recursive(cherry.config.media.basedir.str, skipfirst=True) except: log.e('error during media update. database update incomplete.') finally: self.__create_index_if_non_exist() self.update_word_occurences() log.i('media database update complete.')
def inputfilter(cls, files_iter): basedir = cherry.config['media.basedir'] for f in files_iter: if not f.exists: log.e('file not found: ' + f.fullpath + ' . skipping.') continue if not f.fullpath.startswith(basedir): log.e('file not in basedir: ' + f.fullpath + ' . skipping.') continue if f.islink: rp = os.path.realpath(f.fullpath) if os.path.abspath(basedir).startswith(rp) \ or (os.path.islink(basedir) and os.path.realpath(basedir).startswith(rp)): log.e("Cyclic symlink found: " + f.relpath + " creates a circle if followed. Skipping.") continue if not (f.parent is None or f.parent.parent is None): log.e("Deeply nested symlink found: " + f.relpath + " All links must be directly in your basedir (" + os.path.abspath(basedir) + "). The program cannot" " safely handle them otherwise. Skipping.") continue yield f
def changePassword(self, username, newpassword): if not newpassword.strip(): return _("not a valid password") if self.getIdByName(username) is None: msg = 'cannot change password: "******" does not exist!' % username log.e(msg) return msg newuser = User.create(username, newpassword, False) #dummy user for salt self.conn.execute(''' UPDATE users SET password = ?, salt = ? WHERE username = ? ''', (newuser.password, newuser.salt, newuser.name) ) self.conn.commit() return "success"
def inputfilter(cls, files_iter): basedir = cherry.config['media.basedir'] for f in files_iter: if not f.exists: log.e(_('file not found: %s. skipping.' % f.fullpath)) continue if not f.fullpath.startswith(basedir): log.e(_('file not in basedir: %s. skipping.') % f.fullpath) continue if f.islink and not os.path.isfile(f.fullpath): rp = os.path.realpath(f.fullpath) if os.path.abspath(basedir).startswith(rp) \ or (os.path.islink(basedir) and os.path.realpath(basedir).startswith(rp)): log.e(_(("Cyclic symlink found: %s creates a circle " "if followed. Skipping.")) % f.relpath) continue if not (f.parent is None or f.parent.parent is None): log.e(_(("Deeply nested directory symlink found: %s . " "All symlinks to directories " "must be directly in your basedir (%s). The " "program cannot safely handle them otherwise." " Skipping.")) % (f.relpath, os.path.abspath(basedir))) continue yield f
def __init__(self, method='amazon', timeout=10): """define the urls of the services and a regex to fetch images """ self.MAX_IMAGE_SIZE_BYTES = 100 * 1024 self.IMAGE_SIZE = 80 # the GET parameter value of the searchterm must be appendable # to the urls defined in "methods". if not method in self.methods: log.e( _(('''unknown album art fetch method: '%(method)s', ''' '''using default.''')), {'method': method}) method = 'google' self.method = method self.timeout = timeout
def changePassword(self, username, newpassword): if not newpassword.strip(): return _("not a valid password") if self.getIdByName(username) is None: msg = 'cannot change password: "******" does not exist!' % username log.e(msg) return msg newuser = User.create(username, newpassword, False) #dummy user for salt self.conn.execute( ''' UPDATE users SET password = ?, salt = ? WHERE lower(username) = lower(?) ''', (newuser.password, newuser.salt, newuser.name)) self.conn.commit() return "success"
def addUser(self, username, password, admin): if not (username.strip() or password.strip()): log.d(_('empty username or password!')) return False user = User.create(username, password, admin) try: self.conn.execute(''' INSERT INTO users (username, admin, password, salt) VALUES (?,?,?,?)''', (user.name, 1 if user.isadmin else 0, user.password, user.salt)) except sqlite3.IntegrityError: log.e('cannot create user "%s", already exists!' % user.name) return False self.conn.commit() log.i('added user: ' + user.name) return True
def api_downloadcheck(self, filelist): status = self.download_check_files(filelist) if status == 'not_permitted': return """You are not allowed to download files.""" elif status == 'invalid_file': return """Error: File has invalid filename""" % f elif status == 'too_big': size_limit = cherry.config['media.maximum_download_size'] return """Can't download: Playlist is bigger than %s mB. The server administrator can change this configuration. """ % (size_limit/1024/1024) elif status == 'ok': return status else: message = "Error status check for download: '%s'" % status log.e(message) return message
def api_downloadcheck(self, filelist): status = self.download_check_files(filelist) if status == 'not_permitted': return """You are not allowed to download files.""" elif status == 'invalid_file': return "Error: invalid filename found in {list}".format(list=filelist) elif status == 'too_big': size_limit = cherry.config['media.maximum_download_size'] return """Can't download: Playlist is bigger than {maxsize} mB. The server administrator can change this configuration. """.format(maxsize=size_limit/1024/1024) elif status == 'ok': return status else: message = "Error status check for download: {status!r}".format(status=status) log.e(message) return message
def partial_update(self, path, *paths): basedir = cherry.config['media.basedir'] paths = (path,) + paths log.i(_('updating paths: %s') % (paths,)) for path in paths: path = os.path.normcase(path) abspath = path if os.path.isabs(path) else os.path.join(basedir, path) normpath = os.path.normpath(abspath) if not normpath.startswith(basedir): log.e(_('path is not in basedir. skipping %r') % abspath) continue log.i(_('updating %r...') % path) try: self.update_db_recursive(normpath, skipfirst=False) except Exception as exception: log.e(_('update incomplete: %r'), exception) self.update_word_occurrences() log.i(_('done updating paths.'))
def addUser(self, username, password, admin): if not (username.strip() or password.strip()): log.d(_('empty username or password!')) return False user = User.create(username, password, admin) try: self.conn.execute( ''' INSERT INTO users (username, admin, password, salt) VALUES (?,?,?,?)''', (user.name, 1 if user.isadmin else 0, user.password, user.salt)) except sqlite3.IntegrityError: log.e('cannot create user "%s", already exists!' % user.name) return False self.conn.commit() log.d('added user: ' + user.name) return True
def getSongInfo(filepath): if has_stagger: try: tag = stagger.read_tag(filepath) except Exception: tag = MockTag() else: tag = MockTag() if has_audioread: try: with audioread.audio_open(filepath) as f: audiolength = f.duration except Exception: log.e('audioread failed! (%s)', filepath) audiolength = 0 else: audiolength = 0 return Metainfo(tag.artist, tag.album, tag.title, tag.track, audiolength)
def listdir(self, dirpath, filterstr=''): if dirpath is None: absdirpath = cherry.config['media.basedir'] else: absdirpath = CherryModel.abspath(dirpath) if cherry.config['browser.pure_database_lookup']: allfilesindir = self.cache.listdir(dirpath) # NOT absdirpath! else: in_basedir = (os.path.normpath(absdirpath) + '/').startswith( cherry.config['media.basedir']) if not in_basedir: raise ValueError('dirpath not in basedir: %r' % dirpath) try: allfilesindir = os.listdir(absdirpath) except OSError as e: log.e( _('Error listing directory %s: %s') % (absdirpath, str(e))) allfilesindir = [] #remove all files not inside the filter if filterstr: filterstr = filterstr.lower() allfilesindir = [ f for f in allfilesindir if f.lower().startswith(filterstr) ] else: allfilesindir = [f for f in allfilesindir if not f.startswith('.')] musicentries = [] maximum_shown_files = cherry.config['browser.maxshowfiles'] compactlisting = len(allfilesindir) > maximum_shown_files if compactlisting: upper_case_files = [x.upper() for x in allfilesindir] filterstr = os.path.commonprefix(upper_case_files) filterlength = len(filterstr) + 1 currentletter = '/' # impossible first character # don't care about natural number order in compact listing sortedfiles = self.sortFiles(allfilesindir, number_ordering=False) for dir in sortedfiles: filter_match = dir.upper().startswith(currentletter.upper()) if filter_match and not len(currentletter) < filterlength: continue else: currentletter = dir[:filterlength] #if the filter equals the foldername if len(currentletter) == len(filterstr): subpath = os.path.join(absdirpath, dir) CherryModel.addMusicEntry(subpath, musicentries) else: musicentries.append( MusicEntry(strippath(absdirpath), repr=currentletter, compact=True)) else: # enable natural number ordering for real directories and files sortedfiles = self.sortFiles(allfilesindir, absdirpath, number_ordering=True) for dir in sortedfiles: subpath = os.path.join(absdirpath, dir) CherryModel.addMusicEntry(subpath, musicentries) if cherry.config['media.show_subfolder_count']: for musicentry in musicentries: musicentry.count_subfolders_and_files() return musicentries