def add_dir(self, path): ''' Add a directory with one fake child to the tree ''' name = tools.dirname(path) name = tools.htmlEscape(unicode(name, errors='replace')) parent = self.tree.appendRow((icons.dirMenuIcon(), name, TYPE_DIR, path), None) # add fake child self.tree.appendRow((icons.dirMenuIcon(), '', TYPE_NONE, ''), parent)
def getDirContents(self, directory): """ Return a tuple of sorted rows (directories, playlists, mediaFiles) for the given directory """ playlists = [] mediaFiles = [] directories = [] for (file, path) in tools.listDir(directory, self.showHiddenFiles): if isdir(path): directories.append( (icons.dirMenuIcon(), tools.htmlEscape(unicode(file, errors='replace')), TYPE_DIR, path)) elif isfile(path): if media.isSupported(file): mediaFiles.append( (icons.mediaFileMenuIcon(), tools.htmlEscape(unicode(file, errors='replace')), TYPE_FILE, path)) elif playlist.isSupported(file): playlists.append( (icons.mediaFileMenuIcon(), tools.htmlEscape(unicode(file, errors='replace')), TYPE_FILE, path)) playlists.sort(key=self.sortKey) mediaFiles.sort(key=self.sortKey) directories.sort(key=self.sortKey) return (directories, playlists, mediaFiles)
def onSearchAppend(self, results, query): self._remove_searching_node() # Make sure we never call this method without calling onSearchStart first if not self.displaying_results: return dirs, files = results for path, name in dirs: new_node = self.tree.appendRow((icons.dirMenuIcon(), name, TYPE_DIR, path), None) # add fake child self.tree.appendRow((icons.dirMenuIcon(), '', TYPE_NONE, ''), new_node) for file, name in files: self.tree.appendRow((icons.mediaFileMenuIcon(), name, TYPE_FILE, file), None)
def getDirContents(self, directory): """ Return a tuple of sorted rows (directories, playlists, mediaFiles) for the given directory """ playlists = [] mediaFiles = [] directories = [] for (file, path) in tools.listDir(unicode(directory)): # Make directory names prettier junk = ['_'] pretty_name = file for item in junk: pretty_name = pretty_name.replace(item, ' ') if isdir(path): directories.append((icons.dirMenuIcon(), tools.htmlEscape(pretty_name), TYPE_DIR, path)) elif isfile(path): if media.isSupported(file): mediaFiles.append((icons.mediaFileMenuIcon(), tools.htmlEscape(pretty_name), TYPE_FILE, path)) ##elif playlist.isSupported(file): ## playlists.append((icons.mediaFileMenuIcon(), tools.htmlEscape(unicode(file, errors='replace')), TYPE_FILE, path)) # Individually sort each type of file by name playlists.sort(key=self._filename) mediaFiles.sort(key=self._filename) directories.sort(key=self._filename) return (directories, playlists, mediaFiles)
def updateDirNodes(self, parent): """ This generator updates the directory nodes, based on whether they should be expandable """ for child in self.tree.iterChildren(parent): # Only directories need to be updated and since they all come first, # we can stop as soon as we find something else if self.tree.getItem(child, ROW_TYPE) != TYPE_DIR: break # Make sure it's readable directory = self.tree.getItem(child, ROW_FULLPATH) hasContent = False if os.access(directory, os.R_OK | os.X_OK): for (file, path) in tools.listDir(directory): supported = (media.isSupported(file) or playlist.isSupported(file)) if isdir(path) or (isfile(path) and supported): hasContent = True break # Append/remove children if needed if hasContent and self.tree.getNbChildren(child) == 0: self.tree.appendRow((icons.dirMenuIcon(), '', TYPE_NONE, ''), child) elif not hasContent and self.tree.getNbChildren(child) > 0: self.tree.removeAllChildren(child) yield True if parent is not None: self.stopLoading(parent) yield False
def updateDirNodes(self, parent): """ This generator updates the directory nodes, based on whether they should be expandable """ for child in self.tree.iterChildren(parent): # Only directories need to be updated and since they all come first, we can stop as soon as we find something else if self.tree.getItem(child, ROW_TYPE) != TYPE_DIR: break # Make sure it's readable directory = self.tree.getItem(child, ROW_FULLPATH) hasContent = False if os.access(directory, os.R_OK | os.X_OK): for (file, path) in tools.listDir(directory, self.showHiddenFiles): if isdir(path) or (isfile(path) and (media.isSupported(file) or playlist.isSupported(file))): hasContent = True break # Append/remove children if needed if hasContent and self.tree.getNbChildren(child) == 0: self.tree.appendRow((icons.dirMenuIcon(), '', TYPE_NONE, ''), child) elif not hasContent and self.tree.getNbChildren(child) > 0: self.tree.removeAllChildren(child) yield True if parent is not None: self.stopLoading(parent) yield False
def onAppStarted(self): """ The module has been loaded """ self.tree = None self.cfgWin = None self.folders = prefs.get(__name__, 'media-folders', PREFS_DEFAULT_MEDIA_FOLDERS) self.scrolled = gtk.ScrolledWindow() self.currRoot = None self.treeState = prefs.get(__name__, 'saved-states', {}) self.addByFilename = prefs.get(__name__, 'add-by-filename', PREFS_DEFAULT_ADD_BY_FILENAME) self.showHiddenFiles = prefs.get(__name__, 'show-hidden-files', PREFS_DEFAULT_SHOW_HIDDEN_FILES) self.scrolled.set_shadow_type(gtk.SHADOW_IN) self.scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.scrolled.show() for name in self.folders: modules.postMsg( consts.MSG_CMD_EXPLORER_ADD, { 'modName': MOD_L10N, 'expName': name, 'icon': icons.dirMenuIcon(), 'widget': self.scrolled })
def restoreTreeDump(self, dump, parent=None): """ Recursively restore the dump under the given parent (None for the root of the tree) """ for item in dump: (name, type, path) = item[0] if type == TYPE_FILE: self.tree.appendRow((icons.mediaFileMenuIcon(), name, TYPE_FILE, path), parent) else: newNode = self.tree.appendRow((icons.dirMenuIcon(), name, TYPE_DIR, path), parent) if item[1] is not None: fakeChild = self.tree.appendRow((icons.dirMenuIcon(), '', TYPE_NONE, ''), newNode) if len(item[1]) != 0: # We must expand the row before adding the real children, but this works only if there is already at least one child self.tree.expandRow(newNode) self.restoreTreeDump(item[1], newNode) self.tree.removeRow(fakeChild)
def onButtonPressed(self, tree, event, path): """ A mouse button has been pressed """ if event.button == 3: self.showPopupMenu(tree, event.button, event.time, path) elif path is not None and tree.getItem(path, ROW_TYPE) != TYPE_NONE: if event.button == 2: self.playPaths(tree, [path], False) elif event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS: if tree.getItem(path, ROW_PIXBUF) != icons.dirMenuIcon(): self.playPaths(tree, None, True) elif tree.row_expanded(path): tree.collapse_row(path) else: tree.expand_row(path, False)
def onMouseButton(self, tree, event, path): """ A mouse button has been pressed """ if event.button == 3: self.onShowPopupMenu(tree, event.button, event.time, path) elif path is not None: if event.button == 2: self.play(False, path) elif event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS: if tree.getItem(path, ROW_PIXBUF) != icons.dirMenuIcon(): self.play(True) elif tree.row_expanded(path): tree.collapse_row(path) else: tree.expand_row(path, False)
def restoreTreeDump(self, dump, parent=None): """ Recursively restore the dump under the given parent (None for the root of the tree) """ for item in dump: (name, type, path) = item[0] if type == TYPE_FILE: self.tree.appendRow( (icons.mediaFileMenuIcon(), name, TYPE_FILE, path), parent) else: newNode = self.tree.appendRow( (icons.dirMenuIcon(), name, TYPE_DIR, path), parent) if item[1] is not None: fakeChild = self.tree.appendRow( (icons.dirMenuIcon(), '', TYPE_NONE, ''), newNode) if len(item[1]) != 0: # We must expand the row before adding the real children, but this works only if there is already at least one child self.tree.expandRow(newNode) self.restoreTreeDump(item[1], newNode) self.tree.removeRow(fakeChild)
def loadArtists(self, tree, name): """ Load the given library """ libPath = os.path.join(ROOT_PATH, name) # Make sure the version number is the good one if not os.path.exists(os.path.join(libPath, 'VERSION_%u' % VERSION)): logger.error('[%s] Version number does not match, loading of library "%s" aborted' % (MOD_INFO[modules.MODINFO_NAME], name)) error = _('This library is deprecated, please refresh it.') tree.replaceContent([(icons.errorMenuIcon(), None, error, TYPE_NONE, None, None)]) return rows = [] icon = icons.dirMenuIcon() prevChar = '' allArtists = pickleLoad(os.path.join(libPath, 'artists')) self.allGenres = pickleLoad(os.path.join(libPath, 'genres')) # Filter artists by genre if needed if self.currGenre is not None: allArtists = [artist for artist in allArtists if artist[ART_NAME] in self.allGenres[self.currGenre]] rows.append((icons.infoMenuIcon(), None, '<b>%s</b>' % self.currGenre.capitalize(), TYPE_GENRE_BANNER, None, None)) else: rows.append((icons.infoMenuIcon(), None, '<b>%s</b>' % _('All genres'), TYPE_GENRE_BANNER, None, None)) # Filter artists by favorites if needed if self.showOnlyFavs: allArtists = [artist for artist in allArtists if self.isArtistInFavorites(artist[ART_NAME])] rows.append((icons.starMenuIcon(), None, '<b>%s</b>' % _('My Favorites'), TYPE_FAVORITES_BANNER, None, None)) # Create the rows for artist in allArtists: if len(artist[ART_NAME]) != 0: currChar = unicode(artist[ART_NAME], errors='replace')[0].lower() else: currChar = prevChar if prevChar != currChar and not (prevChar.isdigit() and currChar.isdigit()): prevChar = currChar if currChar.isdigit(): rows.append((None, None, '<b>0 - 9</b>', TYPE_HEADER, None, None)) else: rows.append((None, None, '<b>%s</b>' % currChar.upper(), TYPE_HEADER, None, None)) rows.append((icon, None, htmlEscape(artist[ART_NAME]), TYPE_ARTIST, os.path.join(libPath, artist[ART_INDEX]), artist[ART_NAME])) # Insert all rows, and then add a fake child to each artist tree.replaceContent(rows) for node in tree.iterChildren(None): if tree.getItem(node, ROW_TYPE) == TYPE_ARTIST: tree.appendRow(FAKE_CHILD, node)
def onAddFolder(self, btn): """ Let the user add a new folder to the list """ from gui import selectPath result = selectPath.SelectPath(MOD_L10N, self.cfgWin, self.folders.keys()).run() if result is not None: name, path = result self.folders[name] = path self.populateFolderList() modules.postMsg( consts.MSG_CMD_EXPLORER_ADD, { 'modName': MOD_L10N, 'expName': name, 'icon': icons.dirMenuIcon(), 'widget': self.scrolled })
def getDirContents(self, directory): """ Return a tuple of sorted rows (directories, playlists, mediaFiles) for the given directory """ playlists = [] mediaFiles = [] directories = [] for (file, path) in tools.listDir(directory, self.showHiddenFiles): if isdir(path): directories.append((icons.dirMenuIcon(), tools.htmlEscape(unicode(file, errors='replace')), TYPE_DIR, path)) elif isfile(path): if media.isSupported(file): mediaFiles.append((icons.mediaFileMenuIcon(), tools.htmlEscape(unicode(file, errors='replace')), TYPE_FILE, path)) elif playlist.isSupported(file): playlists.append((icons.mediaFileMenuIcon(), tools.htmlEscape(unicode(file, errors='replace')), TYPE_FILE, path)) playlists.sort(key=self.sortKey) mediaFiles.sort(key=self.sortKey) directories.sort(key=self.sortKey) return (directories, playlists, mediaFiles)
def onAppStarted(self): """ The module has been loaded """ self.tree = None self.cfgWin = None self.folders = prefs.get(__name__, 'media-folders', PREFS_DEFAULT_MEDIA_FOLDERS) self.scrolled = gtk.ScrolledWindow() self.currRoot = None self.treeState = prefs.get(__name__, 'saved-states', {}) self.addByFilename = prefs.get(__name__, 'add-by-filename', PREFS_DEFAULT_ADD_BY_FILENAME) self.showHiddenFiles = prefs.get(__name__, 'show-hidden-files', PREFS_DEFAULT_SHOW_HIDDEN_FILES) self.scrolled.set_shadow_type(gtk.SHADOW_IN) self.scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.scrolled.show() for name in self.folders: modules.postMsg(consts.MSG_CMD_EXPLORER_ADD, {'modName': MOD_L10N, 'expName': name, 'icon': icons.dirMenuIcon(), 'widget': self.scrolled})
def refreshLibrary(self, parent, libName, path, creation=False): """ Refresh the given library, must be called through idle_add() """ import collections, shutil from gui import progressDlg # First show a progress dialog if creation: header = _('Creating library') else: header = _('Refreshing library') progress = progressDlg.ProgressDlg(parent, header, _('The directory is scanned for media files. This can take some time.\nPlease wait.')) yield True libPath = os.path.join(ROOT_PATH, libName) # Location of the library # If the version number has changed or does not exist, don't reuse any existing file and start from scratch if not os.path.exists(os.path.join(libPath, 'VERSION_%u' % VERSION)): self.__createEmptyLibrary(libName) db = {} # The dictionnary used to create the library queue = collections.deque((path,)) # Faster structure for appending/removing elements mediaFiles = [] # All media files found newLibrary = {} # Reflect the current file structure of the library oldLibrary = pickleLoad(os.path.join(libPath, 'files')) # Previous file structure of the same library # Make sure the root directory still exists if not os.path.exists(path): queue.pop() while len(queue) != 0: currDir = queue.pop() currDirMTime = os.stat(currDir).st_mtime # Retrieve previous information on the current directory, if any if currDir in oldLibrary: oldDirMTime, oldDirectories, oldFiles = oldLibrary[currDir] else: oldDirMTime, oldDirectories, oldFiles = -1, [], {} # If the directory has not been modified, keep old information if currDirMTime == oldDirMTime: files, directories = oldFiles, oldDirectories else: files, directories = {}, [] for (filename, fullPath) in tools.listDir(currDir): if isdir(fullPath): directories.append(fullPath) elif isfile(fullPath) and media.isSupported(filename): if filename in oldFiles: files[filename] = oldFiles[filename] else: files[filename] = [-1, FileTrack(fullPath)] # Determine which files need to be updated for filename, (oldMTime, track) in files.iteritems(): mTime = os.stat(track.getFilePath()).st_mtime if mTime != oldMTime: files[filename] = [mTime, media.getTrackFromFile(track.getFilePath())] newLibrary[currDir] = (currDirMTime, directories, files) mediaFiles.extend([track for mTime, track in files.itervalues()]) queue.extend(directories) # Update the progress dialog try: text = ngettext('Scanning directories (one track found)', 'Scanning directories (%(nbtracks)u tracks found)', len(mediaFiles)) progress.pulse(text % {'nbtracks': len(mediaFiles)}) yield True except progressDlg.CancelledException: progress.destroy() if creation: shutil.rmtree(libPath) yield False # From now on, the process should not be cancelled progress.setCancellable(False) if creation: progress.pulse(_('Creating library...')) else: progress.pulse(_('Refreshing library...')) yield True # Create the database for track in mediaFiles: album = track.getExtendedAlbum() genre = track.getGenre().lower() if track.hasAlbumArtist(): artist = track.getAlbumArtist() else: artist = track.getArtist() if artist in db: allAlbums = db[artist] try: albumNfo = allAlbums[album] albumNfo[0][genre] = None albumNfo[1].append(track) except: allAlbums[album] = ({genre: None}, [track]) else: db[artist] = {album: ({genre: None}, [track])} progress.pulse() yield True # If an artist name begins with a known prefix, put it at the end (e.g., Future Sound of London (The)) prefixes = prefs.get(__name__, 'prefixes', PREFS_DEFAULT_PREFIXES) for artist in db.keys(): artistLower = artist.lower() for prefix in prefixes: if artistLower.startswith(prefix): db[artist[len(prefix):] + ' (%s)' % artist[:len(prefix)-1]] = db[artist] del db[artist] progress.pulse() yield True # Load favorites before removing the files from the disk if self.currLib == libName: favorites = self.favorites else: favorites = self.loadFavorites(libName) # Re-create the library structure on the disk if isdir(libPath): shutil.rmtree(libPath) os.mkdir(libPath) # Put a version number tools.touch(os.path.join(libPath, 'VERSION_%u' % VERSION)) overallNbAlbums = 0 overallNbTracks = 0 overallNbArtists = len(db) # The 'artists' file contains all known artists with their index, the 'files' file contains the file structure of the root path allArtists = sorted([(artist, str(indexArtist), len(albums)) for indexArtist, (artist, albums) in enumerate(db.iteritems())], key = lambda a: a[0].lower()) pickleSave(os.path.join(libPath, 'files'), newLibrary) pickleSave(os.path.join(libPath, 'artists'), allArtists) # Keep track of genre through nested dictionaries genres -> artists -> albums allGenres = {} for (artist, indexArtist, nbAlbums) in allArtists: artistPath = os.path.join(libPath, indexArtist) overallNbAlbums += nbAlbums os.mkdir(artistPath) albums = [] for index, (name, (albumGenres, tracks)) in enumerate(db[artist].iteritems()): length = sum([track.getLength() for track in tracks]) overallNbTracks += len(tracks) albums.append((name, str(index), len(tracks), length)) pickleSave(os.path.join(artistPath, str(index)), sorted(tracks, key = lambda track: track.getNumber())) # Update the dictionary with the genres for genre in albumGenres: try: allGenres[genre][artist][name] = None except: try: allGenres[genre][artist] = {name: None} except: allGenres[genre] = {artist: {name: None}} albums.sort() pickleSave(os.path.join(artistPath, 'albums'), albums) progress.pulse() yield True pickleSave(os.path.join(libPath, 'genres'), allGenres) self.libraries[libName] = (path, overallNbArtists, overallNbAlbums, overallNbTracks) self.fillLibraryList() if creation: modules.postMsg(consts.MSG_CMD_EXPLORER_ADD, {'modName': MOD_L10N, 'expName': libName, 'icon': icons.dirMenuIcon(), 'widget': self.scrolled}) progress.destroy() # Trim favorites and save them newFavorites = {} for (artist, albums) in favorites.iteritems(): if artist in db: newFavorites[artist] = {} for album in albums: if album in db[artist][1]: newFavorites[artist][album] = None self.saveFavorites(libName, newFavorites) # If the refreshed library is currently displayed, refresh the treeview as well if self.currLib == libName: self.saveTreeState() self.favorites = newFavorites self.loadArtists(self.tree, self.currLib) self.restoreTreeState() yield False
def onRowCollapsed(self, tree, path): """ Replace all children by a fake child """ tree.removeAllChildren(path) tree.appendRow((icons.dirMenuIcon(), '', TYPE_NONE, ''), path)
def addAllExplorers(self): """ Add all libraries to the Explorer module """ for (name, (path, nbArtists, nbAlbums, nbTracks)) in self.libraries.iteritems(): modules.postMsg(consts.MSG_CMD_EXPLORER_ADD, {'modName': MOD_L10N, 'expName': name, 'icon': icons.dirMenuIcon(), 'widget': self.scrolled})
def onAddFolder(self, btn): """ Let the user add a new folder to the list """ from gui import selectPath result = selectPath.SelectPath(MOD_L10N, self.cfgWin, self.folders.keys()).run() if result is not None: name, path = result self.folders[name] = path self.populateFolderList() modules.postMsg(consts.MSG_CMD_EXPLORER_ADD, {'modName': MOD_L10N, 'expName': name, 'icon': icons.dirMenuIcon(), 'widget': self.scrolled})