Exemple #1
0
def test_server_different_port():
    # server, will be tested:
    server_3000 = OSCThreadServer(encoding='utf8')
    sock_3000 = server_3000.listen(address='0.0.0.0', port=3000, default=True)
    server_3000.bind(b'/callback_3000', callback_3000)
    # clients sending to different ports, used to test the server:
    client_3000 = OSCClient(address='localhost', port=3000, encoding='utf8')

    # server sends message to himself, should work:
    server_3000.send_message(b'/callback_3000', ["a"],
                             ip_address='localhost',
                             port=3000)
    sleep(0.05)
    # client sends message to server, will be received properly:
    client_3000.send_message(b'/callback_3000', ["b"])
    sleep(0.05)
    # sever sends message on different port, might crash the server on windows:
    server_3000.send_message(b'/callback_3000',
                             ["nobody is going to receive this"],
                             ip_address='localhost',
                             port=3001)
    sleep(0.05)
    # client sends message to server again. if server is dead, message will not be received:
    client_3000.send_message(b'/callback_3000', ["c"])
    sleep(0.1)  # give time to finish transmissions

    # if 'c' is missing in the received checklist, the server thread crashed and could not recieve the last message from the client:
    assert checklist == ['a', 'b', 'c']

    server_3000.stop()  # clean up
Exemple #2
0
def test_answer():
    cont = []

    osc_1 = OSCThreadServer()
    osc_1.listen(default=True)

    @osc_1.address(b'/ping')
    def ping(*values):
        if True in values:
            osc_1.answer(b'/zap', [True], port=osc_3.getaddress()[1])
        else:
            osc_1.answer(bundle=[(b'/pong', [])])

    osc_2 = OSCThreadServer()
    osc_2.listen(default=True)

    @osc_2.address(b'/pong')
    def pong(*values):
        osc_2.answer(b'/ping', [True])

    osc_3 = OSCThreadServer()
    osc_3.listen(default=True)

    @osc_3.address(b'/zap')
    def zap(*values):
        if True in values:
            cont.append(True)

    osc_2.send_message(b'/ping', [], *osc_1.getaddress())

    with pytest.raises(RuntimeError) as e_info:  # noqa
        osc_1.answer(b'/bing', [])

    timeout = time() + 2
    while not cont:
        if time() > timeout:
            raise OSError('timeout while waiting for success message.')
        sleep(10e-9)
Exemple #3
0
def test_send_message_without_socket():
    osc = OSCThreadServer()
    with pytest.raises(RuntimeError):
        osc.send_message(b'/test', [], 'localhost', 0)
