def __getTracksFromPaths(self, tree, paths):
        """
            Return the list of tracks extracted from:
                * The list 'paths' if it is not None
                * The currently selected rows if 'paths' is None
        """
        tracks = []

        if paths is None:
            paths = tree.getSelectedPaths()

        for currPath in paths:
            row = tree.getRow(currPath)
            if row[ROW_TYPE] == TYPE_TRACK:
                tracks.append(row[ROW_TAGS])
            elif row[ROW_TYPE] == TYPE_ALBUM:
                tracks.extend(pickleLoad(row[ROW_FULLPATH]))
            elif row[ROW_TYPE] == TYPE_ARTIST:
                for album in pickleLoad(os.path.join(row[ROW_FULLPATH], 'albums')):
                    tracks.extend(pickleLoad(os.path.join(row[ROW_FULLPATH], album[ALB_INDEX])))
            elif row[ROW_TYPE] == TYPE_HEADER:
                for path in xrange(currPath[0]+1, sys.maxint):
                    if not tree.isValidPath(path):
                        break

                    row = tree.getRow(path)
                    if row[ROW_TYPE] == TYPE_HEADER:
                        break

                    for album in pickleLoad(os.path.join(row[ROW_FULLPATH], 'albums')):
                        tracks.extend(pickleLoad(os.path.join(row[ROW_FULLPATH], album[ALB_INDEX])))

        return tracks
