<commit_before>from binascii import a2b_hex
from app_util import default_logger

class parser_template():

    def __init__(self, **kwargs):
        """
        kwargs must contain: logger, debug_level
        """
        self.logger = kwargs.get("logger", default_logger)
        self.debug_level = kwargs.get("debug_level", 0)

    def parse_by_format(self, data, format_tab):
        """
        parse the bytes data according to the format table passed.
        data: byte data.
        format_tab: the structure of the format table is a list like below:
            a dict key, a function, start byte, end byte
            e.g. [ ..., ( "accel_x", self.parse_acx, 13, 15 ), ... ]
            if the function is None, the key is skipped.
        """
        ret = {}
        for f in format_tab:
            if f[1] is not None:
                ret.update({ f[0]: f[1](data[f[2]:f[3]]) })
        return ret

    def parse_number(self, data):
        """
        convert bytes in big endien into a unsigned number.
        """
        return int.from_bytes(data,"big")

    def parse_number_d1(self, data):
        """
        convert bytes in big endien into a unsigned number with unit of 0.1.
        """
        return round(int.from_bytes(data,"big")/10,1)

    def parse_number_d2(self, data):
        """
        convert bytes in big endien into a unsigned number with unit of 0.01.
        """
        return round(int.from_bytes(data,"big")/100,2)

    def parse_signed_number(self, data):
        """
        convert bytes in big endien into a signed number.
        """
        return int.from_bytes(data,"big",signed=True)

    def parse_signed_number_d1(self, data):
        """
        convert bytes in big endien into a signed number with unit of 0.1.
        """
        return round(int.from_bytes(data,"big",signed=True)/10,1)

    def parse_signed_number_d2(self, data):
        """
        convert bytes in big endien into a signed number with unit of 0.01.
        """
        return round(int.from_bytes(data,"big",signed=True)/100,2)

    def parse_number_le(self, data):
        """
        convert bytes in little endien into a unsigned number.
        """
        return int.from_bytes(data,"little")

    def parse_signed_number_le(self, data):
        """
        convert bytes in little endien into a signed number.
        """
        return int.from_bytes(data,"little",signed=True)

    def parse_nothing(self, data):
        """
        return data as it is.
        """
        return data

    def parse_bytes(self, byte_data):
        """
        TEMPLATE should be overwritten.
        parse the payload in bytes and return a Python dict object.
        """
        raise NotImplementedError

    def parse_hex(self, hex_string):
        """
        - parse the payload in hex string and return a Python dict object.
        - the return value:
            + None: ignore parsing.
            + False: something error happened.
            + Others: a Python dict should be put in KEY_APP_DATA.
        """
        # check whether it should be ignored.
        # this is to compare a **string** of "None", not the None object.
        if hex_string == "None":
            return None
        return self.parse_bytes(a2b_hex(hex_string))

    def parse_binary16_to_float(self, data):
        # referred to https://gist.github.com/zed/59a413ae2ed4141d2037
        assert 2 == len(data) and isinstance(data, bytes)
        n = int.from_bytes(data,"big")
        sign = n >> 15
        exp = (n >> 10) & 0b011111
        fraction = n & (2**10 - 1)
        if exp == 0:
            if fraction == 0:
                return -0.0 if sign else 0.0
            else:
                return (-1)**sign * fraction / 2**10 * 2**(-14)  # subnormal
        elif exp == 0b11111:
            if fraction == 0:
                return float('-inf') if sign else float('inf')
            else:
                return float('nan')
        return (-1)**sign * (1 + fraction / 2**10) * 2**(exp - 15)

    def test_eval(self, test_data):
        """
        test_data: a list of test data in hex string.
            e.g. [ { "data":"01020304", "result": { "temp:...} }, ... ]
        """
        for s in test_data:
            if not ("data" in s and "result" in s):
                print("ERROR: invalid data, both data and result must exist.")
            s["data"] = "".join(s["data"].split())
            print("TEST:", s["data"])
            r = self.parse_hex(s["data"]);
            if r is not False:
                for k,v in s["result"].items():
                    if k not in r:
                        print("ERROR: {} must exist in data".format(k))
                        continue
                    print("  {} = {}".format(k,r[k]))
                    if v != r[k]:
                        print("ERROR: invalid value, expected {} but {}"
                              .format(v,r[k]))
                        continue
            else:
                print("  ERROR")

    def test(self, test_data):
        """
        test_data: a list of test data in hex string.
            e.g. [ "01020304", "ffff0000", ... ]
        """
        for s in test_data:
            s = "".join(s.split())
            print("TEST:", s)
            v = self.parse_hex(s);
            if v is not False:
                for k,v in v.items():
                    print("  {} = {}".format(k,v))
            else:
                print("  ERROR")
<commit_msg>added code that avoids raising the exception when default_logger is imported.<commit_after>from binascii import a2b_hex
try:
    from app_util import default_logger
except:
    default_logger = None

class parser_template():

    def __init__(self, **kwargs):
        """
        kwargs must contain: logger, debug_level
        """
        self.logger = kwargs.get("logger", default_logger)
        self.debug_level = kwargs.get("debug_level", 0)

    def parse_by_format(self, data, format_tab):
        """
        parse the bytes data according to the format table passed.
        data: byte data.
        format_tab: the structure of the format table is a list like below:
            a dict key, a function, start byte, end byte
            e.g. [ ..., ( "accel_x", self.parse_acx, 13, 15 ), ... ]
            if the function is None, the key is skipped.
        """
        ret = {}
        for f in format_tab:
            if f[1] is not None:
                ret.update({ f[0]: f[1](data[f[2]:f[3]]) })
        return ret

    def parse_number(self, data):
        """
        convert bytes in big endien into a unsigned number.
        """
        return int.from_bytes(data,"big")

    def parse_number_d1(self, data):
        """
        convert bytes in big endien into a unsigned number with unit of 0.1.
        """
        return round(int.from_bytes(data,"big")/10,1)

    def parse_number_d2(self, data):
        """
        convert bytes in big endien into a unsigned number with unit of 0.01.
        """
        return round(int.from_bytes(data,"big")/100,2)

    def parse_signed_number(self, data):
        """
        convert bytes in big endien into a signed number.
        """
        return int.from_bytes(data,"big",signed=True)

    def parse_signed_number_d1(self, data):
        """
        convert bytes in big endien into a signed number with unit of 0.1.
        """
        return round(int.from_bytes(data,"big",signed=True)/10,1)

    def parse_signed_number_d2(self, data):
        """
        convert bytes in big endien into a signed number with unit of 0.01.
        """
        return round(int.from_bytes(data,"big",signed=True)/100,2)

    def parse_number_le(self, data):
        """
        convert bytes in little endien into a unsigned number.
        """
        return int.from_bytes(data,"little")

    def parse_signed_number_le(self, data):
        """
        convert bytes in little endien into a signed number.
        """
        return int.from_bytes(data,"little",signed=True)

    def parse_nothing(self, data):
        """
        return data as it is.
        """
        return data

    def parse_bytes(self, byte_data):
        """
        TEMPLATE should be overwritten.
        parse the payload in bytes and return a Python dict object.
        """
        raise NotImplementedError

    def parse_hex(self, hex_string):
        """
        - parse the payload in hex string and return a Python dict object.
        - the return value:
            + None: ignore parsing.
            + False: something error happened.
            + Others: a Python dict should be put in KEY_APP_DATA.
        """
        # check whether it should be ignored.
        # this is to compare a **string** of "None", not the None object.
        if hex_string == "None":
            return None
        return self.parse_bytes(a2b_hex(hex_string))

    def parse_binary16_to_float(self, data):
        # referred to https://gist.github.com/zed/59a413ae2ed4141d2037
        assert 2 == len(data) and isinstance(data, bytes)
        n = int.from_bytes(data,"big")
        sign = n >> 15
        exp = (n >> 10) & 0b011111
        fraction = n & (2**10 - 1)
        if exp == 0:
            if fraction == 0:
                return -0.0 if sign else 0.0
            else:
                return (-1)**sign * fraction / 2**10 * 2**(-14)  # subnormal
        elif exp == 0b11111:
            if fraction == 0:
                return float('-inf') if sign else float('inf')
            else:
                return float('nan')
        return (-1)**sign * (1 + fraction / 2**10) * 2**(exp - 15)

    def test_eval(self, test_data):
        """
        test_data: a list of test data in hex string.
            e.g. [ { "data":"01020304", "result": { "temp:...} }, ... ]
        """
        for s in test_data:
            if not ("data" in s and "result" in s):
                print("ERROR: invalid data, both data and result must exist.")
            s["data"] = "".join(s["data"].split())
            print("TEST:", s["data"])
            r = self.parse_hex(s["data"]);
            if r is not False:
                for k,v in s["result"].items():
                    if k not in r:
                        print("ERROR: {} must exist in data".format(k))
                        continue
                    print("  {} = {}".format(k,r[k]))
                    if v != r[k]:
                        print("ERROR: invalid value, expected {} but {}"
                              .format(v,r[k]))
                        continue
            else:
                print("  ERROR")

    def test(self, test_data):
        """
        test_data: a list of test data in hex string.
            e.g. [ "01020304", "ffff0000", ... ]
        """
        for s in test_data:
            s = "".join(s.split())
            print("TEST:", s)
            v = self.parse_hex(s);
            if v is not False:
                for k,v in v.items():
                    print("  {} = {}".format(k,v))
            else:
                print("  ERROR")
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-05-03 11:00
from __future__ import unicode_literals

