Ejemplo n.º 1
0
async def main():

    pictures_queue = Queue()
    workers_count = 300
    connection = {
        'user': '******',  # input your postgres username
        'database': 'your database name',  # input your database name here
        'host': '127.0.0.1',  # change your host if it's not local
        'password': '******'  # input your password for this database
    }
    dsn = 'postgresql://{user}:{password}@{host}/{database}'.format(**connection)

    engine = create_engine(dsn)
    result = engine.execute('''select picture from "your_table_name"''')
    res_list = []
    for row in result:
        clean_jpg = row['picture'].split("\n")
        for i in clean_jpg:
            res_list.append(i)
    print(len(res_list))

    for pic in res_list:
        pictures_queue.put_nowait(pic)

    session = AsyncHTMLSession()

    tasks = []
    for num in range(workers_count):
        task = worker(pictures_queue, num, session)
        tasks.append(task)
    await asyncio.gather(*tasks)
Ejemplo n.º 2
0
class Database:
    def __init__(self, database, loop=None):
        self.database = database
        self.loop = loop if loop else asyncio.get_event_loop()
        self.connection_semaphore = Queue(maxsize=1)
        self.connection_semaphore.put_nowait({})
        self.__connect_ref = None

    async def __acquire_connection(self):
        await self.connection_semaphore.get()
        return sqlite3.connect(self.database, check_same_thread=False)

    async def __call__(self, *args, **kwargs):
        db = await self.__acquire_connection()
        return Connection(db, self)

    async def __aenter__(self):
        db = await self.__acquire_connection()
        conn = Connection(db, self)
        self.__connect_ref = conn
        return conn

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        if self.__connect_ref:
            await self.__connect_ref.close()
Ejemplo n.º 3
0
class TaskPool(object):
    def __init__(self, num_workers):
        self.loop = asyncio.get_event_loop()
        self.tasks = Queue(loop=self.loop)
        self.workers = []
        for _ in range(num_workers):
            worker = asyncio.create_task(self.worker())
            self.workers.append(worker)

    async def worker(self):
        while True:
            future, task = await self.tasks.get()
            if task == "TERMINATOR":
                break
            result = await asyncio.wait_for(task, None, loop=self.loop)
            future.set_result(result)

    def submit(self, task):
        future = asyncio.Future(loop=self.loop)
        self.tasks.put_nowait((future, task))
        return future

    async def close(self):
        for _ in self.workers:
            self.tasks.put_nowait((None, "TERMINATOR"))
        await asyncio.gather(*self.workers, loop=self.loop)
Ejemplo n.º 4
0
class QueueMailBox(MailBox):
    def __init__(self, name, *args, **kwargs):
        super(QueueMailBox, self).__init__(*args, **kwargs)
        self._name = name
        self._queue = Queue()
        self._ready = True

    async def prepare(self):
        self._ready = True

    async def put(self, msg=None):
        if await self.policy():
            self._queue.put_nowait(msg)

    async def size(self):
        return self._queue.qsize()

    async def empty(self):
        return self._queue.empty()

    async def get(self):
        result = None
        result = await self._queue.get()
        return result

    async def policy(self):
        mem_percent = psutil.virtual_memory().percent
        if mem_percent > 80:
            logger.warning("memory usage is gt than 80")
        return True
class TaskPool(object):
    def __init__(self, loop, num_workers):
        self.loop = loop
        self.tasks = Queue(loop=self.loop)
        self.workers = []
        for _ in range(num_workers):
            worker = asyncio.ensure_future(self.worker(), loop=self.loop)
            self.workers.append(worker)

    async def worker(self):
        while True:
            future, task = await self.tasks.get()
            if task is TERMINATOR:
                break
            result = await asyncio.wait_for(task, None, loop=self.loop)
            future.set_result(result)

    def submit(self, task):
        future = asyncio.Future(loop=self.loop)
        self.tasks.put_nowait((future, task))
        return future

    async def join(self):
        for _ in self.workers:
            self.tasks.put_nowait((None, TERMINATOR))
        await asyncio.gather(*self.workers, loop=self.loop)
Ejemplo n.º 6
0
class ChannelHandler(logging.Handler):
    def __init__(self, bot: discord.Client, cog, cog_name: str,
                 channel: discord.Channel, *args, **kwargs):
        self.cog = cog
        self.cog_name = cog_name
        self.queue = Queue()
        self.channel = channel
        self.bot = bot
        super(ChannelHandler, self).__init__(*args, **kwargs)

    def emit(self, record):
        if self.channel:
            self.queue.put_nowait(record)

    async def update_task(self):
        while self.cog == self.bot.get_cog(self.cog_name):
            if not self.channel:
                await asyncio.sleep(1)
            else:
                record = await self.queue.get()
                if self.cog != self.bot.get_cog(self.cog_name):
                    return
                msg = self.format(record)
                await self.bot.send_message(content=msg,
                                            destination=self.channel)
Ejemplo n.º 7
0
class IRCClientProtocol(asyncio.Protocol):
    """Low-level protocol that speaks the client end of IRC.

    This isn't responsible for very much besides the barest minimum definition
    of an IRC client: connecting and responding to PING.

    You probably want `read_message`, or the higher-level client class.
    """

    def __init__(self, loop, nick, password, charset="utf8"):
        self.nick = nick
        self.password = password
        self.charset = charset

        self.buf = b""
        self.message_queue = Queue(loop=loop)
        self.registered = False

    def connection_made(self, transport):
        self.transport = transport
        if self.password:
            self.send_message("PASS", self.password)
        self.send_message("NICK", self.nick)
        self.send_message("USER", "dywypi", "-", "-", "dywypi Python IRC bot")

    def data_received(self, data):
        data = self.buf + data
        while True:
            raw_message, delim, data = data.partition(b"\r\n")
            if not delim:
                # Incomplete message; stop here and wait for more
                self.buf = raw_message
                return

            # TODO valerr
            message = IRCMessage.parse(raw_message.decode(self.charset))
            logger.debug("recv: %r", message)
            self.handle_message(message)

    def handle_message(self, message):
        if message.command == "PING":
            self.send_message("PONG", message.args[-1])

        elif message.command == "RPL_WELCOME":
            # 001, first thing sent after registration
            if not self.registered:
                self.registered = True

        self.message_queue.put_nowait(message)

    def send_message(self, command, *args):
        message = IRCMessage(command, *args)
        logger.debug("sent: %r", message)
        self.transport.write(message.render().encode(self.charset) + b"\r\n")

    @asyncio.coroutine
    def read_message(self):
        return (yield from self.message_queue.get())
Ejemplo n.º 8
0
 async def _serve(self, websocket, path):
     import websockets
     connection_id = await websocket.recv()
     with self.queue_lock:
         #if connection_id in self._preclosed_connections:
         #    self._preclosed_connections[connection_id].set()
         #    return
         pmqueue = self._pending_message_queues.get(connection_id, None)
         myqueue = Queue()
         if pmqueue is not None:
             events = []
             while 1:
                 try:
                     e = pmqueue.get_nowait()
                 except QueueEmpty:
                     break
                 events.append(e)
             if len(events) > self.CACHE_EVENTS_FIRST + self.CACHE_EVENTS_LAST:
                 events = events[:self.CACHE_EVENTS_FIRST] + \
                     events[-self.CACHE_EVENTS_LAST:]
             for enr, e in enumerate(events):
                 swallow = False
                 if e.get("type", None) == "var":
                     varname = e.get("var", None)
                     if varname is not None:
                         for e2 in events[enr+1:]:
                             if e2.get("type", None) != "var":
                                 continue
                             if e2.get("var", None) != varname:
                                 continue
                             swallow = True
                             break
                 if swallow:
                     continue
                 myqueue.put_nowait(e)
                 pmqueue.put_nowait(e) #put the events back
         if connection_id not in self._message_queue_lists:
             self._message_queue_lists[connection_id] = []
         self._message_queue_lists[connection_id].append(myqueue)
     while True:
         #print("WAIT")
         try:
             message = await myqueue.get()
         except Exception:
             break
         message = json.dumps(message)
         #print("SEND?", message)
         if message is None: #terminating message
             break
         try:
             #print("SEND", message)
             await websocket.send(message)
         except websockets.exceptions.ConnectionClosed:
             break
     with self.queue_lock:
         self._message_queue_lists[connection_id].remove(myqueue)
Ejemplo n.º 9
0
class Core(object):

    def __init__(self, bot):
        self.bot = bot
        self.timeout = int(self.bot.config.get('timeout'))
        self.ping_queue = Queue(loop=bot.loop)

    def connection_made(self):
        self.bot.loop.call_later(self.timeout, self.check_ping)
        self.ping_queue.put_nowait(self.bot.loop.time())

    def check_ping(self):  # pragma: no cover
        # check if we received a ping
        # reconnect if queue is empty
        self.bot.log.debug(
            'Ping queue size: {}'.format(self.ping_queue.qsize()))
        if self.ping_queue.empty():
            self.bot.loop.call_soon(self.bot.protocol.transport.close)
        else:
            self.bot.loop.call_later(self.timeout, self.check_ping)
        while not self.ping_queue.empty():
            self.ping_queue.get_nowait()

    @event(rfc.PING)
    def pong(self, data):
        """PING reply"""
        self.ping_queue.put_nowait(self.bot.loop.time())
        self.bot.send('PONG ' + data)

    @event(rfc.NEW_NICK)
    def recompile(self, nick=None, new_nick=None, **kw):
        """recompile regexp on new nick"""
        if self.bot.nick == nick.nick:
            self.bot.config['nick'] = new_nick
            self.bot.recompile()

    @event(rfc.ERR_NICK)
    def badnick(self, me=None, nick=None, **kw):
        """Use alt nick on nick error"""
        if me == '*':
            self.bot.set_nick(self.bot.nick + '_')
        self.bot.log.debug('Trying to regain nickname in 30s...')
        self.bot.loop.call_later(30, self.bot.set_nick, self.bot.original_nick)

    @event(rfc.RPL_ENDOFMOTD)
    def autojoin(self, **kw):
        """autojoin at the end of MOTD"""
        self.bot.config['nick'] = kw['me']
        self.bot.recompile()
        channels = utils.as_list(self.bot.config.get('autojoins', []))
        for channel in channels:
            channel = utils.as_channel(channel)
            self.bot.log.info('Trying to join %s', channel)
            self.bot.join(channel)
Ejemplo n.º 10
0
async def searchWorker(name: str, searchQueue: Queue, queueProgress: tqdm):
    while True:
        # Get a "work item" out of the queue.
        data: QueueObj = await searchQueue.get()
        query = data['query']
        url = queryToUrl(query)
        print(Fore.MAGENTA + f'{name} got url: {url} to work on' + Fore.RESET)
        start = time.time()
        custom_timeout = aiohttp.ClientTimeout(total=60)  # type: ignore
        # custom_timeout.total = 2*60
        async with aiohttp.ClientSession(timeout=custom_timeout) as session:
            # nonlocal results
            results = []
            try:
                results = await getSearchQueryResults(session, url)
            except asyncio.TimeoutError as e:
                logging.exception(
                    Fore.RED +
                    f'Exception raised by worker = {name}; error = {e}' +
                    Fore.RESET)
            except Exception as e:  # pylint: disable=broad-except
                logging.exception(
                    Fore.RED +
                    f'Exception raised by worker = {name}; error = {e}' +
                    Fore.RESET)
        print(
            Fore.YELLOW +
            f'{name}\'s work for url {url} done; time taken {time.time() - start} seconds'
            + Fore.RESET)
        if (len(results) > 0 and len(results[0]['results']) > 0
                and len(results[0]['results'][query]) > 0):
            print(f'query: \'{query}\' fetched with data'.encode(
                encoding='utf-8'))
            allResults = fromSearchData(results, query)
            searchQueue.task_done()
            onlyTitles = [item['title'] for item in allResults]
            relevantIndices = getRelevantTitleIndices(onlyTitles)
            relevantSearchData = [
                allResults[index] for index in relevantIndices
            ]
            bookRow = booksDf[booksDf['id'] == data['id']].copy()
            bookRow['searchLinks'] = [relevantSearchData]
            bookRow['query'] = query
            saveLinks(bookRow)
            queueProgress.update(1)
            # searchResults.append({
            #     query: results
            # })
        else:
            print(f'query: \'{query}\' unsuccessful, adding to queue again'.
                  encode(encoding='utf-8'))
            searchQueue.task_done()
            searchQueue.put_nowait(data)
Ejemplo n.º 11
0
class Port(object):
    def __init__(self, tag="data", maxsize=1, name=None, loop=None):
        loop = loop if loop is not None else asyncio.get_event_loop()
        self.loop = loop
        self.name = name if name is not None else str(uuid1())
        self._queue = Queue(maxsize, loop=self.loop)
        self.default_value = None
        self.default_value_set = False
        self.connected = False
        self.belong_to_block = None
        self.data_tag = tag

    def set_default_value(self, value):
        if not isinstance(value, Payload):
            raise Exception("value should be Payload type")
        self.default_value = value
        self.default_value_set = True

    async def get(self):
        if self.default_value_set:
            if self._queue.empty():
                return self.default_value, self.default_value[self.data_tag]

        payload = await self._queue.get()
        return payload, payload[self.data_tag]

    def get_nowait(self):
        if self.default_value_set:
            if self._queue.empty():
                return self.default_value, self.default_value[self.data_tag]

        payload = self._queue.get_nowait()
        return payload, payload[self.data_tag]

    async def put(self, payload, item):
        if self.connected:
            payload[self.data_tag] = item
            await self._queue.put(payload)

    def put_nowait(self, payload, item):
        if self.connected:
            payload[self.data_tag] = item
            self._queue.put_nowait(payload)

    def empty(self):
        return self._queue.empty()

    def full(self):
        return self._queue.full()

    def set_buffer_size(self, maxsize):
        self._queue = Queue(maxsize, loop=self.loop)
Ejemplo n.º 12
0
async def test_commit_concurrency(aconn):
    # Check the condition reported in psycopg2#103
    # Because of bad status check, we commit even when a commit is already on
    # its way. We can detect this condition by the warnings.
    notices = Queue()
    aconn.add_notice_handler(
        lambda diag: notices.put_nowait(diag.message_primary))
    stop = False

    async def committer():
        nonlocal stop
        while not stop:
            await aconn.commit()
            await asyncio.sleep(0)  # Allow the other worker to work

    async def runner():
        nonlocal stop
        cur = aconn.cursor()
        for i in range(1000):
            await cur.execute("select %s;", (i, ))
            await aconn.commit()

        # Stop the committer thread
        stop = True

    await asyncio.gather(committer(), runner())
    assert notices.empty(), "%d notices raised" % notices.qsize()
Ejemplo n.º 13
0
class FoundComponentIterator:
    def __init__(self, loop, components_file_request_list):
        self.loop = loop
        self.pending_tasks = components_file_request_list
        self._to_remove = None
        self.done = Queue(loop=self.loop)
        self.add_done_callback()

    def add_done_callback(self):
        for task in self.pending_tasks:
            task.add_done_callback(self.on_completion)

    def on_completion(self, task):
        self.pending_tasks.remove(task)
        self.done.put_nowait(task)

    async def cancel_pending_tasks(self):
        for task in self.pending_tasks:
            task.cancel()
        if len(self.pending_tasks):
            await asyncio.wait(self.pending_tasks)
        while not self.done.empty():
            task = self.done.get_nowait()
            try:
                task.result()
            except:
                pass

    def __aiter__(self):
        return self

    async def __anext__(self):
        try:
            while len(self.pending_tasks) > 0 or not self.done.empty():
                try:
                    future = await self.done.get()
                    component_key, fetched_files = await future
                    self._to_remove = future
                    if len(fetched_files) > 0:
                        return {'key': component_key, 'files': fetched_files}
                except (RejectRequest, StopRequest):
                    # Not fatal at all, just one of many
                    pass
        except OfflineHostException:
            await self.cancel_pending_tasks()
            raise
        raise StopAsyncIteration
Ejemplo n.º 14
0
async def main():
    category_queue = Queue()
    workers_count = 100
    category_base_url = 'https://rozetka.com.ua/ua/notebooks/c80004/page={}'

    for i in range(1, 100):
        cat_url = category_base_url.format(i)
        category_queue.put_nowait(cat_url)

    session = AsyncHTMLSession()

    tasks = []
    for num in range(workers_count):
        task = worker(category_queue, num, session)
        tasks.append(task)

    await asyncio.gather(*tasks)
Ejemplo n.º 15
0
async def main():
    category_queue = Queue()
    workers_count = 100
    with open('notebook_links.txt', 'r', encoding='utf-8') as f:
        links = f.read().split('\n')
        for link in links:
            category_queue.put_nowait(link)

        session = AsyncHTMLSession()
        engine1 = await create_engine(**connection)

        tasks = []
        for num in range(workers_count):
            task = worker(category_queue, num, session, engine1)
            tasks.append(task)

        await asyncio.gather(*tasks)
