Example #1
0
 def __init__(self, db_url=cfg['COUCHDB_URL'],
               db_username=cfg['COUCHDB_USER'],
               db_password=cfg['COUCHDB_PASSWORD']):
     """Sets up the database connection and starts loading songs."""
     # Initiate a connection to the database server
     self.shutting_down = threading.Event()
     self.building_cache = threading.Event()
     logger.debug("Initiating the database connection.")
     filters = []
     if db_username or db_password:
         filters.append(BasicAuth(db_username, db_password))
     self._server = Server(db_url, filters=filters)
     connect_retries = 0
     while True:
         try:
             # Get a reference to our database
             self.db = self._server.get_or_create_db(cfg['DATABASE_NAME'])
             break
         except Exception as e:
             logger.error(str(e))
             if connect_retries < 3:
                 logger.error("Error connecting to CouchDB.")
                 connect_retries += 1
                 sleep(3)
             else:
                 logger.error("Could not connect to CouchDB. Quitting.")
                 sys.exit(1)
     logger.debug("Loading database design documents.")
     # Load our database views from the filesystem
     loader = FileSystemDocsLoader(os.path.join(cfg['ASSETS_DIR'],
                                   'views/_design'))
     try:
         loader.sync(self.db, verbose=True)
     except:
         pass
     logger.debug("Initializing the database cache.")
     self.cache = BlofeldCache(self.db)
     self.scanner = Scanner(cfg['MUSIC_PATH'], self.db)
     self.update()