from django.db import migrations
import djstripe.fields

class Migration(migrations.Migration):

    dependencies = [
        ('djstripe', '0029_coupon'),
    ]

    operations = [
        migrations.AlterField(
            model_name='coupon',
            name='livemode',
            field=djstripe.fields.StripeNullBooleanField(default=None, help_text=b'Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.'),
        ),
    ]
<commit_msg>Remove the coupon migration (added elsewhere)<commit_after><|endoftext|>


<commit_before># -*- coding: iso8859-1 -*-
#
# Copyright (C) 2003, 2004 Edgewall Software
# Copyright (C) 2003, 2004 Jonas Borgstrm <jonas@edgewall.com>
#
# Trac is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version.
#
# Trac is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
# Author: Jonas Borgstrm <jonas@edgewall.com>
#
# FIXME:
# * We need to figure out the encoding used somehow.

import os
import sys
import time
import urllib
from xml.sax import saxutils

import svn

import perm
import util
import Module
from WikiFormatter import wiki_to_html

class FileCommon(Module.Module):
    CHUNK_SIZE = 4096
    DISP_MAX_FILE_SIZE = 256 * 1024

    filename = None
    mime_type = None
    def render (self):
        self.perm.assert_permission (perm.FILE_VIEW)

    def display(self):
        self.env.log.debug("Displaying file: %s  mime-type: %s" % (self.filename,
                                                            self.mime_type))
        # We don't have to guess if the charset is specified in the
        # svn:mime-type property
        ctpos = self.mime_type.find('charset=')
        if ctpos >= 0:
            charset = self.mime_type[ctpos + 8:]
            self.env.log.debug("Charset %s selected" % charset)
        else:
            charset = self.env.get_config('trac', 'default_charset', 'iso-8859-15')
        data = util.to_utf8(self.read_func(self.DISP_MAX_FILE_SIZE), charset)

        if len(data) == self.DISP_MAX_FILE_SIZE:
            self.req.hdf.setValue('file.max_file_size_reached', '1')
            self.req.hdf.setValue('file.max_file_size', str(self.DISP_MAX_FILE_SIZE))
            vdata = ' '
        else:
            vdata = self.env.mimeview.display(data, filename=self.filename,
                                              mimetype=self.mime_type)
        self.req.hdf.setValue('file.highlighted_html', vdata)
        self.req.hdf.setValue('title', self.path)
        self.req.display('file.cs')

    def display_raw(self):
        self.req.send_response(200)
        self.req.send_header('Content-Type', self.mime_type)
        self.req.send_header('Content-Length', str(self.length))
        self.req.send_header('Last-Modified', self.last_modified)
        self.req.send_header('Pragma', 'no-cache')
        self.req.send_header('Expires', 'Mon, 26 Jul 1997 05:00:00 GMT')
        self.req.send_header('Cache-Control',
                             'no-store, no-cache, must-revalidate, max-age=0')
        self.req.send_header('Cache-Control', 'post-check=0, pre-check=0')
        self.req.end_headers()
        i = 0
        while 1:
            data = self.read_func(self.CHUNK_SIZE)
            if not data:
                break
            self.req.write(data)
            i += self.CHUNK_SIZE

    def display_text(self):
        self.mime_type = 'text/plain'
        self.display_raw()

class Attachment(FileCommon):
    def get_attachment_parent_link(self):
        if self.attachment_type == 'ticket':
            return ('#' + self.attachment_id,
                    self.env.href.ticket(int(self.attachment_id)))
        elif self.attachment_type == 'wiki':
            return (self.attachment_id,
                    self.env.href.wiki(self.attachment_id))
        assert 0

    def render(self):
        FileCommon.render(self)
        self.view_form = 0
        self.attachment_type = self.args.get('type', None)
        self.attachment_id = self.args.get('id', None)
        self.filename = self.args.get('filename', None)
        if self.filename:
            self.filename = os.path.basename(self.filename)

        if not self.attachment_type or not self.attachment_id:
            raise util.TracError('Unknown request')

        if self.filename and len(self.filename) > 0 and \
               self.args.has_key('delete'):
            self.perm.assert_permission (perm.TRAC_ADMIN)
            self.env.delete_attachment(self.db,
                                       self.attachment_type,
                                       self.attachment_id,
                                       self.filename)
            text, link = self.get_attachment_parent_link()
            self.req.redirect(link)

        if self.filename and len(self.filename) > 0:
            # Send an attachment
            perm_map = {'ticket':perm.TICKET_VIEW, 'wiki': perm.WIKI_VIEW}
            self.perm.assert_permission (perm_map[self.attachment_type])

            self.path = os.path.join(self.env.get_attachments_dir(),
                                     self.attachment_type,
                                     urllib.quote(self.attachment_id),
                                     urllib.quote(self.filename))
            try:
                fd = open(self.path, 'rb')
            except IOError:
                raise util.TracError('Attachment not found')

            stat = os.fstat(fd.fileno())
            self.length = stat[6]
            self.last_modified = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
                                               time.gmtime(stat[8]))
            self.read_func = lambda x, f=fd: f.read(x)
            self.mime_type = self.env.mimeview.get_mimetype(self.filename) \
                             or 'application/octet-stream'
            return

        if self.args.has_key('description') and \
               self.args.has_key('author') and \
               self.args.has_key('attachment') and \
               hasattr(self.args['attachment'], 'file'):

            # Create a new attachment
            if not self.attachment_type in ['ticket', 'wiki']:
                raise util.TracError('Unknown attachment type')

            perm_map = {'ticket':perm.TICKET_MODIFY, 'wiki': perm.WIKI_MODIFY}
            self.perm.assert_permission (perm_map[self.attachment_type])

            filename = self.env.create_attachment(self.db,
                                                  self.attachment_type,
                                                  self.attachment_id,
                                                  self.args['attachment'],
                                                  self.args.get('description'),
                                                  self.args.get('author'),
                                                  self.req.remote_addr)
            # Redirect the user to the newly created attachment
            self.req.redirect(self.env.href.attachment(self.attachment_type,
                                                       self.attachment_id,
                                                       filename))
        else:
            # Display an attachment upload form
            self.view_form = 1

    def display(self):
        text, link = self.get_attachment_parent_link()
        self.req.hdf.setValue('file.attachment_parent', text)
        self.req.hdf.setValue('file.attachment_parent_href', link)
        if self.view_form:
            self.req.hdf.setValue('attachment.type', self.attachment_type)
            self.req.hdf.setValue('attachment.id', self.attachment_id)
            self.req.display('attachment.cs')
            return
        self.req.hdf.setValue('file.rawurl',
                              self.env.href.attachment(self.attachment_type,
                                                       self.attachment_id,
                                                       self.filename,
                                                       'raw'))
        self.req.hdf.setValue('file.texturl',
                              self.env.href.attachment(self.attachment_type,
                                                       self.attachment_id,
                                                       self.filename,
                                                       'text'))
        self.req.hdf.setValue('file.filename', urllib.unquote(self.filename))
        self.req.hdf.setValue('trac.active_module', self.attachment_type) # Kludge
        FileCommon.display(self)