Exemple #4
0
class OBSRemote:
    def __init__(self,
                 osc_port,
                 osc_client_host,
                 osc_client_port,
                 host,
                 port,
                 password=''):
        self.client = obsws(host, port, password)
        self.register_callbacks()

        self.osc = OSCThreadServer(
            encoding='utf8',
            advanced_matching=True,
        )

        self.socket = None
        self.osc_client_host = osc_client_host
        self.osc_client_port = osc_client_port
        self.osc_port = osc_port
        self.volume_changed = False
        self.levels = [0, 0]

        self.scenes = []

    def start(self):
        self.client.connect()
        self.socket = self.osc.listen(address='0.0.0.0',
                                      port=self.osc_port,
                                      default=True)

        self.update_audio_sources()
        self.update_audio_levels()
        self.update_scenes()
        self.update_mute_status()
        self.register_osc_addresses()

        self.client.call(requests.SetHeartbeat(True))

    def stop(self):
        self.client.disconnect()
        self.osc.stop()
        self.socket = None

    def register_callbacks(self):
        self.client.register(self.scene_changed, event=events.SwitchScenes)
        self.client.register(self.mute_changed,
                             event=events.SourceMuteStateChanged)
        self.client.register(self.update_scenes, event=events.ScenesChanged)
        self.client.register(self.update_scenes,
                             event=events.SceneCollectionChanged)
        self.client.register(self.update_audio_sources,
                             event=events.SourceRenamed)
        self.client.register(self.update_audio_levels,
                             event=events.SourceVolumeChanged)
        self.client.register(self.status_update, event=events.Heartbeat)

    def register_osc_addresses(self):
        @self.osc.address('/scene/?/?', get_address=True)
        def scene_cb(address, values):
            if values < 1.0:
                return

            scene_id = int(address.split(b'/')[-1]) - 1
            if scene_id < len(self.scenes):
                self.osc.answer(address, [1.0])
                self.client.call(
                    requests.SetCurrentScene(self.scenes[scene_id]))

        @self.osc.address('/mic')
        def mic_cb(values):
            self.osc.answer('/mic', [values])
            self.client.call(
                requests.SetMute(self.audio_sources['mic'], values < 1.0))

        @self.osc.address('/audio')
        def audio_cb(values):
            self.osc.answer('/audio', [values])
            self.client.call(
                requests.SetMute(self.audio_sources['desktop'], values < 1.0))

        @self.osc.address('/cam')
        def cam_cb(values):
            self.osc.answer('/cam', [values])
            self.client.call(
                requests.SetSceneItemProperties('Webcam',
                                                visible=values > 0.0))
            self.client.call(
                requests.SetSceneItemProperties('Overlay: Webcam',
                                                visible=values > 0.0))

        @self.osc.address('/audio_level/?', get_address=True)
        def audio_level_cb(address, values):
            slider_id = int(address.split(b'/')[-1]) - 1
            self.levels[slider_id] = values
            self.volume_changed = True

        @self.osc.address('/rec')
        def rec_cb(values):
            if values > 0.0:
                self.client.call(requests.StartRecording())
            else:
                self.client.call(requests.StopRecording())
            self.osc.answer('/rec', [values])

        @self.osc.address('/stream')
        def stream_cb(values):
            if values > 0.0:
                self.client.call(requests.StartStreaming())
            else:
                self.client.call(requests.StopStreaming())
            self.osc.answer('/stream', [values])

    def mute_changed(self, event):
        name = event.getSourceName()
        muted = event.getMuted()
        if name == self.audio_sources['mic']:
            self._send_osc('/mic', 1.0 if muted is False else 0.0)
        if name == self.audio_sources['desktop']:
            self._send_osc('/audio', 1.0 if muted is False else 0.0)

    def scene_changed(self, event):
        name = event.getSceneName()
        for idx, scene_name in enumerate(self.scenes):
            if name == scene_name:
                self._send_osc('/scene/1/{}'.format(idx + 1), 1.0)
        self.update_mute_status(sources=event.getSources())

    def status_update(self, event: events.Heartbeat):
        streaming = event.getStreaming()
        recording = event.getRecording()
        try:
            stream_time = timedelta(seconds=event.getTotalStreamTime())
        except KeyError:
            stream_time = 0
        try:
            rec_time = timedelta(seconds=event.getTotalRecordTime())
        except KeyError:
            rec_time = 0
        self._send_osc('/stream', 1.0 if streaming else 0.0)
        self._send_osc('/rec', 1.0 if recording else 0.0)
        self._send_osc('/stream_time', str(stream_time))
        self._send_osc('/rec_time', str(rec_time))

    def update_mute_status(self, sources=None):
        if sources is None:
            sources = self.client.call(requests.GetCurrentScene()).getSources()
        webcam_found = False
        for source in sources:
            if source['name'] in ['Webcam', 'Overlay: Webcam']:
                webcam_found = True
                self._send_osc('/cam', 1.0 if source['render'] else 0.0)
        if not webcam_found:
            self._send_osc('/cam', 0.0)

    def update_scenes(self, *args):
        if len(args) > 0:
            self.scenes = [
                s['name'] for s in args[0].getScenes()
                if not s['name'].lower().startswith('overlay:')
            ]
        else:
            self.scenes = [
                s['name']
                for s in self.client.call(requests.GetSceneList()).getScenes()
                if not s['name'].lower().startswith('overlay:')
            ]

        for idx in range(0, 8):
            try:
                name = self.scenes[idx]
            except IndexError:
                name = ''

            self._send_osc('/scene_label_{}'.format(idx), name)
        self.update_mute_status()

    def update_audio_sources(self, *args):
        sources = self.client.call(requests.GetSpecialSources())
        self.audio_sources = {
            "mic": sources.getMic1(),
            "desktop": sources.getDesktop1()
        }

    def update_audio_levels(self, *args):
        if len(args) > 0:
            event = args[0]
            name = event.getSourceName()
            volume = None
            if isinstance(event, events.SourceVolumeChanged):
                volume = event.getVolume()
            muted = None
            if isinstance(event, events.SourceMuteStateChanged):
                muted = event.getMuted()

            if name == self.audio_sources['mic']:
                if volume is not None:
                    self._send_osc('/audio_level/1', volume)
                    self.levels[0] = volume
                if muted is not None:
                    self._send_osc('/mic', 1.0 if not muted else 0.0)
            elif name == self.audio_sources['desktop']:
                if volume is not None:
                    self._send_osc('/audio_level/2', volume)
                if muted is not None:
                    self._send_osc('/audio', 1.0 if not muted else 0.0)
        else:
            desktop = self.client.call(
                requests.GetVolume(self.audio_sources['desktop']))
            mic = self.client.call(
                requests.GetVolume(self.audio_sources['mic']))

            self._send_osc('/audio_level/2', desktop.getVolume())
            self.levels[1] = desktop.getVolume()
            self._send_osc('/audio', 1.0 if not desktop.getMuted() else 0.0)
            self._send_osc('/audio_level/1', mic.getVolume())
            self.levels[0] = mic.getVolume()
            self._send_osc('/mic', 1.0 if not mic.getMuted() else 0.0)

    def _send_osc(self, address, value):
        self.osc.send_message(address, [value], self.osc_client_host,
                              self.osc_client_port)

    def tick(self):
        if self.volume_changed:
            self.volume_changed = False
            self.client.call(
                requests.SetVolume(
                    list(self.audio_sources.values())[0], self.levels[0]))
            self.client.call(
                requests.SetVolume(
                    list(self.audio_sources.values())[1], self.levels[1]))