Ejemplo n.º 16
0
class IrcConnection(asyncio.Protocol):
    """asyncio protocol to handle an irc connection"""

    def connection_made(self, transport):
        self.transport = transport
        self.closed = False
        self.queue = Queue()

    def data_received(self, data):
        encoding = getattr(self, 'encoding', 'ascii')
        data = data.decode(encoding, 'ignore')
        if not self.queue.empty():
            data = self.queue.get_nowait() + data
        lines = data.split('\r\n')
        self.queue.put_nowait(lines.pop(-1))
        for line in lines:
            self.factory.dispatch(line)

    def write(self, data):
        if data is not None:
            if isinstance(data, text_type):
                data = data.encode(self.encoding)
            if not data.endswith(b'\r\n'):
                data = data + b'\r\n'
            self.transport.write(data)

    def connection_lost(self, exc):  # pragma: no cover
        self.factory.log.critical('connection lost (%s): %r',
                                  id(self.transport),
                                  exc)
        self.factory.notify('connection_lost')
        if not self.closed:
            self.close()
            # wait a few before reconnect
            self.factory.loop.call_later(
                2, self.factory.create_connection, self.__class__)

    def close(self):  # pragma: no cover
        if not self.closed:
            self.factory.log.critical('closing old transport (%r)',
                                      id(self.transport))
            try:
                self.transport.close()
            finally:
                self.closed = True
Ejemplo n.º 17
0
async def main():
    urls_queue = Queue()
    workers_count = 100
    pages = 97
    category_base_url = 'https://www.searchengines.ru/category/seo/page/{}'
    for i in range(1, pages):
        cat_url = category_base_url.format(i)
        urls_queue.put_nowait(cat_url)

    session = AsyncHTMLSession()
    engine = await create_engine(**connection)

    tasks = []
    for num in range(workers_count):
        task = worker(urls_queue, num, session, engine)
        tasks.append(task)

    await asyncio.gather(*tasks)
Ejemplo n.º 18
0
class IrcConnection(asyncio.Protocol):
    """asyncio protocol to handle an irc connection"""
    def connection_made(self, transport):
        self.transport = transport
        self.closed = False
        self.queue = Queue()

    def data_received(self, data):
        encoding = getattr(self, 'encoding', 'ascii')
        data = data.decode(encoding, 'ignore')
        if not self.queue.empty():
            data = self.queue.get_nowait() + data
        lines = data.split('\r\n')
        self.queue.put_nowait(lines.pop(-1))
        for line in lines:
            self.factory.dispatch(line)

    def write(self, data):
        if data is not None:
            if isinstance(data, text_type):
                data = data.encode(self.encoding)
            if not data.endswith(b'\r\n'):
                data = data + b'\r\n'
            self.transport.write(data)

    def connection_lost(self, exc):  # pragma: no cover
        self.factory.log.critical('connection lost (%s): %r',
                                  id(self.transport), exc)
        self.factory.notify('connection_lost')
        if not self.closed:
            self.close()
            # wait a few before reconnect
            self.factory.loop.call_later(2, self.factory.create_connection,
                                         self.__class__)

    def close(self):  # pragma: no cover
        if not self.closed:
            self.factory.log.critical('closing old transport (%r)',
                                      id(self.transport))
            try:
                self.transport.close()
            finally:
                self.closed = True
Ejemplo n.º 19
0
class LocalChat(BaseChat):
    def __init__(self, bridge):
        super().__init__(bridge)
        self.queue = Queue()

    def _create_channel(self, channel):
        return channel

    async def _send_msg(self, msg):
        print(msg.to_dict())
        print(msg.message)

    async def _listen_to(self, channel):
        loop = get_running_loop()
        loop.add_reader(sys.stdin,
                        lambda: self.queue.put_nowait(sys.stdin.readline()))

        while True:
            msg = await self.queue.get()
            self.handle_msg(
                IncomingMessage(message=msg,
                                channel=channel,
                                date=datetime.now(tz=timezone.utc),
                                **_BASE_MSG))
Ejemplo n.º 20
0
async def dump1090Receiver(incomingQ: Queue,
                           db_worker_Q: Queue = None,
                           radar_Q=None,
                           websocket_Q=None):
    """
        Receive a raw mesage from a Queue parse it sent it to the database_worker (if queue is not None) process the message otherwise)

    :param incomingQ: incoming messaged from 1090
    :param db_worker_Q: queue to send processed messages to db layer -- if not None
    :param radar_Q: queue to send processed messages to radar -- if not None
    :return:
    """
    LOG.info("Starting")
    try:
        while True:
            raw_sbs1_msg = await incomingQ.get()
            #print(f"{raw_item}")

            sbs1_msg = SBS1Message(raw_sbs1_msg)
            if sbs1_msg.isValid:

                #process(sbs1_msg)

                result = process2(sbs1_msg)
                if result is None:
                    continue

                # higher level
                new_aircraft = result.new_aircraft
                position = any([
                    result.callsign, result.squawk, result.altitude,
                    result.ground_speed, result.track, result.lat, result.lon,
                    result.vertical_rate, result.on_ground
                ])
                emergency = any([result.alert, result.emergency])

                if any([new_aircraft, position, emergency]):
                    #print(F"[{sbs1_msg.messageType}{sbs1_msg.transmissionType}] New Aircraft: {new_aircraft} Position: {position} Emergency: {emergency}")
                    #dump_row(db2, sbs1_msg.icao24)

                    actionTypesList = []
                    if new_aircraft:
                        actionTypesList.append("new_aircraft")
                    if position:
                        actionTypesList.append("position")
                    if emergency:
                        actionTypesList.append("emergency")

                    incoming_action = INCOMING_ACTION_TYPE(
                        actionTypes=actionTypesList,
                        actionMsg=result.icao_record,
                        aircraftId=sbs1_msg.icao24)

                    STATS["incoming"] += 1

                    if STATS["incoming"] % 100 == 0:
                        LOG.info(F"Incoming {STATS['incoming']}")

                    if db_worker_Q:
                        db_worker_Q.put_nowait(incoming_action)

                    if radar_Q:
                        radar_Q.put_nowait(incoming_action)

                    if websocket_Q:
                        websocket_Q.put_nowait(incoming_action)

                flush_cache(300)

            else:
                STATS["parsing_error"] += 1
                LOG.warning(
                    F"Invalid sbs1_msg. Failed processing count: {STATS['parsing_error']}"
                )

    except CancelledError:
        LOG.info("Cancelling")
    except Exception:
        LOG.exception("Any exception")
    finally:
        pass
    LOG.info("Exiting")
Ejemplo n.º 21
0
class DywypiShell(UrwidProtocol):
    """Creates a Twisted-friendly urwid app that allows interacting with dywypi
    via a shell.
    """

    # TODO i don't think client.nick should really be part of the exposed
    # interface; should be a .me returning a peer probably
    # TODO for some reason this is the client even though the thing below is
    # actually called a Client so we should figure this the f**k out
    nick = 'dywypi'

    def __init__(self, loop, network, *args, **kwargs):
        super().__init__(loop, **kwargs)

        self.event_queue = Queue(loop=self.loop)

        self.network = network

        self.me = Peer('dywypi', 'dywypi', 'localhost')
        self.you = Peer('user', 'user', 'localhost')


    def build_toplevel_widget(self):
        self.pane = UnselectableListBox(urwid.SimpleListWalker([]))
        prompt = FancyEdit('>>> ')
        urwid.connect_signal(prompt, 'line_submitted', self.handle_line)

        return urwid.Pile(
            [
                self.pane,
                ('flow', prompt),
            ],
            focus_item=prompt,
        )

    def build_palette(self):
        return [
            ('default', 'default', 'default'),
            ('logging-debug', 'dark gray', 'default'),
            ('logging-info', 'light gray', 'default'),
            ('logging-warning', 'light red', 'default'),
            ('logging-error', 'dark red', 'default'),
            ('logging-critical', 'light magenta', 'default'),
            ('shell-input', 'light gray', 'default'),
            ('bot-output', 'default', 'default'),
            ('bot-output-label', 'dark cyan', 'default'),
        ]

    def unhandled_input(self, key):
        # Try passing the key along to the listbox, so pgup/pgdn still work.
        # Note that this is a Pile method specifically, and requires an index
        # rather than a widget
        # TODO no indication whether we're currently scrolled up.  scroll back
        # to bottom after x seconds with no input?
        listsize = self.widget.get_item_size(
            self.urwid_loop.screen_size, 0, False)
        key = self.pane.keypress(listsize, key)
        if key:
            # `key` gets returned if it wasn't consumed
            self.add_log_line(key)

    def start(self):
        super(DywypiShell, self).start()

        #self.hub.network_connected(self.network, self)

    def add_log_line(self, line, color='default'):
        # TODO generalize this color thing in a way compatible with irc, html, ...
        # TODO i super duper want this for logging, showing incoming/outgoing
        # messages in the right colors, etc!!
        self._print_text((color, line.rstrip()))

    def _print_text(self, *encoded_text):
        self.pane.body.append(urwid.Text(list(encoded_text)))
        self.pane.set_focus(len(self.pane.body) - 1)
        # TODO should this just mark dirty??
        self.urwid_loop.draw_screen()

    def handle_line(self, line):
        """Deal with a line of input."""
        try:
            self._handle_line(line)
        except Exception as e:
            log.exception(e)

    def _handle_line(self, line):
        """All the good stuff happens here.

        Various things happen depending on what the line starts with.

        Colon: This is a command; pretend it was sent as a private message.
        """
        # Whatever it was, log it
        self.pane.body.append(urwid.Text(['>>> ', ('shell-input', line)]))

        if line.startswith(':'):
            command_string = line[1:]

            # TODO rather we didn't need raw_message...
            raw_message = ShellMessage(self.me.name, command_string)
            event = Message(self, raw_message)
            self.event_queue.put_nowait(event)

    def _send_message(self, target, message, as_notice=True):
        # TODO cool color
        self.add_log_line(message)

    @asyncio.coroutine
    def say(self, target, message):
        # TODO target should probably be a peer, eh
        if target == self.you.name:
            prefix = "bot to you: "
        else:
            prefix = "bot to {}: ".format(target)
        self._print_text(('bot-output-label', prefix), ('bot-output', message))

    def format_transition(self, current_style, new_style):
        # TODO wait lol shouldn't this be converting to urwid-style tuples
        if new_style == Style.default():
            # Just use the reset sequence
            return '\x1b[0m'

        ansi_codes = []
        if new_style.fg != current_style.fg:
            ansi_codes.append(FOREGROUND_CODES[new_style.fg])

        if new_style.bold != current_style.bold:
            ansi_codes.append(BOLD_CODES[new_style.bold])

        return '\x1b[' + ';'.join(ansi_codes) + 'm'
Ejemplo n.º 22
0
class GoogleReportState(iot_base.BaseIoT):
    """Report states to Google.

    Uses a queue to send messages.
    """
    def __init__(self, cloud: Cloud):
        """Initialize Google Report State."""
        super().__init__(cloud)
        self._connect_lock = asyncio.Lock()
        self._to_send = Queue(100)
        self._message_sender_task = None
        # Local code waiting for a response
        self._response_handler: Dict[str, asyncio.Future] = {}
        self.register_on_connect(self._async_on_connect)
        self.register_on_disconnect(self._async_on_disconnect)

        # Register start/stop
        cloud.register_on_stop(self.disconnect)

    @property
    def package_name(self) -> str:
        """Return the package name for logging."""
        return __name__

    @property
    def ws_server_url(self) -> str:
        """Server to connect to."""
        # https -> wss, http -> ws
        return f"ws{self.cloud.google_actions_report_state_url[4:]}/v1"

    async def async_send_message(self, msg):
        """Send a message."""
        msgid = uuid.uuid4().hex

        # Since connect is async, guard against send_message called twice in parallel.
        async with self._connect_lock:
            if self.state == iot_base.STATE_DISCONNECTED:
                self.cloud.run_task(self.connect())
                # Give connect time to start up and change state.
                await asyncio.sleep(0)

        if self._to_send.full():
            discard_msg = self._to_send.get_nowait()
            self._response_handler.pop(discard_msg["msgid"]).set_exception(
                ErrorResponse(ERR_DISCARD_CODE, ERR_DISCARD_MSG))

        fut = self._response_handler[msgid] = asyncio.Future()

        self._to_send.put_nowait({"msgid": msgid, "payload": msg})

        try:
            return await fut
        finally:
            self._response_handler.pop(msgid, None)

    def async_handle_message(self, msg):
        """Handle a message."""
        response_handler = self._response_handler.get(msg["msgid"])

        if response_handler is not None:
            if "error" in msg:
                response_handler.set_exception(
                    ErrorResponse(msg["error"], msg["message"]))
            else:
                response_handler.set_result(msg.get("payload"))
            return

        self._logger.warning("Got unhandled message: %s", msg)

    async def _async_on_connect(self):
        """On Connect handler."""
        self._message_sender_task = self.cloud.run_task(
            self._async_message_sender())

    async def _async_on_disconnect(self):
        """On disconnect handler."""
        self._message_sender_task.cancel()
        self._message_sender_task = None

    async def _async_message_sender(self):
        """Start sending messages."""
        self._logger.debug("Message sender task activated")
        try:
            while True:
                await self.async_send_json_message(await self._to_send.get())
        except asyncio.CancelledError:
            pass
        self._logger.debug("Message sender task shut down")
Ejemplo n.º 23
0
    class HttpProtocol(asyncio.Protocol):
        def __init__(self, loop, handler):
            self.parser = cparser.HttpRequestParser(
                self.on_headers, self.on_body, self.on_error)
            self.loop = loop
            self.response = Response()

        if flavor == 'queue':
            def connection_made(self, transport):
                self.transport = transport
                self.queue = Queue(loop=self.loop)
                self.loop.create_task(handle_requests(self.queue, transport))
        else:
            def connection_made(self, transport):
                self.transport = transport

        def connection_lost(self, exc):
            self.parser.feed_disconnect()

        def data_received(self, data):
            self.parser.feed(data)

        def on_headers(self, request):
            return

        if flavor == 'block':
            def on_body(self, request):
                handle_request_block(request, self.transport, self.response)
        elif flavor == 'dump':
            def on_body(self, request):
                handle_dump(request, self.transport, self.response)
        elif flavor == 'task':
            def on_body(self, request):
                self.loop.create_task(handle_request(request, self.transport))
        elif flavor == 'queue':
            def on_body(self, request):
                self.queue.put_nowait(request)
        elif flavor == 'inline':
            def on_body(self, request):
                body = 'Hello inlin!'
                status_code = 200
                mime_type = 'text/plain'
                encoding = 'utf-8'
                text = [b'HTTP/1.1 ']
                text.extend([str(status_code).encode(), b' OK\r\n'])
                text.append(b'Connection: keep-alive\r\n')
                text.append(b'Content-Length: ')
                text.extend([str(len(body)).encode(), b'\r\n'])
                text.extend([
                    b'Content-Type: ', mime_type.encode(),
                    b'; encoding=', encoding.encode(), b'\r\n\r\n'])
                text.append(body.encode())

                self.transport.write(b''.join(text))

        elif flavor == 'static':
            def on_body(self, request):
                self.transport.write(static_response)

        def on_error(self, error):
            print(error)
Ejemplo n.º 24
0
class StreamConnection:
    def __init__(self, sr, sw, *, loop=None):
        if not loop:
            loop = asyncio.get_event_loop()
        self._loop = loop
        self._sr = sr
        self._sw = sw
        self._msgs = Queue(loop=loop)
        self._worker = loop.create_task(self._run())

    @asyncio.coroutine
    def _run(self):
        while self.alive():
            try:
                data = yield from self._sr.readline()
                if data and len(data):
                    self._msgs.put_nowait(self._convert(data))
            except asyncio.CancelledError:
                logger.debug("readline from stream reader was cancelled.")
            except ConnectionError:
                logger.debug("connection error")
                break
        logger.debug("connection closed")

    def _convert(self, data):
        return data.strip()

    @asyncio.coroutine
    def recv(self):
        try:
            return self._msgs.get_nowait()
        except QueueEmpty:
            pass

        # Wait for a message until the connection is closed
        next_message = self._loop.create_task(self._msgs.get())
        done, pending = yield from asyncio.wait(
                [next_message, self._worker],
                loop=self._loop, return_when=asyncio.FIRST_COMPLETED)
        if next_message in done:
            return next_message.result()
        else:
            next_message.cancel()

    def send(self, data):
        if not self.alive():
            raise ConnectionError("connection was closed.")
        try:
            data = data + b'\n'
            self._sw.write(data)
        except OSError:
            raise ConnectionError("can't send data.")
        except Exception:
            logger.debug("Q___Q")

    def alive(self):
        return not self._sr.at_eof()

    @asyncio.coroutine
    def drain():
        yield from self._sw.drain()
    
    @asyncio.coroutine
    def close(self):
        if self.alive():
            try:
                yield from self._sw.drain()
                self._sw.write_eof()
            except ConnectionError:
                pass
            else:
                self._sr.feed_eof()
                self._sw.close()
        self._worker.cancel()
