Example #1
0
    def __init__(self, session, artist):
        self.__condition_list = ConditionList()
        self.__session = session
        self.__artist = artist
        self.__album_data = {}
        self.__is_loaded = False
        self.__loader_task = None

        #Avoid locking this thread and continue in another one
        self.continue_in_background()
Example #2
0
    def __init__(self, session, container, playlist_manager):
        self.__session = session
        self.__container = container
        self.__playlist_manager = playlist_manager
        self.__playlists = []
        self.__conditions = ConditionList()
        self.__loader_lock = threading.RLock()
        self.__list_lock = threading.RLock()
        self.__is_loaded = False

        #Register the callbacks
        self.__container.add_callbacks(ContainerCallbacks(self))

        #Load the rest in the background
        self.load_in_background()
Example #3
0
 def __init__(self, session, artist):
     self.__condition_list = ConditionList()
     self.__session = session
     self.__artist = artist
     self.__album_data = {}
     self.__is_loaded = False
     self.__loader_task = None
     
     #Avoid locking this thread and continue in another one
     self.continue_in_background()
Example #4
0
    def __init__(self, session, playlist, playlist_manager):

        #Initialize all instance vars
        self.__playlist = playlist
        self.__playlist_manager = playlist_manager
        self.__conditions = ConditionList()
        self.__loader_lock = threading.Lock()
        self.__is_loaded = False
        self.__has_errors = False
        self.__thumbnails = []

        #Fire playlist loading if neccesary
        if not playlist.is_in_ram(session):
            playlist.set_in_ram(session, True)

        #Add the playlist callbacks
        self.__playlist.add_callbacks(PlaylistCallbacks(self))

        #Finish the rest in the background
        self.load_in_background()
Example #5
0
 def __init__(self, session, container, playlist_manager):
     self.__session = session
     self.__container = container
     self.__playlist_manager = playlist_manager
     self.__playlists = []
     self.__conditions = ConditionList()
     self.__loader_lock = threading.RLock()
     self.__list_lock = threading.RLock()
     self.__is_loaded = False
     
     #Register the callbacks
     self.__container.add_callbacks(ContainerCallbacks(self))
     
     #Load the rest in the background
     self.load_in_background()
Example #6
0
 def __init__(self, session, playlist, playlist_manager):
     
     #Initialize all instance vars
     self.__playlist = playlist
     self.__playlist_manager = playlist_manager
     self.__conditions = ConditionList()
     self.__loader_lock = threading.Lock()
     self.__is_loaded = False
     self.__has_errors = False
     self.__thumbnails = []
     
     #Fire playlist loading if neccesary
     if not playlist.is_in_ram(session):
         playlist.set_in_ram(session, True)
     
     #Add the playlist callbacks
     self.__playlist.add_callbacks(PlaylistCallbacks(self))
     
     #Finish the rest in the background
     self.load_in_background()