class File(FileCommon):
    def generate_path_links(self, rev, rev_specified):
        # FIXME: Browser, Log and File should share implementation of this
        # function.
        list = self.path.split('/')
        self.filename = list[-1]
        path = '/'
        self.req.hdf.setValue('file.filename', list[-1])
        self.req.hdf.setValue('file.path.0', 'root')
        if rev_specified:
            self.req.hdf.setValue('file.path.0.url' , self.env.href.browser(path,
                                                                            rev))
        else:
            self.req.hdf.setValue('file.path.0.url' , self.env.href.browser(path))
        i = 0
        for part in list[:-1]:
            i = i + 1
            if part == '':
                continue
            path = path + part + '/'
            self.req.hdf.setValue('file.path.%d' % i, part)
            if rev_specified:
                self.req.hdf.setValue('file.path.%d.url' % i,
                                      self.env.href.browser(path, rev))
            else:
                self.req.hdf.setValue('file.path.%d.url' % i,
                                      self.env.href.browser(path))

    def display(self):
        self.authzperm.assert_permission(self.path)
        FileCommon.display(self)

    def render(self):
        FileCommon.render(self)

        rev = self.args.get('rev', None)
        self.path = self.args.get('path', '/')
        if not rev:
            rev_specified = 0
            rev = svn.fs.youngest_rev(self.fs_ptr, self.pool)
        else:
            rev_specified = 1
            try:
                rev = int(rev)
            except ValueError:
                rev_specified = 0
                rev = svn.fs.youngest_rev(self.fs_ptr, self.pool)

        self.generate_path_links(rev, rev_specified)

        try:
            root = svn.fs.revision_root(self.fs_ptr, rev, self.pool)
        except svn.core.SubversionException:
            raise util.TracError('Invalid revision number: %d' % rev)

        history = svn.fs.node_history(root, self.path, self.pool)
        history = svn.fs.history_prev(history, 0, self.pool)
        history_path, history_rev = svn.fs.history_location(history, self.pool);
        if rev != history_rev:
            rev = history_rev

        author = svn.fs.revision_prop(self.fs_ptr, rev,
                                      svn.core.SVN_PROP_REVISION_AUTHOR, self.pool)
        msg = svn.fs.revision_prop(self.fs_ptr, rev,
                                   svn.core.SVN_PROP_REVISION_LOG, self.pool)
        msg_html = wiki_to_html(util.wiki_escape_newline(msg), self.req.hdf, self.env)
        date = svn.fs.revision_prop(self.fs_ptr, rev,
                                    svn.core.SVN_PROP_REVISION_DATE, self.pool)
        sdate = util.svn_date_to_string(date, self.pool)

        self.req.hdf.setValue('file.chgset_href', self.env.href.changeset(rev))
        self.req.hdf.setValue('file.rev', str(rev))
        self.req.hdf.setValue('file.rev_author', str(author))
        self.req.hdf.setValue('file.rev_date', sdate)
        self.req.hdf.setValue('file.rev_msg', msg_html)
        self.req.hdf.setValue('file.path', self.path)
        self.req.hdf.setValue('file.rawurl',
            saxutils.escape(self.env.href.file(self.path, rev, 'raw')))
        self.req.hdf.setValue('file.texturl',
            saxutils.escape(self.env.href.file(self.path, rev, 'text')))
        self.req.hdf.setValue('file.logurl',
            saxutils.escape(self.env.href.log(self.path, rev)))

        # Try to do an educated guess about the mime-type
        self.mime_type = svn.fs.node_prop (root, self.path,
                                           svn.util.SVN_PROP_MIME_TYPE,
                                           self.pool)
        if not self.mime_type:
            self.mime_type = self.env.mimeview.get_mimetype(filename=self.path) or 'text/plain'

        elif self.mime_type == 'application/octet-stream':
            self.mime_type = self.env.mimeview.get_mimetype(filename=self.path) or \
                             'application/octet-stream'

        self.length = svn.fs.file_length(root, self.path, self.pool)
        date = svn.fs.revision_prop(self.fs_ptr, rev,
                                svn.util.SVN_PROP_REVISION_DATE, self.pool)
        date_seconds = svn.util.svn_time_from_cstring(date, self.pool) / 1000000
        self.last_modified = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
                                      time.gmtime(date_seconds))
        fd = svn.fs.file_contents(root, self.path, self.pool)
        self.read_func = lambda x, f=fd: svn.util.svn_stream_read(f, x)

<commit_msg>in File, if a revision lower than the oldest is given, show the oldest. If a higher than the youngest, show youngest. This make viewing specific revisions slightly more intuitive. Fixes #589.<commit_after># -*- coding: iso8859-1 -*-
#
# Copyright (C) 2003, 2004 Edgewall Software
# Copyright (C) 2003, 2004 Jonas Borgstrm <jonas@edgewall.com>
#
# Trac is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version.
#
# Trac is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
# Author: Jonas Borgstrm <jonas@edgewall.com>
#
# FIXME:
# * We need to figure out the encoding used somehow.

import os
import sys
import time
import urllib
from xml.sax import saxutils

import svn

import perm
import util
import Module
from WikiFormatter import wiki_to_html

class FileCommon(Module.Module):
    CHUNK_SIZE = 4096
    DISP_MAX_FILE_SIZE = 256 * 1024

    filename = None
    mime_type = None
    def render (self):
        self.perm.assert_permission (perm.FILE_VIEW)

    def display(self):
        self.env.log.debug("Displaying file: %s  mime-type: %s" % (self.filename,
                                                            self.mime_type))
        # We don't have to guess if the charset is specified in the
        # svn:mime-type property
        ctpos = self.mime_type.find('charset=')
        if ctpos >= 0:
            charset = self.mime_type[ctpos + 8:]
            self.env.log.debug("Charset %s selected" % charset)
        else:
            charset = self.env.get_config('trac', 'default_charset', 'iso-8859-15')
        data = util.to_utf8(self.read_func(self.DISP_MAX_FILE_SIZE), charset)

        if len(data) == self.DISP_MAX_FILE_SIZE:
            self.req.hdf.setValue('file.max_file_size_reached', '1')
            self.req.hdf.setValue('file.max_file_size', str(self.DISP_MAX_FILE_SIZE))
            vdata = ' '
        else:
            vdata = self.env.mimeview.display(data, filename=self.filename,
                                              mimetype=self.mime_type)
        self.req.hdf.setValue('file.highlighted_html', vdata)
        self.req.hdf.setValue('title', self.path)
        self.req.display('file.cs')

    def display_raw(self):
        self.req.send_response(200)
        self.req.send_header('Content-Type', self.mime_type)
        self.req.send_header('Content-Length', str(self.length))
        self.req.send_header('Last-Modified', self.last_modified)
        self.req.send_header('Pragma', 'no-cache')
        self.req.send_header('Expires', 'Mon, 26 Jul 1997 05:00:00 GMT')
        self.req.send_header('Cache-Control',
                             'no-store, no-cache, must-revalidate, max-age=0')
        self.req.send_header('Cache-Control', 'post-check=0, pre-check=0')
        self.req.end_headers()
        i = 0
        while 1:
            data = self.read_func(self.CHUNK_SIZE)
            if not data:
                break
            self.req.write(data)
            i += self.CHUNK_SIZE

    def display_text(self):
        self.mime_type = 'text/plain'
        self.display_raw()

