예제 #1
0
    def test_get_data(self):
        def get_data_now(listener):
            for i in range(3):
                data = listener.get_data()
                if len(data) > 0: return data
                else: sleep(.1)

        L = Listener(self.host, self.port)
        self.assertTrue(L.start())
        sleep(1)
        for id_ in range(5):
            for balance in [None, 1, 2, 3]:
                s = Sender(id_, self.host, self.port)
                text = s.send(balance=balance)
                listened = get_data_now(L)[0]
                if balance:
                    self.assertEqual(
                        text,
                        "{id}%%{data}%%{balance}".format(id=listened.id,
                                                         data=listened.data,
                                                         balance=balance))
                else:
                    self.assertEqual(
                        text, "{id}%%{data}".format(id=listened.id,
                                                    data=listened.data))
                self.assertIsInstance(listened.timestamp, datetime.datetime)
                sleep(.1)
        self.assertTrue(L.is_active())
        self.assertTrue(L.stop())
예제 #2
0
class DataEngine(object):
    def __init__(self, host, port, db_file='db.sqlite', db_autoclean_days=60):
        self.datetime_format = "%Y-%m-%d %H:%M:%S.%f"  # it's not familiar for Russia, but sorting would work correctly
        self._db_file = db_file
        self._db_init()
        self.db_autoclean_days = db_autoclean_days
        self.L = Listener(host, int(port))
        self._t = None
        self._sync_loop_enabled = True

    def _db_init(self):
        if not os.path.isfile(self._db_file):
            conn, cur = self._connect_db()
            cur.execute(
                "CREATE TABLE last_messages "
                "(dev_id TEXT NOT NULL PRIMARY KEY, data TEXT, balance REAL, received_at TEXT)"
            )
            cur.execute(
                "CREATE TABLE messages (dev_id TEXT NOT NULL, data TEXT, balance REAL, received_at TEXT)"
            )
            cur.execute(
                "CREATE TABLE users "
                "(username TEXT NOT NULL PRIMARY KEY, password_secret NOT NULL, "
                "last_login TEXT, session_id TEXT)")
            cur.execute("INSERT INTO users(username, password_secret) "
                        "VALUES ('admin', '25d55ad283aa400af464c76d713c07ad')")
            cur.execute(
                "CREATE TABLE mixed (key TEXT NOT NULL PRIMARY KEY, value TEXT)"
            )
            cur.execute(
                "INSERT INTO mixed(key, value) VALUES ('last_clean', ?)",
                (datetime.now().strftime(self.datetime_format), ))
            conn.commit()
        return True

    def _connect_db(self):
        conn = sqlite3.connect(self._db_file)
        cur = conn.cursor()
        return [conn, cur]

    def _sync_messages(self):
        conn, cur = self._connect_db()
        if not self.L.is_active(): self.L.start()
        data = self.L.get_data()
        for message in data:
            try:
                cur.execute(
                    """INSERT OR REPLACE INTO last_messages(dev_id, data, balance, received_at) 
                               VALUES (?, ?, ?, ?)""",
                    (message.id, message.data, message.balance,
                     message.timestamp.strftime(self.datetime_format)))
                cur.execute(
                    "INSERT INTO messages(dev_id, data, balance, received_at) VALUES (?, ?, ?, ?)",
                    (message.id, message.data, message.balance,
                     message.timestamp.strftime(self.datetime_format)))
            except Exception as e:
                print(e)
                return False
        conn.commit()
        return True

    def _sync_loop(self, period):
        counter = period
        while self._sync_loop_enabled:
            if counter < period:
                counter += 1
                sleep(1)
            else:
                counter = 0
                self._sync_messages()

    def start_sync_loop(self, period=1):
        self._sync_loop_enabled = True
        self._t = threading.Thread(target=self._sync_loop,
                                   args=(int(period), ))
        self._t.start()
        return self._t.is_alive()

    def stop_sync_loop(self):
        self.L.stop()
        self._sync_loop_enabled = False
        while self._t.is_alive():
            sleep(.1)
        return True

    def validate_user(self, username: str, password, new_session=True):
        secret = md5(str(password).encode('utf-8')).hexdigest()
        conn, cur = self._connect_db()
        session_id = id_generator(32)
        if new_session:
            cur.execute(
                "UPDATE users SET session_id = ? "
                "WHERE username = ? AND password_secret = ?",
                (session_id, username, secret))
            conn.commit()
            return session_id if cur.rowcount == 1 else None
        else:
            cur.execute(
                "SELECT session_id FROM users WHERE username = ? AND password_secret = ?",
                (username, secret))
            return len(cur.fetchall()) > 0

    def validate_session(self, session_id):
        conn, cur = self._connect_db()
        try:
            cur.execute("SELECT username FROM users WHERE session_id = ?",
                        (session_id, ))
        except IndexError:
            return None
        try:
            return cur.fetchone()[0]
        except TypeError:
            return None

    def get_user_list(self):
        conn, cur = self._connect_db()
        cur.execute("SELECT username FROM users")
        rows = cur.fetchall()
        return [row[0] for row in rows]

    def change_password(self, username, old_password, new_password):
        secret = md5(str(old_password).encode('utf-8')).hexdigest()
        new_secret = md5(str(new_password).encode('utf-8')).hexdigest()
        conn, cur = self._connect_db()
        cur.execute(
            "UPDATE users SET password_secret = ? WHERE username = ? AND password_secret = ?",
            (new_secret, username, secret))
        conn.commit()
        return cur.rowcount == 1

    def add_user(self, username):
        conn, cur = self._connect_db()
        cur.execute(
            "INSERT INTO users(username, password_secret) "
            "VALUES (?, '25d55ad283aa400af464c76d713c07ad')", (username, ))
        conn.commit()
        return cur.rowcount == 1

    def delete_user(self, username, password, username_delete):
        if self.validate_user(username, password, new_session=False):
            conn, cur = self._connect_db()
            cur.execute("DELETE FROM users WHERE username = ?",
                        (username_delete, ))
            conn.commit()
            return True
        else:
            print('user not allowed to delete users')
            return False

    def sync_loop_is_alive(self):
        if self._t:
            return self._t.is_alive()

    def get_last_messages(self,
                          sort_by='received_at',
                          reverse=False,
                          page=0,
                          page_size=100):
        self._delete_old_messages()
        conn, cur = self._connect_db()
        try:
            cur.execute("SELECT COUNT(*) FROM last_messages")
            count = cur.fetchone()[0]
            pages = int(count / page_size) + int(
                count % page_size != 0)  # get num of pages to show
            args = (page_size, page_size * (page - 1))
            cur.execute(
                """
            SELECT dev_id, data, balance, received_at 
            FROM last_messages 
            ORDER BY {0} {1} 
            LIMIT ? 
            OFFSET ?;
                        """.format(sort_by, 'DESC' if reverse else 'ASC'),
                args)
        except Exception as e:
            print('DB seems to be corrupted, error is: ', str(e))
            pages = 0
        data = []
        rows = cur.fetchall()
        for row in rows:
            data.append(tuple(row))
        return data, pages

    def get_messages_by_id(self,
                           id_,
                           sort_by='received_at',
                           reverse=False,
                           page=0,
                           page_size=100):
        if sort_by not in ['dev_id', 'data', 'balance', 'received_at']:
            sort_by = 'received_at'
        conn, cur = self._connect_db()
        try:
            cur.execute("SELECT COUNT(*) FROM messages WHERE dev_id = ?",
                        (str(id_), ))
            count = cur.fetchone()[0]
            pages = int(count / page_size) + int(
                count % page_size != 0)  # get num of pages to show
            cur.execute(
                """
                        SELECT dev_id, data, balance, received_at 
                        FROM messages 
                        WHERE dev_id = ?
                        ORDER BY {key} {rev}
                        LIMIT ?
                        OFFSET ?; 
                        """.format(key=sort_by,
                                   rev='DESC' if reverse else 'ASC'),
                (str(id_), page_size, page_size * (page - 1)))
            rows = cur.fetchall()
            data = [tuple(row) for row in rows]
            return data, pages
        except Exception as e:
            print('DB seems to be corrupted, error is: ', str(e))
            return [], 0

    def delete_messages(self,
                        id_=None,
                        date_before: datetime = datetime.now() +
                        timedelta(days=1)):
        """
        delete all messages or only filtered
        :param id_: messages from this id would be deleted
        :param date_before: all messages received before this date would be deleted
        :return:
        """
        conn, cur = self._connect_db()
        try:
            cur.execute("SELECT dev_id, data, received_at FROM messages")
        except Exception as e:
            print('DB seems to be corrupted, error is: ', str(e))
        rows = cur.fetchall()
        counter_deleted = 0
        for row in rows:
            if datetime.strptime(row[2], self.datetime_format) < date_before:
                if id_ != 0 and not id_:
                    cur.execute("DELETE FROM messages WHERE received_at = ?",
                                (row[2], ))
                    cur.execute(
                        "DELETE FROM last_messages WHERE received_at = ?",
                        (row[2], ))
                    counter_deleted += 1
                else:
                    if str(row[0]) == str(id_):
                        cur.execute(
                            "DELETE FROM messages WHERE received_at = ? AND dev_id = ?",
                            (row[2], str(id_)))
                        cur.execute(
                            "DELETE FROM last_messages WHERE dev_id = ?",
                            (str(id_), ))
                        counter_deleted += 1
        conn.commit()
        return counter_deleted

    def _delete_old_messages(self):
        def delete(self):
            conn, cur = self._connect_db()
            cur.execute("SELECT value FROM mixed WHERE key = 'last_clean'")
            try:
                last_clean_date = cur.fetchone()[0]
                last_clean_date = datetime.strptime(last_clean_date,
                                                    self.datetime_format)
                need_cleaning = (datetime.now() -
                                 last_clean_date).days > self.db_autoclean_days
            except TypeError:
                need_cleaning = True
            if need_cleaning:
                date_before = datetime.now() - timedelta(
                    days=self.db_autoclean_days)
                self.delete_messages(date_before=date_before)
                cur.execute(
                    "INSERT OR REPLACE INTO mixed (key, value) VALUES ('last_clean', ?)",
                    (datetime.now().strftime(self.datetime_format), ))
            conn.commit()

        t = threading.Thread(target=delete, args=(self, ))
        t.daemon = True
        t.start()
예제 #3
0
 def test_start_stop(self):
     L = Listener(self.host, self.port)
     self.assertTrue(L.start())
     self.assertTrue(L.is_active())
     self.assertTrue(L.stop())