Example #7
0
class ArtistAlbumLoader:
    __condition_list = None
    __session = None
    __artist = None
    __album_data = None
    __artistbrowse = None
    __is_loaded = None
    __sorted_albums = None
    __loader_task = None
    
    
    def __init__(self, session, artist):
        self.__condition_list = ConditionList()
        self.__session = session
        self.__artist = artist
        self.__album_data = {}
        self.__is_loaded = False
        self.__loader_task = None
        
        #Avoid locking this thread and continue in another one
        self.continue_in_background()
    
    
    def check(self):
        self.__loader_task.notify()
    
    
    def _wait_for_album_info(self, album_info):
        def info_is_loaded():
            return album_info.is_loaded()
        
        if not info_is_loaded():
            current_task().condition_wait(info_is_loaded, 10)
    
    
    def _num_available_tracks(self, album_info):
        count = 0
        
        #Return true if it has at least one playable track
        for track in album_info.tracks():
            track_status = track.get_availability(self.__session)
            if track_status == TrackAvailability.Available:
                count += 1
        
        return count
    
    
    def _is_same_artist(self, artist1, artist2):
        album1_str = link.create_from_artist(artist1).as_string()
        album2_str = link.create_from_artist(artist2).as_string()
        
        return album1_str == album2_str
    
    
    def _get_album_type(self, album):
        if album.type() == SpotifyAlbumType.Single:
            return AlbumType.Single
        
        elif album.type() == SpotifyAlbumType.Compilation:
            return AlbumType.Compilation
        
        if not self._is_same_artist(self.__artist, album.artist()):
            return AlbumType.AppearsIn
        
        else:
            return AlbumType.Album
    
    
    @run_in_thread(group='load_artist_albums', max_concurrency=5)
    def load_album_info(self, index, album):
        
        #Directly discard unavailable albums
        if not album.is_available():
            self.__album_data[index] = {
                'available_tracks': 0,
                'type': self._get_album_type(album),
            }
        
        #Otherwise load it's data
        else:
            cb = AlbumCallbacks(current_task())
            album_info = albumbrowse.Albumbrowse(self.__session, album, cb)
            
            #Now wait until it's loaded
            self._wait_for_album_info(album_info)
            
            #Populate it's data
            self.__album_data[index] = {
                'available_tracks': self._num_available_tracks(album_info),
                'type': self._get_album_type(album),
            }
            
            #Tell that we've done
            self.check()
        
        
    def _wait_for_album_list(self):
        
        #Add the artistbrowse callbacks
        self.__artistbrowse = artistbrowse.Artistbrowse(
            self.__session, self.__artist,
            BrowseType.NoTracks, ArtistCallbacks(self)
        )
        
        if not self.__artistbrowse.is_loaded():
            current_task().condition_wait(
                self.__artistbrowse.is_loaded, 60 #Should be enough?
            )
    
    
    def _add_album_processed_check(self, index):
        def album_is_processed():
            return index in self.__album_data
        
        if not album_is_processed():
            self.__condition_list.add_condition(album_is_processed)
    
    
    @run_in_thread(group='load_artist_albums',max_concurrency=1)
    def continue_in_background(self):
        
        #Set the reference to the current task
        self.__loader_task = current_task()
        
        #Wait until the album list got loaded
        self._wait_for_album_list()
        
        #Now load albumbrowse data from each one
        for index, album in enumerate(self.__artistbrowse.albums()):
            
            #Add a condition for the next wait
            self._add_album_processed_check(index)
            
            #Start loading the info in the background
            self.load_album_info(index, album)
        
        #Now wait until all info gets loaded
        current_task().condition_wait(self.__condition_list, 60)
        
        #Final steps...
        self.__is_loaded = True
        xbmc.executebuiltin("Action(Noop)")
    
    
    def is_loaded(self):
        return self.__is_loaded
    
    
    def get_album_available_tracks(self, index):
        return self.__album_data[index]['available_tracks']
    
    
    def get_album_type(self, index):
        return self.__album_data[index]['type']
    
    
    def get_album(self, index):
        return self.__artistbrowse.album(index)
    
    
    def get_non_similar_albums(self):
        name_dict = {}
        
        for index, album in self.get_albums():
            name = album.name()
            available_tracks = self.get_album_available_tracks(index)
            
            #If that name is new to us just store it
            if name not in name_dict:
                name_dict[name] = (index, available_tracks)
            
            #If the album has more playable tracks than the stored one 
            elif available_tracks > name_dict[name][1]:
                name_dict[name] = (index, available_tracks)
        
        #Now return the list if indexes
        return [item[0] for item in name_dict.itervalues()]
    
    
    def get_albums(self):
        def sort_func(album_index):
            #Sort by album type and then by year (desc)
            return (
                self.get_album_type(album_index),
                -self.__artistbrowse.album(album_index).year()
            )
        
        #Do nothing if is loading
        if self.is_loaded():
            #Build the sorted album list if needed
            if self.__sorted_albums is None:
                album_indexes = self.__album_data.keys()
                sorted_indexes = sorted(album_indexes, key=sort_func)
                ab = self.__artistbrowse
                self.__sorted_albums = [
                    (index, ab.album(index)) for index in sorted_indexes
                ]
            
            return self.__sorted_albums