class Attachment(FileCommon):
    def get_attachment_parent_link(self):
        if self.attachment_type == 'ticket':
            return ('#' + self.attachment_id,
                    self.env.href.ticket(int(self.attachment_id)))
        elif self.attachment_type == 'wiki':
            return (self.attachment_id,
                    self.env.href.wiki(self.attachment_id))
        assert 0

    def render(self):
        FileCommon.render(self)
        self.view_form = 0
        self.attachment_type = self.args.get('type', None)
        self.attachment_id = self.args.get('id', None)
        self.filename = self.args.get('filename', None)
        if self.filename:
            self.filename = os.path.basename(self.filename)

        if not self.attachment_type or not self.attachment_id:
            raise util.TracError('Unknown request')

        if self.filename and len(self.filename) > 0 and \
               self.args.has_key('delete'):
            self.perm.assert_permission (perm.TRAC_ADMIN)
            self.env.delete_attachment(self.db,
                                       self.attachment_type,
                                       self.attachment_id,
                                       self.filename)
            text, link = self.get_attachment_parent_link()
            self.req.redirect(link)

        if self.filename and len(self.filename) > 0:
            # Send an attachment
            perm_map = {'ticket':perm.TICKET_VIEW, 'wiki': perm.WIKI_VIEW}
            self.perm.assert_permission (perm_map[self.attachment_type])

            self.path = os.path.join(self.env.get_attachments_dir(),
                                     self.attachment_type,
                                     urllib.quote(self.attachment_id),
                                     urllib.quote(self.filename))
            try:
                fd = open(self.path, 'rb')
            except IOError:
                raise util.TracError('Attachment not found')

            stat = os.fstat(fd.fileno())
            self.length = stat[6]
            self.last_modified = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
                                               time.gmtime(stat[8]))
            self.read_func = lambda x, f=fd: f.read(x)
            self.mime_type = self.env.mimeview.get_mimetype(self.filename) \
                             or 'application/octet-stream'
            return

        if self.args.has_key('description') and \
               self.args.has_key('author') and \
               self.args.has_key('attachment') and \
               hasattr(self.args['attachment'], 'file'):

            # Create a new attachment
            if not self.attachment_type in ['ticket', 'wiki']:
                raise util.TracError('Unknown attachment type')

            perm_map = {'ticket':perm.TICKET_MODIFY, 'wiki': perm.WIKI_MODIFY}
            self.perm.assert_permission (perm_map[self.attachment_type])

            filename = self.env.create_attachment(self.db,
                                                  self.attachment_type,
                                                  self.attachment_id,
                                                  self.args['attachment'],
                                                  self.args.get('description'),
                                                  self.args.get('author'),
                                                  self.req.remote_addr)
            # Redirect the user to the newly created attachment
            self.req.redirect(self.env.href.attachment(self.attachment_type,
                                                       self.attachment_id,
                                                       filename))
        else:
            # Display an attachment upload form
            self.view_form = 1

    def display(self):
        text, link = self.get_attachment_parent_link()
        self.req.hdf.setValue('file.attachment_parent', text)
        self.req.hdf.setValue('file.attachment_parent_href', link)
        if self.view_form:
            self.req.hdf.setValue('attachment.type', self.attachment_type)
            self.req.hdf.setValue('attachment.id', self.attachment_id)
            self.req.display('attachment.cs')
            return
        self.req.hdf.setValue('file.rawurl',
                              self.env.href.attachment(self.attachment_type,
                                                       self.attachment_id,
                                                       self.filename,
                                                       'raw'))
        self.req.hdf.setValue('file.texturl',
                              self.env.href.attachment(self.attachment_type,
                                                       self.attachment_id,
                                                       self.filename,
                                                       'text'))
        self.req.hdf.setValue('file.filename', urllib.unquote(self.filename))
        self.req.hdf.setValue('trac.active_module', self.attachment_type) # Kludge
        FileCommon.display(self)

class File(FileCommon):
    def generate_path_links(self, rev, rev_specified):
        # FIXME: Browser, Log and File should share implementation of this
        # function.
        list = self.path.split('/')
        self.filename = list[-1]
        path = '/'
        self.req.hdf.setValue('file.filename', list[-1])
        self.req.hdf.setValue('file.path.0', 'root')
        if rev_specified:
            self.req.hdf.setValue('file.path.0.url' , self.env.href.browser(path,
                                                                            rev))
        else:
            self.req.hdf.setValue('file.path.0.url' , self.env.href.browser(path))
        i = 0
        for part in list[:-1]:
            i = i + 1
            if part == '':
                continue
            path = path + part + '/'
            self.req.hdf.setValue('file.path.%d' % i, part)
            if rev_specified:
                self.req.hdf.setValue('file.path.%d.url' % i,
                                      self.env.href.browser(path, rev))
            else:
                self.req.hdf.setValue('file.path.%d.url' % i,
                                      self.env.href.browser(path))

    def display(self):
        self.authzperm.assert_permission(self.path)
        FileCommon.display(self)

    def render(self):
        FileCommon.render(self)

        rev = self.args.get('rev', None)
        self.path = self.args.get('path', '/')
        if not rev:
            rev_specified = 0
            rev = svn.fs.youngest_rev(self.fs_ptr, self.pool)
        else:
            rev_specified = 1
            try:
                rev = int(rev)
            except ValueError:
                rev_specified = 0
                rev = svn.fs.youngest_rev(self.fs_ptr, self.pool)

        self.generate_path_links(rev, rev_specified)

        try:
            root = svn.fs.revision_root(self.fs_ptr, rev, self.pool)
        except svn.core.SubversionException:
            rev = svn.fs.youngest_rev(self.fs_ptr, self.pool)
            root = svn.fs.revision_root(self.fs_ptr, rev, self.pool)

        node_type = svn.fs.check_path(root, self.path, self.pool)
        if not node_type in [svn.core.svn_node_dir, svn.core.svn_node_file]:
            rev = svn.fs.youngest_rev(self.fs_ptr, self.pool)
            root = svn.fs.revision_root(self.fs_ptr, rev, self.pool)
            oh = svn.fs.node_history(root, self.path, self.pool)
            while oh:
                h = oh
                oh = svn.fs.history_prev(h, 0, self.pool)
            history = h
        else:
            history = svn.fs.node_history(root, self.path, self.pool)
            history = svn.fs.history_prev(history, 0, self.pool)
        history_path, history_rev = svn.fs.history_location(history, self.pool);

        if rev != history_rev:
            rev = history_rev

        author = svn.fs.revision_prop(self.fs_ptr, rev,
                                      svn.core.SVN_PROP_REVISION_AUTHOR, self.pool)
        msg = svn.fs.revision_prop(self.fs_ptr, rev,
                                   svn.core.SVN_PROP_REVISION_LOG, self.pool)
        msg_html = wiki_to_html(util.wiki_escape_newline(msg), self.req.hdf, self.env)
        date = svn.fs.revision_prop(self.fs_ptr, rev,
                                    svn.core.SVN_PROP_REVISION_DATE, self.pool)
        sdate = util.svn_date_to_string(date, self.pool)

        self.req.hdf.setValue('file.chgset_href', self.env.href.changeset(rev))
        self.req.hdf.setValue('file.rev', str(rev))
        self.req.hdf.setValue('file.rev_author', str(author))
        self.req.hdf.setValue('file.rev_date', sdate)
        self.req.hdf.setValue('file.rev_msg', msg_html)
        self.req.hdf.setValue('file.path', self.path)
        self.req.hdf.setValue('file.rawurl',
            saxutils.escape(self.env.href.file(self.path, rev, 'raw')))
        self.req.hdf.setValue('file.texturl',
            saxutils.escape(self.env.href.file(self.path, rev, 'text')))
        self.req.hdf.setValue('file.logurl',
            saxutils.escape(self.env.href.log(self.path, rev)))

        # Try to do an educated guess about the mime-type
        self.mime_type = svn.fs.node_prop (root, self.path,
                                           svn.util.SVN_PROP_MIME_TYPE,
                                           self.pool)
        if not self.mime_type:
            self.mime_type = self.env.mimeview.get_mimetype(filename=self.path) or 'text/plain'

        elif self.mime_type == 'application/octet-stream':
            self.mime_type = self.env.mimeview.get_mimetype(filename=self.path) or \
                             'application/octet-stream'

        self.length = svn.fs.file_length(root, self.path, self.pool)
        date = svn.fs.revision_prop(self.fs_ptr, rev,
                                svn.util.SVN_PROP_REVISION_DATE, self.pool)
        date_seconds = svn.util.svn_time_from_cstring(date, self.pool) / 1000000
        self.last_modified = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
                                      time.gmtime(date_seconds))
        fd = svn.fs.file_contents(root, self.path, self.pool)
        self.read_func = lambda x, f=fd: svn.util.svn_stream_read(f, x)

<|endoftext|>


<commit_before>#     Copyright 2022. ThingsBoard
#
#     Licensed under the Apache License, Version 2.0 (the "License");
#     you may not use this file except in compliance with the License.
#     You may obtain a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#     Unless required by applicable law or agreed to in writing, software
#     distributed under the License is distributed on an "AS IS" BASIS,
#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#     See the License for the specific language governing permissions and
#     limitations under the License.
#
#     Licensed under the Apache License, Version 2.0 (the "License");
#     you may not use this file except in compliance with the License.
#     You may obtain a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#     Unless required by applicable law or agreed to in writing, software
#     distributed under the License is distributed on an "AS IS" BASIS,
#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#     See the License for the specific language governing permissions and
#     limitations under the License.

from threading import Thread
from platform import system
from time import time, sleep
import asyncio

from bleak import BleakClient, BleakError

from thingsboard_gateway.connectors.connector import log
from thingsboard_gateway.tb_utility.tb_loader import TBModuleLoader

MAC_ADDRESS_FORMAT = {
    'Darwin': '-',
    'other': ':'
}
DEFAULT_CONVERTER_CLASS_NAME = 'BytesBLEUplinkConverter'

