text: import asyncio
  import base64
  import datetime
  import hashlib
  import json
  import os
  import smopy
  import traceback
  from json import JSONDecodeError
  from uuid import uuid4
  
  import bcrypt
  import peewee
  from PIL import Image, ImageDraw
  from peewee import (
      Model,
      CharField,
      TextField,
      IntegerField,
      ForeignKeyField,
      DateTimeField,
      BooleanField,
  )
  from playhouse.sqlite_ext import (
      SqliteExtDatabase,
      FTSModel)
  from pysyncobj import SyncObj, replicated
  from asgiref.sync import sync_to_async
  
  from . import PATH_DATABASE
  
  loop = asyncio.get_event_loop()
  
  pragmas = [
      ('journal_mode', 'wal'),
      ('cache_size', -1024 * 32)]
  
  db = SqliteExtDatabase(PATH_DATABASE, pragmas=pragmas)
  
  
  class BaseModel(Model):
      timestamp = DateTimeField(default=datetime.datetime.utcnow, index=True)
  
      class Meta:
          database = db
  
  
  class User(BaseModel):
      username = CharField(index=True, unique=True)
      password = CharField(index=False)
      note = TextField(index=True, default='')
  
  
  class Preference(BaseModel):
      key = CharField(index=True, unique=True)
      value = CharField(index=False)
  
  
  class Frame(BaseModel):
      # Zentropi fields:
      uuid = CharField(index=True, unique=True)
      kind = IntegerField(index=True)
      name = TextField(index=True)
      data = TextField(default='')
      meta = TextField(default='')
      # piebus fields:
      publish = BooleanField(default=False, index=True)
      render = CharField(index=True, default='default')
      source = CharField(default='')
      tags = TextField(index=True, default='')
  
      @property
      def jdata(self):
          try:
              if self.data:
                  return dict(json.loads(self.data) or {})
          except JSONDecodeError:
              print('cannot decode data', self.data)
              pass
          return {}
  
      @property
      def jmeta(self):
          try:
              if self.meta:
                  return dict(json.loads(self.meta) or {})
          except JSONDecodeError:
              print('cannot decode meta', self.meta)
              pass
          return {}
  
      async def fetch_map_async(self):
          tsurl = """http://c.tile.stamen.com/watercolor/${z}/${x}/${y}.jpg"""
          data = self.jdata
          if 'location' in data:
              lat = data['location'].get('latitude')
              lon = data['location'].get('longitude')
              map = await sync_to_async(smopy.Map)((lat - 1, lat + 1., lon - 1, lon + 1), z=4)
              await sync_to_async(map.save_png)(f'map_{self.uuid}.png', tileserver=tsurl)
          else:
              raise KeyError(f'No location data found in frame: {self}')
  
      def fetch_map(self):
          data = self.jdata
          if 'location' in data:
              lat = data['location'].get('latitude')
              lon = data['location'].get('longitude')
              map = smopy.Map((lat - 0.006, lon - 0.038, lat + 0.006, lon + 0.038), z=12,
                              tileserver="http://tile.basemaps.cartocdn.com/light_all/{z}/{x}/{y}@2x.png",
                              tilesize=512, maxtiles=16)
              x, y = map.to_pixels(lat, lon)
              x = int(x)
              y = int(y)
              fname = f'map_{self.uuid}.png'
              map.save_png(f'content/{fname}')
              img = Image.open(open(f'content/{fname}', 'rb'))
              draw = ImageDraw.Draw(img)
              draw.ellipse([(x-10, y-10), (x+10, y+10)], fill=128,  width=10)
              del draw
              ffname = f'content/loc_{fname}'
              img.save(ffname, "PNG")
              data.update({'media_url': ffname})
              self.data = json.dumps(data)
              self.save()
              return f'loc_{fname}'
          else:
              raise KeyError(f'No location data found in frame: {self}')
  
  
  class FTSEntry(FTSModel):
      content = TextField()
  
      class Meta:
          database = db
  
      @classmethod
      def index_frame(cls, frame: Frame):
          try:
              existing = cls.get_or_none(docid=frame.id)
              if frame.name == 'telegram-message':
                  content = '\n'.join([frame.jdata.get('text', ''), frame.jdata.get('caption', ''), frame.tags])
              else:
                  content = '\n'.join([frame.name, str(frame.data), frame.tags])
              if not content.strip():
                  return
              if existing:
                  existing.update(content=content)
              else:
                  cls.create(docid=frame.id, content=content)
          except peewee.OperationalError:
              cls.create_table()
              cls.index_frame(frame)
  
  
  class Kind(object):
      command = 0
      event = 1
      message = 2
      request = 3
      response = 4
      state = 5
      stream = 6
  
  
  def ensure_db():
      if os.path.exists(PATH_DATABASE):
          return False
      User.create_table()
      Preference.create_table()
      Frame.create_table()
      return True
  
  
  class PiebusAPI(SyncObj):
      def __init__(self, *args, **kwargs):
          super().__init__(*args, **kwargs)
  
      @replicated
      def _register(self, username, password):
          password = base64.b64encode(hashlib.sha256(password.encode('utf-8')).digest())
          hashed = bcrypt.hashpw(password, bcrypt.gensalt())
          try:
              User.create(username=username, password=hashed)
              return True
          except:
              traceback.print_exc()
              return False
  
      async def register(self, username, password):
          return await sync_to_async(self._register)(username, password, sync=True)
  
      @replicated
      def _login(self, username, password):
          user = User.get_or_none(username=username)
          if not user:
              return False
          password = base64.b64encode(hashlib.sha256(password.encode('utf-8')).digest())
          hashed = user.password.encode('utf-8')
          if bcrypt.checkpw(password, hashed):
              return True
          return False
  
      async def login(self, username, password):
          return await sync_to_async(self._login)(username, password, sync=True)
  
      @replicated
      def _logout(self, username):
          return True
  
      async def logout(self, username):
          return await sync_to_async(self._logout)(username, sync=True)
  
      def _get_preference(self, key, default=None):
          pref = Preference.get_or_none(key=key)
          if pref is None:
              return default
          return pref.value if pref.value is not None else default
  
      @replicated
      def _set_preference(self, key, value):
          pref = Preference.get_or_none(key=key)
          if pref is not None:
              q = Preference.update(value=value).where(Preference.key == key)
              q.execute()
          else:
              Preference.create(key=key, value=value)
          return value
  
      async def preference(self, key, value=None):
          if value is not None:
              await sync_to_async(self._set_preference)(key, value, sync=True)
          return self._get_preference(key)
  
      async def enable_register(self, value=None):
          if value is not None:
              await sync_to_async(self._set_preference)('enable_register', 1 if value else 0, sync=True)
          return bool(int(self._get_preference('enable_register', 0)))
  
      @replicated
      def _create_frame(self, kind, name, data, meta, publish, render, source, tags):
          frame = Frame.create(
              uuid=uuid4().hex,
              kind=int(kind) if kind != '' else 1,
              name=name,
              data=json.dumps(data) or '',
              meta=json.dumps(meta) or '',
              publish=publish or False,
              render=render or 'default',
              source=source or '',
              tags=tags or '',
          )
          return frame
  
      async def create_frame(self, kind, name, data=None, meta=None, publish=False, render='', tags=''):
          source = meta.get('source', '') if meta else ''
          frame = await sync_to_async(self._create_frame)(kind=kind, name=name,
                                                          data=data, meta=meta,
                                                          publish=publish, render=render,
                                                          source=source, tags=tags,
                                                          sync=True)
          await sync_to_async(FTSEntry.index_frame)(frame)
          return frame
  
      async def list_frames(self, limit=10):
          frames = Frame.select().order_by(Frame.timestamp.desc()).limit(limit)
          return frames
  
      @replicated
      def _index_frames(self):
          frames = Frame.select().order_by(Frame.timestamp.desc())
          for frame in frames:
              FTSEntry.index_frame(frame)
  
      async def index_frames(self):
          await sync_to_async(self._index_frames)(sync=True)
          return True
  
      async def search_frames(self, query):
          frames = Frame \
              .select() \
              .join(FTSEntry, on=(Frame.id == FTSEntry.docid)) \
              .where(FTSEntry.match(query)).order_by(Frame.timestamp.desc())
          return frames
  
      async def search_public_frames(self, query):
          frames = Frame \
              .select() \
              .join(FTSEntry, on=(Frame.id == FTSEntry.docid)) \
              .where((FTSEntry.match(query)) & (Frame.publish == True)).order_by(Frame.timestamp.desc())
          return frames
  
      async def list_public_frames(self, limit=10):
          frames = Frame.select() \
              .where(Frame.publish == True) \
              .order_by(Frame.timestamp.desc()) \
              .limit(limit)
          return frames
  
      async def frame_from_uuid(self, uuid):
          frame = Frame.get(Frame.uuid == uuid)
          return frame
  
      async def publish(self, uuid, status):
          frame = Frame.get(Frame.uuid == uuid)
          frame.publish = status
          await sync_to_async(frame.save)()
          return frame
  