Example #8
0
class BasePlaylistLoader:
    __playlist = None
    __playlist_manager = None
    __conditions = None
    __loader_task = None
    __loader_lock = None
    
    
    #Playlist attributes
    __name = None
    __num_tracks = None
    __thumbnails = None
    __is_collaborative = None
    __is_loaded = None
    __has_errors = None
    __has_changes = None
    
    
    def __init__(self, session, playlist, playlist_manager):
        
        #Initialize all instance vars
        self.__playlist = playlist
        self.__playlist_manager = playlist_manager
        self.__conditions = ConditionList()
        self.__loader_lock = threading.Lock()
        self.__is_loaded = False
        self.__has_errors = False
        self.__thumbnails = []
        
        #Fire playlist loading if neccesary
        if not playlist.is_in_ram(session):
            playlist.set_in_ram(session, True)
        
        #Add the playlist callbacks
        self.__playlist.add_callbacks(PlaylistCallbacks(self))
        
        #Finish the rest in the background
        self.load_in_background()
    
    
    @run_in_thread(group='load_playlists', max_concurrency=10)
    def load_in_background(self):
        
        #Avoid entering this loop multiple times
        if self.__loader_lock.acquire(False):
            try:
                
                #Set the current task object
                self.__loader_task = current_task()
                
                #Reset everyting
                self._set_changes(False)
                self._set_error(False)
                
                #And call the method that does the actual loading task
                self._load()
            
            except:
                
                #Set the playlist's error flag
                self._set_error(True)
            
            finally:
                
                #Release and clear everything
                self.__loader_task = None
                self.__loader_lock.release()
                
                #If changes or errors were detected...
                if self.has_changes() or self.has_errors():
                    self.end_loading()
    
    
    def get_playlist(self):
        return self.__playlist
    
    
    def _set_thumbnails(self, thumbnails):
        self.__thumbnails = thumbnails
    
    
    def get_thumbnails(self):
        return self.__thumbnails
    
    
    def _set_name(self, name):
        self.__name = name
    
    
    def get_name(self):
        return self.__name
    
    
    def get_num_tracks(self):
        return self.__num_tracks
    
    
    def get_tracks(self):
        return self.__playlist.tracks()
    
    
    def get_track(self, index):
        track_list = self.get_tracks()
        return track_list[index]
    
    
    def get_is_collaborative(self):
        return self.__is_collaborative
    
    
    def _track_is_ready(self, track, test_album=True, test_artists=True):
        def album_is_loaded():
            album = track.album()
            return album is not None and album.is_loaded()
        
        def artists_are_loaded():
            for item in track.artists():
                if item is None or not item.is_loaded():
                    return False
            return True
        
        #If track has an error stop further processing
        if track.error() not in [ErrorType.Ok, ErrorType.IsLoading]:
            return True
        
        #Always test for the track data
        if not track.is_loaded():
            return False
        
        #If album data was requested
        elif test_album and not album_is_loaded():
            return False
        
        #If artist data was requested
        elif test_artists and not artists_are_loaded():
            return False
        
        #Otherwise everything was ok
        else:
            return True
    
    
    def _wait_for_playlist(self):
        if not self.__playlist.is_loaded():
            self.__conditions.add_condition(self.__playlist.is_loaded)
            current_task.condition_wait(self.__conditions, 10)
    
    
    def _wait_for_track_metadata(self, track):
        def test_is_loaded():
            return self._track_is_ready(
                track, test_album=True, test_artists=False
            )
        
        if not test_is_loaded():
            self.__conditions.add_condition(test_is_loaded)
            current_task.condition_wait(self.__conditions, 10)
    
    
    def _load_thumbnails(self):
        pm = self.__playlist_manager
        
        #If playlist has an image
        playlist_image = self.__playlist.get_image()
        if playlist_image is not None:
            thumbnails = [pm.get_image_url(playlist_image)]
        
        #Otherwise get them from the album covers
        else:
            thumbnails = []
            for item in self.__playlist.tracks():
                #Wait until this track is fully loaded
                self._wait_for_track_metadata(item)
                
                #Check if item was loaded without errors
                if item.is_loaded() and item.error() == 0:
                    #Append the cover if it's new
                    image_id = item.album().cover()
                    image_url = pm.get_image_url(image_id)
                    if image_url not in thumbnails:
                        thumbnails.append(image_url)
                    
                    #If we reached to the desired thumbnail count...
                    if len(thumbnails) == 4:
                        break
        
        #If the thumnbail count is still zero...
        if len(thumbnails) == 0:
            self.__thumbnails = ['common/pl-default.png']
            return True
        
        #If the stored thumbnail data changed...
        if self.__thumbnails != thumbnails:
            self.__thumbnails = thumbnails
            return True
    
    
    def _load_name(self):
        if self.__name != self.__playlist.name():
            self.__name = self.__playlist.name()
            return True
        else:
            return False
    
    
    def _load_num_tracks(self):
        if self.__num_tracks != self.__playlist.num_tracks():
            self.__num_tracks = self.__playlist.num_tracks()
            return True
        else:
            return False
    
    
    def _load_is_collaborative(self):
        if self.__is_collaborative != self.__playlist.is_collaborative():
            self.__is_collaborative = self.__playlist.is_collaborative()
            return True
        else:
            return False
    
    
    def _load_attributes(self):
        #Now check for changes
        has_changes = False
        
        if self._load_name():
            has_changes = True
        
        if self._load_num_tracks():
            has_changes = True
        
        if self._load_is_collaborative():
            has_changes = True
        
        #If we detected something different
        return has_changes
    
    
    def _add_condition(self, condition):
        self.__conditions.add_condition(condition)
   
   
    def _wait_for_conditions(self, timeout):
        current_task().condition_wait(self.__conditions, timeout)
    
    
    def check(self):
        
        #If a loading process was not active, start a new one
        if self.__loader_lock.acquire(False):
            try:
                self.load_in_background()
            finally:
                self.__loader_lock.release()
        
        #Otherwise notify the task
        else:
            try:
                self.__loader_task.notify()
            except:
                pass
    
    
    def _set_loaded(self, status):
        self.__is_loaded = status 
    
    
    def is_loaded(self):
        return self.__is_loaded
    
    
    def _set_error(self, status):
        self.__has_errors = status
        
    
    def has_errors(self):
        return self.__has_errors
    
    
    def _set_changes(self, status):
        self.__has_changes = status
    
    
    def has_changes(self):
        return self.__has_changes
    
    
    def _load(self):
        raise NotImplementedError()
    
    
    def end_loading(self):
        pass