Exemple #5
0
class ServerSong():
    def __init__(self, app, pos_callback, state_callback, port):
        from oscpy.server import OSCThreadServer
        self.app = app
        self.pos_callback = pos_callback
        self.state_callback = state_callback
        self.state = 'stop'
        self.length = 30
        self.is_complete = False
        self.song_object = None
        self.ready = False

        self.osc = OSCThreadServer()
        self.osc.listen(port=port, default=True)
        self.osc.bind(b'/pos', self._get_pos)
        self.osc.bind(b'/set_state', self.set_state)
        self.osc.bind(b'/set_length', self.set_length)
        self.osc.bind(b'/set_complete', self.set_complete)
        self.osc.bind(b'/playing', self.playing)
        self.osc.bind(b'/update_playlist', self.update_playlist)
        self.osc.bind(b'/ready', self.ready_callback)

    def ready_callback(self):
        self.ready = True
        Logger.debug("ACTIVITY: Service is ready.")

    def playing(self, id, pos):
        Logger.debug('ACTIVITY: Playing.')
        self.state = 'play'
        self.last_pos = pos
        song = self.app.playlist.get_track(id=id)
        play_button = self.app.play_button
        if self.song_object != song:
            play_button.load_song(song, playing=True)
        play_button.update_track_current(current=pos)
        if play_button.event:
            play_button.event.cancel()
        play_button.event = Clock.schedule_interval(
            play_button.update_track_current, 1)

    def set_state(self, value):
        Logger.debug('ACTIVITY: State %s', value)
        self.state = value.decode()

    def set_length(self, value):
        Logger.debug('ACTIVITY: Length %s', value)
        self.length = value

    def set_complete(self, value):
        Logger.debug('ACTIVITY: Song is_complete=%s', value)
        self.is_complete = value

    def set_volume(self, value):
        Logger.debug('ACTIVITY -> Service: Set volume %s', value)
        self.osc.send_message(b'/set_volume', [value], *self.server_address)

    def getaddress(self):
        return self.osc.getaddress()

    def load(self, song):
        Logger.debug('ACTIVITY -> SERVER: /load')
        self.osc.send_message(b'/load', [song.id], *self.server_address)

    def unload(self):
        Logger.debug('ACTIVITY -> SERVER: /unload')
        self.osc.send_message(b'/unload', [], *self.server_address)

    def play(self, seek, volume):
        Logger.debug('ACTIVITY -> SERVER: /play pos %s vol %s', seek, volume)
        self.osc.send_message(b'/play', [seek, volume], *self.server_address)

    def play_new_playlist(self):
        Logger.debug('ACTIVITY -> SERVER: /play_new_playlist')
        self.osc.send_message(b'/play_new_playlist', [], *self.server_address)

    def load_play(self, song, volume):
        Logger.debug('ACTIVITY -> SERVER: /load_play %s vol %s', song.id,
                     volume)
        self.osc.send_message(b'/load_play', [song.id, volume],
                              *self.server_address)

    def stop(self):
        Logger.debug('ACTIVITY -> SERVER: /stop')
        self.osc.send_message(b'/stop', [], *self.server_address)

    def seek(self, position):
        Logger.debug('ACTIVITY -> SERVER: /seek %s', position)
        self.osc.send_message(b'/seek', [position], *self.server_address)

    def save_pos(self, callback):
        Logger.debug('ACTIVITY -> SERVER: /get_pos')
        self.pos_callback = callback
        self.osc.send_message(b'/get_pos', [], *self.server_address)

    def get_pos(self, callback):
        Logger.debug('ACTIVITY -> SERVER: get pos and call %s', callback)
        self.pos_callback = callback
        self.osc.send_message(b'/get_pos', [], *self.server_address)

    def _get_pos(self, value):
        Logger.debug('ACTIVITY: pos received %s', value)
        self.pos_callback(value)

    def update_playlist(self):
        self.app.playlist = self.app.db.get_playlist()
        Logger.debug('ACTIVITY: Updated playlist.')
Exemple #6
0
class Console:
    def __init__(self, console_ip):
        self.console_ip = console_ip
        self.console_port = 10023
        self.name_list = []

        # Initialize name_list with 32 empty strings.
        for n in range(32):
            self.name_list.append("")

        # Setup socket to console connection and start listening
        hostname = gethostname()
        local_ip = gethostbyname(hostname)
        self.server = OSCThreadServer(advanced_matching=True)
        self.sock = self.server.listen(address=local_ip, port=0, default=True)
        self.server.bind(b"/ch/../config/name",
                         self.callback,
                         sock=self.sock,
                         get_address=True)
        print("Listening to console at", self.console_ip)

        # Query for all channel names
        for ch in range(1, 33):
            ch = two_digits(ch)
            message = "/ch/" + ch + "/config/name"
            self.message(message, "")

    def callback(self, address, values):
        # Extract channel number from address string
        address = str(address)
        channel = int(address[6:8])

        # Extract channel name string
        values = str(values)
        name = values[2:-1]

        # Find if there are battery bars in the name and remove.
        batt_indicator = name.find(" (")
        if batt_indicator > -1:
            name = name[:batt_indicator]
            value = bytes(name, 'utf-8')
            name_message = "/ch/" + two_digits(channel) + "/config/name"
            self.message(name_message, value)

        # Find if there are h:mm in the name and remove.
        batt_indicator = name.find(":")
        if batt_indicator > -1:
            name = name[:batt_indicator - 2]
            value = bytes(name, 'utf-8')
            name_message = "/ch/" + two_digits(channel) + "/config/name"
            self.message(name_message, value)

        # Add channel name into the name list at the appropriate index
        self.name_list[channel - 1] = name
        self.duplicate_names(channel)

    def subscribe(self):
        # Subscribe to console updates via OSC /xremote command.
        while True:
            self.server.send_message(b"/xremote",
                                     "",
                                     self.console_ip,
                                     self.console_port,
                                     sock=self.sock,
                                     safer=True)
            sleep(5)

    def message(self, message, value):
        # Send one OSC message to console.
        self.server.send_message(bytes(message, 'utf-8'),
                                 value,
                                 self.console_ip,
                                 self.console_port,
                                 sock=self.sock,
                                 safer=True)

    def duplicate_names(self, ch):
        dups = []
        a = self.name_list[ch - 1]

        # Search through all values in self.name_list and compare with ch.
        # Do not compare to itself, ignore blank channels.
        for y in range(32):
            b = self.name_list[y]
            if (ch - 1) != y:
                if a == b and a != "":
                    dups.append(ch)
                    dups.append(y + 1)

        # Report duplicate channels from temporary duplicate list, sort, and print warning message.
        dups = list(set(dups))
        dups.sort()
        if len(dups) > 1:
            print('Warning: Duplicate channel name!  Channels ' +
                  str(dups)[1:-1] + ' = ' + self.name_list[ch - 1])