class Device(Thread):
    def __init__(self, config):
        super().__init__()
        self.loop = None
        self.stopped = False
        self.name = config['name']
        self.device_type = config.get('deviceType', 'default')
        self.timeout = config.get('timeout', 10000) / 1000
        self.show_map = config.get('showMap', False)
        self.__connector_type = config['connector_type']

        self.daemon = True

        try:
            self.mac_address = self.validate_mac_address(config['MACAddress'])
            self.client = BleakClient(self.mac_address)
        except ValueError as e:
            self.client = None
            self.stopped = True
            log.error(e)

        self.poll_period = config.get('pollPeriod', 5000) / 1000
        self.config = {
            'extension': config.get('extension', DEFAULT_CONVERTER_CLASS_NAME),
            'telemetry': config.get('telemetry', []),
            'attributes': config.get('attributes', []),
            'attributeUpdates': config.get('attributeUpdates', []),
            'serverSideRpc': config.get('serverSideRpc', [])
        }
        self.callback = config['callback']
        self.last_polled_time = 0

        self.notifying_chars = []

        self.__converter = None
        self.__load_converter()

        self.start()

    @staticmethod
    def validate_mac_address(mac_address):
        os_name = system()

        if MAC_ADDRESS_FORMAT[os_name if os_name == 'Darwin' else 'other'] not in mac_address:
            raise ValueError(f'Mac-address is invalid for {os_name} os')

        return mac_address.upper()

    def __load_converter(self):
        converter_class_name = self.config['extension']
        module = TBModuleLoader.import_module(self.__connector_type, converter_class_name)

        if module:
            log.debug('Converter %s for device %s - found!', converter_class_name, self.name)
            self.__converter = module
        else:
            log.error("Cannot find converter for %s device", self.name)
            self.stopped = True

    async def timer(self):
        await self.__process_self()
        self.last_polled_time = time()

        while True:
            if time() - self.last_polled_time >= self.poll_period:
                self.last_polled_time = time()
                await self.__process_self()
            else:
                await asyncio.sleep(.2)

    async def notify_callback(self, sender: int, data: bytearray):
        not_converted_data = {'telemetry': [], 'attributes': []}
        for section in ('telemetry', 'attributes'):
            for item in self.config[section]:
                if item.get('handle') and item['handle'] == sender:
                    not_converted_data[section].append({'data': data, **item})

                    data_for_converter = {
                        'deviceName': self.name,
                        'deviceType': self.device_type,
                        'converter': self.__converter,
                        'config': {
                            'attributes': self.config['attributes'],
                            'telemetry': self.config['telemetry']
                        },
                        'data': not_converted_data
                    }

                    self.callback(data_for_converter)

    async def notify(self, char_id):
        await self.client.start_notify(char_id, self.notify_callback)

    async def __process_self(self):
        not_converted_data = {'telemetry': [], 'attributes': []}
        for section in ('telemetry', 'attributes'):
            for item in self.config[section]:
                char_id = item['characteristicUUID']

                if item['method'] == 'read':
                    try:
                        data = await self.client.read_gatt_char(char_id)
                        not_converted_data[section].append({'data': data, **item})
                    except BleakError as e:
                        log.error(e)
                elif item['method'] == 'notify' and char_id not in self.notifying_chars:
                    try:
                        self.__set_char_handle(item, char_id)
                        self.notifying_chars.append(char_id)
                        await self.notify(char_id)
                    except BleakError:
                        log.error(e)

        if len(not_converted_data['telemetry']) > 0 or len(not_converted_data['attributes']) > 0:
            data_for_converter = {
                'deviceName': self.name,
                'deviceType': self.device_type,
                'converter': self.__converter,
                'config': {
                    'attributes': self.config['attributes'],
                    'telemetry': self.config['telemetry']
                },
                'data': not_converted_data
            }
            self.callback(data_for_converter)

    def __set_char_handle(self, item, char_id):
        for serv in self.client.services:
            for char in serv.characteristics:
                if char.uuid == char_id:
                    item['handle'] = char.handle
                    return

    async def connect_to_device(self):
        try:
            await self.client.connect(timeout=self.timeout)
        except Exception as e:
            log.error(e)

    async def run_client(self):
        while not self.stopped and not self.client.is_connected:
            await self.connect_to_device()

            sleep(.2)

        if self.client and self.client.is_connected:
            log.info('Connected to %s device', self.name)

            if self.show_map:
                await self.__show_map()

            await self.timer()

    def run(self):
        self.loop = asyncio.new_event_loop()
        self.loop.run_until_complete(self.run_client())

    async def __show_map(self, return_result=False):
        result = f'MAP FOR {self.name.upper()}'

        for service in self.client.services:
            result += f'\n| [Service] {service}'
            for char in service.characteristics:
                if "read" in char.properties:
                    try:
                        value = bytes(await self.client.read_gatt_char(char.uuid))
                        result += f"\n| \t[Characteristic] {char} ({','.join(char.properties)}), Value: {value}"
                    except Exception as e:
                        result += f"\n| \t[Characteristic] {char} ({','.join(char.properties)}), Value: {e}"

                else:
                    value = None
                    result += f"\n| \t[Characteristic] {char} ({','.join(char.properties)}), Value: {value}"

                for descriptor in char.descriptors:
                    try:
                        value = bytes(
                            await self.client.read_gatt_descriptor(descriptor.handle)
                        )
                        result += f"\n| \t\t[Descriptor] {descriptor}) | Value: {value}"
                    except Exception as e:
                        result += f"| \t\t[Descriptor] {descriptor}) | Value: {e}"

        if return_result:
            return result
        else:
            log.info(result)

    def scan_self(self, return_result):
        task = self.loop.create_task(self.__show_map(return_result))

        while not task.done():
            sleep(.2)

        return task.result()

    async def __write_char(self, char_id, data):
        try:
            await self.client.write_gatt_char(char_id, data, response=True)
            await asyncio.sleep(1.0)
            return 'Ok'
        except Exception as e:
            log.error('Can\'t write data to device')
            log.exception(e)
            return e

    def write_char(self, char_id, data):
        task = self.loop.create_task(self.__write_char(char_id, data))

        while not task.done():
            sleep(.2)

        return task.result()

    async def __read_char(self, char_id):
        try:
            return await self.client.read_gatt_char(char_id)
        except Exception as e:
            log.exception(e)

    def read_char(self, char_id):
        task = self.loop.create_task(self.__read_char(char_id))

        while not task.done():
            sleep(.2)

        return task.result().decode('UTF-8')

    def __str__(self):
        return f'{self.name}'
<commit_msg>Added reconnecting functionality for BLE Device<commit_after>#     Copyright 2022. ThingsBoard
#
#     Licensed under the Apache License, Version 2.0 (the "License");
#     you may not use this file except in compliance with the License.
#     You may obtain a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#     Unless required by applicable law or agreed to in writing, software
#     distributed under the License is distributed on an "AS IS" BASIS,
#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#     See the License for the specific language governing permissions and
#     limitations under the License.
#
#     Licensed under the Apache License, Version 2.0 (the "License");
#     you may not use this file except in compliance with the License.
#     You may obtain a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#     Unless required by applicable law or agreed to in writing, software
#     distributed under the License is distributed on an "AS IS" BASIS,
#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#     See the License for the specific language governing permissions and
#     limitations under the License.

from threading import Thread
from platform import system
from time import time, sleep
import asyncio

from bleak import BleakClient, BleakError

from thingsboard_gateway.connectors.connector import log
from thingsboard_gateway.tb_utility.tb_loader import TBModuleLoader

MAC_ADDRESS_FORMAT = {
    'Darwin': '-',
    'other': ':'
}
DEFAULT_CONVERTER_CLASS_NAME = 'BytesBLEUplinkConverter'