Example #9
0
class ContainerLoader:
    __session = None
    __container = None
    __playlist_manager = None
    __playlists = None
    __checker = None
    __loader_task = None
    __loader_lock = None
    __list_lock = None
    __is_loaded = None
    
    
    def __init__(self, session, container, playlist_manager):
        self.__session = session
        self.__container = container
        self.__playlist_manager = playlist_manager
        self.__playlists = []
        self.__conditions = ConditionList()
        self.__loader_lock = threading.RLock()
        self.__list_lock = threading.RLock()
        self.__is_loaded = False
        
        #Register the callbacks
        self.__container.add_callbacks(ContainerCallbacks(self))
        
        #Load the rest in the background
        self.load_in_background()
    
    
    def _fill_spaces(self, position):
        try:
            self.__list_lock.acquire()
            
            if position >= len(self.__playlists):
                for idx in range(len(self.__playlists), position + 1):
                    self.__playlists.append(None)
        
        finally:
            self.__list_lock.release()
    
    
    def is_playlist(self, position):
        playlist_type = self.__container.playlist_type(position)
        return playlist_type == playlist.PlaylistType.Playlist
    
    
    def add_playlist(self, playlist, position):
        try:
            self.__list_lock.acquire()
            
            #Ensure that it gets added in the correct position
            self._fill_spaces(position - 1)
            
            #Instantiate a loader if it's a real playlist
            if self.is_playlist(position):
                item = ContainerPlaylistLoader(
                    self.__session, playlist, self.__playlist_manager, self 
                )
            
            #Ignore if it's not a real playlist
            else:
                item = None
            
            #Insert the generated item
            self.__playlists.insert(position, item)
                
        
        finally:
            self.__list_lock.release()
    
    
    def remove_playlist(self, position):
        try:
            self.__list_lock.acquire()
            del self.__playlists[position]
        
        finally:
            self.__list_lock.release()
    
    
    def move_playlist(self, position, new_position):
        try:
            self.__list_lock.acquire()
            self.__playlists.insert(new_position, self.__playlists[position])
            
            #Calculate new position
            if position > new_position:
                position += 1
            
            del self.__playlists[position]
        
        finally:
            self.__list_lock.release()
    
    
    def _add_missing_playlists(self):
        
        #Ensure that the container and loader length is the same
        self._fill_spaces(self.__container.num_playlists() - 1)
        
        #Iterate over the container to add the missing ones
        for pos, item in enumerate(self.__container.playlists()):
            
            #Check if we should continue
            current_task().check_status()
            
            if self.is_playlist(pos) and self.__playlists[pos] is None:
                self.add_playlist(item, pos)
    
    
    def _check_playlist(self, playlist):
        def is_playlist_loaded():
            #If it has errors, say yes.
            if playlist.has_errors():
                return True
            
            #And if it was loaded, say yes
            if playlist.is_loaded():
                return True
        
        self.__conditions.add_condition(is_playlist_loaded)
    
    
    def _load_container(self):
        
        #Wait for the container to be fully loaded
        self.__conditions.add_condition(self.__container.is_loaded)
        current_task().condition_wait(self.__conditions)
        
        #Fill the container with unseen playlists
        self._add_missing_playlists()
        
        #Add a load check for each playlist
        for item in self.__playlists:
            if item is not None and not item.is_loaded():
                self._check_playlist(item)
        
        #Wait until all conditions become true
        current_task().condition_wait(
            self.__conditions, self.__container.num_playlists() * 5
        )
        
        #Set the status of the loader
        self.__is_loaded = True
        
        #Check and log errors for not loaded playlists
        for idx, item in enumerate(self.__playlists):
            if item is not None and item.has_errors():
                get_logger().error('Playlist #%s failed loading.' % idx)
        
        #Finally tell the gui we are done
        xbmc.executebuiltin("Action(Noop)")
    
    
    @run_in_thread(group='load_playlists', max_concurrency=10)
    def load_in_background(self):
        
        #Avoid entering here multiple times
        if self.__loader_lock.acquire(False):
            try:
                
                #Set the current task object
                self.__loader_task = current_task()
                
                #And take care of the rest
                self._load_container()
            
            finally:
                
                #Release and clear everything
                self.__loader_task = None
                self.__loader_lock.release()
    
    
    def check(self):
        
        #If a loading process was not active, start a new one
        if self.__loader_lock.acquire(False):
            try:
                self.load_in_background()
            finally:
                self.__loader_lock.release()
        
        #Otherwise notify the task
        else:
            try:
                self.__loader_task.notify()
            except:
                pass
    
    
    def is_loaded(self):
        return self.__is_loaded
    
    
    def playlist(self, index):
        return self.__playlists[index]
    
    
    def num_playlists(self):
        return len(self.__playlists)
    
    
    def playlists(self):
        return CallbackIterator(self.num_playlists, self.playlist)
    
    
    def get_container(self):
        return self.__container
