import os
import sys
sys.path.append(os.path.join(os.path.dirname(__file__), "resources", "lib"))

from main_service import MainService
from httpproxy import ProxyRunner
from utils import log_msg
import xbmc

kodimonitor = xbmc.Monitor()

# start the webservice (which hosts our silenced audio tracks)
proxy_runner = ProxyRunner(host='127.0.0.1', allow_ranges=True)
proxy_runner.start()
webport = proxy_runner.get_port()
log_msg('started webproxy at port {0}'.format(webport))

# run the main background service
main = MainService(kodimonitor=kodimonitor, webport=webport)
main.start()

# keep thread alive and send signal when we need to exit
while not kodimonitor.waitForAbort(10):
    pass

# stop requested
log_msg("Abort requested !", xbmc.LOGNOTICE)
main.stop()
proxy_runner.stop()
log_msg("Stopped", xbmc.LOGNOTICE)
class MainService:
    '''our main background service running the various threads'''
    sp = None
    addon = None
    connect_player = None
    connect_daemon = None
    webservice = None
    spotty = None
    current_user = None
    auth_token = None

    def __init__(self):
        self.addon = xbmcaddon.Addon(id=ADDON_ID)
        self.win = xbmcgui.Window(10000)
        self.kodimonitor = xbmc.Monitor()
        self.spotty = Spotty()

        # spotipy and the webservice are always prestarted in the background
        # the auth key for spotipy will be set afterwards
        # the webserver is also used for the authentication callbacks from spotify api
        self.sp = spotipy.Spotify()
        self.connect_player = ConnectPlayer(sp=self.sp, spotty=self.spotty)

        self.proxy_runner = ProxyRunner(self.spotty)
        self.proxy_runner.start()
        webport = self.proxy_runner.get_port()
        log_msg('started webproxy at port {0}'.format(webport))

        # authenticate at startup
        self.renew_token()

        # start mainloop
        self.main_loop()

    def main_loop(self):
        '''main loop which keeps our threads alive and refreshes the token'''
        loop_timer = 5
        while not self.kodimonitor.waitForAbort(loop_timer):
            # monitor logged in user
            cmd = self.win.getProperty("spotify-cmd")
            if cmd == "__LOGOUT__":
                log_msg("logout cmd received")
                self.stop_connect_daemon()
                self.win.clearProperty("spotify-cmd")
                self.current_user = None
                self.auth_token = None
                self.switch_user(True)
            elif not self.auth_token:
                # we do not yet have a token
                log_msg("retrieving token...")
                if self.renew_token():
                    xbmc.executebuiltin("Container.Refresh")
            elif self.auth_token and self.auth_token['expires_at'] - 60 <= (
                    int(time.time())):
                # token needs refreshing !
                log_msg("token needs to be refreshed")
                self.renew_token()
            elif self.connect_player.connect_playing or cmd == "__RECONNECT__":
                # monitor for remote track changes
                loop_timer = 2
                reconnect = cmd == "__RECONNECT__"
                if reconnect:
                    self.win.clearProperty("spotify-cmd")
                self.connect_player.update_info(reconnect)
            else:
                loop_timer = 5

        # end of loop: we should exit
        self.close()

    def close(self):
        '''shutdown, perform cleanup'''
        log_msg('Shutdown requested !', xbmc.LOGINFO)
        kill_spotty()
        self.proxy_runner.stop()
        self.connect_player.close()
        self.stop_connect_daemon()
        del self.connect_player
        del self.addon
        del self.kodimonitor
        del self.win
        log_msg('stopped', xbmc.LOGINFO)

    def switch_user(self, restart_daemon=False):
        '''called whenever we switch to a different user/credentials'''
        log_msg("login credentials changed")
        if self.renew_token():
            xbmc.executebuiltin("Container.Refresh")

    def get_username(self):
        ''' get the current configured/setup username'''
        username = self.spotty.get_username()
        if not username:
            username = self.addon.getSetting("username")
            if not username and self.addon.getSetting(
                    "multi_account") == "true":
                username1 = self.addon.getSetting("username1")
                password1 = self.addon.getSetting("password1")
                if username1 and password1:
                    self.addon.setSetting("username", username1)
                    self.addon.setSetting("password", password1)
                    username = username1
        return username

    def stop_connect_daemon(self):
        ''' stop spotty connect daemon if needed '''
        if self.connect_daemon and self.connect_daemon.daemon_active:
            self.connect_daemon.stop()
            del self.connect_daemon

    def start_connect_daemon(self):
        '''start experimental spotify connect daemon'''
        if (not self.connect_daemon or not self.connect_daemon.daemon_active):
            if self.addon.getSetting(
                    "connect_player"
            ) == "true" and self.spotty.playback_supported:
                if not self.connect_daemon:
                    self.connect_daemon = ConnectDaemon(self.spotty)
                if not self.connect_daemon.daemon_active:
                    self.connect_daemon.start()

    def renew_token(self):
        '''refresh/retrieve the token'''
        result = False
        auth_token = None
        username = self.get_username()
        if username:
            # stop connect daemon
            self.stop_connect_daemon()
            # retrieve token
            log_msg("Retrieving auth token....")
            auth_token = get_token(self.spotty)
        if auth_token:
            log_msg("Retrieved auth token")
            self.auth_token = auth_token
            # only update token info in spotipy object
            self.sp._auth = auth_token["access_token"]
            me = self.sp.me()
            self.current_user = me["id"]
            log_msg("Logged in to Spotify - Username: %s" % self.current_user,
                    xbmc.LOGINFO)
            # store authtoken and username as window prop for easy access by plugin entry
            self.win.setProperty("spotify-token", auth_token["access_token"])
            self.win.setProperty("spotify-username", self.current_user)
            self.win.setProperty("spotify-country", me["country"])
            result = True
        # start experimental spotify connect daemon
        self.start_connect_daemon()
        return result