class SocketP2P(object):
    '''
    A peer to peer socket server base on OSCPy
    '''
    server = None
    allow_finding = False
    is_host = False
    is_running = False

    config_data = ''

    _my_name = ''

    def get_my_name(self):
        return self._my_name

    def set_my_name(self, name):
        if name:
            self._my_name = name

    my_name = property(get_my_name, set_my_name)

    __events__ = ('on_create_server', 'on_scan_device', 'on_found_device',
                  'on_device_hided', 'on_connect_user',
                  'on_accepted_connection', 'on_request_config',
                  'on_got_config', 'on_new_device', 'on_delete_device')
    _callback_collec = {}

    def __init__(self, port, bind_collection={}, **kwargs):
        # super(SocketP2P, self).__init__(**kwargs)

        self._port = port
        self._bind_collection = bind_collection
        self._bind_collection.update({
            '/probe': self._probe,
            '/found': self._found,
            '/hided': self._hided_host,
            '/get_conf': self._send_conf,
            '/conf': self._got_conf,
            '/new_device': self._new_device,
            '/delete_device': self._delete_device
        })
        for event in SocketP2P.__events__:
            self._callback_collec[event] = [getattr(self, event)]

        sock = socket.socket(AF_INET, SOCK_DGRAM)
        sock.setsockopt(SOL_SOCKET, SO_BROADCAST, 1)
        sock.settimeout(1)
        self._scan_client = OSCClient(address='255.255.255.255',
                                      port=self._port,
                                      sock=sock,
                                      encoding='utf8')

        self.create_server()

    @_catch_exception
    def _identify_me(self, time):
        global MY_IP

        if time == self._time:
            self.server.unbind('/identify_me', self._identify_me)
            _, ip_address, _ = self.server.get_sender()
            MY_IP = ip_address
            if self._my_name == '':
                self.my_name = ip_address
            Log_P2P(f'P2P: IP: {self.myip}')

    def stop(self):
        if self.server:
            try:
                self.server.stop_all()
            except Exception as e:
                Logger.exception('P2P: On close')
        self.is_running = False

    def bind(self, **kwargs):
        for event, cb in kwargs.items():
            if event in SocketP2P.__events__:
                self._callback_collec[event].append(cb)

    def unbind(self, event, callback):
        if event in SocketP2P.__events__ and\
                callback in self._callback_collec[event]:
            self._callback_collec[event].remove(callback)

    def bind_address(self, address, callback):
        self.server.bind(address, callback)
        self._bind_collection[address] = callback

    def unbind_address(self, address):
        self.server.unbind(address, self._bind_collection.pop(address, None))

    def dispatch(self, event, *args, **kwargs):
        if event in SocketP2P.__events__:
            args = [self] + list(args)
            for cb in self._callback_collec[event]:
                try:
                    cb(*args, **kwargs)
                except TypeError as tye:
                    Logger.exception(
                        f'TypeError: {cb.__qualname__} with {args} {kwargs}')
                except Exception as e:
                    Logger.exception(f'P2P: Error in {cb.__qualname__}')

    @property
    def myip(self):
        return MY_IP

    @_catch_exception
    def create_server(self):
        self.stop()
        self.reset_data()

        # Fix [Errno 98] Address already in use
        sleep(0.01)

        self.server = OSCThreadServer(encoding='utf8')
        self.server.listen(address='0.0.0.0', port=self._port, default=True)

        for address, cb in self._bind_collection.items():
            self.server.bind(address, cb)

        self.server.bind('/identify_me', self._identify_me)
        self._time = str(datetime.now())
        try:
            self._scan_client.send_message('/identify_me', [self._time])
        except OSError:
            Logger.exception('P2P: No network')
            self.server.unbind('/identify_me', self._identify_me)
            return

        self.dispatch('on_create_server', self.server)
        self.is_running = True

    def reset_data(self):
        self.list_device = []
        self.list_name = {}
        self.waiting_list = []

    @_catch_exception
    def scan_device(self):
        self.dispatch('on_scan_device')
        self._scan_client.send_message(b'/probe', [])

    @_catch_exception
    def _probe(self):
        if self.allow_finding:
            Log_P2P('P2P: /probe')
            self.server.answer(b'/found',
                               values=[MY_IP, self.my_name],
                               port=self._port)
        else:
            self.server.answer(b'/hided', values=[MY_IP], port=self._port)

    @_catch_exception
    def _found(self, ip, ip_name):
        if ip != MY_IP and ip not in self.list_device\
                and ip not in self.waiting_list:
            Log_P2P(f'P2P: /found {ip}')
            self.waiting_list.append(ip)
            self.dispatch('on_found_device', ip, ip_name)

    @_catch_exception
    def _hided_host(self, ip):
        if ip in self.waiting_list:
            Log_P2P(f'P2P: /hided {ip}')
            self.waiting_list.remove(ip)
            self.dispatch('on_device_hided', ip)

    def connect_user(self, ip):
        if ip in self.waiting_list:
            self.dispatch('on_connect_user', ip)
            self.get_config(ip)

    @_catch_exception
    def send_to_all(self, address, data, list_ip=None, and_myip=False):
        if not isinstance(data, list):
            data - list(data)
        if not list_ip:
            list_ip = self.list_device.copy()
        if and_myip:
            list_ip += [self.myip]

        Log_P2P(f'P2P: send {address}\nData: {data}\nTo: {list_ip}')
        for other in list_ip:
            self.server.send_message(address, data, other, self._port)

    def generate_token(self, *args):
        return '1234'

    def handle_token(self, token):
        if token == '1234':
            return True
        else:
            return False

    def get_config(self, ip):
        self.server.send_message(
            b'/get_conf', [MY_IP, self.generate_token(), self.my_name], ip,
            self._port)

    @_catch_exception
    def _send_conf(self, ip, token, ip_name):
        if self.handle_token(token) and self.allow_finding:
            Log_P2P(f'P2P: /get_conf {ip}')

            if ip not in self.list_device:
                for other in self.list_device:
                    self.send_to_all(
                        '/new_device',
                        [MY_IP, self.generate_token(), ip, ip_name])
                self.list_device.append(ip)
                self.list_name[ip] = ip_name
                self.dispatch('on_accepted_connection', ip, ip_name)

            list_send = {MY_IP: self.my_name}
            list_send.update(self.list_name)
            self.server.answer(b'/conf', [
                MY_IP,
                self.generate_token(), self.config_data,
                json.dumps(list_send)
            ],
                               port=self._port)
            self.dispatch('on_request_config', ip)

    @_catch_exception
    def _got_conf(self, ip, token, config_data, list_device):
        if self.handle_token(token):
            Log_P2P(f'P2P: /conf {ip} with {list_device}')
            self.list_device = []
            for i, name in json.loads(list_device).items():
                if i != MY_IP:
                    self.list_device.append(i)
                    self.list_name[i] = name

            if ip in self.waiting_list:
                self.waiting_list.remove(ip)

            self.dispatch('on_got_config', ip, config_data)

    @_catch_exception
    def _new_device(self, ip, token, new_ip, ip_name):
        if self.handle_token(token) and new_ip not in self.list_device:
            Log_P2P(f'P2P: /new_device {new_ip} {ip_name}')
            if ip in self.waiting_list:
                self.waiting_list.remove(new_ip)
            self.list_device.append(new_ip)
            self.list_name[new_ip] = ip_name
            self.dispatch('on_new_device', new_ip, ip_name)

    def remove_device(self):
        self.send_to_all('/delete_device',
                         [MY_IP, self.generate_token(), MY_IP],
                         and_myip=True)
        self.reset_data()

    @_catch_exception
    def _delete_device(self, ip, token, del_ip):
        if self.handle_token(token):
            Log_P2P(f'P2P: /delete_device {del_ip}')
            if del_ip in self.waiting_list:
                self.waiting_list.remove(del_ip)
            if del_ip in self.list_device:
                self.list_device.remove(del_ip)
            self.list_name.pop(del_ip, None)
            self.dispatch('on_delete_device', ip, del_ip)

    def on_create_server(self, ins, server):
        pass

    def on_scan_device(self, ins):
        pass

    def on_found_device(self, ins, ip, ip_name):
        pass

    def on_device_hided(self, ins, ip):
        pass

    def on_connect_user(self, ins, ip):
        pass

    def on_accepted_connection(self, ins, ip, ip_name):
        pass

    def on_request_config(self, ins, ip):
        pass

    def on_got_config(self, ins, ip, config_data):
        pass

    def on_new_device(self, ins, new_ip, ip_name):
        pass

    def on_delete_device(self, ins, ip, del_ip):
        pass