Example #10
0
class ArtistAlbumLoader:
    __condition_list = None
    __session = None
    __artist = None
    __album_data = None
    __artistbrowse = None
    __is_loaded = None
    __sorted_albums = None
    __loader_task = None

    def __init__(self, session, artist):
        self.__condition_list = ConditionList()
        self.__session = session
        self.__artist = artist
        self.__album_data = {}
        self.__is_loaded = False
        self.__loader_task = None

        #Avoid locking this thread and continue in another one
        self.continue_in_background()

    def check(self):
        self.__loader_task.notify()

    def _wait_for_album_info(self, album_info):
        def info_is_loaded():
            return album_info.is_loaded()

        if not info_is_loaded():
            current_task().condition_wait(info_is_loaded, 10)

    def _num_available_tracks(self, album_info):
        count = 0

        #Return true if it has at least one playable track
        for track in album_info.tracks():
            track_status = track.get_availability(self.__session)
            if track_status == TrackAvailability.Available:
                count += 1

        return count

    def _is_same_artist(self, artist1, artist2):
        album1_str = link.create_from_artist(artist1).as_string()
        album2_str = link.create_from_artist(artist2).as_string()

        return album1_str == album2_str

    def _get_album_type(self, album):
        if album.type() == SpotifyAlbumType.Single:
            return AlbumType.Single

        elif album.type() == SpotifyAlbumType.Compilation:
            return AlbumType.Compilation

        if not self._is_same_artist(self.__artist, album.artist()):
            return AlbumType.AppearsIn

        else:
            return AlbumType.Album

    @run_in_thread(group='load_artist_albums', max_concurrency=5)
    def load_album_info(self, index, album):

        # Directly discard unavailable albums
        if not album.is_available():
            self.__album_data[index] = {
                'available_tracks': 0,
                'type': self._get_album_type(album),
            }

        # Otherwise load its data
        else:
            cb = AlbumCallbacks(current_task())
            album_info = albumbrowse.Albumbrowse(self.__session, album, cb)

            # Now wait until it's loaded
            self._wait_for_album_info(album_info)

            # Populate its data
            self.__album_data[index] = {
                'available_tracks': self._num_available_tracks(album_info),
                'type': self._get_album_type(album),
            }

            # Tell that we're done
            self.check()

    def _wait_for_album_list(self):

        #Add the artistbrowse callbacks
        self.__artistbrowse = artistbrowse.Artistbrowse(
            self.__session, self.__artist,
            BrowseType.NoTracks, ArtistCallbacks(self)
        )

        if not self.__artistbrowse.is_loaded():
            current_task().condition_wait(
                self.__artistbrowse.is_loaded, 60  # Should be enough?
            )

    def _add_album_processed_check(self, index):
        def album_is_processed():
            return index in self.__album_data

        if not album_is_processed():
            self.__condition_list.add_condition(album_is_processed)

    @run_in_thread(group='load_artist_albums', max_concurrency=1)
    def continue_in_background(self):

        # Set the reference to the current task
        self.__loader_task = current_task()

        # Wait until the album list got loaded
        self._wait_for_album_list()

        # Now load albumbrowse data from each one
        for index, album in enumerate(self.__artistbrowse.albums()):

            # Add a condition for the next wait
            self._add_album_processed_check(index)

            # Start loading the info in the background
            self.load_album_info(index, album)

        # Now wait until all info gets loaded
        current_task().condition_wait(self.__condition_list, 60)

        # Final steps...
        self.__is_loaded = True
        xbmc.executebuiltin("Action(Noop)")

    def is_loaded(self):
        return self.__is_loaded

    def get_album_available_tracks(self, index):
        return self.__album_data[index]['available_tracks']

    def get_album_type(self, index):
        return self.__album_data[index]['type']

    def get_album(self, index):
        return self.__artistbrowse.album(index)

    def get_non_similar_albums(self):
        name_dict = {}

        for index, album in self.get_albums():
            name = album.name()
            available_tracks = self.get_album_available_tracks(index)

            # If that name is new to us just store it
            if name not in name_dict:
                name_dict[name] = (index, available_tracks)

            # If the album has more playable tracks than the stored one
            elif available_tracks > name_dict[name][1]:
                name_dict[name] = (index, available_tracks)

        # Now return the list if indexes
        return [item[0] for item in name_dict.itervalues()]

    def get_albums(self):
        def sort_func(album_index):
            # Sort by album type and then by year (desc)
            return (
                self.get_album_type(album_index),
                -self.__artistbrowse.album(album_index).year()
            )

        # Do nothing if is loading
        if self.is_loaded():
            # Build the sorted album list if needed
            if self.__sorted_albums is None:
                album_indexes = self.__album_data.keys()
                sorted_indexes = sorted(album_indexes, key=sort_func)
                ab = self.__artistbrowse
                self.__sorted_albums = [
                    (index, ab.album(index)) for index in sorted_indexes
                ]

            return self.__sorted_albums