Beispiel #2
0
    def onAppStarted(self):
        """ This is the real initialization function, called when the module has been loaded """
        wTree                  = tools.prefs.getWidgetsTree()
        self.playtime          = 0
        self.bufferedTrack     = None
        # Retrieve widgets
        self.window     = wTree.get_object('win-main')

        columns = (('',   [(gtk.CellRendererPixbuf(), gtk.gdk.Pixbuf), (gtk.CellRendererText(), TYPE_STRING)], True),
                   (None, [(None, TYPE_PYOBJECT)], False),
                  )

        self.tree = TrackTreeView(columns, use_markup=True)
        self.tree.enableDNDReordering()
        self.tree.setDNDSources([DND_INTERNAL_TARGET])

        wTree.get_object('scrolled-tracklist').add(self.tree)

        # GTK handlers
        self.tree.connect('exttreeview-button-pressed', self.onMouseButton)
        self.tree.connect('tracktreeview-dnd', self.onDND)
        self.tree.connect('key-press-event', self.onKeyboard)
        self.tree.get_model().connect('row-deleted', self.onRowDeleted)

        (options, args) = prefs.getCmdLine()

        self.savedPlaylist = os.path.join(consts.dirCfg, 'saved-playlist')
        self.paused = False


        # Populate the playlist with the saved playlist
        dump = None
        if os.path.exists(self.savedPlaylist):
            try:
                dump = pickleLoad(self.savedPlaylist)
            except:
                msg = '[%s] Unable to restore playlist from %s\n\n%s'
                log.logger.error(msg % (MOD_INFO[modules.MODINFO_NAME],
                                self.savedPlaylist, traceback.format_exc()))

        if dump:
            self.restoreTreeDump(dump)
            log.logger.info('[%s] Restored playlist' % MOD_INFO[modules.MODINFO_NAME])
            self.tree.collapse_all()
            self.select_last_played_track()
            self.onListModified()

        commands, args = tools.separate_commands_and_tracks(args)

        # Add commandline tracks to the playlist
        if args:
            log.logger.info('[%s] Filling playlist with files given on command line' % MOD_INFO[modules.MODINFO_NAME])
            tracks  = media.getTracks([os.path.abspath(arg) for arg in args])
            playNow = not 'stop' in commands and not 'pause' in commands
            modules.postMsg(consts.MSG_CMD_TRACKLIST_ADD, {'tracks': tracks, 'playNow': playNow})
        elif 'play' in commands:
            modules.postMsg(consts.MSG_CMD_TOGGLE_PAUSE)

        # Automatically save the content at regular intervals
        gobject.timeout_add_seconds(SAVE_INTERVAL, self.save_track_tree)
    def loadTracks(self, tree, node, fakeChild):
        """ Initial load of all tracks of the given node, assuming it is of type TYPE_ALBUM """
        allTracks = pickleLoad(tree.getItem(node, ROW_FULLPATH))
        rows      = [(consts.icoMediaFile, None, '%02u. %s' % (track.getNumber(), cgi.escape(track.getTitle())), TYPE_TRACK, track.getFilePath(), track) for track in allTracks]

        tree.appendRows(rows, node)
        tree.removeRow(fakeChild)
    def loadLibrary(self, tree, name):
        """ Load the given library """
        rows     = []
        path     = os.path.join(ROOT_PATH, name)
        prevChar = ''

        # Make sure the version number is the good one
        if not os.path.exists(os.path.join(path, 'VERSION_%u' % VERSION)):
            logger.error('[%s] Version number does not match, loading of library "%s" aborted' % (MOD_NAME, name))
            error = _('This library is deprecated, please refresh it.')
            tree.replaceContent([(consts.icoError, error, TYPE_NONE, None, None)])
            return

        # Create the rows, with alphabetical header if needed
        for artist in pickleLoad(os.path.join(path, 'artists')):

            if len(artist[ART_NAME]) != 0: currChar = unicode(artist[ART_NAME], errors='replace')[0]
            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, TYPE_HEADER, None, None))

            rows.append((consts.icoDir, None, cgi.escape(artist[ART_NAME]), TYPE_ARTIST, os.path.join(path, artist[ART_INDEX]), None))

        # 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((None, None, '', TYPE_NONE, '', None), node)
    def loadAlbums(self, tree, node, fakeChild):
        """ Initial load of the albums of the given node, assuming it is of type TYPE_ARTIST """
        rows      = []
        path      = tree.getItem(node, ROW_FULLPATH)
        artist    = tree.getItem(node, ROW_DATA)
        allAlbums = pickleLoad(os.path.join(tree.getItem(node, ROW_FULLPATH), 'albums'))

        # Filter albums if only favorites should be shown
        if self.showOnlyFavs:
            allAlbums = [album for album in allAlbums if self.isAlbumInFavorites(artist, album[ALB_NAME])]

        # Filter artists by genre if needed
        if self.currGenre is not None:
            allAlbums = [album for album in allAlbums if album[ALB_NAME] in self.allGenres[self.currGenre][artist]]

        # The icon depends on whether the album is in the favorites
        for album in allAlbums:
            if self.isAlbumInFavorites(artist, album[ALB_NAME]): icon = icons.starDirMenuIcon()
            else:                                                icon = icons.mediaDirMenuIcon()

            rows.append((icon, '[%s]' % tools.sec2str(album[ALB_LENGTH], True), '%s' % htmlEscape(album[ALB_NAME]),
                            TYPE_ALBUM, os.path.join(path, album[ALB_INDEX]), album[ALB_NAME]))

        # Add all the rows, and then add a fake child to each of them
        tree.appendRows(rows, node)
        tree.removeRow(fakeChild)
        for child in tree.iterChildren(node):
            tree.appendRow(FAKE_CHILD, child)
    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 loadAlbums(self, tree, node, fakeChild):
        """ Initial load of all albums of the given node, assuming it is of type TYPE_ARTIST """
        allAlbums = pickleLoad(os.path.join(tree.getItem(node, ROW_FULLPATH), 'albums'))
        path      = tree.getItem(node, ROW_FULLPATH)
        rows      = [(consts.icoMediaDir, '[%s]' % tools.sec2str(album[ALB_LENGTH], True), '%s' % cgi.escape(album[ALB_NAME]), TYPE_ALBUM, os.path.join(path, album[ALB_INDEX]), None) for album in allAlbums]

        # Add all the rows, and then add a fake child to each of them
        tree.freeze_child_notify()
        tree.appendRows(rows, node)
        tree.removeRow(fakeChild)
        for child in tree.iterChildren(node):
            tree.appendRow((None, None, '', TYPE_NONE, '', None), child)
        tree.thaw_child_notify()
Beispiel #8
0
    def getFromCache(self, artist, album):
        """ Return the path to the cached cover, or None if it's not cached """
        cachePath    = os.path.join(self.cacheRootPath, str(abs(hash(artist))))
        cacheIdxPath = os.path.join(cachePath, 'INDEX')

        try:
            cacheIdx = tools.pickleLoad(cacheIdxPath)
            cover    = os.path.join(cachePath, cacheIdx[artist + album])
            if os.path.exists(cover):
                return cover
        except:
            pass

        return None
    def onAppStarted(self):
        """ Try to fill the playlist by using the files given on the command line or by restoring the last playlist """
        (options, args)    = prefs.getCmdLine()
        self.savedPlaylist = os.path.join(consts.dirCfg, 'saved-playlist.txt')

        if len(args) != 0:
            log.logger.info('[%s] Filling playlist with files given on command line' % MOD_NAME)
            modules.postMsg(consts.MSG_CMD_TRACKLIST_SET, {'tracks': media.getTracks(args), 'playNow': True})
        else:
            try:
                tracks = [media.track.unserialize(serialTrack) for serialTrack in tools.pickleLoad(self.savedPlaylist)]
                modules.postMsg(consts.MSG_CMD_TRACKLIST_SET, {'tracks': tracks, 'playNow': False})
                log.logger.info('[%s] Restored playlist' % MOD_NAME)
            except:
                log.logger.error('[%s] Unable to restore playlist from %s\n\n%s' % (MOD_NAME, self.savedPlaylist, traceback.format_exc()))