class Device(Thread):
    def __init__(self, config):
        super().__init__()
        self.loop = None
        self.stopped = False
        self.name = config['name']
        self.device_type = config.get('deviceType', 'default')
        self.timeout = config.get('timeout', 10000) / 1000
        self.show_map = config.get('showMap', False)
        self.__connector_type = config['connector_type']

        self.daemon = True

        try:
            self.mac_address = self.validate_mac_address(config['MACAddress'])
            self.client = BleakClient(self.mac_address)
        except ValueError as e:
            self.client = None
            self.stopped = True
            log.error(e)

        self.poll_period = config.get('pollPeriod', 5000) / 1000
        self.config = {
            'extension': config.get('extension', DEFAULT_CONVERTER_CLASS_NAME),
            'telemetry': config.get('telemetry', []),
            'attributes': config.get('attributes', []),
            'attributeUpdates': config.get('attributeUpdates', []),
            'serverSideRpc': config.get('serverSideRpc', [])
        }
        self.callback = config['callback']
        self.last_polled_time = 0

        self.notifying_chars = []

        self.__converter = None
        self.__load_converter()

        self.start()

    @staticmethod
    def validate_mac_address(mac_address):
        os_name = system()

        if MAC_ADDRESS_FORMAT[os_name if os_name == 'Darwin' else 'other'] not in mac_address:
            raise ValueError(f'Mac-address is invalid for {os_name} os')

        return mac_address.upper()

    def __load_converter(self):
        converter_class_name = self.config['extension']
        module = TBModuleLoader.import_module(self.__connector_type, converter_class_name)

        if module:
            log.debug('Converter %s for device %s - found!', converter_class_name, self.name)
            self.__converter = module
        else:
            log.error("Cannot find converter for %s device", self.name)
            self.stopped = True

    async def timer(self):
        await self.__process_self()
        self.last_polled_time = time()

        while True:
            try:
                if time() - self.last_polled_time >= self.poll_period:
                    self.last_polled_time = time()
                    await self.__process_self()
                else:
                    await asyncio.sleep(.2)
            except Exception as e:
                log.exception(e)

                try:
                    await self.client.disconnect()
                except Exception as err:
                    log.exception(err)

                while not self.stopped and not self.client.is_connected:
                    await self.connect_to_device()

                    sleep(.2)

    async def notify_callback(self, sender: int, data: bytearray):
        not_converted_data = {'telemetry': [], 'attributes': []}
        for section in ('telemetry', 'attributes'):
            for item in self.config[section]:
                if item.get('handle') and item['handle'] == sender:
                    not_converted_data[section].append({'data': data, **item})

                    data_for_converter = {
                        'deviceName': self.name,
                        'deviceType': self.device_type,
                        'converter': self.__converter,
                        'config': {
                            'attributes': self.config['attributes'],
                            'telemetry': self.config['telemetry']
                        },
                        'data': not_converted_data
                    }

                    self.callback(data_for_converter)

    async def notify(self, char_id):
        await self.client.start_notify(char_id, self.notify_callback)

    async def __process_self(self):
        not_converted_data = {'telemetry': [], 'attributes': []}
        for section in ('telemetry', 'attributes'):
            for item in self.config[section]:
                char_id = item['characteristicUUID']

                if item['method'] == 'read':
                    try:
                        data = await self.client.read_gatt_char(char_id)
                        not_converted_data[section].append({'data': data, **item})
                    except BleakError as e:
                        log.error(e)
                elif item['method'] == 'notify' and char_id not in self.notifying_chars:
                    try:
                        self.__set_char_handle(item, char_id)
                        self.notifying_chars.append(char_id)
                        await self.notify(char_id)
                    except BleakError as e:
                        log.error(e)

        if len(not_converted_data['telemetry']) > 0 or len(not_converted_data['attributes']) > 0:
            data_for_converter = {
                'deviceName': self.name,
                'deviceType': self.device_type,
                'converter': self.__converter,
                'config': {
                    'attributes': self.config['attributes'],
                    'telemetry': self.config['telemetry']
                },
                'data': not_converted_data
            }
            self.callback(data_for_converter)

    def __set_char_handle(self, item, char_id):
        for serv in self.client.services:
            for char in serv.characteristics:
                if char.uuid == char_id:
                    item['handle'] = char.handle
                    return

    async def connect_to_device(self):
        try:
            log.info('Trying to connect to %s with %s MAC address', self.name, self.mac_address)
            await self.client.connect(timeout=self.timeout)
        except Exception as e:
            log.error(e)

    async def run_client(self):
        while not self.stopped and not self.client.is_connected:
            await self.connect_to_device()

            sleep(.2)

        if self.client and self.client.is_connected:
            log.info('Connected to %s device', self.name)

            if self.show_map:
                await self.__show_map()

            await self.timer()

    def run(self):
        self.loop = asyncio.new_event_loop()
        self.loop.run_until_complete(self.run_client())

    async def __show_map(self, return_result=False):
        result = f'MAP FOR {self.name.upper()}'

        for service in self.client.services:
            result += f'\n| [Service] {service}'
            for char in service.characteristics:
                if "read" in char.properties:
                    try:
                        value = bytes(await self.client.read_gatt_char(char.uuid))
                        result += f"\n| \t[Characteristic] {char} ({','.join(char.properties)}), Value: {value}"
                    except Exception as e:
                        result += f"\n| \t[Characteristic] {char} ({','.join(char.properties)}), Value: {e}"

                else:
                    value = None
                    result += f"\n| \t[Characteristic] {char} ({','.join(char.properties)}), Value: {value}"

                for descriptor in char.descriptors:
                    try:
                        value = bytes(
                            await self.client.read_gatt_descriptor(descriptor.handle)
                        )
                        result += f"\n| \t\t[Descriptor] {descriptor}) | Value: {value}"
                    except Exception as e:
                        result += f"| \t\t[Descriptor] {descriptor}) | Value: {e}"

        if return_result:
            return result
        else:
            log.info(result)

    def scan_self(self, return_result):
        task = self.loop.create_task(self.__show_map(return_result))

        while not task.done():
            sleep(.2)

        return task.result()

    async def __write_char(self, char_id, data):
        try:
            await self.client.write_gatt_char(char_id, data, response=True)
            await asyncio.sleep(1.0)
            return 'Ok'
        except Exception as e:
            log.error('Can\'t write data to device')
            log.exception(e)
            return e

    def write_char(self, char_id, data):
        task = self.loop.create_task(self.__write_char(char_id, data))

        while not task.done():
            sleep(.2)

        return task.result()

    async def __read_char(self, char_id):
        try:
            return await self.client.read_gatt_char(char_id)
        except Exception as e:
            log.exception(e)

    def read_char(self, char_id):
        task = self.loop.create_task(self.__read_char(char_id))

        while not task.done():
            sleep(.2)

        return task.result().decode('UTF-8')

    def __str__(self):
        return f'{self.name}'
<|endoftext|>


<commit_before>from typing import List
from discord import Member

from .role_service import RoleService
from ..bunkbot import BunkBot
from ..channel.channel_service import ChannelService
from ..core.bunk_user import BunkUser
from ..core.constants import ROLE_GAMING, ROLE_SHOW_GAMING, ROLE_STREAMING, ROLE_MODERATOR, ROLE_VIP
from ..core.event_hook import EventHook
from ..core.service import Service
from ..db.database_service import DatabaseService
from ..db.database_user import DatabaseUser