Ejemplo n.º 25
0
async def radar(radar_Q: Queue = None):

    if radar_Q is None:
        print("Radar task is terminating. The radar_Q is None. Nothing to do")
        return

    try:
        q = Queue()

        while True:

            async with async_timeout.timeout(5) as tm:
                while True:
                    try:
                        incoming_action:INCOMING_ACTION_TYPE = await radar_Q.get()
                        q.put_nowait(incoming_action)
                        STATS['number_rx'] += 1
                        STATS['max_incoming_q_size'] = max(radar_Q.qsize(), STATS['max_incoming_q_size'])
                    except Exception as x:
                        break

            if not tm.expired:
                continue

            time_now = datetime.utcnow()

            while not q.empty():

                item = q.get_nowait()

                icao_rec = item.actionMsg

                # print(F"DB_WORKER_RXED: {action_types} {icao_rec}")

                # await db_process_sbs1_msg2(database, sbs1_msg)
                id = item.aircraftId
                last_seen = icao_rec['last_seen']
                data  = icao_rec['current']

                delta_sec = (time_now - last_seen).total_seconds()
                if delta_sec > 300:
                    print(F"@Radar: Got an old one: {id} {last_seen} {delta_sec}")
                    STATS['rx_expired'] += 1
                    remove_track(id)
                    del history[id]
                    continue

                history[id] = last_seen

                STATS['max_history_size'] = max(STATS['max_history_size'],len(history))

                if data['lon'] is not None and data['lat'] is not None:
                    update_track(id,data['lat'], data['lon'])
                    STATS['updates'] += 1

                update_table_store(id, last_seen, data)
                render_table_store()
            # endwhile

            # cleanup any lingering tracks:
            flush()

            refresh()


    except CancelledError:
            print("Cancellling radar task")

    except Exception as x:
        print(F"Exception {x}")
Ejemplo n.º 26
0
async def searchWorker(name: str, searchQueue: Queue, queueProgress: tqdm):
    while True:
        # Get a "work item" out of the queue.
        data: QueueObj = await searchQueue.get()
        factsLink = data['factsLink']
        # url = queryToUrl(query)
        print(Fore.MAGENTA + f'{name} got url: {factsLink} to work on' +
              Fore.RESET)
        start = time.time()
        custom_timeout = aiohttp.ClientTimeout(total=60)  # type: ignore
        # custom_timeout.total = 2*60
        async with aiohttp.ClientSession(timeout=custom_timeout) as session:
            # nonlocal results
            summary_sents = {}
            docs_dict = {0: ''}
            sents_dict = {0: ''}
            isSkipped = True
            try:
                if (urlparse(factsLink).path.endswith(".pdf") == False):
                    isSkipped = False
                    docs_dict, sents_dict = getDocsAndSentsPerUrl(factsLink, 5)
                    summary_sents = getTfidfSummary(tfidfModel=tfidf,
                                                    dictionary=dictionary,
                                                    docs_dict=docs_dict,
                                                    sents_dict=sents_dict)
            except asyncio.TimeoutError as e:
                logging.exception(
                    Fore.RED +
                    f'Exception raised by worker = {name}; error = {e}' +
                    Fore.RESET)
            except Exception as e:  # pylint: disable=broad-except
                logging.exception(
                    Fore.RED +
                    f'Exception raised by worker = {name}; error = {e}' +
                    Fore.RESET)
        print(
            Fore.YELLOW +
            f'{name}\'s work for url {factsLink} done; time taken {time.time() - start} seconds'
            + Fore.RESET)
        if (isSkipped == True):
            print(f'factsLink: \'{factsLink}\' skipped because its pdf'.encode(
                encoding='utf-8'))
            searchQueue.task_done()
            bookRow = dfDeduped[dfDeduped['id'] == data['id']].copy()
            bookRow['factsLink'] = factsLink
            bookRow['facts'] = [['']]
            saveFacts(bookRow)
            queueProgress.update(1)
        elif (len(sents_dict[0]) > 2):
            print(f'factsLink: \'{factsLink}\' fetched with > 2 sentences'.
                  encode(encoding='utf-8'))
            searchQueue.task_done()
            bookRow = dfDeduped[dfDeduped['id'] == data['id']].copy()
            bookRow['factsLink'] = factsLink
            bookRow['facts'] = [[sent for score, sent in summary_sents[0]]]
            saveFacts(bookRow)
            queueProgress.update(1)
        else:
            print(
                f'factsLink: \'{factsLink}\' unsuccessful, returned < 2 sents, adding to queue again'
                .encode(encoding='utf-8'))
            searchQueue.task_done()
            queueProgress.update(1)
            searchQueue.put_nowait(data)
Ejemplo n.º 27
0
class IRCClient:
    """Higher-level IRC client.  Takes care of most of the hard parts of IRC:
    incoming server messages are bundled into more intelligible events (see
    ``dywypi.event``), and commands that expect replies are implemented as
    coroutines.
    """

    def __init__(self, loop, network):
        self.loop = loop
        self.network = network
        # TODO should this be a param?  a property of the network?  or, more
        # likely, channel-specific and decoded separately and...
        self.charset = "utf8"

        self.joined_channels = {}  # name => Channel

        # IRC server features, as reported by ISUPPORT, with defaults taken
        # from the RFC.
        self.len_nick = 9
        self.len_channel = 200
        self.len_message = 510
        # These lengths don't have limits mentioned in the RFC, so going with
        # the smallest known values in the wild
        self.len_kick = 80
        self.len_topic = 80
        self.len_away = 160
        self.max_watches = 0
        self.max_targets = 1
        self.channel_types = set("#&")
        self.channel_modes = {}  # TODO, haha.
        self.channel_prefixes = {}  # TODO here too.  IRCMode is awkward.
        self.network_title = self.network.name
        self.features = {}

        # Various intermediate state used for waiting for replies and
        # aggregating multi-part replies
        # TODO hmmm so what happens if state just gets left here forever?  do
        # we care?
        self._pending_names = {}
        self._names_futures = {}
        self._pending_topics = {}
        self._join_futures = {}

        self._message_waiters = deque()

        self.read_queue = Queue(loop=loop)

    def get_channel(self, channel_name):
        """Returns a `Channel` object containing everything the client
        definitively knows about the given channel.

        Note that if you, say, ask for the topic of a channel you aren't in and
        then immediately call `get_channel`, the returned object won't have its
        topic populated.  State is only tracked persistently for channels the
        bot is in; otherwise there's no way to know whether or not it's stale.
        """
        if channel_name in self.joined_channels:
            return self.joined_channels[channel_name]
        else:
            return IRCChannel(self, channel_name)

    @asyncio.coroutine
    def connect(self):
        """Coroutine for connecting to a single server.

        Note that this will nonblock until the client is "registered", defined
        as the first PING/PONG exchange.
        """
        # TODO this is a poor excuse for round-robin  :)
        server = self.current_server = self.network.servers[0]

        # TODO i'm pretty sure the server tells us what our nick is, and we
        # should believe that instead
        self.nick = self.network.preferred_nick

        # TODO: handle disconnection, somehow.  probably affects a lot of
        # things.
        self._reader, self._writer = yield from server.connect(self.loop)

        if server.password:
            self.send_message("PASS", server.password)
        self.send_message("NICK", self.nick)
        self.send_message("USER", "dywypi", "-", "-", "dywypi Python IRC bot")

        # Start the reader loop, or we can't respond to anything
        self._read_loop_task = asyncio.Task(self._start_read_loop())
        asyncio.async(self._read_loop_task, loop=self.loop)

    @asyncio.coroutine
    def disconnect(self):
        # Quit
        self.send_message("QUIT", "Seeya!")

        # Flush the write buffer
        yield from self._writer.drain()
        self._writer.close()

        # Stop reading events
        self._read_loop_task.cancel()
        # This looks a little funny since this task is already running, but we
        # want to block until it's actually done, which might require dipping
        # back into the event loop
        yield from self._read_loop_task

        # Read until the connection closes
        while not self._reader.at_eof():
            yield from self._reader.readline()

    @asyncio.coroutine
    def _start_read_loop(self):
        """Internal coroutine that just keeps reading from the server in a
        loop.  Called once after a connect and should never be called again
        after that.
        """
        # TODO this is currently just to keep the message queue going, but
        # eventually it should turn them into events and stuff them in an event
        # queue
        while not self._reader.at_eof():
            try:
                yield from self._read_message()
            except CancelledError:
                return
            except Exception:
                log.exception("Smothering exception in IRC read loop")

    @asyncio.coroutine
    def gather_messages(self, *start, finish):
        fut = asyncio.Future()
        messages = {}
        for command in start:
            messages[command] = False
        for command in finish:
            messages[command] = True
        collected = []
        self._message_waiters.append((fut, messages, collected))
        yield from fut
        return collected

    def _possibly_gather_message(self, message):
        if not self._message_waiters:
            return

        # TODO there is a general ongoing problem here with matching up
        # responses.  ESPECIALLY when error codes are possible.  something here
        # is gonna have to get a bit fancier.

        fut, waiting_on, collected = self._message_waiters[0]
        # TODO is it possible for even a PING to appear in the middle of
        # some other response?
        # TODO this is still susceptible to weirdness when there's, say, a
        # queued error response to a PRIVMSG on its way back; it'll look
        # like the call we just made failed, and all the real responses
        # will be dropped.  can we assume some set of error replies ONLY
        # happen in response to sending a message of some kind, maybe?
        # TODO for that matter, where does the error response to a PRIVMSG
        # even go?  the whole problem is that we can't know for sure when
        # it succeeded, unless we put a timeout on every call to say()
        finish = False
        if message.command in waiting_on:
            finish = waiting_on[message.command]
        elif message.is_error:
            # Always consider an error as finishing
            # TODO but we might have gotten this error in response to something
            # else we did before this message...  :S
            if message.command in {"ERR_CANNOTSENDTOCHAN"}:
                # Looks like a PRIVMSG error or similar, so probably not a
                # response to this particular message.
                return
            finish = True
        elif not collected:
            # Got a regular response we weren't expecting, AND this future
            # hasn't started collecting yet -- the response probably just
            # hasn't started coming back yet, so don't do anything yet.
            return

        # If we get here, we expected this response, and should keep
        # feeding into this future.
        collected.append(message)

        if finish:
            # Done, one way or another
            self._message_waiters.popleft()
            if message.is_error:
                fut.set_exception(IRCError(message))
            else:
                fut.set_result(collected)

    @asyncio.coroutine
    def _read_message(self):
        """Internal dispatcher for messages received from the server."""
        line = yield from self._reader.readline()
        assert line.endswith(b"\r\n")
        line = line[:-2]

        # TODO valerr, unicodeerr
        message = IRCMessage.parse(line.decode(self.charset))
        log.debug("recv: %r", message)

        # TODO unclear whether this should go before or after _handle_foo
        self._possibly_gather_message(message)

        # Boy do I ever hate this pattern but it's slightly more maintainable
        # than a 500-line if tree.
        handler = getattr(self, "_handle_" + message.command, None)
        event = None
        if handler:
            event = handler(message)
        self.read_queue.put_nowait((message, event))

    def _handle_PING(self, message):
        # PONG
        self.send_message("PONG", message.args[-1])

    def _handle_RPL_WELCOME(self, message):
        # Initial registration: do autojoins, and any other onconnect work
        for channel_name in self.network.autojoins:
            asyncio.async(self.join(channel_name), loop=self.loop)

    def _handle_RPL_ISUPPORT(self, message):
        me, *features, human_text = message.args
        for feature_string in features:
            feature, _, value = feature_string.partition("=")
            if value is None:
                value = True

            self.features[feature] = value

            if feature == "NICKLEN":
                self.len_nick = int(value)
            elif feature == "CHANNELLEN":
                self.len_channel = int(value)
            elif feature == "KICKLEN":
                self.len_kick = int(value)
            elif feature == "TOPICLEN":
                self.len_topic = int(value)
            elif feature == "AWAYLEN":
                self.len_away = int(value)
            elif feature == "WATCH":
                self.max_watches = int(value)
            elif feature == "CHANTYPES":
                self.channel_types = set(value)
            elif feature == "PREFIX":
                # List of channel user modes, in relative priority order, in
                # the format (ov)@+
                assert value[0] == "("
                letters, symbols = value[1:].split(")")
                assert len(letters) == len(symbols)
                self.channel_prefixes.clear()
                for letter, symbol in zip(letters, symbols):
                    mode = IRCMode(letter, prefix=symbol)
                    self.channel_modes[letter] = mode
                    self.channel_prefixes[symbol] = mode
            elif feature == "MAXTARGETS":
                self.max_targets = int(value)
            elif feature == "CHANMODES":
                # Four groups delimited by lists: list-style (+b), arg required
                # (+k), arg required only to set (+l), argless
                lists, args, argsets, argless = value.split(",")
                for letter in lists:
                    self.channel_modes[letter] = IRCMode(letter, multi=True)
                for letter in args:
                    self.channel_modes[letter] = IRCMode(letter, arg_on_set=True, arg_on_remove=True)
                for letter in argsets:
                    self.channel_modes[letter] = IRCMode(letter, arg_on_set=True)
                for letter in argless:
                    self.channel_modes[letter] = IRCMode(letter)
            elif feature == "NETWORK":
                self.network_title = value

    def _handle_JOIN(self, message):
        channel_name, = message.args
        joiner = Peer.from_prefix(message.prefix)
        # TODO should there be a self.me?  how...
        if joiner.name == self.nick:
            # We just joined a channel
            # assert channel_name not in self.joined_channels
            # TODO key?  do we care?
            # TODO what about channel configuration and anon non-joined
            # channels?  how do these all relate...
            channel = IRCChannel(self, channel_name)
            self.joined_channels[channel.name] = channel
        else:
            # Someone else just joined the channel
            self.joined_channels[channel_name].add_user(joiner)

    def _handle_RPL_TOPIC(self, message):
        # Topic.  Sent when joining or when requesting the topic.
        # TODO this doesn't handle the "requesting" part
        # TODO what if me != me?
        me, channel_name, topic_text = message.args
        self._pending_topics[channel_name] = IRCTopic(topic_text)

    def _handle_RPL_TOPICWHOTIME(self, message):
        # Topic author (NONSTANDARD).  Sent after RPL_TOPIC.
        # Unfortunately, there's no way to know whether to expect this.
        # TODO this doesn't handle the "requesting" part
        # TODO what if me != me?
        me, channel_name, author, timestamp = message.args
        topic = self._pending_topics.setdefault(channel_name, IRCTopic(""))
        topic.author = Peer.from_prefix(author)
        topic.timestamp = datetime.utcfromtimestamp(int(timestamp))

    def _handle_RPL_NAMREPLY(self, message):
        # Names response.  Sent when joining or when requesting a names
        # list.  Must be ended with a RPL_ENDOFNAMES.
        me, useless_equals_sign, channel_name, *raw_names = message.args
        # List of names is actually optional (?!)
        if raw_names:
            raw_names = raw_names[0]
        else:
            raw_names = ""

        names = raw_names.strip(" ").split(" ")
        namelist = self._pending_names.setdefault(channel_name, [])
        # TODO modes?  should those be stripped off here?
        # TODO for that matter should these become peers here?
        namelist.extend(names)

    def _handle_RPL_ENDOFNAMES(self, message):
        # End of names list.  Sent at the very end of a join or the very
        # end of a NAMES request.
        me, channel_name, info = message.args
        namelist = self._pending_names.pop(channel_name, [])

        if channel_name in self._names_futures:
            # TODO we should probably not ever have a names future AND a
            # pending join at the same time.  or, does it matter?
            self._names_futures[channel_name].set_result(namelist)
            del self._names_futures[channel_name]

        if channel_name in self.joined_channels:
            # Join synchronized!
            channel = self.joined_channels[channel_name]
            channel.sync = True

            channel.topic = self._pending_topics.pop(channel_name, None)

            for name in namelist:
                modes = set()
                # TODO use features!
                while name and name[0] in "+%@&~":
                    modes.add(name[0])
                    name = name[1:]

                # TODO haha no this is so bad.
                # TODO the bot should, obviously, keep a record of all
                # known users as well.  alas, mutable everything.
                peer = Peer(name, None, None)

                channel.add_user(peer, modes)

            if channel_name in self._join_futures:
                # Update the Future
                self._join_futures[channel_name].set_result(channel)
                del self._join_futures[channel_name]

    def _handle_PRIVMSG(self, message):
        # PRIVMSG target :text
        target_name, text = message.args

        source = Peer.from_prefix(message.prefix)

        if target_name[0] in self.channel_types:
            target = self.get_channel(target_name)
            cls = PublicMessage
        else:
            # TODO this is /us/, so, surely ought to be known
            target = Peer(target_name, None, None)
            cls = PrivateMessage

        return cls(source, target, text, client=self, raw=message)

    @asyncio.coroutine
    def read_event(self):
        """Produce a single IRC event.

        This client does not do any kind of multiplexing or event handler
        notification; that's left to a higher level.
        """
        message, event = yield from self.read_queue.get()
        return event

    # Implementations of particular commands

    # TODO should these be part of the general client interface, or should
    # there be a separate thing that smooths out the details?
    @asyncio.coroutine
    def whois(self, target):
        """Coroutine that queries for information about a target."""
        self.send_message("WHOIS", target)
        messages = yield from self.gather_messages(
            "RPL_WHOISUSER",
            "RPL_WHOISSERVER",
            "RPL_WHOISOPERATOR",
            "RPL_WHOISIDLE",
            "RPL_WHOISCHANNELS",
            "RPL_WHOISVIRT",
            "RPL_WHOIS_HIDDEN",
            "RPL_WHOISSPECIAL",
            "RPL_WHOISSECURE",
            "RPL_WHOISSTAFF",
            "RPL_WHOISLANGUAGE",
            finish=["RPL_ENDOFWHOIS", "ERR_NOSUCHSERVER", "ERR_NONICKNAMEGIVEN", "ERR_NOSUCHNICK"],
        )

        # nb: The first two args for all the responses are our nick and the
        # target's nick.
        # TODO apparently you can whois multiple nicks at a time
        for message in messages:
            if message.command == "RPL_WHOISUSER":
                ident = message.args[2]
                hostname = message.args[3]
                # args[4] is a literal *
                realname = message.args[5]
            elif message.command == "RPL_WHOISIDLE":
                # Idle time.  Some servers (at least, inspircd) also have
                # signon time as unixtime.
                idle = timedelta(seconds=int(message.args[2]))
            elif message.command == "RPL_WHOISCHANNELS":
                # TODO split and parse out the usermodes
                # TODO don't some servers have an extension with multiple modes
                # here
                channels = message.args[2]
            elif message.command == "RPL_WHOISSERVER":
                server = message.args[2]
                server_desc = message.args[3]

        return messages

    @asyncio.coroutine
    def say(self, target, message):
        """Coroutine that sends a message to a target, which may be either a
        `Channel` or a `Peer`.
        """
        self.send_message("PRIVMSG", target, message)

    @asyncio.coroutine
    def join(self, channel_name, key=None):
        """Coroutine that joins a channel, and nonblocks until the join is
        "synchronized" (defined as receiving the nick list).
        """
        if channel_name in self._join_futures:
            return self._join_futures[channel_name]

        # TODO multiple?  error on commas?
        if key is None:
            self.send_message("JOIN", channel_name)
        else:
            self.send_message("JOIN", channel_name, key)

        # Clear out any lingering names list
        self._pending_names[channel_name] = []

        # Return a Future, to be populated by the message loop
        fut = self._join_futures[channel_name] = asyncio.Future()
        return fut

    @asyncio.coroutine
    def names(self, channel_name):
        """Coroutine that returns a list of names in a channel."""
        # TODO there's some ISUPPORT extension that lists /all/ channel modes
        # on each name that comes back...  support that?
        self.send_message("NAMES", channel_name)

        # No need to do the same thing twice
        if channel_name in self._names_futures:
            return self._names_futures[channel_name]

        # Clear out any lingering names list
        self._pending_names[channel_name] = []

        # Return a Future, to be populated by the message loop
        fut = self._names_futures[channel_name] = asyncio.Future()
        return fut

    def set_topic(self, channel, topic):
        """Sets the channel topic."""
        self.send_message("TOPIC", channel, topic)

    # TODO unclear whether this stuff should be separate or what; it's less
    # about the protocol and more about the dywypi interface
    def send_message(self, command, *args):
        message = IRCMessage(command, *args)
        log.debug("sent: %r", message)
        self._writer.write(message.render().encode(self.charset) + b"\r\n")

    def format_transition(self, current_style, new_style):
        if new_style == Style.default():
            # Reset code, ^O
            return "\x0f"

        if new_style.fg != current_style.fg and new_style.fg is Color.default:
            # IRC has no "reset to default" code.  mIRC claims color 99 is for
            # this, but it lies, at least in irssi.  So we must reset and
            # reapply everything.
            ret = "\x0f"
            if new_style.bold is Bold.on:
                ret += "\x02"
            return ret

        ret = ""
        if new_style.fg != current_style.fg:
            ret += FOREGROUND_CODES[new_style.fg]

        if new_style.bold != current_style.bold:
            # There's no on/off for bold, just a toggle
            ret += "\x02"

        return ret