Example #11
0
class BasePlaylistLoader:
    __playlist = None
    __playlist_manager = None
    __conditions = None
    __loader_task = None
    __loader_lock = None

    # Playlist attributes
    __name = None
    __num_tracks = None
    __thumbnails = None
    __is_collaborative = None
    __is_loaded = None
    __has_errors = None
    __has_changes = None

    def __init__(self, session, playlist, playlist_manager):

        #Initialize all instance vars
        self.__playlist = playlist
        self.__playlist_manager = playlist_manager
        self.__conditions = ConditionList()
        self.__loader_lock = threading.Lock()
        self.__is_loaded = False
        self.__has_errors = False
        self.__thumbnails = []

        #Fire playlist loading if neccesary
        if not playlist.is_in_ram(session):
            playlist.set_in_ram(session, True)

        #Add the playlist callbacks
        self.__playlist.add_callbacks(PlaylistCallbacks(self))

        #Finish the rest in the background
        self.load_in_background()

    @run_in_thread(group='load_playlists', max_concurrency=10)
    def load_in_background(self):

        #Avoid entering this loop multiple times
        if self.__loader_lock.acquire(False):
            try:

                #Set the current task object
                self.__loader_task = current_task()

                #Reset everyting
                self._set_changes(False)
                self._set_error(False)

                #And call the method that does the actual loading task
                self._load()

            except:

                #Set the playlist's error flag
                self._set_error(True)

            finally:

                #Release and clear everything
                self.__loader_task = None
                self.__loader_lock.release()

                #If changes or errors were detected...
                if self.has_changes() or self.has_errors():
                    self.end_loading()

    def get_playlist(self):
        return self.__playlist

    def _set_thumbnails(self, thumbnails):
        self.__thumbnails = thumbnails

    def get_thumbnails(self):
        return self.__thumbnails

    def _set_name(self, name):
        self.__name = name

    def get_name(self):
        return self.__name

    def get_num_tracks(self):
        return self.__num_tracks

    def get_tracks(self):
        return self.__playlist.tracks()

    def get_track(self, index):
        track_list = self.get_tracks()
        return track_list[index]

    def get_is_collaborative(self):
        return self.__is_collaborative

    def _track_is_ready(self, track, test_album=True, test_artists=True):
        def album_is_loaded():
            album = track.album()
            return album is not None and album.is_loaded()

        def artists_are_loaded():
            for item in track.artists():
                if item is None or not item.is_loaded():
                    return False
            return True

        #If track has an error stop further processing
        if track.error() not in [ErrorType.Ok, ErrorType.IsLoading]:
            return True

        #Always test for the track data
        if not track.is_loaded():
            return False

        #If album data was requested
        elif test_album and not album_is_loaded():
            return False

        #If artist data was requested
        elif test_artists and not artists_are_loaded():
            return False

        #Otherwise everything was ok
        else:
            return True

    def _wait_for_playlist(self):
        if not self.__playlist.is_loaded():
            self.__conditions.add_condition(self.__playlist.is_loaded)
            current_task.condition_wait(self.__conditions, 10)

    def _wait_for_track_metadata(self, track):
        def test_is_loaded():
            return self._track_is_ready(track,
                                        test_album=True,
                                        test_artists=False)

        if not test_is_loaded():
            self.__conditions.add_condition(test_is_loaded)
            current_task.condition_wait(self.__conditions, 10)

    def _load_thumbnails(self):
        pm = self.__playlist_manager

        #If playlist has an image
        playlist_image = self.__playlist.get_image()
        if playlist_image is not None:
            thumbnails = [pm.get_image_url(playlist_image)]

        #Otherwise get them from the album covers
        else:
            thumbnails = []
            for item in self.__playlist.tracks():
                #Wait until this track is fully loaded
                self._wait_for_track_metadata(item)

                #Check if item was loaded without errors
                if item.is_loaded() and item.error() == 0:
                    #Append the cover if it's new
                    image_id = item.album().cover()
                    image_url = pm.get_image_url(image_id)
                    if image_url not in thumbnails:
                        thumbnails.append(image_url)

                    #If we reached to the desired thumbnail count...
                    if len(thumbnails) == 4:
                        break

        #If the thumnbail count is still zero...
        if len(thumbnails) == 0:
            self.__thumbnails = ['common/pl-default.png']
            return True

        #If the stored thumbnail data changed...
        if self.__thumbnails != thumbnails:
            self.__thumbnails = thumbnails
            return True

    def _load_name(self):
        if self.__name != self.__playlist.name():
            self.__name = self.__playlist.name()
            return True
        else:
            return False

    def _load_num_tracks(self):
        if self.__num_tracks != self.__playlist.num_tracks():
            self.__num_tracks = self.__playlist.num_tracks()
            return True
        else:
            return False

    def _load_is_collaborative(self):
        if self.__is_collaborative != self.__playlist.is_collaborative():
            self.__is_collaborative = self.__playlist.is_collaborative()
            return True
        else:
            return False

    def _load_attributes(self):
        #Now check for changes
        has_changes = False

        if self._load_name():
            has_changes = True

        if self._load_num_tracks():
            has_changes = True

        if self._load_is_collaborative():
            has_changes = True

        #If we detected something different
        return has_changes

    def _add_condition(self, condition):
        self.__conditions.add_condition(condition)

    def _wait_for_conditions(self, timeout):
        current_task().condition_wait(self.__conditions, timeout)

    def check(self):

        #If a loading process was not active, start a new one
        if self.__loader_lock.acquire(False):
            try:
                self.load_in_background()
            finally:
                self.__loader_lock.release()

        #Otherwise notify the task
        else:
            try:
                self.__loader_task.notify()
            except:
                pass

    def _set_loaded(self, status):
        self.__is_loaded = status

    def is_loaded(self):
        return self.__is_loaded

    def _set_error(self, status):
        self.__has_errors = status

    def has_errors(self):
        return self.__has_errors

    def _set_changes(self, status):
        self.__has_changes = status

    def has_changes(self):
        return self.__has_changes

    def _load(self):
        raise NotImplementedError()

    def end_loading(self):
        pass