Beispiel #10
0
    def refreshLibrary(self, parent, libName, path, creation=False):
        """ Refresh the given library, must be called through idle_add() """
        # First show a progress dialog
        if creation: header = _('Creating library')
        else:        header = _('Refreshing library')

        progress = 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()

            if track.hasAlbumArtist(): artist = track.getAlbumArtist()
            else:                      artist = track.getArtist()

            if artist in db:
                allAlbums = db[artist]
                if album in allAlbums: allAlbums[album].append(track)
                else:                  allAlbums[album] = [track]
            else:
                db[artist] = {album: [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

        # 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(db[artist])) for indexArtist, artist in enumerate(db)], key = lambda a: a[0])
        pickleSave(os.path.join(libPath, 'files'),   newLibrary)
        pickleSave(os.path.join(libPath, 'artists'), allArtists)

        for (artist, indexArtist, nbAlbums) in allArtists:
            artistPath       = os.path.join(libPath, indexArtist)
            overallNbAlbums += nbAlbums
            os.mkdir(artistPath)

            albums = []
            for index, (name, 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()))

            albums.sort(cmp = lambda a1, a2: cmp(db[artist][a1[0]][0], db[artist][a2[0]][0]))
            pickleSave(os.path.join(artistPath, 'albums'), albums)
            progress.pulse()
            yield True

        self.libraries[libName] = (path, overallNbArtists, overallNbAlbums, overallNbTracks)
        self.fillLibraryList()
        if creation:
            modules.postMsg(consts.MSG_CMD_EXPLORER_ADD, {'modName': MOD_L10N, 'expName': libName, 'icon': None, 'widget': self.scrolled})
        progress.destroy()

        # If the refreshed library is currently displayed, refresh the treeview as well
        if self.currLib == libName:
            treeState = self.tree.saveState(ROW_NAME)
            self.loadLibrary(self.tree, self.currLib)
            self.tree.restoreState(treeState, ROW_NAME)

        yield False
Beispiel #11
0
 def getDiscFromCache(self, discInfo):
     """ Return CDDB information from the cache, or None if that disc is not cached """
     try:    return tools.pickleLoad(os.path.join(self.cacheDir, str(discInfo[DISC_CHECKSUM])))
     except: return None
 def loadFavorites(self, libName):
     """ Load favorites from the disk """
     try:    return pickleLoad(os.path.join(ROOT_PATH, libName, 'favorites'))
     except: return {}
Beispiel #13
0
            data    = stream.read()

            if len(data) < 1024:
                raise Exception, 'The cover image seems incorrect (%u bytes is too small)' % len(data)
        except:
            logger.error('[%s] Cover image request failed\n\n%s' % (MOD_NAME, traceback.format_exc()))
            return None

        # So far, so good: let's cache the image
        cachePath    = os.path.join(self.cacheRootPath, str(abs(hash(artist))))
        cacheIdxPath = os.path.join(cachePath, 'INDEX')

        if not os.path.exists(cachePath):
            os.mkdir(cachePath)

        try:    cacheIdx = tools.pickleLoad(cacheIdxPath)
        except: cacheIdx = {}

        nextInt   = len(cacheIdx) + 1
        filename  = str(nextInt) + coverFormat
        coverPath = os.path.join(cachePath, filename)

        cacheIdx[artist + album] = filename
        tools.pickleSave(cacheIdxPath, cacheIdx)

        try:
            output = open(coverPath, 'wb')
            output.write(data)
            output.close()
            return coverPath
        except:
Beispiel #14
0
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA

import os, threading, tools


# Load user preferences from the disk
try:    __usrPrefs = tools.pickleLoad(tools.consts.filePrefs)
except: __usrPrefs = {}

__mutex      = threading.Lock() # Prevent concurrent calls to functions
__appGlobals = {}               # Some global values shared by all the components of the application


def save():
    """ Save user preferences to the disk """
    __mutex.acquire()
    tools.pickleSave(tools.consts.filePrefs, __usrPrefs)
    os.chmod(tools.consts.filePrefs, 0600)
    __mutex.release()


def set(module, name, value):