Ejemplo n.º 28
0
class IRCClient:
    """Higher-level IRC client.  Takes care of most of the hard parts of IRC:
    incoming server messages are bundled into more intelligible events (see
    ``dywypi.event``), and commands that expect replies are implemented as
    coroutines.
    """

    def __init__(self, loop, network):
        self.loop = loop
        self.network = network
        # TODO should this be a param?  a property of the network?  or, more
        # likely, channel-specific and decoded separately and...
        self.charset = 'utf8'

        self.joined_channels = {}  # name => Channel

        # IRC server features, as reported by ISUPPORT, with defaults taken
        # from the RFC.
        self.len_nick = 9
        self.len_channel = 200
        self.len_message = 510
        # These lengths don't have limits mentioned in the RFC, so going with
        # the smallest known values in the wild
        self.len_kick = 80
        self.len_topic = 80
        self.len_away = 160
        self.max_watches = 0
        self.max_targets = 1
        self.channel_types = set('#&')
        self.channel_modes = {}  # TODO, haha.
        self.channel_prefixes = {}  # TODO here too.  IRCMode is awkward.
        self.network_title = self.network.name
        self.features = {}

        # Various intermediate state used for waiting for replies and
        # aggregating multi-part replies
        # TODO hmmm so what happens if state just gets left here forever?  do
        # we care?
        self._pending_names = {}
        self._names_futures = {}
        self._pending_topics = {}
        self._join_futures = {}

        self._message_waiters = OrderedDict()

        self.read_queue = Queue(loop=loop)

    def get_channel(self, channel_name):
        """Returns a `Channel` object containing everything the client
        definitively knows about the given channel.

        Note that if you, say, ask for the topic of a channel you aren't in and
        then immediately call `get_channel`, the returned object won't have its
        topic populated.  State is only tracked persistently for channels the
        bot is in; otherwise there's no way to know whether or not it's stale.
        """
        if channel_name in self.joined_channels:
            return self.joined_channels[channel_name]
        else:
            return IRCChannel(self, channel_name)

    @asyncio.coroutine
    def connect(self):
        """Coroutine for connecting to a single server.

        Note that this will nonblock until the client is "registered", defined
        as the first PING/PONG exchange.
        """
        # TODO this is a poor excuse for round-robin  :)
        server = self.current_server = self.network.servers[0]

        # TODO i'm pretty sure the server tells us what our nick is, and we
        # should believe that instead
        self.nick = self.network.preferred_nick

        # TODO: handle disconnection, somehow.  probably affects a lot of
        # things.
        self._reader, self._writer = yield from server.connect(self.loop)
        log.debug('connected!')

        if server.password:
            self.send_message('PASS', server.password)
        self.send_message('NICK', self.nick)
        self.send_message('USER', 'dywypi', '-', '-', 'dywypi Python IRC bot')

        # Start the reader loop, or we can't respond to anything
        self._read_loop_task = asyncio.Task(self._start_read_loop())
        asyncio.async(self._read_loop_task, loop=self.loop)

    @asyncio.coroutine
    def disconnect(self):
        # Quit
        self.send_message('QUIT', 'Seeya!')

        # Flush the write buffer
        yield from self._writer.drain()
        self._writer.close()

        # Stop reading events
        self._read_loop_task.cancel()
        # This looks a little funny since this task is already running, but we
        # want to block until it's actually done, which might require dipping
        # back into the event loop
        yield from self._read_loop_task

        # Read until the connection closes
        while not self._reader.at_eof():
            yield from self._reader.readline()

    @asyncio.coroutine
    def _start_read_loop(self):
        """Internal coroutine that just keeps reading from the server in a
        loop.  Called once after a connect and should never be called again
        after that.
        """
        # TODO this is currently just to keep the message queue going, but
        # eventually it should turn them into events and stuff them in an event
        # queue
        while not self._reader.at_eof():
            try:
                yield from self._read_message()
            except CancelledError:
                return
            except Exception:
                log.exception("Smothering exception in IRC read loop")

    @asyncio.coroutine
    def gather_messages(self, *middle, end, errors=()):
        fut = asyncio.Future()
        messages = {}
        for command in middle:
            messages[command] = 'middle'
        for command in end:
            messages[command] = 'end'
        for command in errors:
            messages[command] = 'error'
        collected = []
        self._message_waiters[fut] = (messages, collected)
        yield from fut
        return collected

    @asyncio.coroutine
    def _read_message(self):
        """Internal dispatcher for messages received from the server."""
        line = yield from self._reader.readline()
        assert line.endswith(b'\r\n')
        line = line[:-2]

        # TODO valerr, unicodeerr
        message = IRCMessage.parse(line.decode(self.charset))
        log.debug("recv: %r", message)

        # TODO there is a general ongoing problem here with matching up
        # responses.  ESPECIALLY when error codes are possible.  something here
        # is gonna have to get a bit fancier.

        for fut, (waiting_on, collected) in self._message_waiters.items():
            # TODO this needs to handle error codes too, or the future will
            # linger forever!  potential problem: if the server is lagging
            # behind us, an error code might actually map to a privmsg we tried
            # to send (which has no success response) and we'll get all f****d
            # up.  i don't know if there's any way to solve this.
            # TODO hey stupid question: after we've seen ANY of the waited-on
            # messages, should we pipe all subsequent messages into that future
            # until we see the one that's supposed to end it?  something like
            # a forced JOIN could screw up a join attempt, for example, but if
            # we're getting RPL_TOPIC when we didn't actually ask for the
            # topic, THEN we know we're definitely in the join sequence.
            # TODO also given normal irc response flow, i'm pretty sure we
            # should only ever need to check the first pending future.  there's
            # no way we should need to skip around.
            # TODO maybe give these a timeout so a bad one doesn't f**k us up
            # forever
            if message.command in waiting_on:
                collected.append(message)
                if waiting_on[message.command] == 'end':
                    fut.set_result(collected)
                    del self._message_waiters[fut]
                elif waiting_on[message.command] == 'error':
                    fut.set_exception(IRCError(message))
                    del self._message_waiters[fut]
                break

        # Boy do I ever hate this pattern but it's slightly more maintainable
        # than a 500-line if tree.
        handler = getattr(self, '_handle_' + message.command, None)
        event = None
        if handler:
            event = handler(message)
        self.read_queue.put_nowait((message, event))

    def _handle_PING(self, message):
        # PONG
        self.send_message('PONG', message.args[-1])

    def _handle_RPL_WELCOME(self, message):
        # Initial registration: do autojoins, and any other onconnect work
        self.network.hostname = message.args[1].rsplit(sep='@')[-1]
        for channel_name in self.network.autojoins:
            asyncio.async(self.join(channel_name), loop=self.loop)

    def _handle_RPL_ISUPPORT(self, message):
        me, *features, human_text = message.args
        for feature_string in features:
            feature, _, value = feature_string.partition('=')
            if value is None:
                value = True

            self.features[feature] = value

            if feature == 'NICKLEN':
                self.len_nick = int(value)
            elif feature == 'CHANNELLEN':
                self.len_channel = int(value)
            elif feature == 'KICKLEN':
                self.len_kick = int(value)
            elif feature == 'TOPICLEN':
                self.len_topic = int(value)
            elif feature == 'AWAYLEN':
                self.len_away = int(value)
            elif feature == 'WATCH':
                self.max_watches = int(value)
            elif feature == 'CHANTYPES':
                self.channel_types = set(value)
            elif feature == 'PREFIX':
                # List of channel user modes, in relative priority order, in
                # the format (ov)@+
                assert value[0] == '('
                letters, symbols = value[1:].split(')')
                assert len(letters) == len(symbols)
                self.channel_prefixes.clear()
                for letter, symbol in zip(letters, symbols):
                    mode = IRCMode(letter, prefix=symbol)
                    self.channel_modes[letter] = mode
                    self.channel_prefixes[symbol] = mode
            elif feature == 'MAXTARGETS':
                self.max_targets = int(value)
            elif feature == 'CHANMODES':
                # Four groups delimited by lists: list-style (+b), arg required
                # (+k), arg required only to set (+l), argless
                lists, args, argsets, argless = value.split(',')
                for letter in lists:
                    self.channel_modes[letter] = IRCMode(
                        letter, multi=True)
                for letter in args:
                    self.channel_modes[letter] = IRCMode(
                        letter, arg_on_set=True, arg_on_remove=True)
                for letter in argsets:
                    self.channel_modes[letter] = IRCMode(
                        letter, arg_on_set=True)
                for letter in argless:
                    self.channel_modes[letter] = IRCMode(letter)
            elif feature == 'NETWORK':
                self.network_title = value

    def _handle_JOIN(self, message):
        channel_name, = message.args
        joiner = Peer.from_prefix(message.prefix)
        # TODO should there be a self.me?  how...
        if joiner.name == self.nick:
            # We just joined a channel
            #assert channel_name not in self.joined_channels
            # TODO key?  do we care?
            # TODO what about channel configuration and anon non-joined
            # channels?  how do these all relate...
            channel = IRCChannel(self, channel_name)
            self.joined_channels[channel.name] = channel
        else:
            # Someone else just joined the channel
            self.joined_channels[channel_name].add_user(joiner)

    def _handle_RPL_TOPIC(self, message):
        # Topic.  Sent when joining or when requesting the topic.
        # TODO this doesn't handle the "requesting" part
        # TODO what if me != me?
        me, channel_name, topic_text = message.args
        self._pending_topics[channel_name] = IRCTopic(topic_text)

    def _handle_RPL_TOPICWHOTIME(self, message):
        # Topic author (NONSTANDARD).  Sent after RPL_TOPIC.
        # Unfortunately, there's no way to know whether to expect this.
        # TODO this doesn't handle the "requesting" part
        # TODO what if me != me?
        me, channel_name, author, timestamp = message.args
        topic = self._pending_topics.setdefault(channel_name, IRCTopic(''))
        topic.author = Peer.from_prefix(author)
        topic.timestamp = datetime.utcfromtimestamp(int(timestamp))

    def _handle_RPL_NAMREPLY(self, message):
        # Names response.  Sent when joining or when requesting a names
        # list.  Must be ended with a RPL_ENDOFNAMES.
        me, useless_equals_sign, channel_name, *raw_names = message.args
        # List of names is actually optional (?!)
        if raw_names:
            raw_names = raw_names[0]
        else:
            raw_names = ''

        names = raw_names.strip(' ').split(' ')
        namelist = self._pending_names.setdefault(channel_name, [])
        # TODO modes?  should those be stripped off here?
        # TODO for that matter should these become peers here?
        namelist.extend(names)

    def _handle_RPL_ENDOFNAMES(self, message):
        # End of names list.  Sent at the very end of a join or the very
        # end of a NAMES request.
        me, channel_name, info = message.args
        namelist = self._pending_names.pop(channel_name, [])

        if channel_name in self._names_futures:
            # TODO we should probably not ever have a names future AND a
            # pending join at the same time.  or, does it matter?
            self._names_futures[channel_name].set_result(namelist)
            del self._names_futures[channel_name]

        if channel_name in self.joined_channels:
            # Join synchronized!
            channel = self.joined_channels[channel_name]
            channel.sync = True

            channel.topic = self._pending_topics.pop(channel_name, None)

            for name in namelist:
                modes = set()
                # TODO use features!
                while name and name[0] in '+%@&~':
                    modes.add(name[0])
                    name = name[1:]

                # TODO haha no this is so bad.
                # TODO the bot should, obviously, keep a record of all
                # known users as well.  alas, mutable everything.
                peer = Peer(name, None, None)

                channel.add_user(peer, modes)

            if channel_name in self._join_futures:
                # Update the Future
                self._join_futures[channel_name].set_result(channel)
                del self._join_futures[channel_name]

    def _handle_PRIVMSG(self, message):
        # PRIVMSG target :text
        target_name, text = message.args

        source = Peer.from_prefix(message.prefix)

        if target_name[0] in self.channel_types:
            target = self.get_channel(target_name)
            cls = PublicMessage
        else:
            # TODO this is /us/, so, surely ought to be known
            target = Peer(target_name, None, None)
            cls = PrivateMessage

        return cls(source, target, text, client=self, raw=message)

    @asyncio.coroutine
    def read_event(self):
        """Produce a single IRC event.

        This client does not do any kind of multiplexing or event handler
        notification; that's left to a higher level.
        """
        message, event = yield from self.read_queue.get()
        return event


    # Implementations of particular commands

    # TODO should these be part of the general client interface, or should
    # there be a separate thing that smooths out the details?
    @asyncio.coroutine
    def whois(self, target):
        """Coroutine that queries for information about a target."""
        self.send_message('WHOIS', target)
        messages = yield from self.gather_messages(
            'RPL_WHOISUSER',
            'RPL_WHOISSERVER',
            'RPL_WHOISOPERATOR',
            'RPL_WHOISIDLE',
            'RPL_WHOISCHANNELS',
            'RPL_WHOISVIRT',
            'RPL_WHOIS_HIDDEN',
            'RPL_WHOISSPECIAL',
            'RPL_WHOISSECURE',
            'RPL_WHOISSTAFF',
            'RPL_WHOISLANGUAGE',
            end=[
                'RPL_ENDOFWHOIS',
            ],
            errors=[
                'ERR_NOSUCHSERVER',
                'ERR_NONICKNAMEGIVEN',
                'ERR_NOSUCHNICK',
            ],
        )

        # nb: The first two args for all the responses are our nick and the
        # target's nick.
        # TODO apparently you can whois multiple nicks at a time
        for message in messages:
            if message.command == 'RPL_WHOISUSER':
                ident, hostname
                ident = message.args[2]
                hostname = message.args[3]
                # args[4] is a literal *
                realname = message.args[5]
            elif message.command == 'RPL_WHOISIDLE':
                # Idle time.  Some servers (at least, inspircd) also have
                # signon time as unixtime.
                idle = timedelta(seconds=int(message.args[2]))
            elif message.command == 'RPL_WHOISCHANNELS':
                # TODO split and parse out the usermodes
                # TODO don't some servers have an extension with multiple modes
                # here
                channels = message.args[2]
            elif message.command == 'RPL_WHOISSERVER':
                server = message.args[2]
                server_desc = message.args[3]


        return messages

    @asyncio.coroutine
    def say(self, message, target, notice=False):
        """Coroutine that sends a message to a target, which may be either a
        `Channel` or a `Peer`.
        """
        command = 'NOTICE' if notice else 'PRIVMSG'
        self.send_message(command, target, message)

    @asyncio.coroutine
    def join(self, channel_name, key=None):
        """Coroutine that joins a channel, and nonblocks until the join is
        "synchronized" (defined as receiving the nick list).
        """
        if channel_name in self._join_futures:
            return self._join_futures[channel_name]

        # TODO multiple?  error on commas?
        if key is None:
            self.send_message('JOIN', channel_name)
        else:
            self.send_message('JOIN', channel_name, key)

        # Clear out any lingering names list
        self._pending_names[channel_name] = []

        # Return a Future, to be populated by the message loop
        fut = self._join_futures[channel_name] = asyncio.Future()
        return fut

    @asyncio.coroutine
    def names(self, channel_name):
        """Coroutine that returns a list of names in a channel."""
        self.send_message('NAMES', channel_name)

        # No need to do the same thing twice
        if channel_name in self._names_futures:
            return self._names_futures[channel_name]

        # Clear out any lingering names list
        self._pending_names[channel_name] = []

        # Return a Future, to be populated by the message loop
        fut = self._names_futures[channel_name] = asyncio.Future()
        return fut

    def set_topic(self, channel, topic):
        """Sets the channel topic."""
        self.send_message('TOPIC', channel, topic)

    # TODO unclear whether this stuff should be separate or what; it's less
    # about the protocol and more about the dywypi interface
    def send_message(self, command, *args):
        message = IRCMessage(command, *args)
        log.debug("sent: %r", message)
        self._writer.write(message.render().encode(self.charset) + b'\r\n')

    def format_transition(self, current_style, new_style):
        if new_style == Style.default():
            # Reset code, ^O
            return '\x0f'

        if new_style.fg != current_style.fg and new_style.fg is Color.default:
            # IRC has no "reset to default" code.  mIRC claims color 99 is for
            # this, but it lies, at least in irssi.  So we must reset and
            # reapply everything.
            ret = '\x0f'
            if new_style.bold is Bold.on:
                ret += '\x02'
            return ret

        ret = ''
        if new_style.fg != current_style.fg:
            ret += FOREGROUND_CODES[new_style.fg]

        if new_style.bold != current_style.bold:
            # There's no on/off for bold, just a toggle
            ret += '\x02'

        return ret