"""
Service responsible for handling any
bunk user references + syncing with database
"""
class UserService(Service):
    def __init__(self, bot: BunkBot, database: DatabaseService, roles: RoleService, channels: ChannelService):
        super().__init__(bot, database)
        self.users: List[BunkUser] = []
        self.roles: RoleService = roles
        self.channels: ChannelService = channels
        self.bot.on_user_joined += self.add_new_user
        self.bot.on_user_update += self.handle_user_update
        self.bot.on_user_remove += self.handle_user_removal
        self.on_user_gaming: EventHook = EventHook()

    # when the main bot is loaded, collect
    # members from the server and initialize
    # bunkuser instances for future use
    async def load(self) -> None:
        await super().load()

        new_users: List[str] = []

        if self.server is None:
            self.channels.log_warning("Server could not be found, users cannot be loaded", "UserService")
        else:
            for member in self.server.members:
                # check the database if this user
                # has been added before collecting
                # a new instance of a bunk user
                db_user: DatabaseUser = self.database.get_user_by_member_ref(member)

                # ignore bot users
                if db_user is not None:
                    user: BunkUser = BunkUser(member, db_user)
                    self.users.append(user)
                    await self.update_current_member_state(user, user, True)

                    if db_user.was_added:
                        new_users.append(user.full_name)

                    if user.is_admin:
                        self.bot.ADMIN_USER = user

            if len(new_users) > 0:
                new_user_msg = "New users: {0}".format(", ".join(new_users))
                await self.channels.log_info(new_user_msg)

            if len(self.server.members) > 0:
                await self.roles.prune_orphaned_roles("color-")

    # when a new user has joined the server, check
    # if they already exist in the database (left and came back)
    # and add them accordingly - send a message to the server welcoming
    # the new user
    async def add_new_user(self, member: Member) -> None:
        user: DatabaseUser = self.database.get_user_by_member_ref(member)
        bunk_user: BunkUser = BunkUser(member, user)
        welcome_msg: str = "Welcome {0}, to Bunk Butter!"

        self.users.append(bunk_user)

        if not user.was_added:
            welcome_msg = "Welcome back, {0}"

        await self.channels.send_to_primary_channel(welcome_msg.format(bunk_user.mention))
        await self.channels.log_info("{0} has joined the server {1}".format(bunk_user.name, self.bot.ADMIN_USER.mention))

    # inform moderators when users are removed from the server
    async def handle_user_removal(self, member: Member) -> None:
        await self.channels.log_info(":x: {0} has left the server {1}".format(member.name, self.bot.ADMIN_USER.mention))
        await self.roles.prune_orphaned_roles("color-")

    # retrieve a user based on the member
    # identifier
    def get_by_id(self, mid: int) -> BunkUser:
        try:
            return next((u for u in self.users if u.id == mid), None)
        except Exception as e:
            self.channels.log_error(e, "get_by_id")

    # retrieve a user by a "fuzzy" search
    # on their username (basic)
    def get_by_username(self, un: str) -> None:
        try:
            return next((u for u in self.users if u.name == un), None)
        except Exception as e:
            self.channels.log_error(e, "get_by_username")

    # send out various events to other services when
    # a user has been updated in some way
    async def handle_user_update(self, old_ref: Member, member: Member, force: bool = False) -> None:
        # create a fake user to check the is_gaming status
        old_usr: BunkUser = BunkUser(old_ref, None)
        user: BunkUser = self.get_by_id(member.id)

        if old_usr is not None and user is not None:
            await self.update_current_member_state(old_usr, user, force)

        if self.roles is not None:
            await self.roles.prune_orphaned_roles("color-")

    # resuable function for updating states
    async def update_current_member_state(self, old_usr: BunkUser, user: BunkUser, force: bool = False) -> None:
        await self.check_user_gaming(old_usr, user, force)
        await self.check_user_streaming(old_usr, user, force)

    # check if the user is/was gaming and update their
    # role accordingly
    async def check_user_gaming(self, old_usr: BunkUser, user: BunkUser, force: bool = False) -> None:
        is_gaming: bool = False
        was_gaming: bool = False

        if force:
            is_gaming = user.is_gaming
        else:
            was_gaming = old_usr.is_gaming and not user.is_gaming
            is_gaming = not old_usr.is_gaming and user.is_gaming

        if is_gaming:
            await self.on_user_gaming.emit(user)

            roles_to_add = [ROLE_GAMING]
            sg_role = self.roles.get_role_by_pattern("color-", user.member.roles)

            if sg_role is None:
                # only add the "show gaming" role (green) if the
                # user does not already contain a custom color role
                roles_to_add.append(ROLE_SHOW_GAMING)

            await self.roles.add_roles_to_user(roles_to_add, user)
        elif was_gaming:
            await self.roles.rm_roles_from_user([ROLE_GAMING, ROLE_SHOW_GAMING], user)

    # check if the user is/was streaming and update their
    # role accordingly
    async def check_user_streaming(self, old_usr: BunkUser, user: BunkUser, force: bool = False) -> None:
        is_streaming: bool = False
        was_streaming: bool = False

        if force:
            is_streaming = user.is_streaming
        else:
            was_streaming = old_usr.is_streaming and not user.is_streaming
            is_streaming = not old_usr.is_streaming and user.is_streaming

        if is_streaming:
            await self.roles.add_role_to_user(ROLE_STREAMING, user)
            await self.update_elevated_user_roles(user, True)
            await self.channels.log_info(":tv: {0} is now streaming".format(user.name))
        elif was_streaming:
            await self.roles.rm_role(ROLE_STREAMING, user)
            await self.update_elevated_user_roles(user, False)

    # when coming back from something like streaming, make sure
    # aesthetic roles are reapplied
    async def update_elevated_user_roles(self, user: BunkUser, rm: bool) -> None:
        if user.is_moderator:
            if rm:
                await self.roles.rm_role(ROLE_MODERATOR, user)
            else:
                await self.roles.add_role_to_user(ROLE_MODERATOR, user)

        if user.is_vip:
            if rm:
                await self.roles.rm_role(ROLE_VIP, user)
            else:
                await self.roles.add_role_to_user(ROLE_VIP, user)
<commit_msg>chore: no admin user<commit_after>from typing import List
from discord import Member

from .role_service import RoleService
from ..bunkbot import BunkBot
from ..channel.channel_service import ChannelService
from ..core.bunk_user import BunkUser
from ..core.constants import ROLE_GAMING, ROLE_SHOW_GAMING, ROLE_STREAMING, ROLE_MODERATOR, ROLE_VIP
from ..core.event_hook import EventHook
from ..core.service import Service
from ..db.database_service import DatabaseService
from ..db.database_user import DatabaseUser

"""
Service responsible for handling any
bunk user references + syncing with database
"""
class UserService(Service):
    def __init__(self, bot: BunkBot, database: DatabaseService, roles: RoleService, channels: ChannelService):
        super().__init__(bot, database)
        self.users: List[BunkUser] = []
        self.roles: RoleService = roles
        self.channels: ChannelService = channels
        self.bot.on_user_joined += self.add_new_user
        self.bot.on_user_update += self.handle_user_update
        self.bot.on_user_remove += self.handle_user_removal
        self.on_user_gaming: EventHook = EventHook()

    # when the main bot is loaded, collect
    # members from the server and initialize
    # bunkuser instances for future use
    async def load(self) -> None:
        await super().load()

        new_users: List[str] = []

        if self.server is None:
            self.channels.log_warning("Server could not be found, users cannot be loaded", "UserService")
        else:
            for member in self.server.members:
                # check the database if this user
                # has been added before collecting
                # a new instance of a bunk user
                db_user: DatabaseUser = self.database.get_user_by_member_ref(member)

                # ignore bot users
                if db_user is not None:
                    user: BunkUser = BunkUser(member, db_user)
                    self.users.append(user)
                    await self.update_current_member_state(user, user, True)

                    if db_user.was_added:
                        new_users.append(user.full_name)

                    if user.is_admin:
                        self.bot.ADMIN_USER = user

            if len(new_users) > 0:
                new_user_msg = "New users: {0}".format(", ".join(new_users))
                await self.channels.log_info(new_user_msg)

            if len(self.server.members) > 0:
                await self.roles.prune_orphaned_roles("color-")

    # when a new user has joined the server, check
    # if they already exist in the database (left and came back)
    # and add them accordingly - send a message to the server welcoming
    # the new user
    async def add_new_user(self, member: Member) -> None:
        user: DatabaseUser = self.database.get_user_by_member_ref(member)
        bunk_user: BunkUser = BunkUser(member, user)
        welcome_msg: str = "Welcome {0}, to Bunk Butter!"

        self.users.append(bunk_user)

        if not user.was_added:
            welcome_msg = "Welcome back, {0}"

        await self.channels.send_to_primary_channel(welcome_msg.format(bunk_user.mention))
        await self.channels.log_info("{0} has joined the server".format(bunk_user.name))

    # inform moderators when users are removed from the server
    async def handle_user_removal(self, member: Member) -> None:
        await self.channels.log_info(":x: {0} has left the server".format(member.name))
        await self.roles.prune_orphaned_roles("color-")

    # retrieve a user based on the member
    # identifier
    def get_by_id(self, mid: int) -> BunkUser:
        try:
            return next((u for u in self.users if u.id == mid), None)
        except Exception as e:
            self.channels.log_error(e, "get_by_id")

    # retrieve a user by a "fuzzy" search
    # on their username (basic)
    def get_by_username(self, un: str) -> None:
        try:
            return next((u for u in self.users if u.name == un), None)
        except Exception as e:
            self.channels.log_error(e, "get_by_username")

    # send out various events to other services when
    # a user has been updated in some way
    async def handle_user_update(self, old_ref: Member, member: Member, force: bool = False) -> None:
        # create a fake user to check the is_gaming status
        old_usr: BunkUser = BunkUser(old_ref, None)
        user: BunkUser = self.get_by_id(member.id)

        if old_usr is not None and user is not None:
            await self.update_current_member_state(old_usr, user, force)

        if self.roles is not None:
            await self.roles.prune_orphaned_roles("color-")

    # resuable function for updating states
    async def update_current_member_state(self, old_usr: BunkUser, user: BunkUser, force: bool = False) -> None:
        await self.check_user_gaming(old_usr, user, force)
        await self.check_user_streaming(old_usr, user, force)

    # check if the user is/was gaming and update their
    # role accordingly
    async def check_user_gaming(self, old_usr: BunkUser, user: BunkUser, force: bool = False) -> None:
        is_gaming: bool = False
        was_gaming: bool = False

        if force:
            is_gaming = user.is_gaming
        else:
            was_gaming = old_usr.is_gaming and not user.is_gaming
            is_gaming = not old_usr.is_gaming and user.is_gaming

        if is_gaming:
            await self.on_user_gaming.emit(user)

            roles_to_add = [ROLE_GAMING]
            sg_role = self.roles.get_role_by_pattern("color-", user.member.roles)

            if sg_role is None:
                # only add the "show gaming" role (green) if the
                # user does not already contain a custom color role
                roles_to_add.append(ROLE_SHOW_GAMING)

            await self.roles.add_roles_to_user(roles_to_add, user)
        elif was_gaming:
            await self.roles.rm_roles_from_user([ROLE_GAMING, ROLE_SHOW_GAMING], user)

    # check if the user is/was streaming and update their
    # role accordingly
    async def check_user_streaming(self, old_usr: BunkUser, user: BunkUser, force: bool = False) -> None:
        is_streaming: bool = False
        was_streaming: bool = False

        if force:
            is_streaming = user.is_streaming
        else:
            was_streaming = old_usr.is_streaming and not user.is_streaming
            is_streaming = not old_usr.is_streaming and user.is_streaming

        if is_streaming:
            await self.roles.add_role_to_user(ROLE_STREAMING, user)
            await self.update_elevated_user_roles(user, True)
            await self.channels.log_info(":tv: {0} is now streaming".format(user.name))
        elif was_streaming:
            await self.roles.rm_role(ROLE_STREAMING, user)
            await self.update_elevated_user_roles(user, False)

    # when coming back from something like streaming, make sure
    # aesthetic roles are reapplied
    async def update_elevated_user_roles(self, user: BunkUser, rm: bool) -> None:
        if user.is_moderator:
            if rm:
                await self.roles.rm_role(ROLE_MODERATOR, user)
            else:
                await self.roles.add_role_to_user(ROLE_MODERATOR, user)

        if user.is_vip:
            if rm:
                await self.roles.rm_role(ROLE_VIP, user)
            else:
                await self.roles.add_role_to_user(ROLE_VIP, user)