Example #12
0
class ContainerLoader:
    __session = None
    __container = None
    __playlist_manager = None
    __playlists = None
    __checker = None
    __loader_task = None
    __loader_lock = None
    __list_lock = None
    __is_loaded = None

    def __init__(self, session, container, playlist_manager):
        self.__session = session
        self.__container = container
        self.__playlist_manager = playlist_manager
        self.__playlists = []
        self.__conditions = ConditionList()
        self.__loader_lock = threading.RLock()
        self.__list_lock = threading.RLock()
        self.__is_loaded = False

        #Register the callbacks
        self.__container.add_callbacks(ContainerCallbacks(self))

        #Load the rest in the background
        self.load_in_background()

    def _fill_spaces(self, position):
        try:
            self.__list_lock.acquire()

            if position >= len(self.__playlists):
                for idx in range(len(self.__playlists), position + 1):
                    self.__playlists.append(None)

        finally:
            self.__list_lock.release()

    def is_playlist(self, position):
        playlist_type = self.__container.playlist_type(position)
        return playlist_type == playlist.PlaylistType.Playlist

    def add_playlist(self, playlist, position):
        try:
            self.__list_lock.acquire()

            #Ensure that it gets added in the correct position
            self._fill_spaces(position - 1)

            #Instantiate a loader if it's a real playlist
            if self.is_playlist(position):
                item = ContainerPlaylistLoader(self.__session, playlist,
                                               self.__playlist_manager, self)

            #Ignore if it's not a real playlist
            else:
                item = None

            #Insert the generated item
            self.__playlists.insert(position, item)

        finally:
            self.__list_lock.release()

    def remove_playlist(self, position):
        try:
            self.__list_lock.acquire()
            del self.__playlists[position]

        finally:
            self.__list_lock.release()

    def move_playlist(self, position, new_position):
        try:
            self.__list_lock.acquire()
            self.__playlists.insert(new_position, self.__playlists[position])

            #Calculate new position
            if position > new_position:
                position += 1

            del self.__playlists[position]

        finally:
            self.__list_lock.release()

    def _add_missing_playlists(self):

        #Ensure that the container and loader length is the same
        self._fill_spaces(self.__container.num_playlists() - 1)

        #Iterate over the container to add the missing ones
        for pos, item in enumerate(self.__container.playlists()):

            #Check if we should continue
            current_task().check_status()

            if self.is_playlist(pos) and self.__playlists[pos] is None:
                self.add_playlist(item, pos)

    def _check_playlist(self, playlist):
        def is_playlist_loaded():
            #If it has errors, say yes.
            if playlist.has_errors():
                return True

            #And if it was loaded, say yes
            if playlist.is_loaded():
                return True

        self.__conditions.add_condition(is_playlist_loaded)

    def _load_container(self):

        #Wait for the container to be fully loaded
        self.__conditions.add_condition(self.__container.is_loaded)
        current_task().condition_wait(self.__conditions)

        #Fill the container with unseen playlists
        self._add_missing_playlists()

        #Add a load check for each playlist
        for item in self.__playlists:
            if item is not None and not item.is_loaded():
                self._check_playlist(item)

        #Wait until all conditions become true
        current_task().condition_wait(self.__conditions,
                                      self.__container.num_playlists() * 5)

        #Set the status of the loader
        self.__is_loaded = True

        #Check and log errors for not loaded playlists
        for idx, item in enumerate(self.__playlists):
            if item is not None and item.has_errors():
                get_logger().error('Playlist #%s failed loading.' % idx)

        #Finally tell the gui we are done
        xbmc.executebuiltin("Action(Noop)")

    @run_in_thread(group='load_playlists', max_concurrency=10)
    def load_in_background(self):

        #Avoid entering here multiple times
        if self.__loader_lock.acquire(False):
            try:

                #Set the current task object
                self.__loader_task = current_task()

                #And take care of the rest
                self._load_container()

            finally:

                #Release and clear everything
                self.__loader_task = None
                self.__loader_lock.release()

    def check(self):

        #If a loading process was not active, start a new one
        if self.__loader_lock.acquire(False):
            try:
                self.load_in_background()
            finally:
                self.__loader_lock.release()

        #Otherwise notify the task
        else:
            try:
                self.__loader_task.notify()
            except:
                pass

    def is_loaded(self):
        return self.__is_loaded

    def playlist(self, index):
        return self.__playlists[index]

    def num_playlists(self):
        return len(self.__playlists)

    def playlists(self):
        return CallbackIterator(self.num_playlists, self.playlist)

    def get_container(self):
        return self.__container