Ejemplo n.º 29
0
class Ant(abc.ABC):
    response_pipelines: typing.List[Pipeline] = []
    request_pipelines: typing.List[Pipeline] = []
    item_pipelines: typing.List[Pipeline] = []
    request_cls = Request
    response_cls = Response
    request_timeout = DEFAULT_TIMEOUT.total
    request_retries = 3
    request_retry_delay = 5
    request_proxies: typing.List[typing.Union[str, URL]] = []
    request_max_redirects = 10
    request_allow_redirects = True
    response_in_stream = False
    connection_limit = 100  # see "TCPConnector" in "aiohttp"
    connection_limit_per_host = 0
    concurrent_limit = 100

    def __init__(self,
                 loop: typing.Optional[asyncio.AbstractEventLoop] = None):
        self.loop = loop if loop is not None else asyncio.get_event_loop()
        self.logger = logging.getLogger(self.__class__.__name__)
        self.session: aiohttp.ClientSession = ClientSession(
            response_class=self.response_cls,
            connector=aiohttp.TCPConnector(
                limit=self.connection_limit,
                enable_cleanup_closed=True,
                limit_per_host=self.connection_limit_per_host))
        # coroutine`s concurrency support
        self._queue = Queue(loop=self.loop)
        self._done_queue = Queue(loop=self.loop)
        self._running_count = 0
        self._is_closed = False
        # report var
        self._reports: typing.DefaultDict[str, typing.List[
            int, int]] = defaultdict(lambda: [0, 0])
        self._drop_reports: typing.DefaultDict[str, typing.List[
            int, int]] = defaultdict(lambda: [0, 0])
        self._start_time = time.time()
        self._last_time = self._start_time
        self._report_slot = 60  # report once after one minute by default

    @property
    def name(self):
        return self.__class__.__name__

    @property
    def is_running(self) -> bool:
        return self._running_count > 0

    async def request(
            self,
            url: typing.Union[str, URL],
            method: str = aiohttp.hdrs.METH_GET,
            params: typing.Optional[dict] = None,
            headers: typing.Optional[dict] = None,
            cookies: typing.Optional[dict] = None,
            data: typing.Optional[typing.Union[typing.AnyStr, typing.Dict,
                                               typing.IO]] = None,
            proxy: typing.Optional[typing.Union[str, URL]] = None,
            timeout: typing.Optional[typing.Union[int, float]] = None,
            retries: typing.Optional[int] = None,
            response_in_stream: typing.Optional[bool] = None) -> Response:
        if not isinstance(url, URL):
            url = URL(url)
        if proxy and not isinstance(proxy, URL):
            proxy = URL(proxy)
        elif proxy is None:
            proxy = self.get_proxy()
        if timeout is None:
            timeout = self.request_timeout
        if retries is None:
            retries = self.request_retries
        if response_in_stream is None:
            response_in_stream = self.response_in_stream

        req = self.request_cls(method,
                               url,
                               timeout=timeout,
                               params=params,
                               headers=headers,
                               cookies=cookies,
                               data=data,
                               proxy=proxy,
                               response_in_stream=response_in_stream)
        req = await self._handle_thing_with_pipelines(req,
                                                      self.request_pipelines)
        self.report(req)

        if retries > 0:
            res = await self.make_retry_decorator(
                retries, self.request_retry_delay)(self._request)(req)
        else:
            res = await self._request(req)

        res = await self._handle_thing_with_pipelines(res,
                                                      self.response_pipelines)
        self.report(res)
        return res

    async def collect(self, item: Item) -> None:
        self.logger.debug('Collect item: ' + str(item))
        await self._handle_thing_with_pipelines(item, self.item_pipelines)
        self.report(item)

    async def open(self) -> None:
        self.logger.info('Opening')
        for pipeline in itertools.chain(self.item_pipelines,
                                        self.response_pipelines,
                                        self.request_pipelines):
            obj = pipeline.on_spider_open()
            if asyncio.iscoroutine(obj):
                await obj

    async def close(self) -> None:
        await self.wait_scheduled_coroutines()

        for pipeline in itertools.chain(self.item_pipelines,
                                        self.response_pipelines,
                                        self.request_pipelines):
            obj = pipeline.on_spider_close()
            if asyncio.iscoroutine(obj):
                await obj

        await self.session.close()

        self._is_closed = True
        self.logger.info('Closed')

    @abc.abstractmethod
    async def run(self) -> None:
        """App custom entrance"""

    async def main(self) -> None:
        try:
            await self.open()
            await self.run()
        except Exception as e:
            self.logger.exception('Run ant with ' + e.__class__.__name__)
        try:
            await self.close()
        except Exception as e:
            self.logger.exception('Close ant with ' + e.__class__.__name__)
        # total report
        for name, counts in self._reports.items():
            self.logger.info('Get {:d} {:s} in total'.format(counts[1], name))
        for name, counts in self._drop_reports.items():
            self.logger.info('Drop {:d} {:s} in total'.format(counts[1], name))
        self.logger.info('Run {:s} in {:f} seconds'.format(
            self.__class__.__name__,
            time.time() - self._start_time))

    @staticmethod
    def make_retry_decorator(
            retries: int, delay: float
    ) -> typing.Callable[[typing.Callable], typing.Callable]:
        return retry(wait=wait_fixed(delay),
                     retry=(retry_if_result(lambda res: res.status >= 500)
                            | retry_if_exception_type(
                                exception_types=aiohttp.ClientError)),
                     stop=stop_after_attempt(retries + 1))

    def get_proxy(self) -> typing.Optional[URL]:
        """Chose a proxy, default by random"""
        try:
            return URL(random.choice(self.request_proxies))
        except IndexError:
            return None

    def schedule_coroutine(self, coroutine: typing.Coroutine) -> None:
        """Like "asyncio.ensure_future", it schedule coroutine in event loop
        and return immediately.

        Call "self.wait_scheduled_coroutines" make sure all coroutine has been
        done.
        """
        def _done_callback(f):
            self._running_count -= 1
            self._done_queue.put_nowait(f)
            try:
                if (self.concurrent_limit == -1
                        or self._running_count < self.concurrent_limit):
                    next_coroutine = self._queue.get_nowait()
                    self._running_count += 1
                    asyncio.ensure_future(
                        next_coroutine,
                        loop=self.loop).add_done_callback(_done_callback)
            except QueueEmpty:
                pass

        if self._is_closed:
            self.logger.warning('This pool has be closed!')
            return

        if (self.concurrent_limit == -1
                or self._running_count < self.concurrent_limit):
            self._running_count += 1
            asyncio.ensure_future(
                coroutine, loop=self.loop).add_done_callback(_done_callback)
        else:
            self._queue.put_nowait(coroutine)

    def schedule_coroutines(
            self, coroutines: typing.Iterable[typing.Coroutine]) -> None:
        """A short way to schedule many coroutines.
        """
        for coroutine in coroutines:
            self.schedule_coroutine(coroutine)

    async def wait_scheduled_coroutines(self):
        """Wait scheduled coroutines to be done, can be called many times.
        """
        while self._running_count > 0 or self._done_queue.qsize() > 0:
            await self._done_queue.get()

    def as_completed(
        self,
        coroutines: typing.Iterable[typing.Coroutine],
        limit: typing.Optional[int] = None
    ) -> typing.Generator[typing.Coroutine, None, None]:
        """Like "asyncio.as_completed",
        run and iter coroutines out of the pool.

        :param limit: set to "self.concurrent_limit" by default,
        this "limit" is not shared with pool`s limit
        """
        limit = self.concurrent_limit if limit is None else limit

        coroutines = iter(coroutines)
        queue = Queue(loop=self.loop)
        todo = []

        def _done_callback(f):
            queue.put_nowait(f)
            todo.remove(f)
            try:
                nf = asyncio.ensure_future(next(coroutines))
                nf.add_done_callback(_done_callback)
                todo.append(nf)
            except StopIteration:
                pass

        async def _wait_for_one():
            f = await queue.get()
            return f.result()

        if limit <= 0:
            fs = {
                asyncio.ensure_future(cor, loop=self.loop)
                for cor in coroutines
            }
        else:
            fs = {
                asyncio.ensure_future(cor, loop=self.loop)
                for cor in islice(coroutines, 0, limit)
            }
        for f in fs:
            f.add_done_callback(_done_callback)
            todo.append(f)

        while len(todo) > 0 or queue.qsize() > 0:
            yield _wait_for_one()

    async def as_completed_with_async(
        self,
        coroutines: typing.Iterable[typing.Coroutine],
        limit: typing.Optional[int] = None,
        raise_exception: bool = True,
    ) -> typing.AsyncGenerator[typing.Any, None]:
        """as_completed`s async version, can catch and log exception inside.
        """
        for coro in self.as_completed(coroutines, limit=limit):
            try:
                yield await coro
            except Exception as e:
                if raise_exception:
                    raise e
                else:
                    self.logger.exception('Get exception {:s} in '
                                          '"as_completed_with_async"'.format(
                                              str(e)))

    def report(self, thing: Things, dropped: bool = False) -> None:
        now_time = time.time()
        if now_time - self._last_time > self._report_slot:
            self._last_time = now_time
            for name, counts in self._reports.items():
                count = counts[1] - counts[0]
                counts[0] = counts[1]
                self.logger.info(
                    'Get {:d} {:s} in total with {:d}/{:d}s rate'.format(
                        counts[1], name, count, self._report_slot))
            for name, counts in self._drop_reports.items():
                count = counts[1] - counts[0]
                counts[0] = counts[1]
                self.logger.info(
                    'Drop {:d} {:s} in total with {:d}/{:d} rate'.format(
                        counts[1], name, count, self._report_slot))
        report_type = thing.__class__.__name__
        if dropped:
            reports = self._drop_reports
        else:
            reports = self._reports
        counts = reports[report_type]
        counts[1] += 1

    async def _handle_thing_with_pipelines(
            self, thing: Things, pipelines: typing.List[Pipeline]) -> Things:
        """Process thing one by one, break the process chain when get
        exception.
        """
        self.logger.debug('Process thing: ' + str(thing))
        raw_thing = thing
        for pipeline in pipelines:
            try:
                thing = pipeline.process(thing)
                if asyncio.iscoroutine(thing):
                    thing = await thing
            except Exception as e:
                if isinstance(e, ThingDropped):
                    self.report(raw_thing, dropped=True)
                raise e
        return thing

    async def _request(self, req: Request) -> Response:
        if req.proxy is not None:
            # proxy auth not work in one session with many requests,
            # add auth header to fix it
            if req.proxy.scheme == 'http' and req.proxy.user is not None:
                req.headers[aiohttp.hdrs.PROXY_AUTHORIZATION] = \
                    aiohttp.BasicAuth.from_url(req.proxy).encode()

        # cookies in headers, params in url
        req_kwargs = dict(headers=req.headers,
                          data=req.data,
                          timeout=req.timeout,
                          proxy=req.proxy,
                          max_redirects=self.request_max_redirects,
                          allow_redirects=self.request_allow_redirects)
        response = await self.session._request(req.method, req.url,
                                               **req_kwargs)

        if not req.response_in_stream:
            await response.read()
            response.close()
            await response.wait_for_close()
        return response
Ejemplo n.º 30
0
class Client:
    """ Client class handles all the connection and data transfer that happens
        through the websocket for all types of image viewers.
    """
    def __init__(self, addr, images, labels, **view_args):
        """
        Args:
            addr: address of the websocket
            images: a list of images
            labels: a list of labels for each image
        """
        self._addr = addr
        self._images = images
        self._labels = labels
        self.ws = None
        # initialize queue and worker process
        self.queue = Queue()
        asyncio.create_task(self.worker())
        asyncio.create_task(self.websocket_connect(view_args))

    async def worker(self):
        """ Handles asynchronous tasks that are enqueued to the work queue.
        """
        while True:
            message = await self.queue.get()
            await self.ws.write_message(message)

    async def websocket_connect(self, view_args):
        """ Connects to the websocket at given address. Sends attributes and
            data following a signal to the client.

            Arguments:
                view_args: a dictionary of arguments to be passed to the
                           Javascript side
        """
        self.ws = await ws.websocket_connect(self._addr)
        message = {"py_client": None}
        await self.write_message(message)
        # Wait to send data until signal received from server
        message = await self.ws.read_message()
        await self.write_message({"attrs": view_args})
        await self.send_data()

    async def write_message(self, message):
        """ Writes message to the websocket.

            Arguments:
                message: a dictionary that contains the message to be sent to
                         the Javascript side
        """
        message_json = json.dumps(message)
        self.queue.put_nowait(message_json)

    async def send_data(self):
        """ Send image data to front end.
        """
        images_bytes = list(map(Client.image2bytes, self._images))
        data_dict = {"data": {}}
        for i, image in enumerate(images_bytes):
            data_dict["data"][i] = image
        await self.write_message(data_dict)

    @staticmethod
    def image2bytes(image, quality=80):
        """ Converts PIL Image to bytearray.

            Arguments:
                image: image buffer in PIL Image format
                quality: the quality of the output image
        """
        bytesIO = io.BytesIO()
        image.save(bytesIO, format="JPEG", optimize=True, quality=quality)

        return base64.b64encode(bytesIO.getvalue()).decode()