Example #2
0
class Library:
    """This is the Blofeld library module. It handles loading songs from the
    various backends (such as the filesystem or Rhythmbox) and inserting
    their metadata into the database. It also handles making calls to the
    database.
    """
    def __init__(self, db_url=cfg['COUCHDB_URL'],
                  db_username=cfg['COUCHDB_USER'],
                  db_password=cfg['COUCHDB_PASSWORD']):
        """Sets up the database connection and starts loading songs."""
        # Initiate a connection to the database server
        self.shutting_down = threading.Event()
        self.building_cache = threading.Event()
        logger.debug("Initiating the database connection.")
        filters = []
        if db_username or db_password:
            filters.append(BasicAuth(db_username, db_password))
        self._server = Server(db_url, filters=filters)
        connect_retries = 0
        while True:
            try:
                # Get a reference to our database
                self.db = self._server.get_or_create_db(cfg['DATABASE_NAME'])
                break
            except Exception as e:
                logger.error(str(e))
                if connect_retries < 3:
                    logger.error("Error connecting to CouchDB.")
                    connect_retries += 1
                    sleep(3)
                else:
                    logger.error("Could not connect to CouchDB. Quitting.")
                    sys.exit(1)
        logger.debug("Loading database design documents.")
        # Load our database views from the filesystem
        loader = FileSystemDocsLoader(os.path.join(cfg['ASSETS_DIR'],
                                      'views/_design'))
        try:
            loader.sync(self.db, verbose=True)
        except:
            pass
        logger.debug("Initializing the database cache.")
        self.cache = BlofeldCache(self.db)
        self.scanner = Scanner(cfg['MUSIC_PATH'], self.db)
        self.update()

    def update(self, verbose=False, ticket=None):
        """Figures out which backend to load and then updates the database"""
        if ticket:
            status = self.scanner.jobs[self.scanner.current_job]
            if self.scanner.updating.is_set():
                total_time = time() - self.scanner.start_time
            else:
                total_time = status['total_time']
            minutes = total_time / 60
            hours = minutes / 60
            if hours:
                minutes = minutes % 60
            seconds = total_time % 60
            status['total_time'] = "%02d:%02d:%02d" % (hours, minutes, seconds)
            return status
        else:
            ticket = self.scanner.update()
            self.build_cache()
            self.scanner.updating.wait(None)
            return ticket

    def stop(self):
        logger.info("Preventing new library calls.")
        self.shutting_down.set()
        self.scanner.stop()

    def build_cache(self):
        if not self.building_cache.is_set():
            self.cache_thread = threading.Thread(target=self._build_cache)
            self.cache_thread.start()
        else:
            logger.debug("Database cache build requested, but already in progress.")

    def _build_cache(self):
        if self.scanner.updating.is_set():
            self.scanner.finished.wait(None)
            self.scanner.finished.clear()
        logger.debug("Building the database cache.")
        self.building_cache.set()
        start_time = time()
        if not (self.shutting_down.is_set() or self.scanner.updating.is_set()):
            self.artists()
        if not (self.shutting_down.is_set() or self.scanner.updating.is_set()):
            self.albums()
        if not (self.shutting_down.is_set() or self.scanner.updating.is_set()):
            self.songs(suggest="The")
        if not (self.shutting_down.is_set() or self.scanner.updating.is_set()):
            self.artists(query="The")
        if not (self.shutting_down.is_set() or self.scanner.updating.is_set()):
            self.albums(query="The")
        if not (self.shutting_down.is_set() or self.scanner.updating.is_set()):
            self.songs(query="The")
        self.building_cache.clear()
        if not (self.shutting_down.is_set() or self.scanner.updating.is_set()):
            logger.debug("Finished building the database cache in %0.2d seconds." % (time() - start_time))
        else:
            logger.debug("Database cache building was interrupted after %0.2d seconds." % (time() - start_time))

    def songs(self, artists=None, albums=None, query=None, suggest=None):
        '''Returns a list of songs as dictionary objects.'''
        logger.debug("Generating song list.")
        start_time = time()
        # Create a list to hold songs we're going to return to the client
        result = []
        if suggest:
            if query:
                query = utils.clean_text(query)
                for song in self.cache.view('search/suggestion', group="true"):
                    if utils.clean_text(song['key']).startswith(query):
                        result.append(song['key'])
                        if len(result) == 10:
                            break
            else:
                for song in self.cache.view('search/suggestion', group="true"):
                    result.append(song['key'])
            finish_time = time() - start_time
            logger.debug("Generated list of %d songs in %0.2f seconds." % (len(result), finish_time))
            return result
        # If the client didn't supply any arguments, we just get all the songs
        # from the database
        if not query and not artists and not albums:
            for song in self.cache.view('songs/all'):
                result.append(song['value'])
        if artists:
            # The client only wants songs by a specific artist(s)
            for artist in artists:
                for song in self.cache.view('songs/by_artist_id', key=artist):
                    result.append(song['value'])
        if albums:
            # The client only wants songs from a specific album(s). We need
            # to see if we've already got some songs we just need to filter or
            # whether we need to grab some from the database.
            if result:
                temp_result = []
                # Look through our existing results and add the ones that are
                # from the specified album(s) to our temporary list.
                for song in result:
                    if song['album_hash'] in albums:
                        temp_result.append(song)
                # Replace the existing results with our refined ones
                result = temp_result
            # Since our results are empty, make sure that's not because the
            # search just didn't have any results, in which case we want to
            # return an empty list.
            elif not artists:
                # Grab the songs from the specified album(s) from the database
                for album in albums:
                    for song in self.cache.view('songs/by_album_id', key=album):
                        result.append(song['value'])
        if query:
            # Clean up the search string
            query = utils.clean_text(query).split(' ')
            # Figure out whether we already have some songs we need to filter
            # or if we need to grab them all from the database.
            if not (albums or artists):
                # Get all the songs from the database.
                for song in self.cache.view('songs/all'):
                    # Create a searchable string by joining together the artist,
                    # album and title fields and cleaning them up.
                    search_field = utils.clean_text(";".join([song['key'][0],
                                              song['key'][1], song['key'][3]]))
                    match = True
                    for term in query:
                        if term not in search_field:
                            match = False
                    if match:
                        result.append(song['value'])
            elif result:
                temp_result = []
                # Look through our existing results for any songs that contain
                # our search term.
                for song in result:
                    search_field = utils.clean_text(";".join([song['artist'],
                                                song['album'], song['title']]))
                    match = True
                    for term in query:
                        if term not in search_field:
                            match = False
                    if match:
                        temp_result.append(song)
                # Replace the existing results with the filtered ones.
                result = temp_result
        finish_time = time() - start_time
        logger.debug("Generated list of %d songs in %0.2f seconds." % (len(result), finish_time))
        return sorted(result, key=itemgetter('artist', 'album',
                                               'tracknumber'))

    def albums(self, artists=None, query=None):
        '''Returns a list of albums as dictionary objects.'''
        logger.debug("Generating album list.")
        start_time = time()
        # Create a list to hold the albums we're going to return to the client
        result = []
        # If the client didn't give any arguments, get all the artists from the
        # database and append them to our results list.
        if not artists and not query:
            for album in self.cache.view('albums/all', group="true"):
                result.append({'id': album['value'], 'title': album['key']})
        if query and artists:
            # Clean up the search term
            query = utils.clean_text(query).split(' ')
            for artist in artists:
                # Get all the albums from the database using the search view
                for album in self.cache.view('albums/search', key=artist):
                    # Create an object that we can append to the results list
                    entry = {
                            'id': album['value']['album_hash'],
                            'title': album['value']['album']
                            }
                    if entry in result:
                        continue
                    match = True
                    for term in query:
                        if term not in utils.clean_text(album['value']['search_string']):
                            match = False
                    if match:
                        result.append(entry)
        if query and not artists:
            # Clean up the search term
            query = utils.clean_text(query).split(' ')
            # Get all the albums from the database using the search view
            for album in self.cache.view('albums/search'):
                # Create an object that we can append to the results list
                entry = {
                    'id': album['value']['album_hash'],
                    'title': album['value']['album']
                    }
                if entry in result:
                    continue
                # Clean up the search field and see if our search term is
                # in it. If it is, make sure it's not a duplicate result
                # and then append it to the results list.
                match = True
                for term in query:
                    if term not in utils.clean_text(album['value']['search_string']):
                        match = False
                if match:
                    result.append(entry)
        if artists and not query:
            # Client asked for albums by a specific artist(s), so get only
            # those albums from the database.
            for artist_hash in artists:
                for album in self.cache.view('albums/by_artist_id',
                                            key=artist_hash):
                    # Create an object to append to our results list
                    entry = {
                        'id': album['value']['album_hash'],
                        'title': album['value']['album']
                        }
                    # Get the artist ID
                    artist = album['key']
                    # Make sure this isn't a duplicate result
                    if entry not in result:
                        result.append(entry)
        finish_time = time() - start_time
        logger.debug("Generated list of %d albums in %0.2f seconds." % (len(result), finish_time))
        return sorted(result, key=itemgetter('title'))

    def artists(self, query=None):
        '''Returns a list of artists as dictionary objects.'''
        logger.debug("Generating artist list.")
        start_time = time()
        # Make a list to hold the results of our search
        result = []
        if query:
            # Clean up the search term
            query = utils.clean_text(query).split(' ')
            # Get all the artists from the database using the search view,
            # the key of which is a list consisting of [artist, album, title].
            # This is done so that the results will be alphabetized by artist
            # name and also so we have those fields to search.
            for artist in self.cache.view('artists/search'):
                # Create an object to append to our results
                entry = {
                    'id': artist['value'],
                    'name': artist['key'][0]
                    }
                if entry in result:
                    continue
                search_field =  utils.clean_text(';'.join(artist['key']))
                match = True
                for term in query:
                    if term not in search_field:
                        match = False
                if match:
                    result.append(entry)
        else:
            # Get all the artists from the database and append them to the
            # results list.
            for artist in self.cache.view('artists/all', group="true"):
                entry = {
                    'id': artist['value'],
                    'name': artist['key']
                    }
                result.append(entry)
        finish_time = time() - start_time
        logger.debug("Generated list of %d artists in %0.2f seconds." % (len(result), finish_time))
        return result