Example #3
0
class MainService:
    '''our main background service running the various threads'''
    sp = None
    addon = None
    connect_player = None
    webservice = None
    spotty = None
    token_info = None

    def __init__(self):
        self.addon = xbmcaddon.Addon(id=ADDON_ID)
        self.kodimonitor = xbmc.Monitor()
        self.spotty = Spotty()

        # spotipy and the webservice are always prestarted in the background
        # the auth key for spotipy will be set afterwards
        # the webserver is also used for the authentication callbacks from spotify api
        self.sp = spotipy.Spotify()
        self.connect_player = ConnectPlayer(sp=self.sp, spotty=self.spotty)

        self.proxy_runner = ProxyRunner(self.spotty)
        self.proxy_runner.start()
        webport = self.proxy_runner.get_port()
        log_msg('started webproxy at port {0}'.format(webport))

        # authenticate
        self.token_info = self.get_auth_token()
        if self.token_info and not self.kodimonitor.abortRequested():

            # initialize spotipy
            self.sp._auth = self.token_info["access_token"]
            me = self.sp.me()
            log_msg("Logged in to Spotify - Username: %s" % me["id"],
                    xbmc.LOGNOTICE)

            # start experimental spotify connect daemon
            if self.addon.getSetting(
                    "connect_player"
            ) == "true" and self.spotty.playback_supported:
                self.connect_player.start()

        # start mainloop
        self.main_loop()

    def main_loop(self):
        '''main loop which keeps our threads alive and refreshes the token'''
        loop_timer = 5
        while not self.kodimonitor.waitForAbort(loop_timer):
            # monitor logged in user
            username = self.addon.getSetting("username").decode("utf-8")
            if username and self.spotty.username != username:
                # username and/or password changed !
                self.switch_user()
            # monitor auth token expiration
            elif self.spotty.username and not self.token_info:
                # we do not yet have a token
                self.renew_token()
            elif self.token_info and self.token_info['expires_at'] - 60 <= (
                    int(time.time())):
                # token needs refreshing !
                self.renew_token()
            elif not username and self.addon.getSetting(
                    "multi_account") == "true":
                # edge case where user sets multi user directly at first start
                # in that case copy creds to default
                username1 = self.addon.getSetting("username1").decode("utf-8")
                password1 = self.addon.getSetting("password1").decode("utf-8")
                if username1 and password1:
                    self.addon.setSetting("username", username1)
                    self.addon.setSetting("password", password1)
                    self.switch_user()
            elif self.connect_player.connect_playing:
                # monitor fake connect OSD for remote track changes
                loop_timer = 2
                cur_playback = self.sp.current_playback()
                if cur_playback["is_playing"]:
                    player_title = xbmc.getInfoLabel(
                        "MusicPlayer.Title").decode("utf-8")
                    if player_title and player_title != cur_playback["item"][
                            "name"]:
                        log_msg(
                            "Next track requested by Spotify Connect player")
                        trackdetails = cur_playback["item"]
                        self.connect_player.start_playback(trackdetails["id"])
                elif not xbmc.getCondVisibility("Player.Paused"):
                    log_msg("Stop requested by Spotify Connect")
                    self.connect_player.stop()
            else:
                loop_timer = 5

        # end of loop: we should exit
        self.close()

    def close(self):
        '''shutdown, perform cleanup'''
        log_msg('Shutdown requested !', xbmc.LOGNOTICE)
        kill_spotty()
        self.proxy_runner.stop()
        self.connect_player.close()
        del self.connect_player
        del self.addon
        del self.kodimonitor
        log_msg('stopped', xbmc.LOGNOTICE)

    def get_auth_token(self):
        '''check for valid credentials and grab token'''
        auth_token = None
        username = self.addon.getSetting("username").decode("utf-8")
        password = self.addon.getSetting("password").decode("utf-8")
        if username and password:
            self.spotty.username = username
            self.spotty.password = password
            auth_token = get_token(self.spotty)
        if auth_token:
            log_msg("Retrieved auth token")
            # store authtoken as window prop for easy access by plugin entry
            xbmc.executebuiltin("SetProperty(spotify-token, %s, Home)" %
                                auth_token['access_token'])
        return auth_token

    def switch_user(self):
        '''called whenever we switch to a different user/credentials'''
        log_msg("login credentials changed")
        if self.renew_token():
            xbmc.executebuiltin("Container.Refresh")
            me = self.sp.me()
            log_msg("Logged in to Spotify - Username: %s" % me["id"],
                    xbmc.LOGNOTICE)
            # restart daemon
            if self.connect_player.daemon_active:
                self.connect_player.stop_thread()
                audio_device = self.addon.getSetting("audio_device") == "true"
                self.connect_player = ConnectPlayer(sp=self.sp,
                                                    audio_device=audio_device,
                                                    spotty=self.spotty)
                self.connect_player.start()

    def renew_token(self):
        '''refresh the token'''
        self.token_info = self.get_auth_token()
        if self.token_info:
            log_msg("Authentication token updated...")
            # only update token info in spotipy object
            self.sp._auth = self.token_info['access_token']
            return True
        return False