Ejemplo n.º 31
0
class HomeworksProtocol(asyncio.Protocol):
    _non_login_reply_received_timer: TimerHandle
    read_queue: Queue[Message]
    _transport: Transport

    PROMPT_REQUESTS = [b'LNET> ', b'L232> ']
    LOGIN_REQUEST = b'LOGIN: '******'\r\n'

    def __init__(self, credentials: Optional[Union[str, bytes]] = None):
        self.ready_future = asyncio.Future()
        self.connection_lost_future = asyncio.Future()
        self.read_queue = Queue()
        self._buffer = b''
        self._credentials = ensure_bytes(credentials)

    def data_received(self, data: bytes) -> None:
        self._buffer += data
        self.handle_buffer_increment()

    def connection_made(self, transport: Transport) -> None:
        self._transport = transport

        self._non_login_reply_received_timer = asyncio.get_event_loop(
        ).call_later(0.2, self._notify_ready)

    def connection_lost(self, exc: Optional[Exception]) -> None:
        if not self._transport.is_closing():
            self._transport.close()
        self._transport = None
        self._non_login_reply_received_timer.cancel()

        exception = HomeworksConnectionLost(
            f'Connection lost before ready state: {exc}')
        if not self.ready_future.done():
            self.ready_future.set_exception(exception)

        self.connection_lost_future.set_exception(exception)

    def handle_buffer_increment(self):
        while any([
                self._check_login_prompt(),
                self._trim_prompts(),
                self._check_messages()
        ]):
            pass

    def write(self, data: bytes):
        data = ensure_bytes(data)

        if not self._transport.is_closing():
            self._transport.write(data)

    def _check_login_prompt(self) -> bool:
        return self._trim_prefix(self.LOGIN_REQUEST,
                                 self._on_login_prompt_found)

    def _trim_prompts(self) -> bool:
        return any(
            self._trim_prefix(prompt, self._on_prompt_found)
            for prompt in self.PROMPT_REQUESTS)

    def _on_prompt_found(self, _):
        self._notify_ready()

    def _on_login_prompt_found(self, _):
        self._non_login_reply_received_timer.cancel()
        if not self._credentials:
            self._raise_exception(HomeworksNoCredentialsProvided())

        self.write(self._credentials + self.COMMAND_SEPARATOR)

    def _trim_prefix(self, prefix: bytes, on_match: Callable[[bytes],
                                                             None]) -> bool:
        if self._buffer.startswith(prefix):
            self._buffer = self._buffer[len(prefix):]
            on_match(prefix)
            return True

        return False

    def _check_messages(self) -> bool:
        (command, separator,
         remainder) = self._buffer.partition(self.COMMAND_SEPARATOR)
        if separator != self.COMMAND_SEPARATOR:
            return False
        self._buffer = remainder

        command = command.strip()
        if command == b'':
            return True

        self._handle_message(command.decode(ENCODING))

        return True

    def _handle_message(self, message: str):
        if message == "login successful":
            self._notify_ready()
            return
        elif message == "login incorrect":
            self._raise_exception(InvalidCredentialsProvided())

        self._notify_ready()
        self.read_queue.put_nowait(message)

    def _notify_ready(self):
        self._non_login_reply_received_timer.cancel()
        if self._transport is not None and not self.ready_future.done():
            self.ready_future.set_result(True)

    def _raise_exception(self, exc: Exception):
        if not self.ready_future.done():
            self.ready_future.set_exception(exc)

        raise exc
Ejemplo n.º 32
0
class WebSocketCommonProtocol(asyncio.StreamReaderProtocol):
    """
    This class implements common parts of the WebSocket protocol.

    It assumes that the WebSocket connection is established. The handshake is
    managed in subclasses such as
    :class:`~websockets.server.WebSocketServerProtocol` and
    :class:`~websockets.client.WebSocketClientProtocol`.

    It runs a task that stores incoming data frames in a queue and deals with
    control frames automatically. It sends outgoing data frames and performs
    the closing handshake.

    The `host`, `port` and `secure` parameters are simply stored as attributes
    for handlers that need them.

    The `timeout` parameter defines the maximum wait time in seconds for
    completing the closing handshake and, only on the client side, for
    terminating the TCP connection. :meth:`close()` will complete in at most
    this time on the server side and twice this time on the client side.

    Once the connection is closed, the status code is available in the
    :attr:`close_code` attribute and the reason in :attr:`close_reason`.
    """

    # There are only two differences between the client-side and the server-
    # side behavior: masking the payload and closing the underlying TCP
    # connection. This class implements the server-side behavior by default.
    # To get the client-side behavior, set is_client = True.

    is_client = False
    state = 'OPEN'

    def __init__(self,
                 *,
                 host=None,
                 port=None,
                 secure=None,
                 timeout=10,
                 loop=None):
        self.host = host
        self.port = port
        self.secure = secure

        self.timeout = timeout

        super().__init__(asyncio.StreamReader(), self.client_connected, loop)

        self.close_code = None
        self.close_reason = ''

        # Futures tracking steps in the connection's lifecycle.
        self.opening_handshake = asyncio.Future()
        self.closing_handshake = asyncio.Future()
        self.connection_closed = asyncio.Future()

        # Queue of received messages.
        self.messages = Queue()

        # Mapping of ping IDs to waiters, in chronological order.
        self.pings = collections.OrderedDict()

        # Task managing the connection.
        self.worker = asyncio. async (self.run())

        # In a subclass implementing the opening handshake, the state will be
        # CONNECTING at this point.
        if self.state == 'OPEN':
            self.opening_handshake.set_result(True)

    # Public API

    @property
    def open(self):
        """
        This property is ``True`` when the connection is usable.

        It may be used to handle disconnections gracefully.
        """
        return self.state == 'OPEN'

    @asyncio.coroutine
    def close(self, code=1000, reason=''):
        """
        This coroutine performs the closing handshake.

        This is the expected way to terminate a connection on the server side.

        It waits for the other end to complete the handshake. It doesn't do
        anything once the connection is closed.

        It's usually safe to wrap this coroutine in `asyncio.async()` since
        errors during connection termination aren't particularly useful.

        The `code` must be an :class:`int` and the `reason` a :class:`str`.
        """
        if self.state == 'OPEN':
            # 7.1.2. Start the WebSocket Closing Handshake
            self.close_code, self.close_reason = code, reason
            yield from self.write_frame(OP_CLOSE,
                                        serialize_close(code, reason))
            # 7.1.3. The WebSocket Closing Handshake is Started
            self.state = 'CLOSING'

        # If the connection doesn't terminate within the timeout, break out of
        # the worker loop.
        try:
            yield from asyncio.wait_for(self.worker, timeout=self.timeout)
        except asyncio.TimeoutError:
            self.worker.cancel()

        # The worker should terminate quickly once it has been cancelled.
        yield from self.worker

    @asyncio.coroutine
    def recv(self):
        """
        This coroutine receives the next message.

        It returns a :class:`str` for a text frame and :class:`bytes` for a
        binary frame.

        When the end of the message stream is reached, or when a protocol
        error occurs, :meth:`recv` returns ``None``, indicating that the
        connection is closed.
        """
        # Return any available message
        try:
            return self.messages.get_nowait()
        except QueueEmpty:
            pass

        # Wait for a message until the connection is closed
        next_message = asyncio.Task(self.messages.get())
        done, pending = yield from asyncio.wait(
            [next_message, self.worker], return_when=asyncio.FIRST_COMPLETED)
        if next_message in done:
            return next_message.result()

    @asyncio.coroutine
    def send(self, data):
        """
        This coroutine sends a message.

        It sends a :class:`str` as a text frame and :class:`bytes` as a binary
        frame.

        It raises a :exc:`TypeError` for other inputs and
        :exc:`InvalidState` once the connection is closed.
        """
        if isinstance(data, str):
            opcode = 1
            data = data.encode('utf-8')
        elif isinstance(data, bytes):
            opcode = 2
        else:
            raise TypeError("data must be bytes or str")
        yield from self.write_frame(opcode, data)

    @asyncio.coroutine
    def ping(self, data=None):
        """
        This coroutine sends a ping.

        It returns a Future which will be completed when the corresponding
        pong is received and which you may ignore if you don't want to wait.

        A ping may serve as a keepalive.
        """
        # Protect against duplicates if a payload is explicitly set.
        if data in self.pings:
            raise ValueError("Already waiting for a pong with the same data")
        # Generate a unique random payload otherwise.
        while data is None or data in self.pings:
            data = struct.pack('!I', random.getrandbits(32))

        self.pings[data] = asyncio.Future()
        yield from self.write_frame(OP_PING, data)
        return self.pings[data]

    @asyncio.coroutine
    def pong(self, data=b''):
        """
        This coroutine sends a pong.

        An unsolicited pong may serve as a unidirectional heartbeat.
        """
        yield from self.write_frame(OP_PONG, data)

    # Private methods - no guarantees.

    @asyncio.coroutine
    def run(self):
        # This coroutine guarantees that the connection is closed at exit.
        yield from self.opening_handshake
        while not self.closing_handshake.done():
            try:
                msg = yield from self.read_message()
                if msg is None:
                    break
                self.messages.put_nowait(msg)
            except asyncio.CancelledError:
                break
            except WebSocketProtocolError:
                yield from self.fail_connection(1002)
            except UnicodeDecodeError:
                yield from self.fail_connection(1007)
            except Exception:
                yield from self.fail_connection(1011)
                raise
        yield from self.close_connection()

    @asyncio.coroutine
    def read_message(self):
        # Reassemble fragmented messages.
        frame = yield from self.read_data_frame()
        if frame is None:
            return
        if frame.opcode == OP_TEXT:
            text = True
        elif frame.opcode == OP_BINARY:
            text = False
        else:  # frame.opcode == OP_CONT
            raise WebSocketProtocolError("Unexpected opcode")

        # Shortcut for the common case - no fragmentation
        if frame.fin:
            return frame.data.decode('utf-8') if text else frame.data

        # 5.4. Fragmentation
        chunks = []
        if text:
            decoder = codecs.getincrementaldecoder('utf-8')(errors='strict')
            append = lambda f: chunks.append(decoder.decode(f.data, f.fin))
        else:
            append = lambda f: chunks.append(f.data)
        append(frame)

        while not frame.fin:
            frame = yield from self.read_data_frame()
            if frame is None:
                raise WebSocketProtocolError("Incomplete fragmented message")
            if frame.opcode != OP_CONT:
                raise WebSocketProtocolError("Unexpected opcode")
            append(frame)

        return ('' if text else b'').join(chunks)

    @asyncio.coroutine
    def read_data_frame(self):
        # Deal with control frames automatically and return next data frame.
        # 6.2. Receiving Data
        while True:
            frame = yield from self.read_frame()
            # 5.5. Control Frames
            if frame.opcode == OP_CLOSE:
                self.close_code, self.close_reason = parse_close(frame.data)
                if self.state != 'CLOSING':
                    # 7.1.3. The WebSocket Closing Handshake is Started
                    self.state = 'CLOSING'
                    yield from self.write_frame(OP_CLOSE, frame.data,
                                                'CLOSING')
                self.closing_handshake.set_result(True)
                return
            elif frame.opcode == OP_PING:
                # Answer pings.
                yield from self.pong(frame.data)
            elif frame.opcode == OP_PONG:
                # Do not acknowledge pings on unsolicited pongs.
                if frame.data in self.pings:
                    # Acknowledge all pings up to the one matching this pong.
                    ping_id = None
                    while ping_id != frame.data:
                        ping_id, waiter = self.pings.popitem(0)
                        if not waiter.cancelled():
                            waiter.set_result(None)
            # 5.6. Data Frames
            else:
                return frame

    @asyncio.coroutine
    def read_frame(self):
        is_masked = not self.is_client
        frame = yield from read_frame(self.reader.readexactly, is_masked)
        side = 'client' if self.is_client else 'server'
        logger.debug("%s << %s", side, frame)
        return frame

    @asyncio.coroutine
    def write_frame(self, opcode, data=b'', expected_state='OPEN'):
        # This may happen if a user attempts to write on a closed connection.
        if self.state != expected_state:
            raise InvalidState("Cannot write to a WebSocket "
                               "in the {} state".format(self.state))
        frame = Frame(True, opcode, data)
        side = 'client' if self.is_client else 'server'
        logger.debug("%s >> %s", side, frame)
        is_masked = self.is_client
        write_frame(frame, self.writer.write, is_masked)
        # Handle flow control automatically.
        try:
            yield from self.writer.drain()
        except ConnectionResetError:
            pass

    @asyncio.coroutine
    def close_connection(self):
        # 7.1.1. Close the WebSocket Connection
        if self.state == 'CLOSED':
            return

        # Defensive assertion for protocol compliance.
        if self.state != 'CLOSING':  # pragma: no cover
            raise InvalidState("Cannot close a WebSocket connection "
                               "in the {} state".format(self.state))

        if self.is_client:
            try:
                yield from asyncio.wait_for(self.connection_closed,
                                            timeout=self.timeout)
            except (asyncio.CancelledError, asyncio.TimeoutError):
                pass

            if self.state == 'CLOSED':
                return

        if self.writer.can_write_eof():
            self.writer.write_eof()
        self.writer.close()

        try:
            yield from asyncio.wait_for(self.connection_closed,
                                        timeout=self.timeout)
        except (asyncio.CancelledError, asyncio.TimeoutError):
            pass

    @asyncio.coroutine
    def fail_connection(self, code=1011, reason=''):
        # Losing the connection usually results in a protocol error.
        # Preserve the original error code in this case.
        if self.close_code != 1006:
            self.close_code, self.close_reason = code, reason
        # 7.1.7. Fail the WebSocket Connection
        logger.info("Failing the WebSocket connection: %d %s", code, reason)
        if self.state == 'OPEN':
            yield from self.write_frame(OP_CLOSE,
                                        serialize_close(code, reason))
            self.state = 'CLOSING'
        if not self.closing_handshake.done():
            self.closing_handshake.set_result(False)
        yield from self.close_connection()

    # asyncio StreamReaderProtocol methods

    def client_connected(self, reader, writer):
        self.reader = reader
        self.writer = writer

    def connection_lost(self, exc):
        # 7.1.4. The WebSocket Connection is Closed
        self.state = 'CLOSED'
        if not self.connection_closed.done():
            self.connection_closed.set_result(None)
        if self.close_code is None:
            self.close_code = 1006
        super().connection_lost(exc)
Ejemplo n.º 33
0
class DCCClient:
    def __init__(self, loop, network, send=False):
        self.loop = loop
        self.network = network
        self.read_queue = Queue(loop=loop)
        self.send = send #ugh what if i want to RECEIVE though.
        #not sure what the use case would be but...?

    @asyncio.coroutine
    def connect(self, port=None):
        if not self.send:
            server = self.current_server = self.network.servers[0]
            self._reader, self._writer = yield from server.connect(self.loop)
            self._read_loop_task = asyncio.Task(self._start_read_loop())
            asyncio.async(self._read_loop_task, loop=self.loop)
        else:
            self._waiting = asyncio.Lock()
            yield from self._waiting.acquire()
            if port:
                self.network = yield from asyncio.start_server(self._handle_client,
                    host=socket.gethostbyname(socket.gethostname()), port=port, loop=self.loop)
            else:
                logger.error("No port provided for send")

    @asyncio.coroutine
    def _handle_client(self, client_reader, client_writer):
        self._reader = client_reader
        self._writer = client_writer
        self._waiting.release()
        self._read_loop_task = asyncio.Task(self._start_read_loop())
        asyncio.async(self._read_loop_task, loop=self.loop)

    @asyncio.coroutine
    def disconnect(self):
        yield from self._writer.drain()
        self._writer.write_eof()

        self._read_loop_task.cancel()
        yield from self._read_loop_task

        while not self._reader.at_eof():
            yield from self._reader.readline()

        if self.send:
            self.network.close()

    @asyncio.coroutine
    def _start_read_loop(self):
        if not self.send: #acks don't really do anything so don't listen for them
            while not self._reader.at_eof():
                try:
                    yield from self._read_message()
                except CancelledError:
                    return
                except Exception:
                    logger.exception("Smothering exception in DCC read loop")

    @asyncio.coroutine
    def _read_message(self):
         line = yield from self._reader.readline()
         m = re.match(b'(.*)(\r|\n|\r\n)$', line)
         assert m
         line = m.group(1)
         message = DCCMessage.parse(line)
         logger.debug("recv: %r", message)
         event = DirectMessage(self, message)
         self.read_queue.put_nowait((message, event))

    @asyncio.coroutine
    def read_event(self):
        message, event = yield from self.read_queue.get()
        return event

    @asyncio.coroutine
    def say(self, message, target=None, no_respond=None):
        self.send_message(message)

    @asyncio.coroutine
    def send_message(self, message):
        message = DCCMessage(message)
        logger.debug("sent: %r", message)
        self._writer.write(message.render().encode('utf8') + b'\r\n')

    @asyncio.coroutine
    def transfer(self, path):
        yield from self._waiting.acquire()
        f = open(str(path), 'rb')
        block = b'\x01'
        while block != b'':
            block = f.read(1024)
            self._writer.write(block)
        f.close()
        self._waiting.release()
        return True