<|endoftext|>


<commit_before># encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models

class Migration(SchemaMigration):

    def forwards(self, orm):

        # Adding model 'Message'
        db.create_table('threaded_messages_message', (
            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
            ('body', self.gf('django.db.models.fields.TextField')()),
            ('sender', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='sent_messages', null=True, to=orm['auth.User'])),
            ('parent_msg', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='next_messages', null=True, to=orm['threaded_messages.Message'])),
            ('sent_at', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
        ))
        db.send_create_signal('threaded_messages', ['Message'])

        # Adding model 'Thread'
        db.create_table('threaded_messages_thread', (
            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
            ('subject', self.gf('django.db.models.fields.CharField')(max_length=120)),
            ('latest_msg', self.gf('django.db.models.fields.related.ForeignKey')(related_name='thread_latest', to=orm['threaded_messages.Message'])),
        ))
        db.send_create_signal('threaded_messages', ['Thread'])

        # Adding M2M table for field all_msgs on 'Thread'
        db.create_table('threaded_messages_thread_all_msgs', (
            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
            ('thread', models.ForeignKey(orm['threaded_messages.thread'], null=False)),
            ('message', models.ForeignKey(orm['threaded_messages.message'], null=False))
        ))
        db.create_unique('threaded_messages_thread_all_msgs', ['thread_id', 'message_id'])

        # Adding model 'Participant'
        db.create_table('threaded_messages_participant', (
            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
            ('thread', self.gf('django.db.models.fields.related.ForeignKey')(related_name='participants', to=orm['threaded_messages.Thread'])),
            ('user', self.gf('django.db.models.fields.related.ForeignKey')(related_name='threads', to=orm['auth.User'])),
            ('read_at', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),
            ('replied_at', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),
            ('deleted_at', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),
        ))
        db.send_create_signal('threaded_messages', ['Participant'])

    def backwards(self, orm):

        # Deleting model 'Message'
        db.delete_table('threaded_messages_message')

        # Deleting model 'Thread'
        db.delete_table('threaded_messages_thread')

        # Removing M2M table for field all_msgs on 'Thread'
        db.delete_table('threaded_messages_thread_all_msgs')

        # Deleting model 'Participant'
        db.delete_table('threaded_messages_participant')

    models = {
        'auth.group': {
            'Meta': {'object_name': 'Group'},
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
        },
        'auth.permission': {
            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
        },
        'auth.user': {
            'Meta': {'object_name': 'User'},
            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
        },
        'contenttypes.contenttype': {
            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
        },
        'threaded_messages.message': {
            'Meta': {'ordering': "['-sent_at']", 'object_name': 'Message'},
            'body': ('django.db.models.fields.TextField', [], {}),
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
            'parent_msg': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'next_messages'", 'null': 'True', 'to': "orm['threaded_messages.Message']"}),
            'sender': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'sent_messages'", 'null': 'True', 'to': "orm['auth.User']"}),
            'sent_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
        },
        'threaded_messages.participant': {
            'Meta': {'ordering': "['thread']", 'object_name': 'Participant'},
            'deleted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
            'read_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
            'replied_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
            'thread': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'participants'", 'to': "orm['threaded_messages.Thread']"}),
            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'threads'", 'to': "orm['auth.User']"})
        },
        'threaded_messages.thread': {
            'Meta': {'ordering': "['latest_msg']", 'object_name': 'Thread'},
            'all_msgs': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'thread'", 'symmetrical': 'False', 'to': "orm['threaded_messages.Message']"}),
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
            'latest_msg': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'thread_latest'", 'to': "orm['threaded_messages.Message']"}),
            'subject': ('django.db.models.fields.CharField', [], {'max_length': '120'})
        }
    }

    complete_apps = ['threaded_messages']
<commit_msg>add 0001_initial.py<commit_after># -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations
from django.conf import settings

class Migration(migrations.Migration):

    dependencies = [
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
    ]

    operations = [
        migrations.CreateModel(
            name='Message',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                ('body', models.TextField(verbose_name='body')),
                ('sent_at', models.DateTimeField(auto_now_add=True, verbose_name='sent at', db_index=True)),
                ('parent_msg', models.ForeignKey(related_name='next_messages', verbose_name='parent message', blank=True, to='threaded_messages.Message', null=True)),
                ('sender', models.ForeignKey(related_name='sent_messages', verbose_name='sender', blank=True, to=settings.AUTH_USER_MODEL, null=True)),
            ],
            options={
                'ordering': ['-sent_at'],
                'verbose_name': 'Message',
                'verbose_name_plural': 'Messages',
            },
            bases=(models.Model,),
        ),
        migrations.CreateModel(
            name='Participant',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                ('read_at', models.DateTimeField(db_index=True, null=True, verbose_name='read at', blank=True)),
                ('replied_at', models.DateTimeField(db_index=True, null=True, verbose_name='replied at', blank=True)),
                ('deleted_at', models.DateTimeField(db_index=True, null=True, verbose_name='deleted at', blank=True)),
            ],
            options={
                'ordering': ['thread'],
                'verbose_name': 'participant',
                'verbose_name_plural': 'participants',
            },
            bases=(models.Model,),
        ),
        migrations.CreateModel(
            name='Thread',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                ('subject', models.CharField(max_length=120, verbose_name='Subject')),
                ('replied', models.BooleanField(default=False, editable=False)),
                ('all_msgs', models.ManyToManyField(related_name='thread', verbose_name='Messages', to='threaded_messages.Message')),
                ('creator', models.ForeignKey(related_name='created_threads', verbose_name='creator', to=settings.AUTH_USER_MODEL)),
                ('latest_msg', models.ForeignKey(related_name='thread_latest', verbose_name='Latest message', to='threaded_messages.Message')),
            ],
            options={
                'ordering': ['latest_msg'],
                'verbose_name': 'Thread',
                'verbose_name_plural': 'Threads',
            },
            bases=(models.Model,),
        ),
        migrations.AddField(
            model_name='participant',
            name='thread',
            field=models.ForeignKey(related_name='participants', verbose_name='message thread', to='threaded_messages.Thread'),
            preserve_default=True,
        ),
        migrations.AddField(
            model_name='participant',
            name='user',
            field=models.ForeignKey(related_name='threads', verbose_name='participant users', to=settings.AUTH_USER_MODEL),
            preserve_default=True,
        ),
    ]
<|endoftext|>