class SendReceive(Widget):
    selected_store = ObjectProperty()

    device_ip = StringProperty()
    other_ip = StringProperty()
    lan_sock = None
    replay_chunk_upload_queue = Queue()
    replay_chunk_download_queue = Queue()

    accept_next_chunk = False

    chunk_size = 1024 * 10
    chunks_total = NumericProperty()
    chunks_uploaded = NumericProperty()
    chunks_downloaded = NumericProperty()
    #  data ratio is unsent:sent
    chunks_ratio = NumericProperty()

    status_text = StringProperty()

    do_polling = None
    polling_thread = None

    # ip = '192.168.0.7' if platform == 'android' else '192.168.0.22'
    # server_ip = StringProperty()
    port = 8002

    successfully_downloaded = []

    # def get_ips_store(self):
    #     store_type = 'ips_store.pydict'
    #     store_path = path.join(App.get_running_app().user_data_dir, store_type)
    #     ips_store = DictStore(store_path)
    #     return ips_store
    #
    # def set_ips(self):
    #     self.ips_store = self.get_ips_store()
    #     self.ips_store['device_ip'] = self.device_ip
    #     self.ips_store['other_ip'] = self.other_ip

    def do_on_pre_enter(self, *args):
        # self.ips_store = self.get_ips_store()
        self.device_ip = '192.168.0.7' if platform == 'android' else '192.168.0.22'
        self.other_ip = '192.168.0.22' if platform == 'android' else '192.168.0.7'
        # try:
        #     self.device_ip = self.ips_store.get('device_ip')
        #     self.other_ip = self.ips_store.get('other_ip')
        # except:
        #     self.device_ip = '192.168.0.'
        #     self.other_ip = '192.168.0.'
        #  fixme actually this should initialise as a client with the ips, and send the server it's details(?)

        if not self.lan_sock:

            # # Importing socket library
            # import socket
            #
            # # Function to display hostname and
            # # IP address
            # def get_Host_name_IP():
            #     try:
            #         host_name = socket.gethostname()
            #         host_ip_info = socket.gethostbyname_ex(host_name)
            #         host_ip_list = host_ip_info[-1]
            #         host_ip = host_ip_list[-1]
            #         print("Hostname :  ", host_name)
            #         print("IP List : ", host_ip_list)
            #         print("IP : ", host_ip)
            #         return str(host_ip)
            #     except:
            #         print("Unable to get Hostname and IP")
            #
            #         # Driver code
            #
            # self.device_ip = get_Host_name_IP()  # Function call

            # if platform == 'android':
            #     print('from AndroidGetIP import getIP')
            #     from AndroidGetIP import getIP
            #     print('self.device_ip = getIP()')
            #     self.device_ip = getIP()
            #     print('...................................')
            # else:
            #     import socket
            #     self.device_ip = [l for l in (
            #     [ip for ip in socket.gethostbyname_ex(socket.gethostname())[2] if not ip.startswith("127.")][:1], [
            #         [(s.connect(('8.8.8.8', 53)), s.getsockname()[0], s.close()) for s in
            #          [socket.socket(socket.AF_INET, socket.SOCK_DGRAM)]][0][1]]) if l][1][0]
            #     print('**************', self.device_ip)

            self.lan_sock = self.osc.listen(self.device_ip, self.port, default=True)

            #  server  #
            @self.osc.address(b'/receive_data_information')
            def receive_data_information(number_of_items, number_of_chunks):
                #  this is (intended) for the windows device to receive
                self.number_of_items_downloading = number_of_items
                self.chunks_total = number_of_chunks
                self.chunks_downloaded = 0
                self.successfully_downloaded = []
                self.status_text = 'Initialising download of {} item(s)'.format(number_of_items)
                self.last_data_received_time = time()

                while not self.replay_chunk_download_queue.empty():
                    self.replay_chunk_download_queue.get()

                self.polling_check_if_data_stopped = Clock.schedule_interval(self.check_if_data_stopped, 2)

            #  server  #
            @self.osc.address(b'/receive_replay_chunk')
            def receive_replay_chunk(chunk_index, chunk):
                #  this is (intended) for the windows device to receive
                print('D {} / {} -> {}'.format(self.chunks_downloaded, self.chunks_total, chunk))
                self.calculate_download_ratio()
                if chunk not in self.successfully_downloaded:
                    #  when there is an error sending a chunk i go back and send the previous chunk
                    #  this could lead to duplicates so i check to see if it's already added
                    self.successfully_downloaded.append(chunk)
                    self.replay_chunk_download_queue.put((chunk_index, chunk))
                    self.chunks_downloaded += 1
                else:
                    print('THIS CHUNK IS ALREADY DOWNLOADED')
                self.last_data_received_time = time()
                if self.chunks_downloaded < self.chunks_total:
                    print('................', self.chunks_downloaded, self.chunks_total)
                    try:
                        self.osc.answer(b'/next_chunk', [], safer=True)
                    except:
                        self.osc.send_message(b'/next_chunk', [], self.other_ip, 8002, self.lan_sock, safer=True)
                else:
                    self.reconstruct_replay_chunks()
                self.status_text = 'Downloading chunk {} out of {}'.format(self.chunks_downloaded, self.chunks_total)

            #  client  #
            @self.osc.address(b'/next_chunk')
            def next_chunk():
                chunk_index, chunk = self.replay_chunk_upload_queue.get()
                self.chunks_uploaded += 1
                print('U {} / {}'.format(chunk_index, self.chunks_total))
                self.calculate_upload_ratio()
                self.send_replay_chunk(chunk_index, chunk)

            #  client  #
            @self.osc.address(b'/receive_resume_from_chunk')
            def receive_resume_from_chunk(chunk_start):
                print('received instruction to resume from chunk', chunk_start)
                self.status_text = 'Resuming from chunk {} out of {}'.format(self.chunks_downloaded, self.chunks_total)
                self.add_to_upload_queue(chunk_start)

    def check_if_data_stopped(self, *args):
        if time() - self.last_data_received_time > 0.125:
            print('RESUMING FROM CHUNK', self.chunks_downloaded)
            self.resume_from_chunk(self.chunks_downloaded)

    def do_on_pre_leave(self, *args):
        # self.osc.stop()
        self.do_polling = None

    def __init__(self, **kwargs):
        super(SendReceive, self).__init__(**kwargs)
        self.osc = OSCThreadServer(encoding=None)
        self.polling_check_if_data_stopped = None

    def resume_from_chunk(self, chunk_start=None):
        if chunk_start is None:
            chunk_start = self.chunks_downloaded  # + 1#-1 # + 1
        self.status_text = 'Resuming from chunk {} out of {}'.format(self.chunks_downloaded, self.chunks_total)
        self.osc.send_message(b'/receive_resume_from_chunk', [chunk_start], self.other_ip, 8002, self.lan_sock)

    def send_replay_chunk(self, chunk_index, chunk):
        #  this is (intended) for the android device to send
        self.status_text = 'Uploading chunk {} out of {}'.format(self.chunks_downloaded, self.chunks_total)
        self.osc.send_message(b'/receive_replay_chunk', [chunk_index, chunk], self.other_ip, 8002, self.lan_sock,
                              safer=True)

    def send_replay_chunks(self):
        print('going to genrate replay chunks')

        app = App.get_running_app()

        if hasattr(app, 'selected_items') and app.selected_items:
            items = app.selected_items
            print('{} item(s) selected -> {}'.format(len(items), items))
            self.status_text = '{} item(s) selected -> {}'.format(len(items), items)
        else:
            items = dict(self.selected_store)
            print('No items selected. Sending all {} -> '.format(len(items), items))
            self.status_text = 'No items selected. Sending all {} -> '.format(len(items), items)

        if not items:
            self.status_text = 'No items to be sent'
            return

        item_json = json.dumps(items)
        self.item_bytes = item_json.encode()

        number_of_items = len(items)
        unrounded_number_of_chunks = len(self.item_bytes) / self.chunk_size
        self.chunks_total = round(unrounded_number_of_chunks + .5)

        while not self.replay_chunk_upload_queue.empty():
            self.replay_chunk_upload_queue.get()

        self.status_text = 'Initialising upload of {} item(s)'.format(number_of_items)
        self.status_text = 'Initialising upload of {} item(s)'.format(number_of_items)
        print('@@@@', type(self.other_ip), self.other_ip)
        self.osc.send_message(b'/receive_data_information', [number_of_items, self.chunks_total], self.other_ip, 8002,
                              self.lan_sock)
        print('{{{{{{{{{{{{{{{ self.chunks_total', self.chunks_total)

        self.chunks_uploaded = 0
        self.add_to_upload_queue_thread = Thread(target=self.add_to_upload_queue, args=[])
        self.add_to_upload_queue_thread.start()

    def add_to_upload_queue(self, chunk_index=0):
        if not hasattr(self, 'item_bytes'):
            return
        x = chunk_index * self.chunk_size
        first_chunk = True
        while True:
            chunk = self.item_bytes[x:x + self.chunk_size]
            print('adding chunk', chunk_index)
            if chunk:
                #  ie if it's the first chunk
                if first_chunk:
                    first_chunk = False
                    #  the first time you dont need to put it in a queue. just send it.
                    #  this will keep off a send and receive parle between the two devices,
                    #  which will .get the subsequent chunks from the replay_chunk_upload_queue (see the else statement)
                    self.send_replay_chunk(chunk_index, chunk)
                else:
                    self.replay_chunk_upload_queue.put((chunk_index, chunk))
                    print(chunk)

                x += self.chunk_size
                chunk_index += 1

            else:
                break
        print(chunk)
        print('{{{{{{{{{{{{{{{ chunk_index', chunk_index)

    def reconstruct_replay_chunks(self):
        if self.polling_check_if_data_stopped:
            self.polling_check_if_data_stopped.cancel()
        self.polling_check_if_data_stopped = None

        # chunk_list = [None for x in range(self.chunks_total)]
        chunk_list = []
        while not self.replay_chunk_download_queue.empty():
            chunk_index, chunk = self.replay_chunk_download_queue.get()
            print('recon:', chunk_index, chunk)
            chunk_list.append(chunk.decode())
            # chunk_list[chunk_index] = chunk.decode()
            # chunk_list.append(chunk.decode())
            #  decode it here

        for i, x in enumerate(chunk_list):
            if x:
                print('>>>', i, x[-15:])
            else:
                print('>>>', i, x)
        item_json = ''.join(chunk_list)
        # print('reconstructed item_json', item_json)
        item_dict = json.loads(item_json)

        added = 0
        replaced = 0
        selected_store = self.selected_store
        for item_id, item_info in item_dict.items():
            item_id = float(item_id)
            if item_id in selected_store:
                replaced += 1
            else:
                added += 1
            selected_store[item_id] = item_info

        selected_store.store_load()
        selected_store.store_sync()

    def calculate_upload_ratio(self, *args):
        if self.chunks_total:
            self.chunks_ratio = self.chunks_uploaded / self.chunks_total
        else:
            self.chunks_ratio = 0

    def calculate_download_ratio(self, *args):
        if self.chunks_total:
            self.chunks_ratio = self.chunks_downloaded / self.chunks_total
        else:
            self.chunks_ratio = 0