Ejemplo n.º 34
0
class WebSocketCommonProtocol(asyncio.StreamReaderProtocol):
    """
    This class implements common parts of the WebSocket protocol.

    It assumes that the WebSocket connection is established. The handshake is
    managed in subclasses such as
    :class:`~websockets.server.WebSocketServerProtocol` and
    :class:`~websockets.client.WebSocketClientProtocol`.

    It runs a task that stores incoming data frames in a queue and deals with
    control frames automatically. It sends outgoing data frames and performs
    the closing handshake.

    The `host`, `port` and `secure` parameters are simply stored as attributes
    for handlers that need them.

    The `timeout` parameter defines the maximum wait time in seconds for
    completing the closing handshake and, only on the client side, for
    terminating the TCP connection. :meth:`close()` will complete in at most
    this time on the server side and twice this time on the client side.

    The `max_size` parameter enforces the maximum size for incoming messages
    in bytes. The default value is 1MB. ``None`` disables the limit. If a
    message larger than the maximum size is received, :meth:`recv()` will
    return ``None`` and the connection will be closed with status code 1009.

    Once the connection is closed, the status code is available in the
    :attr:`close_code` attribute and the reason in :attr:`close_reason`.
    """

    # There are only two differences between the client-side and the server-
    # side behavior: masking the payload and closing the underlying TCP
    # connection. This class implements the server-side behavior by default.
    # To get the client-side behavior, set is_client = True.

    is_client = False
    state = 'OPEN'

    def __init__(self, *,
                 host=None, port=None, secure=None, timeout=10, max_size=2 ** 20, loop=None):
        self.host = host
        self.port = port
        self.secure = secure

        self.timeout = timeout
        self.max_size = max_size

        super().__init__(asyncio.StreamReader(), self.client_connected, loop)

        self.close_code = None
        self.close_reason = ''

        # Futures tracking steps in the connection's lifecycle.
        self.opening_handshake = asyncio.Future()
        self.closing_handshake = asyncio.Future()
        self.connection_failed = asyncio.Future()
        self.connection_closed = asyncio.Future()

        # Queue of received messages.
        self.messages = Queue()

        # Mapping of ping IDs to waiters, in chronological order.
        self.pings = collections.OrderedDict()

        # Task managing the connection.
        self.worker = asyncio.async(self.run())

        # In a subclass implementing the opening handshake, the state will be
        # CONNECTING at this point.
        if self.state == 'OPEN':
            self.opening_handshake.set_result(True)

    # Public API

    @property
    def open(self):
        """
        This property is ``True`` when the connection is usable.

        It may be used to handle disconnections gracefully.
        """
        return self.state == 'OPEN'

    @asyncio.coroutine
    def close(self, code=1000, reason=''):
        """
        This coroutine performs the closing handshake.

        This is the expected way to terminate a connection on the server side.

        It waits for the other end to complete the handshake. It doesn't do
        anything once the connection is closed.

        It's usually safe to wrap this coroutine in `asyncio.async()` since
        errors during connection termination aren't particularly useful.

        The `code` must be an :class:`int` and the `reason` a :class:`str`.
        """
        if self.state == 'OPEN':
            # 7.1.2. Start the WebSocket Closing Handshake
            self.close_code, self.close_reason = code, reason
            yield from self.write_frame(OP_CLOSE, serialize_close(code, reason))
            # 7.1.3. The WebSocket Closing Handshake is Started
            self.state = 'CLOSING'

        # If the connection doesn't terminate within the timeout, break out of
        # the worker loop.
        try:
            yield from asyncio.wait_for(self.worker, timeout=self.timeout)
        except asyncio.TimeoutError:
            self.worker.cancel()

        # The worker should terminate quickly once it has been cancelled.
        yield from self.worker

    @asyncio.coroutine
    def recv(self):
        """
        This coroutine receives the next message.

        It returns a :class:`str` for a text frame and :class:`bytes` for a
        binary frame.

        When the end of the message stream is reached, or when a protocol
        error occurs, :meth:`recv` returns ``None``, indicating that the
        connection is closed.
        """
        # Return any available message
        try:
            return self.messages.get_nowait()
        except QueueEmpty:
            pass

        # Wait for a message until the connection is closed
        next_message = asyncio.async(self.messages.get())
        done, pending = yield from asyncio.wait(
                [next_message, self.worker],
                return_when=asyncio.FIRST_COMPLETED)
        if next_message in done:
            return next_message.result()
        else:
            next_message.cancel()

    @asyncio.coroutine
    def send(self, data):
        """
        This coroutine sends a message.

        It sends a :class:`str` as a text frame and :class:`bytes` as a binary
        frame.

        It raises a :exc:`TypeError` for other inputs and
        :exc:`InvalidState` once the connection is closed.
        """
        if isinstance(data, str):
            opcode = 1
            data = data.encode('utf-8')
        elif isinstance(data, bytes):
            opcode = 2
        else:
            raise TypeError("data must be bytes or str")
        yield from self.write_frame(opcode, data)

    @asyncio.coroutine
    def ping(self, data=None):
        """
        This coroutine sends a ping.

        It returns a Future which will be completed when the corresponding
        pong is received and which you may ignore if you don't want to wait.

        A ping may serve as a keepalive.
        """
        # Protect against duplicates if a payload is explicitly set.
        if data in self.pings:
            raise ValueError("Already waiting for a pong with the same data")
        # Generate a unique random payload otherwise.
        while data is None or data in self.pings:
            data = struct.pack('!I', random.getrandbits(32))

        self.pings[data] = asyncio.Future()
        yield from self.write_frame(OP_PING, data)
        return self.pings[data]

    @asyncio.coroutine
    def pong(self, data=b''):
        """
        This coroutine sends a pong.

        An unsolicited pong may serve as a unidirectional heartbeat.
        """
        yield from self.write_frame(OP_PONG, data)

    # Private methods - no guarantees.

    @asyncio.coroutine
    def run(self):
        # This coroutine guarantees that the connection is closed at exit.
        yield from self.opening_handshake
        while not self.closing_handshake.done():
            try:
                msg = yield from self.read_message()
                if msg is None:
                    break
                self.messages.put_nowait(msg)
            except asyncio.CancelledError:
                break
            except WebSocketProtocolError:
                yield from self.fail_connection(1002)
            except asyncio.IncompleteReadError:
                yield from self.fail_connection(1006)
            except UnicodeDecodeError:
                yield from self.fail_connection(1007)
            except PayloadTooBig:
                yield from self.fail_connection(1009)
            except Exception:
                yield from self.fail_connection(1011)
                raise
        yield from self.close_connection()

    @asyncio.coroutine
    def read_message(self):
        # Reassemble fragmented messages.
        frame = yield from self.read_data_frame(max_size=self.max_size)
        if frame is None:
            return
        if frame.opcode == OP_TEXT:
            text = True
        elif frame.opcode == OP_BINARY:
            text = False
        else:   # frame.opcode == OP_CONT
            raise WebSocketProtocolError("Unexpected opcode")

        # Shortcut for the common case - no fragmentation
        if frame.fin:
            return frame.data.decode('utf-8') if text else frame.data

        # 5.4. Fragmentation
        chunks = []
        max_size = self.max_size
        if text:
            decoder = codecs.getincrementaldecoder('utf-8')(errors='strict')
            if max_size is None:
                def append(frame):
                    nonlocal chunks
                    chunks.append(decoder.decode(frame.data, frame.fin))
            else:
                def append(frame):
                    nonlocal chunks, max_size
                    chunks.append(decoder.decode(frame.data, frame.fin))
                    max_size -= len(frame.data)
        else:
            if max_size is None:
                def append(frame):
                    nonlocal chunks
                    chunks.append(frame.data)
            else:
                def append(frame):
                    nonlocal chunks, max_size
                    chunks.append(frame.data)
                    max_size -= len(frame.data)
        append(frame)

        while not frame.fin:
            frame = yield from self.read_data_frame(max_size=max_size)
            if frame is None:
                raise WebSocketProtocolError("Incomplete fragmented message")
            if frame.opcode != OP_CONT:
                raise WebSocketProtocolError("Unexpected opcode")
            append(frame)

        return ('' if text else b'').join(chunks)

    @asyncio.coroutine
    def read_data_frame(self, max_size):
        # Deal with control frames automatically and return next data frame.
        # 6.2. Receiving Data
        while True:
            frame = yield from self.read_frame(max_size)
            # 5.5. Control Frames
            if frame.opcode == OP_CLOSE:
                self.close_code, self.close_reason = parse_close(frame.data)
                if self.state != 'CLOSING':
                    # 7.1.3. The WebSocket Closing Handshake is Started
                    self.state = 'CLOSING'
                    yield from self.write_frame(OP_CLOSE, frame.data, 'CLOSING')
                if not self.closing_handshake.done():
                    self.closing_handshake.set_result(True)
                return
            elif frame.opcode == OP_PING:
                # Answer pings.
                yield from self.pong(frame.data)
            elif frame.opcode == OP_PONG:
                # Do not acknowledge pings on unsolicited pongs.
                if frame.data in self.pings:
                    # Acknowledge all pings up to the one matching this pong.
                    ping_id = None
                    while ping_id != frame.data:
                        ping_id, waiter = self.pings.popitem(0)
                        if not waiter.cancelled():
                            waiter.set_result(None)
            # 5.6. Data Frames
            else:
                return frame

    @asyncio.coroutine
    def read_frame(self, max_size):
        is_masked = not self.is_client
        frame = yield from read_frame(self.reader.readexactly, is_masked, max_size=max_size)
        side = 'client' if self.is_client else 'server'
        logger.debug("%s << %s", side, frame)
        return frame

    @asyncio.coroutine
    def write_frame(self, opcode, data=b'', expected_state='OPEN'):
        # This may happen if a user attempts to write on a closed connection.
        if self.state != expected_state:
            raise InvalidState("Cannot write to a WebSocket "
                               "in the {} state".format(self.state))
        frame = Frame(True, opcode, data)
        side = 'client' if self.is_client else 'server'
        logger.debug("%s >> %s", side, frame)
        is_masked = self.is_client
        write_frame(frame, self.writer.write, is_masked)
        try:
            # Handle flow control automatically.
            yield from self.writer.drain()
        except ConnectionResetError:
            # Terminate the connection if the socket died,
            # unless it's already being closed.
            if expected_state != 'CLOSING':
                self.state = 'CLOSING'
                yield from self.fail_connection(1006)

    @asyncio.coroutine
    def close_connection(self):
        # 7.1.1. Close the WebSocket Connection
        if self.state == 'CLOSED':
            return

        # Defensive assertion for protocol compliance.
        if self.state != 'CLOSING':                         # pragma: no cover
            raise InvalidState("Cannot close a WebSocket connection "
                               "in the {} state".format(self.state))

        if self.is_client:
            try:
                yield from asyncio.wait_for(self.connection_closed,
                        timeout=self.timeout)
            except (asyncio.CancelledError, asyncio.TimeoutError):
                pass

            if self.state == 'CLOSED':
                return

        # Attempt to terminate the TCP connection properly.
        # If the socket is already closed, this will crash.
        try:
            if self.writer.can_write_eof():
                self.writer.write_eof()
        except Exception:
            pass

        self.writer.close()

        try:
            yield from asyncio.wait_for(self.connection_closed,
                    timeout=self.timeout)
        except (asyncio.CancelledError, asyncio.TimeoutError):
            pass

    @asyncio.coroutine
    def fail_connection(self, code=1011, reason=''):
        # Avoid calling fail_connection more than once to minimize
        # the consequences of race conditions between the two sides.
        if self.connection_failed.done():
            # Wait until the other coroutine calls connection_lost.
            yield from self.connection_closed
            return
        else:
            self.connection_failed.set_result(None)

        # Losing the connection usually results in a protocol error.
        # Preserve the original error code in this case.
        if self.close_code != 1006:
            self.close_code, self.close_reason = code, reason
        # 7.1.7. Fail the WebSocket Connection
        logger.info("Failing the WebSocket connection: %d %s", code, reason)
        if self.state == 'OPEN':
            yield from self.write_frame(OP_CLOSE, serialize_close(code, reason))
            self.state = 'CLOSING'
        if not self.closing_handshake.done():
            self.closing_handshake.set_result(False)
        yield from self.close_connection()

    # asyncio StreamReaderProtocol methods

    def client_connected(self, reader, writer):
        self.reader = reader
        self.writer = writer

    def connection_lost(self, exc):
        # 7.1.4. The WebSocket Connection is Closed
        self.state = 'CLOSED'
        if not self.connection_closed.done():
            self.connection_closed.set_result(None)
        if self.close_code is None:
            self.close_code = 1006
        super().connection_lost(exc)
