Source code for easyplayer.webclient

from path import Path
import platform
import time
from logging import getLogger
from circuits import Component, Timer, Worker, task
from .circus.commands import Status as CircusStatus
from .events import (adjust_systime, command, handle,
                     download_disabled,
                     get_program, get_info,
                     heartbeat,  initial_data_ready,
                     new_program, new_info,
                     supervisor_status,
                     time_adjusted, )
from .remote.client import http_get, http_post, Endpoint
from .utils import tz, now, tnow, get_time_offset, add_time_offset, set_timezone, datetime_format
from .utils.hwinfo import HWInfo
from .utils.stream import MediaStream
from .version import version
from .settings import HEARTBEAT_INTERVAL, TIME_DIFF_LIMIT, SYSTIME_DIFF_LIMIT, VERSION_PREFIX
from .storage import DataNotPresent, InitialProgramData, ProgramData, PlayerInfoData, NewProgramData


CMD_EVENTS = {
    'pr': get_program,
    'in': get_info
}

[docs]class WebClient(Component): """ Sends heartbeat to server with status data, can get a command in response. Command will be sent to workshop component, web commands will be executed immediately - getting player info or program data. Another task is logging played songs to server. """ NUM_WORKERS = 3 def init(self, config): self.logger = getLogger(__name__) self.logger.debug('init: %r', self) self.config = config self.data_dir = Path(config['data_dir']).expanduser() self.token = config['token'] self.remote_url = config['remote_url'] self.connection = { 'token': self.token, 'remote_url': self.remote_url } self.worker = Worker(workers=self.NUM_WORKERS, channel='webclient').register(self) self.programs_storage = ProgramData(self.data_dir) self.info_storage = PlayerInfoData(self.data_dir) self.confirmed = None self.status = {} self.initial = True self.logger.info('Player: %s, version: %s', platform.node(), self._version()) def started(self, component, *args): if component.name == 'App': self.logger.info('started: %r from %r', self, component) Timer(HEARTBEAT_INTERVAL, heartbeat(), persist=True).register(self) Timer(HEARTBEAT_INTERVAL-2, supervisor_status(), persist=True).register(self) self.fire(heartbeat()) def _version(self): # canonical version format v = '.'.join(version.split('.')[:3]) return '{}-{}'.format(VERSION_PREFIX, v) def _get_data(self): try: program_hash = self.programs_storage.get_hash() summary = MediaStream(self.programs_storage).summary() except DataNotPresent: program_hash = '' summary = {} summary['system_time'] = time.time() summary['time_offset'] = get_time_offset() summary['tzname'] = tz.zone data = { 'name': platform.node(), 'version': self._version(), 'timestamp': tnow(), #'system_time': time.time(), #'time_offset': get_time_offset(), #'tzname': tz.zone, 'status': self.status.copy(), 'program_hash': program_hash, 'info_hash': self.info_storage.get_hash(), 'techinfo': HWInfo(self.data_dir).collect() } if summary: data['summary'] = summary # set default to send next time if not updated self.status['playing'] = 'stopped' if self.confirmed: data['command'] = self.confirmed self.confirmed = None self.logger.info('\tconfirm command: %s', data['command']) return data def _dispatch_command(self, cmd): if cmd in CMD_EVENTS: event = CMD_EVENTS[cmd] self.fire(event()) else: if self.parent: self.fire(command(cmd)) def _handle_time_offset(self, server_timestamp, original_timestamp): if original_timestamp is not None: offset = server_timestamp - (tnow() + original_timestamp) / 2 if self.initial and abs(offset) > SYSTIME_DIFF_LIMIT: self.logger.info('Server time difference: %.2fs', offset) self.fire(adjust_systime(offset)) elif abs(offset) > TIME_DIFF_LIMIT: self.logger.info('Adjusting application time offset to: %.2fs', offset) add_time_offset(offset) self.fire(time_adjusted())
[docs] def set_status(self, status): """ used states: playing, stopped, not_allowed, no_song """ self.status['playing'] = status
[docs] def set_dwld_status(self, status): """ used states: download, waiting, suspended, finished """ self.status['download'] = status
def supervisor_status(self): self.status['supervisor'] = CircusStatus().run()
[docs] def task_success(self, event, task, response, **kw): """ returned from succesful task """ if 'webclient' in event.channels: endpoint = task.args[1] success, data = response if success: handle = getattr(self, 'handle_' + str(endpoint), None) if handle: handle(data, **kw) else: self.logger.warning('Task %s failed: %s', endpoint, data)
[docs] def task_failure(self, event, task, error, **kw): """ returned from failed task """ if 'webclient' in event.channels: self.logger.error(error[1])
def heartbeat(self): self.logger.info('heartbeat start') data = self._get_data() self.fire(task(http_post, Endpoint.PlayerCheck, data=data, **self.connection), 'webclient') def confirm_cmd(self, cmd): self.logger.info('confirm command: %s', cmd) self.confirmed = cmd # send the confirmation immediately self.heartbeat()
[docs] def update_finished(self, exit_code): """ send update exit code to server """ self.status['update'] = exit_code self.heartbeat()
[docs] def handle_check(self, response): """ process data from server """ if response: if response.get('status', None) == 'OK': dd = response.get('download_disabled', None) # can be true or false if dd is not None: self.fire(download_disabled(dd)) if response.get('command', None): cmd = response['command'] self.logger.info('heartbeat command: %s', cmd) self._dispatch_command(cmd) if self.initial and response.get('command', None) != 'pr': self.fire(get_program(initial=True)) server_timestamp = response.get('server_timestamp', None) if server_timestamp: original_timestamp = response.get('player_timestamp', None) self._handle_time_offset(server_timestamp, original_timestamp) tz = response.get('time_zone', None) if tz: set_timezone(tz) self.fire(time_adjusted()) else: self.logger.warning('Strange server response: %s', response) else: self.logger.warning('No server response: %s', response)
def get_program(self, initial=False): # get program data from remote server self.logger.info('get program') data = {'initial': True} if initial else None self.fire(task(http_post, Endpoint.ProgramData, data=data, **self.connection), 'webclient') def handle_data(self, data): self.logger.info('handling program data') if data: # save initial data if present initial = data.get('initial', None) if initial is not None: inst = InitialProgramData(self.data_dir) inst.save(initial) self.logger.info('Saved initial program data') # save program data if changed changed = False programs = data.get('programs', None) if programs is not None: storage = ProgramData(self.data_dir) if storage.exists(): newstorage = NewProgramData(self.data_dir) changed = newstorage.save(programs) else: storage.save(programs) changed = True if changed: self.fire(new_program()) else: self.logger.debug('program not changed') else: self.logger.warning('No program data from server') self.initial = False def get_info(self): self.logger.info('get info') self.fire(task(http_get, Endpoint.PlayerInfo, **self.connection), 'webclient') def handle_info(self, data): self.logger.info('handling player info') if data: info = data.get('info', None) # save if info: changed = self.info_storage.save(info) if changed: self.fire(new_info()) else: self.logger.info('player info not changed') def log_song(self, song): self.logger.info('Log song: %s', song) data = { 'song': song.filename, 'item_id': song.item.id, 'time': datetime_format(now()) } self.fire(task(http_post, Endpoint.LogSong, data=data, **self.connection), 'webclient') def warning(self, warning, msg): self.logger.info('Report warning: %s', msg) data = dict(warning=warning, description=msg) self.fire(task(http_post, Endpoint.Warning, data=data, **self.connection), 'webclient') def screenshot(self, path, timestamp): self.logger.info('Sending screeenshot: %s', path) files = {'screenshot': open(path,'rb')} data = {'timestamp': timestamp} self.fire(task(http_post, Endpoint.Screenshot, data=data, files=files, **self.connection), 'webclient') def lastlogs(self, path, timestamp): self.logger.info('Sending logs: %s', path) files = {'lastlogs': open(path,'rb')} data = {'timestamp': timestamp} self.fire(task(http_post, Endpoint.SendLogs, data=data, files=files, **self.connection), 'webclient')