Exemple #9
0
class OSCSever:
    def __init__(self, activity_server_address, port):
        self.song = SoundAndroidPlayer(self.on_complete)
        self.osc = OSCThreadServer()
        self.osc.listen(port=port, default=True)
        self.activity_server_address = activity_server_address
        self.osc.bind(b'/get_pos', self.get_pos)
        self.osc.bind(b'/load', self.load)
        self.osc.bind(b'/play', self.play)
        self.osc.bind(b'/load_play', self.load_play)
        self.osc.bind(b'/seek', self.seek)
        self.osc.bind(b'/play_new_playlist', self.play_next)
        self.osc.bind(b'/set_volume', self.set_volume)
        self.osc.bind(b'/stop', self.pause)
        self.osc.bind(b'/unload', self.unload)

        self.api = API()
        self.db = Database()
        user = self.db.get_user()
        self.genres = user['genres']
        self.artists = user['artists']
        self.volume = user['volume']
        self.songs_path = user['songs_path']
        self.playlist = self.db.get_playlist()
        self.waiting_for_load = False
        self.seek_pos = 0
        self.downloading = None
        self.waiting_for_download = False
        self.downloads = []

    def check_pos(self, *args):
        if self.song.state == "play" and self.song.length - self.song.get_pos(
        ) < 20:
            next_song = self.playlist.preview_next()
            if next_song is None or next_song.preview_file is None:
                if next_song is None:
                    self.playlist = self.get_new_playlist()
                    next_song = self.playlist.preview_next()
                Logger.debug('SERVICE: Preloading next song.')
                self.download_song(next_song)

    def thread_download_song(self, song):
        self.downloads.append(song.id)
        try:
            res = self.api.download_preview(song)
        except Exception as e:
            Logger.error("SERVICE: Download failed. Reason: %s", e)
        else:
            song.preview_file = save_song(self.songs_path, song, res)
            self.db.update_track(song, 'preview_file', song.preview_file)
            Logger.debug('SERVICE: Downloading song finished.')
        self.downloads.remove(song.id)

    def download_song(self, song):
        if song.id not in self.downloads and song.preview_file is None:
            Logger.debug('SERVICE: Downloading %s.', song.id)
            t = threading.Thread(target=self.thread_download_song,
                                 args=(song, ))
            t.daemon = True
            t.start()
        else:
            Logger.debug(
                'SERVICE: Skipped downloading %s. Already in progress.',
                song.id)

    def get_new_playlist(self):
        Logger.debug('SERVICE: getting new playlist.')
        req = self.api.get_recommendations(
            self.genres,
            self.artists,
            song_type='preview',
        )
        playlist = Playlist(req)
        self.osc.send_message(b'/update_playlist', [],
                              *self.activity_server_address)
        # clean up playlist songs
        favorites = self.db.get_favorites()
        for song in self.playlist.tracks:
            if (song.preview_file and song not in favorites
                    and song != self.song.song_object):
                Logger.debug("Service: Removed %s", song.id)
                os.remove(song.preview_file)
        return playlist

    def get_pos(self, *values):
        pos = self.song.get_pos() if self.song and self.song.is_prepared else 0
        Logger.debug('SERVICE -> ACTIVITY: /pos %s', pos)
        self.osc.answer(b'/pos', [pos])

    def getaddress(self):
        return self.osc.getaddress()

    def load(self, id):
        self.first_load = not getattr(self.song, "id", 0)
        self.song.id = id
        self.song.is_prepared = False
        Logger.debug('SERVICE: Loading %d.', id)
        song = self.db.get_track(id)
        if song.preview_file is None and self.downloading != song.id:
            Logger.debug('SERVICE: %d is not downloaded.', id)
            self.download_song(song)
        if song.id in self.downloads:
            Logger.debug('SERVICE: %d is downloading. Returning.', id)
            self.waiting_for_download = song.id
            return
        Logger.debug('SERVICE: %d file is available.', id)
        self.waiting_for_download = None
        if not self.first_load:
            self.song.reset()
        self.song.load(song.preview_file)
        self.song.song_object = song
        self.db.update_current_track(song)
        self.playlist = self.db.get_playlist()
        self.song.is_prepared = True
        self.update_notification()
        Logger.debug('SERVICE: Song loaded.')

    def load_play(self, id, volume=None):
        Logger.debug('SERVICE: Loading and playing %d.', id)
        self.pause()
        self.load(id)
        self.play(0, volume if volume is not None else self.volume)

    def play(self, seek, volume):
        if not self.song.is_prepared:
            song_id = getattr(self.song, "id", None)
            Logger.debug('SERVICE: %s is not prepared.',
                         song_id if song_id else "Song")
            if not self.waiting_for_download:
                self.load(self.playlist.current_track.id)
            else:
                self.waiting_for_load = True
                self.seek_pos = seek
                self.volume = volume
                return
        else:
            self.waiting_for_load = False
            self.song.is_prepared = True
        self.song.play()
        self.song.seek(seek)
        self.song.volume = volume
        pos = self.song.get_pos()
        Logger.debug('SERVICE -> ACTIVITY: /playing %s.', pos)
        values = [self.song.song_object.id, pos]
        self.osc.send_message(b'/playing', values,
                              *self.activity_server_address)
        Logger.debug('SERVICE: Playing %d.', self.song.song_object.id)
        self.update_notification()

    def stop(self, *values):
        Logger.debug('SERVICE: stopping %d.', self.song.song_object.id)
        self.waiting_for_load = False
        if self.song.is_prepared and self.song.state == 'play':
            self.song.is_prepared = False
            self.song.stop()
        self.update_notification()

    def pause(self, *values):
        Logger.debug('SERVICE: pausing Song.')
        self.waiting_for_load = False
        if self.song.is_prepared and self.song.state == 'play':
            self.song.pause()
        self.update_notification()

    def seek(self, value):
        Logger.debug('SERVICE: seeking %s.', value)
        if self.song.is_prepared:
            self.song.seek(value)

    def set_volume(self, value):
        Logger.debug('SERVICE: setting song volume %s.', value)
        self.volume = value
        if self.song.is_prepared:
            self.song.volume = value

    def on_complete(self, *values):
        Logger.debug('SERVICE -> ACTIVITY: /set_complete')
        self.osc.send_message(b'/set_complete', [True],
                              *self.activity_server_address)
        self.play_next()

    def play_next(self):
        Logger.debug('SERVICE: Playing next.')
        self.stop()
        if self.playlist.is_last:
            self.playlist = self.get_new_playlist()
        song = self.playlist.next()
        self.db.update_playlist(self.playlist)
        self.load_play(song.id)

    def play_previous(self):
        Logger.debug('SERVICE: Playing previous.')
        if not self.playlist.is_first:
            self.stop()
            self.load_play(self.playlist.previous().id)

    def on_state(self, instance, value):
        Logger.debug('SERVICE -> ACTIVITY: /set_state %s', value)
        self.osc.send_message(b'/set_state', [value.encode()],
                              *self.activity_server_address)
        if self.song.is_prepared and self.song.length - self.song.get_pos(
        ) < 0.2:
            self.on_complete()

    def unload(self, *values):
        self.song.unload()

    def update_notification(self):
        notification = self.create_notification()
        notification_manager = context.getSystemService(
            Context.NOTIFICATION_SERVICE)
        notification_manager.notify(1, notification)

    def create_notification(self):
        song = getattr(self.song, "song_object", None)
        if api_version >= 26:
            builder = NotificationCompatBuilder(context, "gtplayer")
        else:
            builder = NotificationCompatBuilder(context)
        (builder.setContentTitle(song.name if song else "GTPlayer").
         setContentText(song.artist if song else "GTPlayer").setContentIntent(
             controller.getSessionActivity()).setDeleteIntent(
                 MediaButtonReceiver.buildMediaButtonPendingIntent(
                     context, PlaybackStateCompat.ACTION_STOP)).setVisibility(
                         NotificationCompat.VISIBILITY_PUBLIC).setSmallIcon(
                             icon))
        style = MediaStyle().setShowActionsInCompactView(0)
        builder.setStyle(style)

        if song is not None and not self.playlist.is_first:
            previous_intent = MediaButtonReceiver.buildMediaButtonPendingIntent(
                context, PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS)
            action = NotificationCompatAction(RDrawable.ic_media_previous,
                                              "Previous", previous_intent)
            builder.addAction(action)

        if self.song.state == "play":
            intent = MediaButtonReceiver.buildMediaButtonPendingIntent(
                context, PlaybackStateCompat.ACTION_PAUSE)
            action = NotificationCompatAction(RDrawable.ic_media_pause,
                                              "Pause", intent)
        else:
            intent = MediaButtonReceiver.buildMediaButtonPendingIntent(
                context, PlaybackStateCompat.ACTION_PLAY)
            action = NotificationCompatAction(RDrawable.ic_media_play, "Play",
                                              intent)
        builder.addAction(action)

        coverart = None
        if song is not None:
            next_intent = MediaButtonReceiver.buildMediaButtonPendingIntent(
                context, PlaybackStateCompat.ACTION_SKIP_TO_NEXT)
            action = NotificationCompatAction(RDrawable.ic_media_next, "Next",
                                              next_intent)
            builder.addAction(action)

            path = os.path.join(images_path, f"{song.id}.png")
            if os.path.isfile(path):
                coverart = path

        coverart = coverart if coverart is not None else "images/empty_coverart.png"
        builder.setLargeIcon(BitmapFactory.decodeFile(coverart))

        return builder.build()