Ejemplo n.º 35
0
class IRCClient:
    """Higher-level IRC client.  Takes care of most of the hard parts of IRC:
    incoming server messages are bundled into more intelligible events (see
    ``dywypi.event``), and commands that expect replies are implemented as
    coroutines.
    """

    def __init__(self, loop, network):
        self.loop = loop
        self.network = network

        self.joined_channels = {}  # name => Channel

        # IRC server features, as reported by ISUPPORT, with defaults taken
        # from the RFC.
        self.len_nick = 9
        self.len_channel = 200
        self.len_message = 510
        # These lengths don't have limits mentioned in the RFC, so going with
        # the smallest known values in the wild
        self.len_kick = 80
        self.len_topic = 80
        self.len_away = 160
        self.max_watches = 0
        self.max_targets = 1
        self.channel_types = set('#&')
        self.channel_modes = {}  # TODO, haha.
        self.channel_prefixes = {}  # TODO here too.  IRCMode is awkward.
        self.network_title = self.network.name
        self.features = {}

        # Various intermediate state used for waiting for replies and
        # aggregating multi-part replies
        # TODO hmmm so what happens if state just gets left here forever?  do
        # we care?
        self._pending_names = {}
        self._names_futures = {}
        self._pending_topics = {}
        self._join_futures = {}

        self.event_queue = Queue(loop=loop)

    def get_channel(self, channel_name):
        """Returns a `Channel` object containing everything the client
        definitively knows about the given channel.

        Note that if you, say, ask for the topic of a channel you aren't in and
        then immediately call `get_channel`, the returned object won't have its
        topic populated.  State is only tracked persistently for channels the
        bot is in; otherwise there's no way to know whether or not it's stale.
        """
        if channel_name in self.joined_channels:
            return self.joined_channels[channel_name]
        else:
            return IRCChannel(self, channel_name)

    @asyncio.coroutine
    def connect(self):
        """Coroutine for connecting to a single server.

        Note that this will nonblock until the client is "registered", defined
        as the first PING/PONG exchange.
        """
        # TODO this is a poor excuse for round-robin  :)
        server = self.current_server = self.network.servers[0]

        # TODO i'm pretty sure the server tells us what our nick is, and we
        # should believe that instead
        self.nick = self.network.preferred_nick

        # TODO: handle disconnection, somehow.  probably affects a lot of
        # things.
        # TODO kind of wish this weren't here, since the creation of the
        # connection isn't inherently part of a client.  really it should be on
        # the...  network, perhaps?  and there's no reason i shouldn't be able
        # to "connect" to a unix socket or pipe or anywhere else that has data.
        _, self.proto = yield from self.loop.create_connection(
            lambda: IRCClientProtocol(
                self.loop, self.network.preferred_nick, password=server.password),
            server.host, server.port, ssl=server.tls)

        while True:
            yield from self._read_message()
            # TODO this is dumb garbage; more likely this client itself should
            # just wait for 001/RPL_WELCOME.
            if self.proto.registered:
                break

        # Start the event loop as soon as we've synched, or we can't respond to
        # anything
        asyncio.async(self._advance(), loop=self.loop)

        # Initial joins
        yield from asyncio.gather(*[
            self.join(channel_name)
            for channel_name in self.network.autojoins
        ], loop=self.loop)

    @asyncio.coroutine
    def disconnect(self):
        self.proto.send_message('QUIT', 'Seeya!')
        self.proto.transport.close()

    @asyncio.coroutine
    def _advance(self):
        """Internal coroutine that just keeps the protocol message queue going.
        Called once after a connect and should never be called again after
        that.
        """
        # TODO this is currently just to keep the message queue going, but
        # eventually it should turn them into events and stuff them in an event
        # queue
        yield from self._read_message()

        asyncio.async(self._advance(), loop=self.loop)

    @asyncio.coroutine
    def _read_message(self):
        """Internal dispatcher for messages received from the protocol."""
        message = yield from self.proto.read_message()

        # TODO there is a general ongoing problem here with matching up
        # responses.  ESPECIALLY when error codes are possible.  something here
        # is gonna have to get a bit fancier.  maybe it should live at the
        # protocol level, actually...?

        # Boy do I ever hate this pattern but it's slightly more maintainable
        # than a 500-line if tree.
        handler = getattr(self, '_handle_' + message.command, None)
        if handler:
            handler(message)

    def _handle_RPL_ISUPPORT(self, message):
        me, *features, human_text = message.args
        for feature_string in features:
            feature, _, value = feature_string.partition('=')
            if value is None:
                value = True

            self.features[feature] = value

            if feature == 'NICKLEN':
                self.len_nick = int(value)
            elif feature == 'CHANNELLEN':
                self.len_channel = int(value)
            elif feature == 'KICKLEN':
                self.len_kick = int(value)
            elif feature == 'TOPICLEN':
                self.len_topic = int(value)
            elif feature == 'AWAYLEN':
                self.len_away = int(value)
            elif feature == 'WATCH':
                self.max_watches = int(value)
            elif feature == 'CHANTYPES':
                self.channel_types = set(value)
            elif feature == 'PREFIX':
                # List of channel user modes, in relative priority order, in
                # the format (ov)@+
                assert value[0] == '('
                letters, symbols = value[1:].split(')')
                assert len(letters) == len(symbols)
                self.channel_prefixes.clear()
                for letter, symbol in zip(letters, symbols):
                    mode = IRCMode(letter, prefix=symbol)
                    self.channel_modes[letter] = mode
                    self.channel_prefixes[symbol] = mode
            elif feature == 'MAXTARGETS':
                self.max_targets = int(value)
            elif feature == 'CHANMODES':
                # Four groups delimited by lists: list-style (+b), arg required
                # (+k), arg required only to set (+l), argless
                lists, args, argsets, argless = value.split(',')
                for letter in lists:
                    self.channel_modes[letter] = IRCMode(
                        letter, multi=True)
                for letter in args:
                    self.channel_modes[letter] = IRCMode(
                        letter, arg_on_set=True, arg_on_remove=True)
                for letter in argsets:
                    self.channel_modes[letter] = IRCMode(
                        letter, arg_on_set=True)
                for letter in argless:
                    self.channel_modes[letter] = IRCMode(letter)
            elif feature == 'NETWORK':
                self.network_title = value

    def _handle_JOIN(self, message):
        channel_name, = message.args
        joiner = Peer.from_prefix(message.prefix)
        # TODO should there be a self.me?  how...
        if joiner.name == self.nick:
            # We just joined a channel
            #assert channel_name not in self.joined_channels
            # TODO key?  do we care?
            # TODO what about channel configuration and anon non-joined
            # channels?  how do these all relate...
            channel = IRCChannel(self, channel_name)
            self.joined_channels[channel.name] = channel
        else:
            # Someone else just joined the channel
            self.joined_channels[channel_name].add_user(joiner)

    def _handle_RPL_TOPIC(self, message):
        # Topic.  Sent when joining or when requesting the topic.
        # TODO this doesn't handle the "requesting" part
        # TODO what if me != me?
        me, channel_name, topic_text = message.args
        self._pending_topics[channel_name] = IRCTopic(topic_text)

    def _handle_RPL_TOPICWHOTIME(self, message):
        # Topic author (NONSTANDARD).  Sent after RPL_TOPIC.
        # Unfortunately, there's no way to know whether to expect this.
        # TODO this doesn't handle the "requesting" part
        # TODO what if me != me?
        me, channel_name, author, timestamp = message.args
        topic = self._pending_topics.setdefault(channel_name, IRCTopic(''))
        topic.author = Peer.from_prefix(author)
        topic.timestamp = datetime.utcfromtimestamp(int(timestamp))

    def _handle_RPL_NAMREPLY(self, message):
        # Names response.  Sent when joining or when requesting a names
        # list.  Must be ended with a RPL_ENDOFNAMES.
        me, useless_equals_sign, channel_name, *raw_names = message.args
        # List of names is actually optional (?!)
        if raw_names:
            raw_names = raw_names[0]
        else:
            raw_names = ''

        names = raw_names.strip(' ').split(' ')
        namelist = self._pending_names.setdefault(channel_name, [])
        # TODO modes?  should those be stripped off here?
        # TODO for that matter should these become peers here?
        namelist.extend(names)

    def _handle_RPL_ENDOFNAMES(self, message):
        # End of names list.  Sent at the very end of a join or the very
        # end of a NAMES request.
        me, channel_name, info = message.args
        namelist = self._pending_names.pop(channel_name, [])

        if channel_name in self._names_futures:
            # TODO we should probably not ever have a names future AND a
            # pending join at the same time.  or, does it matter?
            self._names_futures[channel_name].set_result(namelist)
            del self._names_futures[channel_name]

        if channel_name in self.joined_channels:
            # Join synchronized!
            channel = self.joined_channels[channel_name]
            channel.sync = True

            channel.topic = self._pending_topics.pop(channel_name, None)

            for name in namelist:
                modes = set()
                # TODO use features!
                while name and name[0] in '+%@&~':
                    modes.add(name[0])
                    name = name[1:]

                # TODO haha no this is so bad.
                # TODO the bot should, obviously, keep a record of all
                # known users as well.  alas, mutable everything.
                peer = Peer(name, None, None)

                channel.add_user(peer, modes)

            if channel_name in self._join_futures:
                # Update the Future
                self._join_futures[channel_name].set_result(channel)
                del self._join_futures[channel_name]

    def _handle_PRIVMSG(self, message):
        event = Message(self, message)
        self.event_queue.put_nowait(event)

    @asyncio.coroutine
    def read_event(self):
        """Produce a single IRC event.

        This client does not do any kind of multiplexing or event handler
        notification; that's left to a higher level.
        """
        return (yield from self.event_queue.get())


    # Implementations of particular commands

    # TODO should this be part of the general client interface, or should there
    # be a separate thing that smooths out the details?
    @asyncio.coroutine
    def say(self, target, message):
        """Coroutine that sends a message to a target, which may be either a
        `Channel` or a `Peer`.
        """
        yield from self.send_message('PRIVMSG', target, message)

    def join(self, channel_name, key=None):
        """Coroutine that joins a channel, and nonblocks until the join is
        "synchronized" (defined as receiving the nick list).
        """
        if channel_name in self._join_futures:
            return self._join_futures[channel_name]

        # TODO multiple?  error on commas?
        if key is None:
            self.proto.send_message('JOIN', channel_name)
        else:
            self.proto.send_message('JOIN', channel_name, key)

        # Clear out any lingering names list
        self._pending_names[channel_name] = []

        # Return a Future, to be populated by the message loop
        fut = self._join_futures[channel_name] = asyncio.Future()
        return fut

    def names(self, channel_name):
        """Coroutine that returns a list of names in a channel."""
        self.proto.send_message('NAMES', channel_name)

        # No need to do the same thing twice
        if channel_name in self._names_futures:
            return self._names_futures[channel_name]

        # Clear out any lingering names list
        self._pending_names[channel_name] = []

        # Return a Future, to be populated by the message loop
        fut = self._names_futures[channel_name] = asyncio.Future()
        return fut

    def set_topic(self, channel, topic):
        """Sets the channel topic."""
        self.proto.send_message('TOPIC', channel, topic)

    @asyncio.coroutine
    def send_message(self, command, *args):
        self.proto.send_message(command, *args)

    def format_transition(self, current_style, new_style):
        if new_style == Style.default():
            # Reset code, ^O
            return '\x0f'

        if new_style.fg != current_style.fg and new_style.fg is Color.default:
            # IRC has no "reset to default" code.  mIRC claims color 99 is for
            # this, but it lies, at least in irssi.  So we must reset and
            # reapply everything.
            ret = '\x0f'
            if new_style.bold is Bold.on:
                ret += '\x02'
            return ret

        ret = ''
        if new_style.fg != current_style.fg:
            ret += FOREGROUND_CODES[new_style.fg]

        if new_style.bold != current_style.bold:
            # There's no on/off for bold, just a toggle
            ret += '\x02'

        return ret
Ejemplo n.º 36
0
async def dump1090TCPListener(incomingQ: Queue, host: str, port: int):

    LOG.info(F"Starting: Opening TCP Stream to Dump Server at: {host}:{port}")

    while True:

        try:
            stream_reader, _ = await open_connection(host, port)
            STATS['connection_closed'] = False
            STATS['connections'] += 1
        except Exception as x:
            print(F"@dump1090TCPListener: Got exception {x}")
            LOG.exception(x)
            STATS['connections_exception'] += 1
            continue

        LOG.info("Got stream_reader")
        try:
            BUFSIZE = 1024
            data = ""
            while True:

                new_data = await stream_reader.read(BUFSIZE)

                if not new_data:
                    LOG.info("receiver: connection closed")
                    STATS['connection_closed'] = True
                    return
                    # break it into lines

                data += new_data.decode('utf-8')

                #print(F"#######\n{data}######")

                cur_pos = 0
                while True:

                    # print(F"DATA\n{data}\n")

                    cr_index = data.find('\n')

                    if cr_index > 0:
                        # print(F"Slice {cur_pos} : {cr_index} Data: {data[cur_pos:cr_index]}")

                        l = data[cur_pos:cr_index].strip()
                        #print(F"Q<-{l}\n", end='')
                        incomingQ.put_nowait(l)
                        STATS["msg_rx"] += 1
                        STATS["max_incomingQ_size"] = max(
                            incomingQ.qsize(), STATS["max_incomingQ_size"])
                        # print(F"done")

                        if STATS["msg_rx"] % 100 == 0:
                            LOG.info(
                                F"Rxed {STATS['msg_rx']} max_q size {STATS['max_incomingQ_size']}"
                            )

                        cur_pos = cr_index + 1
                        data = data[cur_pos:]
                        cur_pos = 0
                    else:
                        # print(F"Slice {cur_pos} : {cr_index} Data: {data[cur_pos:cr_index]}")
                        break

        except CancelledError:
            LOG.info("Cancelling")
            break
        except Exception:
            LOG.exception("General exception")

        finally:
            pass

    LOG.info(F"Exiting")
Ejemplo n.º 37
0
class HandlerConnectionHandler:
    def __init__(self, host, port, _handler_id, _device_ids, log, app,
                 event) -> None:
        self._mqtt = None
        self._app = app
        self._log = log
        self.event = None
        self.command = None

        self._client_id = f'handler.{_handler_id}'
        self._host = host
        self._port = port
        self._device_ids = _device_ids
        self._handler_id = _handler_id
        self._event = event
        self._message_queue = Queue()

    def start(self):
        self._mqtt = ConnectionHandler(self._host, self._port, self._client_id,
                                       self._log)
        self._log.set_mqtt_client(self._mqtt)
        self._mqtt.init_mqtt_client_callbacks(self._on_connect,
                                              self._on_message,
                                              self._on_disconnect)

        self._mqtt.register_route(
            "Master", lambda topic, payload: self.dispatch_masterapp_message(
                topic, self._mqtt.decode_payload(payload)))
        self._mqtt.register_route(
            "Handler", lambda topic, payload: self.dispatch_masterapp_message(
                topic, self._mqtt.decode_payload(payload)))

        self._mqtt.set_last_will(
            self._generate_handler_status_topic(),
            self._mqtt.create_message(
                self._generate_status_message('crash', '')))
        self._mqtt.start_loop()

    async def stop(self) -> None:
        await self._mqtt.stop_loop()

    def subscribe(self, topic) -> None:
        self._mqtt.subscribe(topic)

    def publish(self, topic, payload, qos=0, retain=False) -> None:
        self._mqtt.publish(topic, json.dumps(payload), qos=qos, retain=retain)

    def publish_state(self, state, message) -> None:
        self._log.log_message(LogLevel.Info(), f'Handler state: {state}')
        self.publish(self._generate_handler_status_topic(),
                     self._generate_status_message(state, message),
                     qos=1,
                     retain=False)

    def _on_connect(self, client, userdata, flags, rc) -> None:
        self._app.startup_done('connection to broker is established')

        for device_id in self._device_ids:
            self.subscribe(self._generate_master_status_topic(device_id))
            self.subscribe(self._generate_command_topic(device_id))
            self.subscribe(self._generate_response_topic(device_id))

    def _on_message(self, client, userdata, message) -> None:
        self._mqtt.router.inject_message(message.topic, message.payload)

    def _on_disconnect(self, client, userdata, rc) -> None:
        self._log.debug("disconnected")

    def dispatch_masterapp_message(self, topic, message) -> None:
        master_id = topic.split('/')[1]
        if "status" in topic:
            master_state = message['state']
            self._app.on_master_state_changed(master_id, master_state)
            self._handle_master_state(message)
        elif "command" in topic or\
             "response" in topic:
            self._put_message(message)
        else:
            assert False

    def _handle_master_state(self, message):
        if message['state'] in INVISIBLE_TESTER_STATES:
            return

        if not self.command:
            return

        self._put_message(message)

    def _put_message(self, message):
        self._message_queue.put_nowait(message)
        self._event.set()

    def get_message(self):
        try:
            return self._message_queue.get_nowait()
        except QueueEmpty:
            return None

    def get_mqtt_client(self):
        return self._mqtt

    def send_command_message(self, message):
        for device_id in self._device_ids:
            self._mqtt.publish(self._generate_master_command_topic(device_id),
                               self._mqtt.create_message(message), False)

    def send_response_message(self, message):
        self._mqtt.publish(self._generate_handler_response_topic(),
                           self._mqtt.create_message(message), False)

    def handle_message(self, message):
        response = self._app.handle_message(message)
        if len(response):
            self._message_queue.put_nowait(response)
            self._event.set()
            return

        if message['type'] == 'temperature':
            self.send_response_message(message)
        else:
            self.send_command_message(message)
            self.command = message['type']

    @staticmethod
    def _generate_message(type, payload):
        return {'type': type, 'payload': payload}

    def _generate_status_message(self, state, message):
        payload = {'state': state, 'message': message}
        return self._generate_message('status', payload)

    @staticmethod
    def _generate_command_topic(device_id):
        return f"ate/{device_id}/Handler/command"

    @staticmethod
    def _generate_response_topic(device_id):
        return f"ate/{device_id}/Master/response"

    @staticmethod
    def _generate_log_message(log_message):
        return {"type": "log", "payload": log_message}

    def _handler_log_topic(self):
        return f'ate/{self._handler_id}/Handler/log/'

    def _generate_handler_status_topic(self):
        return f'ate/{self._handler_id}/Handler/status'

    def _generate_handler_response_topic(self):
        return f'ate/{self._handler_id}/Handler/response'

    @staticmethod
    def _generate_master_status_topic(device_id):
        return f'ate/{device_id}/Master/status'

    @staticmethod
    def _generate_master_command_topic(device_id):
        return f"ate/{device_id}/Master/command"
Ejemplo n.º 38
0
class Connection:
    def __init__(self, cursor, conn: Database):
        self._cursor = cursor
        self._public_cursor = None
        self._connection = conn
        self._thread = ThreadPoolExecutor(max_workers=1)
        self._result_queue = Queue(maxsize=1)

    def _callback(self, result):
        res = result.result()
        self._result_queue.put_nowait(res)

    async def __get_result(self):
        while True:
            try:
                result = self._result_queue.get_nowait()
            except QueueEmpty:
                await asyncio.sleep(0.0001)
            except Exception as e:
                raise e
            else:
                return result

    def _execute(self, sql: str, parameters=None):
        if not parameters:
            parameters = []
        try:
            result = self._cursor.execute(sql, parameters)
            self._public_cursor = result
            return self
        except Exception as e:
            return e

    async def execute(self, sql, parameters=None):
        task = self._thread.submit(self._execute, sql, parameters=parameters)
        task.add_done_callback(self._callback)
        result = await self.__get_result()
        if isinstance(result, Exception):
            raise result
        return result

    def _execute_many(self, sql, parameters):
        try:
            result = self._cursor.executemany(sql, parameters)
            self._public_cursor = result
            return self
        except Exception as e:
            return e

    async def executemany(self, sql, parameters):
        task = self._thread.submit(self._execute_many,
                                   sql,
                                   parameters=parameters)
        task.add_done_callback(self._callback)
        result = await self.__get_result()
        if isinstance(result, Exception):
            raise result
        return result

    def _fetch_one(self):
        try:
            result = self._public_cursor.fetchone()
            return result
        except Exception as e:
            return e

    async def fetchone(self):
        if self._public_cursor is None:
            raise NoActiveCursor
        task = self._thread.submit(self._fetch_one)
        task.add_done_callback(self._callback)
        result = await self.__get_result()
        if isinstance(result, Exception):
            raise result
        return result

    def _fetch_all(self):
        try:
            result = self._public_cursor.fetchall()
            return result
        except Exception as e:
            return e

    async def fetchall(self):
        if self._public_cursor is None:
            raise NoActiveCursor
        task = self._thread.submit(self._fetch_all)
        task.add_done_callback(self._callback)
        result = await self.__get_result()
        if isinstance(result, Exception):
            raise result
        return result

    def _commit(self):
        try:
            self._cursor.commit()
            return None
        except Exception as e:
            return e

    async def commit(self):
        task = self._thread.submit(self._commit)
        task.add_done_callback(self._callback)
        result = await self.__get_result()
        if isinstance(result, Exception):
            raise result
        return result

    async def close(self):
        self._cursor.close()
        await self._connection.connection_semaphore.put({})
        return