Esempio n. 1
0
 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)
Esempio n. 2
0
    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)
Esempio n. 3
0
    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)
Esempio n. 4
0
    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)
Esempio n. 5
0
    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
Esempio n. 6
0
    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
Esempio n. 7
0
    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)
Esempio n. 9
0
 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)
Esempio n. 11
0
    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)
Esempio n. 12
0
 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)
Esempio n. 13
0
    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)
Esempio n. 14
0
    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
                })
Esempio n. 15
0
    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})
Esempio n. 17
0
    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
Esempio n. 18
0
 def onRowCollapsed(self, tree, path):
     """ Replace all children by a fake child """
     tree.removeAllChildren(path)
     tree.appendRow((icons.dirMenuIcon(), '', TYPE_NONE, ''), path)
Esempio n. 19
0
 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})
Esempio n. 21
0
 def onRowCollapsed(self, tree, path):
     """ Replace all children by a fake child """
     tree.removeAllChildren(path)
     tree.appendRow((icons.dirMenuIcon(), '', TYPE_NONE, ''), path)