AG

Angelo Gladding
lahacker.net

dlv5vbq7lzlthol5 4b942a3185b37d00

angelo@lahacker.net

South Pasadena, California, United States currently feels like 56°F

Home CodecanopyCommits

many things

by Angelo Gladding 09CEF88F29CC1A44

Changed Files

--- a/canopy/__init__.py

+++ b/canopy/__init__.py

     tx.origin = f"{protocol}://{hostname}"     from canopy.__web__.util import view     tx.view = view+    return tx   def setup_servers(root) -> None:
             "-canopy-path": "me", "-canopy-uuid": identity}     if domain:         data["alt-svc"] = domain-    tx.kv["owner"] = quick_draft("contact", data, cache=False,+    tx.kv["owner"] = quick_draft("identity", data, cache=False,                                  publish="Post personal public profile.")+    tx.kv["app"] = web.nbrandom(32)     return identity  

--- a/canopy/__web__/__init__.py

+++ b/canopy/__web__/__init__.py

  import collections import cProfile+import email import io import json import pathlib import pickle import pstats+import re import time +import geopy.distance from lxml.html import fromstring # import mf import pendulum from PIL import Image, ImageDraw, ImageFont-import re import requests import web from web import tx
 from ..content import load_json, get_type, dump_json from ..content.read import get_resources from ..content.write import quick_draft-from ..util import get_onion, if_owner, enqueue+from ..util import get_onion, get_path, if_owner, enqueue  # , clean_html from .. import webmention from .util import app, view 
 from . import content from . import hosting from . import security-from . import settings+from . import system   __all__ = ["chat", "content", "hosting", "security",
            "Token", "Micropub", "Mentions", "Actions"]  -app.mount(settings.app)+app.mount(system.app) app.view = view subscription_lease = 60 * 60 * 24 * 90 
     tx.user.is_owner = tx.user.session.get("uri") == tx.host.name     if not tx.host.name.endswith(".onion"):         web.header("Alt-Svc", f'h2="{get_onion()}"; ma=60')-    if "app-mode" in tx.user.session:  # XXX-        tx.kv["app-state"] = tx.request.uri.path+    # XXX if "app-mode" in tx.user.session:  # XXX+    # XXX     tx.kv["app-state"] = tx.request.uri.path     yield  
         return view.icons.spritemap(icons)  -@app.route(r"app")-class App:--    def GET(self):-        if tx.user.is_owner:-            return view.app.install()-        else:-            # tx.user.session["uri"] = tx.host.name-            # tx.user.session["id"] = 1-            # tx.user.is_owner = True-            # tx.user.session["app-mode"] = True--            # raise web.SeeOther(f"/{tx.kv['app-state']}")-            tx.response.naked = True-            return view.app.index()+# XXX @app.route(r"app")+# XXX class App:+# XXX+# XXX     def GET(self):+# XXX         key = web.form("key").key+# XXX         if key != tx.kv["app"]:+# XXX             raise web.BadRequest("cannot authenticate")+# XXX         if tx.user.is_owner:+# XXX             return view.app.install()+# XXX         # XXX tx.user.session["uri"] = tx.host.name+# XXX         # XXX tx.user.session["id"] = 1+# XXX         # XXX tx.user.is_owner = True+# XXX         # XXX tx.user.session["app-mode"] = True+# XXX         # XXX raise web.SeeOther(f"/{tx.kv['app-state']}")+# XXX         tx.response.naked = True+# XXX         return view.app.index()   @app.route(r"debug")
         return view.robots()  +@app.route(r"tracker")+class Tracker:++    def POST(self):+        form = web.form()+        home = (tx.owner["geo"]["latitude"], tx.owner["geo"]["longitude"])+        home = (34.1023321, -118.1733567)+        current_location = (form["latitude"], form["longitude"])+        distance_from_home = geopy.distance.distance(home, current_location)+        details = {"distanceFromHome": distance_from_home.feet}+        tx.response.naked = True+        web.header("Content-Type", "application/json")+        return json.dumps(details)++ @app.route(r"search") class Search: 
             pass         else:             if url.suffix:-                enqueue(cache.put, url, parse=True)-                raise web.SeeOther(f"/cache/{url.minimized}")+                enqueue(cache.update, url.minimized, parse=True)+                raise web.SeeOther(f"/system/cache/{url.minimized}")          ddg_results = []+        tpb_results = []         if tx.user.is_owner:             self.handle_bang(query) +            # DuckDuckGo             ddg = requests.get(f"https://duckduckgo.com/html?q={query}")-            # ddg_results.append(ddg.text)             ddg_doc = fromstring(ddg.text)             for result in ddg_doc.cssselect("#links .result"):                 title = result.cssselect("h2.result__title a")[0]-                url = web.uri.parse("https://duckduckgo.com"-                                    f"{title.attrib['href']}")["uddg"][0]+                try:+                    url = web.uri.parse("https://duckduckgo.com"+                                        f"{title.attrib['href']}")["uddg"][0]+                except IndexError:+                    continue                 if str(url).startswith("https://duckduckgo.com/y.js"):                     continue                 desc = result.cssselect(".result__snippet")[0].text_content()                 ddg_results.append((url, title.text_content(), desc)) +            # The Pirate Bay+            tpb = requests.get(f"https://thepiratebay.org/search"+                               f"/{query}/0/99/0")+            tpb_doc = fromstring(tpb.text)+            for result in tpb_doc.cssselect("#searchResult tr"):+                title = result.cssselect("a.detLink")+                if not title:+                    continue+                magnet = result.cssselect("div.detName + a")[0].attrib["href"]+                seeders, leechers = result.cssselect("td")[2:]+                tpb_results.append((magnet, title[0].text_content(),+                                    seeders.text_content(),+                                    leechers.text_content()))+         types = set()         try:             resources = tx.db.select("finals", what="rank, data", order="rank",
                 results.append(view.entry_template(entry, data,                                                    t(entry, data))) -        return view.search.results(query, ddg_results, results, types)+        return view.search.results(query, ddg_results, tpb_results, results,+                                   types)      def calculate(self, query):-        calculation = eval(query)+        try:+            calculation = eval(query, {"__builtins__": {}})+        except Exception as err:+            return str(err)         return view.search.functions.calculator(query, calculation)      def handle_bang(self, query):
                              timestamp=entry["published"])  -@app.route(r"cache")-class Cache:+@app.route(r"viewer")+class Viewer:      @if_owner     def GET(self):-        return view.settings.cache.index(cache.get_hosts())+        return view.viewer.index(tx.kv["feeds"])  -@app.route(r"cache/{hostslug}(/{pathslug})?.png")-class CachedResourceScreen:+def process_message(message):+    message = dict(message)+    message["to"] = get_identities(message["rowid"], "to")+    message["from"] = get_identities(message["rowid"], "from")+    message["cc"] = get_identities(message["rowid"], "cc")+    return message ++def get_identities(message_id, block_type):+    identities = []+    for identity in tx.db.select("email_identity_blocks AS ib",+                                 join="""email_identities AS i ON+                                         ib.email_identity_id = i.rowid""",+                                 where="""email_message_id = ? AND+                                          block_type = ?""",+                                 vals=[message_id, block_type]):+        identities.append((identity["name"], identity["address"]))+    return identities+++@app.route(r"viewer/email")+class EmailInbox:++    @if_owner     def GET(self):-        host = cache.get_hosts()[self.hostslug]-        path = getattr(self, "pathslug", "")-        filepath = f"{host.name}/{path}".rstrip("/")-        web.header("Content-Type", "image/png")-        web.header("X-Accel-Redirect", f"/X/cache/{filepath}/.screen.png")+        messages = []+        for message in tx.db.select("email_messages", what="rowid, *",+                                    order="sent DESC"):+            messages.append(process_message(message))+        return view.viewer.email.index(messages)  -@app.route(r"cache/{hostslug}(/{pathslug})?")-class CachedResource:+@app.route(r"viewer/email/{email_digest}")+class EmailMessage:      @if_owner     def GET(self):-        path = getattr(self, "pathslug", "")         try:-            host = cache.get_hosts()[self.hostslug]-        except KeyError:-            return "wait and reload"-        else:-            try:-                metadata, _ = cache.get(host / path)-            except FileNotFoundError:-                metadata = None-        return view.settings.cache.resource(host, path, metadata)+            message = tx.db.select("email_messages", what="rowid, *",+                                   where="digest = ?",+                                   vals=[self.email_digest])[0]+        except IndexError:+            raise web.NotFound("message not found")+        with (get_path() / "email" / self.email_digest).open("rb") as fp:+            raw_message = email.message_from_bytes(fp.read())+        return view.viewer.email.message(process_message(message), raw_message) -        # try:-        #     _data = mf.parse(url=url)-        # except (requests.exceptions.SSLError,-        #         requests.exceptions.ConnectionError):-        #     url.scheme = "http"-        #     url.is_secure = False-        #     _data = mf.parse(url=url)-        # card = mf.util.representative_hcard(_data, url)-        # feed = mf.util.interpret_feed(_data, str(url))-        # entry = mf.util.interpret(_data, str(url))-        # return view.url_preview(url, _data, card, feed, entry)++@app.route(r"viewer/email/{email_digest}/payload")+class EmailMessagePayload:++    @if_owner+    def GET(self):+        with (get_path() / "email" / self.email_digest).open("rb") as fp:+            raw_message = email.message_from_bytes(fp.read())+        payload = raw_message.get_payload(decode=True)+        content_type = raw_message["content-type"]+        # TODO keep private & maintain readability -- emailprivacytester.com+        # if "text/html" in content_type:+        #     payload = clean_html(payload)+        tx.response.naked = True+        tx.response.headers.content_type = content_type+        return payload  -@app.route(r"reader")-class Reader:+@app.route(r"viewer/email/{email_digest}/{email_payload}")+class EmailMessageMultiPayload:      @if_owner     def GET(self):-        return view.reader.index(tx.kv["feeds"])+        with (get_path() / "email" / self.email_digest).open("rb") as fp:+            raw_message = email.message_from_bytes(fp.read())+        payload = raw_message.get_payload(int(self.email_payload))+        tx.response.naked = True+        tx.response.headers.content_type = payload.get_content_type()+        return payload.get_payload(decode=True)  -@app.route(r"reader/feeds/{hostslug}(/{pathslug})?")+@app.route(r"viewer/feeds/{hostslug}(/{pathslug})?") class Feed:      def __init__(self, *args, **kwargs):
      @if_owner     def GET(self):-        return view.reader.feed("Unknown Title", self.url,+        return view.viewer.feed("Unknown Title", self.url,                                 tx.kv["feeds", self.url])  -@app.route(r"reader/channels/{tag}")+@app.route(r"viewer/channels/{tag}") class Channel:      @if_owner     def GET(self):         # entries = template_entries(get_entries(tags=self.tags.split("+")))         # reversed(list(entries))-        return view.reader.channel(self.tag, tx.kv["feeds", self.tag])+        return view.viewer.channel(self.tag, tx.kv["feeds", self.tag])      def SOCKET(self, socket):         channel = tx.kv.db.pubsub()
     #         #     publish_message()  # expect message=None?  -@app.route(r"reader/{tag}/settings")+@app.route(r"viewer/{tag}/settings") class ChannelSettings:      @if_owner     def GET(self):-        return view.reader.channel_settings(self.tag)+        return view.viewer.channel_settings(self.tag)   @app.route(r"actions")
         if handler.action in ("reply", "repost"):             return getattr(view, handler.action)(url) -        actions = {"associate": "contacts", "follow": "follows",+        actions = {"associate": "identities", "follow": "follows",                    "bookmark": "bookmarks", "like": "likes", "vote": "votes",                    "issue": "issues", "quote": "quotes", "payment": "payments"}         t = get_type(actions[handler.action])
 class CardDavDiscovery:      def PROPFIND(self):-        raise web.PermanentRedirect("/contacts")+        raise web.PermanentRedirect("/identities")   # @app.route(r"profile/photo")

--- a/canopy/__web__/content.py

+++ b/canopy/__web__/content.py

 import json # import pickle +import geocoder import html2text import mkdn # from PIL import ExifTags, Image
             discard_draft(self.resource)             return "discarded" -        if self.type.s == "contact":+        if self.type.s == "identity":             for prop in ("nickname", "uid-alias", "url", "email", "telephone"):                 try:                     data[prop] = [l.strip() for l in data[prop].splitlines()
             # XXX     except ValueError:             # XXX         pass             # XXX data["url"] = urls + sorted(data["url"])+            mapbox_key = str(tx.kv["providers:mapbox"]["public_key"])+            geo = geocoder.mapbox(", ".join((data["street-address"],+                                             data["locality"], data["region"],+                                             data["postal-code"],+                                             data["country-name"])),+                                  key=mapbox_key)+            data["geo"] = {"latitude": geo.lat, "longitude": geo.lng}          for prop, value in list(data.items()):             if not value:

--- a/canopy/__web__/hosting.py

+++ b/canopy/__web__/hosting.py

-import random-import sys-import time-import uuid--import sh import web from web import tx -from ..util import get_path, enqueue, send_email, send_text-from ..util import digitalocean, dynadot+from ..util import enqueue, send_email, send_text from .util import app, view  -def prepare_clone():-    """--    """-    do = digitalocean.Client(tx.kv["digitalocean_auth_token"])-    key = do_get_key(do)-    droplet = do.create_droplet("Detritus", size="1gb", ssh_keys=[key["id"]])-    do_wait(do, droplet["id"], "generating temporary droplet")-    droplet = do.get_droplet(droplet["id"])-    ip_address = droplet["networks"]["v4"][0]["ip_address"]-    root_ssh = do_get_ssh("root", ip_address, wait_for_live=True)-    print(time.time(), "spawning gaea..")-    root_ssh("wget", "https://lahacker.net/code/canopy/raw/gaea.py")-    root_ssh("python3", "gaea.py", "gaea")-    gaea_ssh = do_get_ssh("gaea", ip_address)-    print(time.time(), "spawning a canopy..")-    gaea_ssh("wget", "https://lahacker.net/code/canopy/raw/gaea.py")-    gaea_ssh("python3", "gaea.py", "canopy")-    print(time.time(), "shutting down..")-    try:-        root_ssh("shutdown", "-h", "now")-    except sh.ErrorReturnCode_255:-        time.sleep(20)-    print(time.time(), "taking snapshot..")-    snapshot = do.take_snapshot(droplet["id"], "Canopy")-    do_wait(do, droplet["id"], "taking snapshot")-    snapshot = do.get_droplet_snapshots(droplet["id"])[0]-    do.delete_droplet(droplet["id"])-    tx.kv["digitalocean_clone_snapshot_id"] = snapshot["id"]---def spawn_machine():-    """--    """-    do = digitalocean.Client(tx.kv["digitalocean_auth_token"])-    key = do_get_key(do)-    snapshot_id = str(tx.kv["digitalocean_clone_snapshot_id"])-    droplet_id = do.create_droplet(str(uuid.uuid4()), size="2gb",-                                   image=snapshot_id, tags=["Canopy"],-                                   ssh_keys=[key["id"]])["id"]-    do_wait(do, droplet_id, "generating droplet")-    ip = do.get_droplet(droplet_id)["networks"]["v4"][0]["ip_address"]-    tx.db.insert("machines", id=droplet_id, ip=ip)---def spawn_identity(name, passphrase, subdomain=None, domain=None):-    """--    """-    machine = random.choice(tx.db.select("machines"))-    if domain:-        if subdomain:-            do = digitalocean.Client(str(tx.kv["digitalocean_auth_token"]))-            do.create_domain_record(domain, subdomain, machine["ip"])-            time.sleep(10)-            domain = f"{subdomain}.{domain}"-    ssh = do_get_ssh("gaea", machine["ip"])-    domain_args = () if domain is None else ("--domain", domain)-    uuid = str(ssh("python3", "gaea.py", "tree",-                   f'"{name}"', f'"{passphrase}"', *domain_args)).strip()-    onion = str(ssh("cat", f"canopy/var/identities/{uuid}"-                           f"/nginx/tor/hostname")).strip()-    tx.db.insert("identities", machine_id=machine["id"],-                 uuid=uuid, onion=onion, domain=domain)---def do_get_key(do):-    """--    """-    key_path = get_path() / "sshkey.pub"-    try:-        with key_path.open() as fp:-            key_data = fp.read().strip()-    except FileNotFoundError:-        sh.ssh_keygen("-o", "-a", "100", "-t", "ed25519", "-N", "",-                      "-f", str(key_path)[:-4])-        with key_path.open() as fp:-            key_data = fp.read().strip()-        key = do.add_key("gaea", key_data)-    else:-        for key in do.get_keys()["ssh_keys"]:-            if key["public_key"] == key_data:-                break-        else:-            key = do.add_key("gaea", key_data)-    return key---def do_get_ssh(user, ip_address, wait_for_live=False):-    """-    return a shell helper function for executing a command as user--    """-    # def process_out(line):-    #     print(line, end="")-    #     sys.stdout.flush()-    def ssh(*command, env=None):-        kwargs = {}-        if env:-            kwargs["_env"] = env-        return sh.ssh("-i", str(get_path() / "sshkey"),-                      "-o", "StrictHostKeyChecking no",-                      f"{user}@{ip_address}", *command, **kwargs)-        # _out=process_out)--    if wait_for_live:-        print("waiting up to two minutes for server to come alive .", end="")-        tries = 60-        while tries:-            try:-                ssh("pwd")-            except sh.ErrorReturnCode_255:-                print(".", end="")-                sys.stdout.flush()-            else:-                break-            time.sleep(3)-            tries -= 1-        else:-            print(" couldn't connect!")-            sys.exit(1)-        time.sleep(2)--        print(" done")-    return ssh---def do_wait(do, droplet_id, message):-    print(f"{message}", end=" ")-    while (do.get_droplet_actions(droplet_id)[0]["status"] == "in-progress"):-        print(".", end="")-        sys.stdout.flush()-        time.sleep(1)-    print(" done")+# google_recaptcha_sitekey = "6Lei8i4UAAAAANIMaYiadBU69XtjQ_VmzORC9WUL"+# google_recaptcha_secretkey = "6Lei8i4UAAAAAHNWENrtsVXhGvHLgcchLt4aRnHb"+# google_recaptcha_verify_ep = \+#   "https://www.google.com/recaptcha/api/siteverify"   @app.route(r"hosting")
      def GET(self):         if tx.user.is_owner:-            machines = tx.db.select("machines",-                                    what="count(id) AS c")[0]["c"]+            machines = tx.db.select("machines", what="count(id) AS c")[0]["c"]             identities = tx.db.select("identities",                                       what="count(uuid) AS c")[0]["c"]             return view.hosting.index(identities, machines)
         return view.hosting.register()  -@app.route(r"hosting/providers")-class Providers:--    def GET(self):-        return view.hosting.providers.index()---@app.route(r"hosting/providers/host")-class Host:--    def GET(self):-        return view.hosting.providers.host()--    def POST(self):-        tx.kv["digitalocean_auth_token"] = web.form("token").token-        return "token stored"---@app.route(r"hosting/providers/registrar")-class Registrar:--    def GET(self):-        return view.hosting.providers.registrar()--    def POST(self):-        tx.kv["dynadot_auth_token"] = web.form("token").token-        return "token stored"---@app.route(r"hosting/snapshot")-class Snapshot:--    def POST(self):-        enqueue(prepare_clone)-        return "preparing master clone.. will take ~20 minutes"---@app.route(r"hosting/machines")-class Machines:--    def GET(self):-        machines = tx.db.select("machines")-        # do = digitalocean.Client(str(tx.kv["digitalocean_auth_token"]))-        # for snapshot in do.get_snapshots_of_droplets()["snapshots"]:-        #     if snapshot["id"] == tx.kv["digitalocean_clone_snapshot_id"]:-        #         break-        # else:-        #     snapshot = None-        return view.hosting.machines(machines)--    def POST(self):-        enqueue(spawn_machine)-        return "spawning machine.. will take ~4 minutes"---@app.route(r"hosting/machines/{machine}")-class Machine:--    def GET(self):-        machine = tx.db.select("machines", where="id = ?",-                               vals=[self.machine])[0]-        identities = tx.db.select("identities", where="machine_id = ?",-                                  vals=[machine["id"]])-        return view.hosting.machine(machine, identities)--    def DELETE(self):-        identities = tx.db.select("identities", what="count(uuid) as c",-                                  where="machine_id = ?",-                                  vals=[self.machine])[0]["c"]-        if identities > 0:-            return "machine contains identities. empty it first."-        do = digitalocean.Client(tx.kv["digitalocean_auth_token"])-        do.delete_droplet(self.machine)-        tx.db.delete("machines", where="id = ?", vals=[self.machine])-        return "ok"---@app.route(r"hosting/domains")-class Domains:--    def GET(self):-        dd = dynadot.Client(str(tx.kv["dynadot_auth_token"]))-        return view.hosting.domains(dd.list_domain())--    def POST(self):-        tx.kv["dynadot_host_domain"] = web.form("domain").domain-        # TODO do = digitalocean.Client(str(tx.kv["digitalocean_auth_token"]))-        # TODO do.create_domain(domain, local_ip)-        # TODO ensure NS and PSL and redirect domain to /hosting-        # TODO point canopy.garden to /pages/Canopy_Garden-        return "set domain for hosting"---@app.route(r"hosting/identities")-class Identities:--    def GET(self):-        identities = tx.db.select("identities")-        return view.hosting.identities(identities)--    def POST(self):-        form = web.form("name", "passphrase", subdomain=None)-        subdomain = None-        if form.subdomain:-            subdomain = form.subdomain-        enqueue(spawn_identity, form.name, form.passphrase, subdomain,-                str(tx.kv["dynadot_host_domain"]))-        return "spawning identity.. will take ~1 minute"---@app.route(r"hosting/identities/{identity}")-class Identity:--    def get_identity(self):-        return tx.db.select("identities", where="onion LIKE ?",-                            vals=[f"{self.identity}%"])[0]--    def GET(self):-        return view.hosting.identity(self.get_identity())--    def DELETE(self):-        # TODO automatically backup and store for period of time-        identity = self.get_identity()-        ip = tx.db.select("machines", where="machine_id = ?",-                          vals=[identity["machine_id"]])[0]["ip"]-        ssh = do_get_ssh("gaea", ip, wait_for_live=True)-        ssh("runinenv", "understory", "canopy", "deletehost", identity["uuid"],-            _env={"WEBCFG": "canopy/etc/web.conf",-                  "KVDB": "canopy/run/redis.sock"})-        return "ok"---# google_recaptcha_sitekey = "6Lei8i4UAAAAANIMaYiadBU69XtjQ_VmzORC9WUL"-# google_recaptcha_secretkey = "6Lei8i4UAAAAAHNWENrtsVXhGvHLgcchLt4aRnHb"-# google_recaptcha_verify_ep = \-#   "https://www.google.com/recaptcha/api/siteverify"-- @app.route(r"hosting/register") class Register: 

--- a/canopy/__web__/security.py

+++ b/canopy/__web__/security.py

  from ..security import verify_passphrase, export_public_key, get_totp # from canopy.types import get_resource-from ..util import get_path, if_owner from .util import app, view # from .view import view 
         form = web.form("passphrase")  # TODO web.secure_form(..)         if not verify_passphrase(tx.db, form.passphrase):             return view.security.authentication_failure("passphrase")-        if tx.kv["totp-secret"]:+        if tx.kv["totp"]:             form = web.form("totp")-            if form.totp != str(get_totp(str(tx.kv["totp-secret"]))):+            if form.totp != str(get_totp(str(tx.kv["totp"]))):                 return view.security.authentication_failure("totp")         tx.user.session["uri"] = tx.host.name         tx.user.session["id"] = 1
             #     person_id = get_resource("person", minimized)             # except IndexError:             #     data = {"name": name, "url": minimized}-            #     person_id = publish("contacts", data)+            #     person_id = publish("person", data)             #     # TODO relation             tx.user.session["id"] = 999  # person_id             # send_mention("network/{}".format(form.identity), identifier,
                 # XXX add_person(owner, identifier, rel="usee", private=True)                  # TODO data = {"name": owner, "url": identifier}-                # TODO publish("contacts", data)+                # TODO publish("person", data)                  pass             # TODO if now owner, add_client

--- a/canopy/__web__/settings.py

+++ b/canopy/__web__/settings.py

-import itertools-import json-import os-import pickle-import re-import socket--import qrcode-import web-from web import tx--from ..types.resources import slugs-from ..util import get_root, get_path, get_repo, if_owner-from .util import view--# TODO backups, third party API credentials---app = web.application("canopy-settings", mount_prefix="settings",-                      hostslug=slugs.host, pathslug=slugs.path, table=r"\w+")---def reverse_readline(fp, buf_size=8192):-    """-    a generator that returns the lines of a file in reverse order--    """-    segment = None-    offset = 0-    fp.seek(0, os.SEEK_END)-    file_size = remaining_size = fp.tell()-    while remaining_size > 0:-        offset = min(file_size, offset + buf_size)-        fp.seek(file_size - offset)-        buffer = fp.read(min(remaining_size, buf_size))-        remaining_size -= buf_size-        lines = buffer.split('\n')-        # the first line of the buffer is probably not a complete line so-        # we'll save it and append it to the last line of the next buffer-        # we read-        if segment is not None:-            # if the previous chunk starts right from the beginning of line-            # do not concact the segment to the last line of new chunk-            # instead, yield the segment first-            if buffer[-1] is not '\n':-                lines[-1] += segment-            else:-                yield segment-        segment = lines[0]-        for index in range(len(lines) - 1, 0, -1):-            if len(lines[index]):-                yield lines[index]-    # Don't yield None if the file was empty-    if segment is not None:-        yield segment---@app.route(r"")-class Settings:--    @if_owner-    def GET(self):-        return view.settings.index()---@app.route(r"security")-class Security:--    @if_owner-    def GET(self):-        return view.settings.security.index()---@app.route(r"security/totp")-class TOTP:--    def GET(self):-        qrcode.make(f"otpauth://totp/{tx.host.name}:me?secret="-                    f"{tx.kv['totp-secret']}&"-                    f"issuer={tx.host.name}").save(get_path() / "totpcode.png")-        web.header("Content-Type", "image/png")-        web.header("X-Accel-Redirect", f"/X/totpcode.png")--    def POST(self):-        tx.kv["totp-secret"] = "ABCDEFGHIJKLMNOP"-        return "regenerated"---@app.route(r"jobs")-class Jobs:--    @if_owner-    def GET(self):-        def get_job(job_id):-            return pickle.loads(tx.kv["jobs"].hgetbytes(job_id))-        active = [get_job(j) for j in tx.kv["jobs", "active"]]-        completed = [get_job(j) for j in tx.kv["jobs", "completed"]]-        return view.settings.jobs(active, completed)---@app.route(r"repository")-class Repository:--    @if_owner-    def GET(self):-        return view.settings.repository.index(get_repo())---@app.route(r"repository/log")-class RepositoryLog:--    @if_owner-    def GET(self):-        return view.settings.repository.log(get_repo())---@app.route(r"data")-class Data:--    @if_owner-    def GET(self):-        return view.settings.data.index()---@app.route(r"data/relational")-class DataRelational:--    @if_owner-    def GET(self):-        return view.settings.data.relational.index()---@app.route(r"data/relational/{table}")-class DataRelationalTable:--    @if_owner-    def GET(self):-        cols = tx.db.columns(self.table)-        rows = tx.db.select(self.table)-        return view.settings.data.relational.table(self.table, cols, rows)---@app.route(r"data/key-value")-class DataKeyValue:--    @if_owner-    def GET(self):-        return view.settings.data.key_value.index()---@app.route(r"stats")-class Stats:--    def GET(self):-        js = ""-        with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s:-            s.connect(str(get_root() / "run/web-stats.sock"))-            while True:-                data = s.recv(4096)-                if len(data) < 1:-                    break-                js += data.decode("utf-8", "ignore")-        return view.settings.stats(json.loads(js))---@app.route(r"logs")-class Logs:--    access_pattern = re.compile("""-        (?P<ipaddress>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\W-        \[(?P<datetime>\d{2}\/[\w]{3}\/\d{4}:\d{2}:\d{2}:\d{2}\W-            (\+|\-)\d{4})\]\W-        (("(?P<method>GET|POST|PUT|DELETE)\W)(?P<url>.+)-            ( HTTP\/(?P<version>1\.1|2\.0)"))\W-        (?P<status>\d{3})\W(?P<duration>[\d.]+)\W(?P<bytessent>\d+)\W-        ("(?P<referrer>(\-)|(.+))")\W("(?P<useragent>.+)")\W-        ("(?P<cookie>.+)")""", re.X)-    error_pattern = re.compile("""-        (?P<datetime>\d{4}\/[\d]{2}\/\d{2}\W\d{2}:\d{2}:\d{2})\W-        \[(?P<level>\w+)\]\W-        (?P<pid>\d+)\#(?P<tid>\d+):\W\*(?P<cid>\d+)\W-        (?P<message>.+)""", re.X)--    @if_owner-    def GET(self):-        with (get_path() / "access.log").open() as fp:-            access = [self.access_pattern.match(line)-                      for line in itertools.islice(reverse_readline(fp), 100)]-        with (get_path() / "error.log").open() as fp:-            error = [self.error_pattern.match(line)-                     for line in itertools.islice(reverse_readline(fp), 100)]-        return view.settings.logs(access, error)---@app.route(r"style")-class Style:--    @if_owner-    def GET(self):-        with open(get_path("stylesheet.css")) as fp:-            stylesheet = fp.read()-        return view.settings.style(stylesheet)--    @if_owner-    def POST(self):-        stylesheet = web.form("stylesheet").stylesheet-        with open(get_path("stylesheet.css"), "w") as fp:-            fp.write(stylesheet)-        return "saved"

--- a/canopy/__web__/static/scripts/enliven.js

+++ b/canopy/__web__/static/scripts/enliven.js

 $.load(function() {     upgradeTimestamps(); -    // TODO var debugSocket = new WebSocketClient.default(socket_origin + "debug",-    // TODO                                               null, {backoff: "exponential"});-    // TODO window.onerror = function(message, url, lineNumber) {  -    // TODO     debugSocket.send({message: message, url: url, lineNumber: lineNumber});-    // TODO };+    window.debugSocket = new WebSocketClient.default(socket_origin + "debug",+                                                     null,+                                                     {backoff: "exponential"});+    window.onerror = function(message, url, lineNumber) {  +        window.debugSocket.send({message: message, url: url, lineNumber: lineNumber});+    };      if (window.navigator.standalone)         $("#signout").style.display = "none";
     updateArticle(window.location, e.state.scroll); } +function executeLoadScripts() {+    loadScripts.forEach(function(handler) { handler(); });+    loadScripts = [];+}+function executeUnloadScripts() {+    unloadScripts.forEach(function(handler) { handler() });+    unloadScripts = [];+}+ function updateArticle(url, scroll) {     $("#loading").style.display = "block";     var xhr = new XMLHttpRequest();     xhr.open("GET", url);     xhr.setRequestHeader("X-Chromeless", "1");     xhr.onload = function() {-        unloadScripts.forEach(function(handler) { handler() });-        unloadScripts = [];+        executeUnloadScripts();          dom = new DOMParser().parseFromString(xhr.responseText, "text/html");         newElement = dom.querySelector("body > article");
             else                 eval(this.innerHTML);         });-        loadScripts.forEach(function(handler) { handler(); });-        loadScripts = [];+        executeLoadScripts();         upgradeTimestamps();         bindWebActions(); 
 window.addEventListener("beforeunload", function() {     unloadScripts.forEach(function(handler) { handler(); }); });+++++++/*****************************************************************/++window.addEventListener('online',  updateOnlineStatus);+window.addEventListener('offline', updateOnlineStatus);++function updateOnlineStatus(event) {+    console.log("Device is "+(navigator.onLine ? "online" : "offline"));+    +    if(navigator.onLine) {+        $("body").addClass("online").removeClass("offline");+        if(window.syncAllPosts) {+            console.log('syncing posts');+            refreshSavedPosts();+            syncAllPosts();    +        }+    } else {+        $("body").addClass("offline").removeClass("online");+        if(window.refreshSavedPosts) {+            console.log('loading posts');+            refreshSavedPosts();+        }+    }+}diff --git a/canopy/__web__/static/scripts/pouchdb-7.0.0.js b/canopy/__web__/static/scripts/pouchdb-7.0.0.jsnew file mode 100644index 0000000..65f3bf3--- /dev/null+++ b/canopy/__web__/static/scripts/pouchdb-7.0.0.js
+// PouchDB 7.0.0+// +// (c) 2012-2018 Dale Harvey and the PouchDB team+// PouchDB may be freely distributed under the Apache license, version 2.0.+// For all details and documentation:+// http://pouchdb.com+!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).PouchDB=e()}}(function(){return function o(s,a,u){function c(t,e){if(!a[t]){if(!s[t]){var n="function"==typeof require&&require;if(!e&&n)return n(t,!0);if(f)return f(t,!0);var r=new Error("Cannot find module '"+t+"'");throw r.code="MODULE_NOT_FOUND",r}var i=a[t]={exports:{}};s[t][0].call(i.exports,function(e){return c(s[t][1][e]||e)},i,i.exports,o,s,a,u)}return a[t].exports}for(var f="function"==typeof require&&require,e=0;e<u.length;e++)c(u[e]);return c}({1:[function(e,t,n){"use strict";t.exports=function(r){return function(){var e=arguments.length;if(e){for(var t=[],n=-1;++n<e;)t[n]=arguments[n];return r.call(this,t)}return r.call(this,[])}}},{}],2:[function(e,t,n){var u=Object.create||function(e){var t=function(){};return t.prototype=e,new t},s=Object.keys||function(e){var t=[];for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&t.push(n);return n},o=Function.prototype.bind||function(e){var t=this;return function(){return t.apply(e,arguments)}};function r(){this._events&&Object.prototype.hasOwnProperty.call(this,"_events")||(this._events=u(null),this._eventsCount=0),this._maxListeners=this._maxListeners||void 0}((t.exports=r).EventEmitter=r).prototype._events=void 0,r.prototype._maxListeners=void 0;var i,a=10;try{var c={};Object.defineProperty&&Object.defineProperty(c,"x",{value:0}),i=0===c.x}catch(e){i=!1}function f(e){return void 0===e._maxListeners?r.defaultMaxListeners:e._maxListeners}function l(e,t,n,r){var i,o,s;if("function"!=typeof n)throw new TypeError('"listener" argument must be a function');if((o=e._events)?(o.newListener&&(e.emit("newListener",t,n.listener?n.listener:n),o=e._events),s=o[t]):(o=e._events=u(null),e._eventsCount=0),s){if("function"==typeof s?s=o[t]=r?[n,s]:[s,n]:r?s.unshift(n):s.push(n),!s.warned&&(i=f(e))&&0<i&&s.length>i){s.warned=!0;var a=new Error("Possible EventEmitter memory leak detected. "+s.length+' "'+String(t)+'" listeners added. Use emitter.setMaxListeners() to increase limit.');a.name="MaxListenersExceededWarning",a.emitter=e,a.type=t,a.count=s.length,"object"==typeof console&&console.warn&&console.warn("%s: %s",a.name,a.message)}}else s=o[t]=n,++e._eventsCount;return e}function d(){if(!this.fired)switch(this.target.removeListener(this.type,this.wrapFn),this.fired=!0,arguments.length){case 0:return this.listener.call(this.target);case 1:return this.listener.call(this.target,arguments[0]);case 2:return this.listener.call(this.target,arguments[0],arguments[1]);case 3:return this.listener.call(this.target,arguments[0],arguments[1],arguments[2]);default:for(var e=new Array(arguments.length),t=0;t<e.length;++t)e[t]=arguments[t];this.listener.apply(this.target,e)}}function h(e,t,n){var r={fired:!1,wrapFn:void 0,target:e,type:t,listener:n},i=o.call(d,r);return i.listener=n,r.wrapFn=i}function p(e,t,n){var r=e._events;if(!r)return[];var i=r[t];return i?"function"==typeof i?n?[i.listener||i]:[i]:n?function(e){for(var t=new Array(e.length),n=0;n<t.length;++n)t[n]=e[n].listener||e[n];return t}(i):y(i,i.length):[]}function v(e){var t=this._events;if(t){var n=t[e];if("function"==typeof n)return 1;if(n)return n.length}return 0}function y(e,t){for(var n=new Array(t),r=0;r<t;++r)n[r]=e[r];return n}i?Object.defineProperty(r,"defaultMaxListeners",{enumerable:!0,get:function(){return a},set:function(e){if("number"!=typeof e||e<0||e!=e)throw new TypeError('"defaultMaxListeners" must be a positive number');a=e}}):r.defaultMaxListeners=a,r.prototype.setMaxListeners=function(e){if("number"!=typeof e||e<0||isNaN(e))throw new TypeError('"n" argument must be a positive number');return this._maxListeners=e,this},r.prototype.getMaxListeners=function(){return f(this)},r.prototype.emit=function(e){var t,n,r,i,o,s,a="error"===e;if(s=this._events)a=a&&null==s.error;else if(!a)return!1;if(a){if(1<arguments.length&&(t=arguments[1]),t instanceof Error)throw t;var u=new Error('Unhandled "error" event. ('+t+")");throw u.context=t,u}if(!(n=s[e]))return!1;var c="function"==typeof n;switch(r=arguments.length){case 1:!function(e,t,n){if(t)e.call(n);else for(var r=e.length,i=y(e,r),o=0;o<r;++o)i[o].call(n)}(n,c,this);break;case 2:!function(e,t,n,r){if(t)e.call(n,r);else for(var i=e.length,o=y(e,i),s=0;s<i;++s)o[s].call(n,r)}(n,c,this,arguments[1]);break;case 3:!function(e,t,n,r,i){if(t)e.call(n,r,i);else for(var o=e.length,s=y(e,o),a=0;a<o;++a)s[a].call(n,r,i)}(n,c,this,arguments[1],arguments[2]);break;case 4:!function(e,t,n,r,i,o){if(t)e.call(n,r,i,o);else for(var s=e.length,a=y(e,s),u=0;u<s;++u)a[u].call(n,r,i,o)}(n,c,this,arguments[1],arguments[2],arguments[3]);break;default:for(i=new Array(r-1),o=1;o<r;o++)i[o-1]=arguments[o];!function(e,t,n,r){if(t)e.apply(n,r);else for(var i=e.length,o=y(e,i),s=0;s<i;++s)o[s].apply(n,r)}(n,c,this,i)}return!0},r.prototype.on=r.prototype.addListener=function(e,t){return l(this,e,t,!1)},r.prototype.prependListener=function(e,t){return l(this,e,t,!0)},r.prototype.once=function(e,t){if("function"!=typeof t)throw new TypeError('"listener" argument must be a function');return this.on(e,h(this,e,t)),this},r.prototype.prependOnceListener=function(e,t){if("function"!=typeof t)throw new TypeError('"listener" argument must be a function');return this.prependListener(e,h(this,e,t)),this},r.prototype.removeListener=function(e,t){var n,r,i,o,s;if("function"!=typeof t)throw new TypeError('"listener" argument must be a function');if(!(r=this._events))return this;if(!(n=r[e]))return this;if(n===t||n.listener===t)0==--this._eventsCount?this._events=u(null):(delete r[e],r.removeListener&&this.emit("removeListener",e,n.listener||t));else if("function"!=typeof n){for(i=-1,o=n.length-1;0<=o;o--)if(n[o]===t||n[o].listener===t){s=n[o].listener,i=o;break}if(i<0)return this;0===i?n.shift():function(e,t){for(var n=t,r=n+1,i=e.length;r<i;n+=1,r+=1)e[n]=e[r];e.pop()}(n,i),1===n.length&&(r[e]=n[0]),r.removeListener&&this.emit("removeListener",e,s||t)}return this},r.prototype.removeAllListeners=function(e){var t,n,r;if(!(n=this._events))return this;if(!n.removeListener)return 0===arguments.length?(this._events=u(null),this._eventsCount=0):n[e]&&(0==--this._eventsCount?this._events=u(null):delete n[e]),this;if(0===arguments.length){var i,o=s(n);for(r=0;r<o.length;++r)"removeListener"!==(i=o[r])&&this.removeAllListeners(i);return this.removeAllListeners("removeListener"),this._events=u(null),this._eventsCount=0,this}if("function"==typeof(t=n[e]))this.removeListener(e,t);else if(t)for(r=t.length-1;0<=r;r--)this.removeListener(e,t[r]);return this},r.prototype.listeners=function(e){return p(this,e,!0)},r.prototype.rawListeners=function(e){return p(this,e,!1)},r.listenerCount=function(e,t){return"function"==typeof e.listenerCount?e.listenerCount(t):v.call(e,t)},r.prototype.listenerCount=v,r.prototype.eventNames=function(){return 0<this._eventsCount?Reflect.ownKeys(this._events):[]}},{}],3:[function(e,f,t){(function(t){"use strict";var n,r,e=t.MutationObserver||t.WebKitMutationObserver;if(e){var i=0,o=new e(c),s=t.document.createTextNode("");o.observe(s,{characterData:!0}),n=function(){s.data=i=++i%2}}else if(t.setImmediate||void 0===t.MessageChannel)n="document"in t&&"onreadystatechange"in t.document.createElement("script")?function(){var e=t.document.createElement("script");e.onreadystatechange=function(){c(),e.onreadystatechange=null,e.parentNode.removeChild(e),e=null},t.document.documentElement.appendChild(e)}:function(){setTimeout(c,0)};else{var a=new t.MessageChannel;a.port1.onmessage=c,n=function(){a.port2.postMessage(0)}}var u=[];function c(){var e,t;r=!0;for(var n=u.length;n;){for(t=u,u=[],e=-1;++e<n;)t[e]();n=u.length}r=!1}f.exports=function(e){1!==u.push(e)||r||n()}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],4:[function(e,t,n){"function"==typeof Object.create?t.exports=function(e,t){e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})}:t.exports=function(e,t){e.super_=t;var n=function(){};n.prototype=t.prototype,e.prototype=new n,e.prototype.constructor=e}},{}],5:[function(e,t,n){var r,i,o=t.exports={};function s(){throw new Error("setTimeout has not been defined")}function a(){throw new Error("clearTimeout has not been defined")}function u(t){if(r===setTimeout)return setTimeout(t,0);if((r===s||!r)&&setTimeout)return r=setTimeout,setTimeout(t,0);try{return r(t,0)}catch(e){try{return r.call(null,t,0)}catch(e){return r.call(this,t,0)}}}!function(){try{r="function"==typeof setTimeout?setTimeout:s}catch(e){r=s}try{i="function"==typeof clearTimeout?clearTimeout:a}catch(e){i=a}}();var c,f=[],l=!1,d=-1;function h(){l&&c&&(l=!1,c.length?f=c.concat(f):d=-1,f.length&&p())}function p(){if(!l){var e=u(h);l=!0;for(var t=f.length;t;){for(c=f,f=[];++d<t;)c&&c[d].run();d=-1,t=f.length}c=null,l=!1,function(t){if(i===clearTimeout)return clearTimeout(t);if((i===a||!i)&&clearTimeout)return i=clearTimeout,clearTimeout(t);try{i(t)}catch(e){try{return i.call(null,t)}catch(e){return i.call(this,t)}}}(e)}}function v(e,t){this.fun=e,this.array=t}function y(){}o.nextTick=function(e){var t=new Array(arguments.length-1);if(1<arguments.length)for(var n=1;n<arguments.length;n++)t[n-1]=arguments[n];f.push(new v(e,t)),1!==f.length||l||u(p)},v.prototype.run=function(){this.fun.apply(null,this.array)},o.title="browser",o.browser=!0,o.env={},o.argv=[],o.version="",o.versions={},o.on=y,o.addListener=y,o.once=y,o.off=y,o.removeListener=y,o.removeAllListeners=y,o.emit=y,o.prependListener=y,o.prependOnceListener=y,o.listeners=function(e){return[]},o.binding=function(e){throw new Error("process.binding is not supported")},o.cwd=function(){return"/"},o.chdir=function(e){throw new Error("process.chdir is not supported")},o.umask=function(){return 0}},{}],6:[function(e,n,r){!function(e){if("object"==typeof r)n.exports=e();else{var t;try{t=window}catch(e){t=self}t.SparkMD5=e()}}(function(f){"use strict";var r=["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"];function c(e,t){var n=e[0],r=e[1],i=e[2],o=e[3];r=((r+=((i=((i+=((o=((o+=((n=((n+=(r&i|~r&o)+t[0]-680876936|0)<<7|n>>>25)+r|0)&r|~n&i)+t[1]-389564586|0)<<12|o>>>20)+n|0)&n|~o&r)+t[2]+606105819|0)<<17|i>>>15)+o|0)&o|~i&n)+t[3]-1044525330|0)<<22|r>>>10)+i|0,r=((r+=((i=((i+=((o=((o+=((n=((n+=(r&i|~r&o)+t[4]-176418897|0)<<7|n>>>25)+r|0)&r|~n&i)+t[5]+1200080426|0)<<12|o>>>20)+n|0)&n|~o&r)+t[6]-1473231341|0)<<17|i>>>15)+o|0)&o|~i&n)+t[7]-45705983|0)<<22|r>>>10)+i|0,r=((r+=((i=((i+=((o=((o+=((n=((n+=(r&i|~r&o)+t[8]+1770035416|0)<<7|n>>>25)+r|0)&r|~n&i)+t[9]-1958414417|0)<<12|o>>>20)+n|0)&n|~o&r)+t[10]-42063|0)<<17|i>>>15)+o|0)&o|~i&n)+t[11]-1990404162|0)<<22|r>>>10)+i|0,r=((r+=((i=((i+=((o=((o+=((n=((n+=(r&i|~r&o)+t[12]+1804603682|0)<<7|n>>>25)+r|0)&r|~n&i)+t[13]-40341101|0)<<12|o>>>20)+n|0)&n|~o&r)+t[14]-1502002290|0)<<17|i>>>15)+o|0)&o|~i&n)+t[15]+1236535329|0)<<22|r>>>10)+i|0,r=((r+=((i=((i+=((o=((o+=((n=((n+=(r&o|i&~o)+t[1]-165796510|0)<<5|n>>>27)+r|0)&i|r&~i)+t[6]-1069501632|0)<<9|o>>>23)+n|0)&r|n&~r)+t[11]+643717713|0)<<14|i>>>18)+o|0)&n|o&~n)+t[0]-373897302|0)<<20|r>>>12)+i|0,r=((r+=((i=((i+=((o=((o+=((n=((n+=(r&o|i&~o)+t[5]-701558691|0)<<5|n>>>27)+r|0)&i|r&~i)+t[10]+38016083|0)<<9|o>>>23)+n|0)&r|n&~r)+t[15]-660478335|0)<<14|i>>>18)+o|0)&n|o&~n)+t[4]-405537848|0)<<20|r>>>12)+i|0,r=((r+=((i=((i+=((o=((o+=((n=((n+=(r&o|i&~o)+t[9]+568446438|0)<<5|n>>>27)+r|0)&i|r&~i)+t[14]-1019803690|0)<<9|o>>>23)+n|0)&r|n&~r)+t[3]-187363961|0)<<14|i>>>18)+o|0)&n|o&~n)+t[8]+1163531501|0)<<20|r>>>12)+i|0,r=((r+=((i=((i+=((o=((o+=((n=((n+=(r&o|i&~o)+t[13]-1444681467|0)<<5|n>>>27)+r|0)&i|r&~i)+t[2]-51403784|0)<<9|o>>>23)+n|0)&r|n&~r)+t[7]+1735328473|0)<<14|i>>>18)+o|0)&n|o&~n)+t[12]-1926607734|0)<<20|r>>>12)+i|0,r=((r+=((i=((i+=((o=((o+=((n=((n+=(r^i^o)+t[5]-378558|0)<<4|n>>>28)+r|0)^r^i)+t[8]-2022574463|0)<<11|o>>>21)+n|0)^n^r)+t[11]+1839030562|0)<<16|i>>>16)+o|0)^o^n)+t[14]-35309556|0)<<23|r>>>9)+i|0,r=((r+=((i=((i+=((o=((o+=((n=((n+=(r^i^o)+t[1]-1530992060|0)<<4|n>>>28)+r|0)^r^i)+t[4]+1272893353|0)<<11|o>>>21)+n|0)^n^r)+t[7]-155497632|0)<<16|i>>>16)+o|0)^o^n)+t[10]-1094730640|0)<<23|r>>>9)+i|0,r=((r+=((i=((i+=((o=((o+=((n=((n+=(r^i^o)+t[13]+681279174|0)<<4|n>>>28)+r|0)^r^i)+t[0]-358537222|0)<<11|o>>>21)+n|0)^n^r)+t[3]-722521979|0)<<16|i>>>16)+o|0)^o^n)+t[6]+76029189|0)<<23|r>>>9)+i|0,r=((r+=((i=((i+=((o=((o+=((n=((n+=(r^i^o)+t[9]-640364487|0)<<4|n>>>28)+r|0)^r^i)+t[12]-421815835|0)<<11|o>>>21)+n|0)^n^r)+t[15]+530742520|0)<<16|i>>>16)+o|0)^o^n)+t[2]-995338651|0)<<23|r>>>9)+i|0,r=((r+=((o=((o+=(r^((n=((n+=(i^(r|~o))+t[0]-198630844|0)<<6|n>>>26)+r|0)|~i))+t[7]+1126891415|0)<<10|o>>>22)+n|0)^((i=((i+=(n^(o|~r))+t[14]-1416354905|0)<<15|i>>>17)+o|0)|~n))+t[5]-57434055|0)<<21|r>>>11)+i|0,r=((r+=((o=((o+=(r^((n=((n+=(i^(r|~o))+t[12]+1700485571|0)<<6|n>>>26)+r|0)|~i))+t[3]-1894986606|0)<<10|o>>>22)+n|0)^((i=((i+=(n^(o|~r))+t[10]-1051523|0)<<15|i>>>17)+o|0)|~n))+t[1]-2054922799|0)<<21|r>>>11)+i|0,r=((r+=((o=((o+=(r^((n=((n+=(i^(r|~o))+t[8]+1873313359|0)<<6|n>>>26)+r|0)|~i))+t[15]-30611744|0)<<10|o>>>22)+n|0)^((i=((i+=(n^(o|~r))+t[6]-1560198380|0)<<15|i>>>17)+o|0)|~n))+t[13]+1309151649|0)<<21|r>>>11)+i|0,r=((r+=((o=((o+=(r^((n=((n+=(i^(r|~o))+t[4]-145523070|0)<<6|n>>>26)+r|0)|~i))+t[11]-1120210379|0)<<10|o>>>22)+n|0)^((i=((i+=(n^(o|~r))+t[2]+718787259|0)<<15|i>>>17)+o|0)|~n))+t[9]-343485551|0)<<21|r>>>11)+i|0,e[0]=n+e[0]|0,e[1]=r+e[1]|0,e[2]=i+e[2]|0,e[3]=o+e[3]|0}function l(e){var t,n=[];for(t=0;t<64;t+=4)n[t>>2]=e.charCodeAt(t)+(e.charCodeAt(t+1)<<8)+(e.charCodeAt(t+2)<<16)+(e.charCodeAt(t+3)<<24);return n}function d(e){var t,n=[];for(t=0;t<64;t+=4)n[t>>2]=e[t]+(e[t+1]<<8)+(e[t+2]<<16)+(e[t+3]<<24);return n}function i(e){var t,n,r,i,o,s,a=e.length,u=[1732584193,-271733879,-1732584194,271733878];for(t=64;t<=a;t+=64)c(u,l(e.substring(t-64,t)));for(n=(e=e.substring(t-64)).length,r=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],t=0;t<n;t+=1)r[t>>2]|=e.charCodeAt(t)<<(t%4<<3);if(r[t>>2]|=128<<(t%4<<3),55<t)for(c(u,r),t=0;t<16;t+=1)r[t]=0;return i=(i=8*a).toString(16).match(/(.*?)(.{0,8})$/),o=parseInt(i[2],16),s=parseInt(i[1],16)||0,r[14]=o,r[15]=s,c(u,r),u}function n(e){var t,n="";for(t=0;t<4;t+=1)n+=r[e>>8*t+4&15]+r[e>>8*t&15];return n}function s(e){var t;for(t=0;t<e.length;t+=1)e[t]=n(e[t]);return e.join("")}function o(e){return/[\u0080-\uFFFF]/.test(e)&&(e=unescape(encodeURIComponent(e))),e}function a(e){var t,n=[],r=e.length;for(t=0;t<r-1;t+=2)n.push(parseInt(e.substr(t,2),16));return String.fromCharCode.apply(String,n)}function u(){this.reset()}return"5d41402abc4b2a76b9719d911017c592"!==s(i("hello"))&&function(e,t){var n=(65535&e)+(65535&t);return(e>>16)+(t>>16)+(n>>16)<<16|65535&n},"undefined"==typeof ArrayBuffer||ArrayBuffer.prototype.slice||function(){function c(e,t){return(e=0|e||0)<0?Math.max(e+t,0):Math.min(e,t)}ArrayBuffer.prototype.slice=function(e,t){var n,r,i,o,s=this.byteLength,a=c(e,s),u=s;return t!==f&&(u=c(t,s)),u<a?new ArrayBuffer(0):(n=u-a,r=new ArrayBuffer(n),i=new Uint8Array(r),o=new Uint8Array(this,a,n),i.set(o),r)}}(),u.prototype.append=function(e){return this.appendBinary(o(e)),this},u.prototype.appendBinary=function(e){this._buff+=e,this._length+=e.length;var t,n=this._buff.length;for(t=64;t<=n;t+=64)c(this._hash,l(this._buff.substring(t-64,t)));return this._buff=this._buff.substring(t-64),this},u.prototype.end=function(e){var t,n,r=this._buff,i=r.length,o=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(t=0;t<i;t+=1)o[t>>2]|=r.charCodeAt(t)<<(t%4<<3);return this._finish(o,i),n=s(this._hash),e&&(n=a(n)),this.reset(),n},u.prototype.reset=function(){return this._buff="",this._length=0,this._hash=[1732584193,-271733879,-1732584194,271733878],this},u.prototype.getState=function(){return{buff:this._buff,length:this._length,hash:this._hash}},u.prototype.setState=function(e){return this._buff=e.buff,this._length=e.length,this._hash=e.hash,this},u.prototype.destroy=function(){delete this._hash,delete this._buff,delete this._length},u.prototype._finish=function(e,t){var n,r,i,o=t;if(e[o>>2]|=128<<(o%4<<3),55<o)for(c(this._hash,e),o=0;o<16;o+=1)e[o]=0;n=(n=8*this._length).toString(16).match(/(.*?)(.{0,8})$/),r=parseInt(n[2],16),i=parseInt(n[1],16)||0,e[14]=r,e[15]=i,c(this._hash,e)},u.hash=function(e,t){return u.hashBinary(o(e),t)},u.hashBinary=function(e,t){var n=s(i(e));return t?a(n):n},(u.ArrayBuffer=function(){this.reset()}).prototype.append=function(e){var t,n,r,i,o,s=(n=this._buff.buffer,r=e,i=!0,(o=new Uint8Array(n.byteLength+r.byteLength)).set(new Uint8Array(n)),o.set(new Uint8Array(r),n.byteLength),i?o:o.buffer),a=s.length;for(this._length+=e.byteLength,t=64;t<=a;t+=64)c(this._hash,d(s.subarray(t-64,t)));return this._buff=t-64<a?new Uint8Array(s.buffer.slice(t-64)):new Uint8Array(0),this},u.ArrayBuffer.prototype.end=function(e){var t,n,r=this._buff,i=r.length,o=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(t=0;t<i;t+=1)o[t>>2]|=r[t]<<(t%4<<3);return this._finish(o,i),n=s(this._hash),e&&(n=a(n)),this.reset(),n},u.ArrayBuffer.prototype.reset=function(){return this._buff=new Uint8Array(0),this._length=0,this._hash=[1732584193,-271733879,-1732584194,271733878],this},u.ArrayBuffer.prototype.getState=function(){var e,t=u.prototype.getState.call(this);return t.buff=(e=t.buff,String.fromCharCode.apply(null,new Uint8Array(e))),t},u.ArrayBuffer.prototype.setState=function(e){return e.buff=function(e,t){var n,r=e.length,i=new ArrayBuffer(r),o=new Uint8Array(i);for(n=0;n<r;n+=1)o[n]=e.charCodeAt(n);return t?o:i}(e.buff,!0),u.prototype.setState.call(this,e)},u.ArrayBuffer.prototype.destroy=u.prototype.destroy,u.ArrayBuffer.prototype._finish=u.prototype._finish,u.ArrayBuffer.hash=function(e,t){var n=s(function(e){var t,n,r,i,o,s,a=e.length,u=[1732584193,-271733879,-1732584194,271733878];for(t=64;t<=a;t+=64)c(u,d(e.subarray(t-64,t)));for(n=(e=t-64<a?e.subarray(t-64):new Uint8Array(0)).length,r=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],t=0;t<n;t+=1)r[t>>2]|=e[t]<<(t%4<<3);if(r[t>>2]|=128<<(t%4<<3),55<t)for(c(u,r),t=0;t<16;t+=1)r[t]=0;return i=(i=8*a).toString(16).match(/(.*?)(.{0,8})$/),o=parseInt(i[2],16),s=parseInt(i[1],16)||0,r[14]=o,r[15]=s,c(u,r),u}(new Uint8Array(e)));return t?a(n):n},u})},{}],7:[function(e,t,n){var r=e(10),i=e(11),o=i;o.v1=r,o.v4=i,t.exports=o},{10:10,11:11}],8:[function(e,t,n){for(var i=[],r=0;r<256;++r)i[r]=(r+256).toString(16).substr(1);t.exports=function(e,t){var n=t||0,r=i;return r[e[n++]]+r[e[n++]]+r[e[n++]]+r[e[n++]]+"-"+r[e[n++]]+r[e[n++]]+"-"+r[e[n++]]+r[e[n++]]+"-"+r[e[n++]]+r[e[n++]]+"-"+r[e[n++]]+r[e[n++]]+r[e[n++]]+r[e[n++]]+r[e[n++]]+r[e[n++]]}},{}],9:[function(e,t,n){var r="undefined"!=typeof crypto&&crypto.getRandomValues.bind(crypto)||"undefined"!=typeof msCrypto&&msCrypto.getRandomValues.bind(msCrypto);if(r){var i=new Uint8Array(16);t.exports=function(){return r(i),i}}else{var o=new Array(16);t.exports=function(){for(var e,t=0;t<16;t++)0==(3&t)&&(e=4294967296*Math.random()),o[t]=e>>>((3&t)<<3)&255;return o}}},{}],10:[function(e,t,n){var p,v,y=e(9),_=e(8),g=0,m=0;t.exports=function(e,t,n){var r=t&&n||0,i=t||[],o=(e=e||{}).node||p,s=void 0!==e.clockseq?e.clockseq:v;if(null==o||null==s){var a=y();null==o&&(o=p=[1|a[0],a[1],a[2],a[3],a[4],a[5]]),null==s&&(s=v=16383&(a[6]<<8|a[7]))}var u=void 0!==e.msecs?e.msecs:(new Date).getTime(),c=void 0!==e.nsecs?e.nsecs:m+1,f=u-g+(c-m)/1e4;if(f<0&&void 0===e.clockseq&&(s=s+1&16383),(f<0||g<u)&&void 0===e.nsecs&&(c=0),1e4<=c)throw new Error("uuid.v1(): Can't create more than 10M uuids/sec");g=u,v=s;var l=(1e4*(268435455&(u+=122192928e5))+(m=c))%4294967296;i[r++]=l>>>24&255,i[r++]=l>>>16&255,i[r++]=l>>>8&255,i[r++]=255&l;var d=u/4294967296*1e4&268435455;i[r++]=d>>>8&255,i[r++]=255&d,i[r++]=d>>>24&15|16,i[r++]=d>>>16&255,i[r++]=s>>>8|128,i[r++]=255&s;for(var h=0;h<6;++h)i[r+h]=o[h];return t||_(i)}},{8:8,9:9}],11:[function(e,t,n){var s=e(9),a=e(8);t.exports=function(e,t,n){var r=t&&n||0;"string"==typeof e&&(t="binary"===e?new Array(16):null,e=null);var i=(e=e||{}).random||(e.rng||s)();if(i[6]=15&i[6]|64,i[8]=63&i[8]|128,t)for(var o=0;o<16;++o)t[r+o]=i[o];return t||a(i)}},{8:8,9:9}],12:[function(e,t,n){"use strict";function h(e,t,n){var r=n[n.length-1];e===r.element&&(n.pop(),r=n[n.length-1]);var i=r.element,o=r.index;if(Array.isArray(i))i.push(e);else if(o===t.length-2){i[t.pop()]=e}else t.push(e)}n.stringify=function(e){var t=[];t.push({obj:e});for(var n,r,i,o,s,a,u,c,f,l,d="";n=t.pop();)if(r=n.obj,d+=n.prefix||"",i=n.val||"")d+=i;else if("object"!=typeof r)d+=void 0===r?null:JSON.stringify(r);else if(null===r)d+="null";else if(Array.isArray(r)){for(t.push({val:"]"}),o=r.length-1;0<=o;o--)s=0===o?"":",",t.push({obj:r[o],prefix:s});t.push({val:"["})}else{for(u in a=[],r)r.hasOwnProperty(u)&&a.push(u);for(t.push({val:"}"}),o=a.length-1;0<=o;o--)f=r[c=a[o]],l=0<o?",":"",l+=JSON.stringify(c)+":",t.push({obj:f,prefix:l});t.push({val:"{"})}return d},n.parse=function(e){for(var t,n,r,i,o,s,a,u,c,f=[],l=[],d=0;;)if("}"!==(t=e[d++])&&"]"!==t&&void 0!==t)switch(t){case" ":case"\t":case"\n":case":":case",":break;case"n":d+=3,h(null,f,l);break;case"t":d+=3,h(!0,f,l);break;case"f":d+=4,h(!1,f,l);break;case"0":case"1":case"2":case"3":case"4":case"5":case"6":case"7":case"8":case"9":case"-":for(n="",d--;;){if(r=e[d++],!/[\d\.\-e\+]/.test(r)){d--;break}n+=r}h(parseFloat(n),f,l);break;case'"':for(i="",o=void 0,s=0;'"'!==(a=e[d++])||"\\"===o&&s%2==1;)i+=a,"\\"===(o=a)?s++:s=0;h(JSON.parse('"'+i+'"'),f,l);break;case"[":u={element:[],index:f.length},f.push(u.element),l.push(u);break;case"{":c={element:{},index:f.length},f.push(c.element),l.push(c);break;default:throw new Error("unexpectedly reached end of input: "+t)}else{if(1===f.length)return f.pop();h(f.pop(),f,l)}}},{}],13:[function(yr,_r,e){(function(u,e){"use strict";function t(e){return e&&"object"==typeof e&&"default"in e?e.default:e}var O=t(yr(1)),T=t(yr(3)),a=yr(2),r=t(yr(4)),d=t(yr(6)),i=t(yr(7)),o=t(yr(12));function s(e){if(e instanceof ArrayBuffer)return function(e){if("function"==typeof e.slice)return e.slice(0);var t=new ArrayBuffer(e.byteLength),n=new Uint8Array(t),r=new Uint8Array(e);return n.set(r),t}(e);var t=e.size,n=e.type;return"function"==typeof e.slice?e.slice(0,t,n):e.webkitSlice(0,t,n)}var q,x,c=Function.prototype.toString,f=c.call(Object);function R(e){var t,n,r,i;if(!e||"object"!=typeof e)return e;if(Array.isArray(e)){for(t=[],n=0,r=e.length;n<r;n++)t[n]=R(e[n]);return t}if(e instanceof Date)return e.toISOString();if(i=e,"undefined"!=typeof ArrayBuffer&&i instanceof ArrayBuffer||"undefined"!=typeof Blob&&i instanceof Blob)return s(e);if(!function(e){var t=Object.getPrototypeOf(e);if(null===t)return!0;var n=t.constructor;return"function"==typeof n&&n instanceof n&&c.call(n)==f}(e))return e;for(n in t={},e)if(Object.prototype.hasOwnProperty.call(e,n)){var o=R(e[n]);void 0!==o&&(t[n]=o)}return t}function l(t){var n=!1;return O(function(e){if(n)throw new Error("once called more than once");n=!0,t.apply(this,e)})}function v(s){return O(function(i){i=R(i);var o=this,t="function"==typeof i[i.length-1]&&i.pop(),e=new Promise(function(n,r){var e;try{var t=l(function(e,t){e?r(e):n(t)});i.push(t),(e=s.apply(o,i))&&"function"==typeof e.then&&n(e)}catch(e){r(e)}});return t&&e.then(function(e){t(null,e)},t),e})}function h(o,e){return v(O(function(r){if(this._closed)return Promise.reject(new Error("database is closed"));if(this._destroyed)return Promise.reject(new Error("database is destroyed"));var i=this;return function(r,i,e){if(r.constructor.listeners("debug").length){for(var t=["api",r.name,i],n=0;n<e.length-1;n++)t.push(e[n]);r.constructor.emit("debug",t);var o=e[e.length-1];e[e.length-1]=function(e,t){var n=["api",r.name,i];n=n.concat(e?["error",e]:["success",t]),r.constructor.emit("debug",n),o(e,t)}}}(i,o,r),this.taskqueue.isReady?e.apply(this,r):new Promise(function(t,n){i.taskqueue.addTask(function(e){e?n(e):t(i[o].apply(i,r))})})}))}function p(e){return"$"+e}function y(){this._store={}}function n(e){if(this._store=new y,e&&Array.isArray(e))for(var t=0,n=e.length;t<n;t++)this.add(e[t])}function m(e,t){for(var n={},r=0,i=t.length;r<i;r++){var o=t[r];o in e&&(n[o]=e[o])}return n}y.prototype.get=function(e){var t=p(e);return this._store[t]},y.prototype.set=function(e,t){var n=p(e);return this._store[n]=t,!0},y.prototype.has=function(e){return p(e)in this._store},y.prototype.delete=function(e){var t=p(e),n=t in this._store;return delete this._store[t],n},y.prototype.forEach=function(e){for(var t=Object.keys(this._store),n=0,r=t.length;n<r;n++){var i=t[n];e(this._store[i],i=i.substring(1))}},Object.defineProperty(y.prototype,"size",{get:function(){return Object.keys(this._store).length}}),n.prototype.add=function(e){return this._store.set(e,!0)},n.prototype.has=function(e){return this._store.has(e)},n.prototype.forEach=function(n){this._store.forEach(function(e,t){n(t)})},Object.defineProperty(n.prototype,"size",{get:function(){return this._store.size}}),!function(){if("undefined"==typeof Symbol||"undefined"==typeof Map||"undefined"==typeof Set)return!1;var e=Object.getOwnPropertyDescriptor(Map,Symbol.species);return e&&"get"in e&&Map[Symbol.species]===Map}()?(q=n,x=y):(q=Set,x=Map);var _,g=6;function b(e){return e}function w(e){return[{ok:e}]}function k(i,u,e){var t=u.docs,c=new x;t.forEach(function(e){c.has(e.id)?c.get(e.id).push(e):c.set(e.id,[e])});var r=c.size,o=0,f=new Array(r);function l(){var n;++o===r&&(n=[],f.forEach(function(t){t.docs.forEach(function(e){n.push({id:t.id,docs:[e]})})}),e(null,{results:n}))}var n=[];c.forEach(function(e,t){n.push(t)});var s=0;function d(){if(!(s>=n.length)){var r,e=Math.min(s+g,n.length),t=n.slice(s,e);r=s,t.forEach(function(o,e){var s=r+e,t=c.get(o),n=m(t[0],["atts_since","attachments"]);n.open_revs=t.map(function(e){return e.rev}),n.open_revs=n.open_revs.filter(b);var a=b;0===n.open_revs.length&&(delete n.open_revs,a=w),["revs","attachments","binary","ajax","latest"].forEach(function(e){e in u&&(n[e]=u[e])}),i.get(o,n,function(e,t){var n,r,i;n=e?[{error:e}]:a(t),r=o,i=n,f[s]={id:r,docs:i},l(),d()})}),s+=t.length}}d()}try{localStorage.setItem("_pouch_check_localstorage",1),_=!!localStorage.getItem("_pouch_check_localstorage")}catch(e){_=!1}function j(){return _}function A(){var t;a.EventEmitter.call(this),this._listeners={},t=this,j()&&addEventListener("storage",function(e){t.emit(e.key)})}function E(e){if("undefined"!=typeof console&&"function"==typeof console[e]){var t=Array.prototype.slice.call(arguments,1);console[e].apply(console,t)}}function M(e){var t,n,r=0;return e||(r=2e3),t=e,n=r,t=parseInt(t,10)||0,(n=parseInt(n,10))!=n||n<=t?n=(t||1)<<1:n+=1,6e5<n&&(t=3e5,n=6e5),~~((n-t)*Math.random()+t)}function S(e,t){E("info","The above "+e+" is totally normal. "+t)}r(A,a.EventEmitter),A.prototype.addListener=function(e,t,n,r){if(!this._listeners[t]){var i=this,o=!1;this._listeners[t]=s,this.on(e,s)}function s(){if(i._listeners[t])if(o)o="waiting";else{o=!0;var e=m(r,["style","include_docs","attachments","conflicts","filter","doc_ids","view","since","query_params","binary","return_docs"]);n.changes(e).on("change",function(e){e.seq>r.since&&!r.cancelled&&(r.since=e.seq,r.onChange(e))}).on("complete",function(){"waiting"===o&&T(s),o=!1}).on("error",function(){o=!1})}}},A.prototype.removeListener=function(e,t){t in this._listeners&&(a.EventEmitter.prototype.removeListener.call(this,e,this._listeners[t]),delete this._listeners[t])},A.prototype.notifyLocalWindows=function(e){j()&&(localStorage[e]="a"===localStorage[e]?"b":"a")},A.prototype.notify=function(e){this.emit(e),this.notifyLocalWindows(e)};var C="function"==typeof Object.assign?Object.assign:function(e){for(var t=Object(e),n=1;n<arguments.length;n++){var r=arguments[n];if(null!=r)for(var i in r)Object.prototype.hasOwnProperty.call(r,i)&&(t[i]=r[i])}return t};function P(e,t,n){Error.call(this,n),this.status=e,this.name=t,this.message=n,this.error=!0}r(P,Error),P.prototype.toString=function(){return JSON.stringify({status:this.status,name:this.name,message:this.message,reason:this.reason})};new P(401,"unauthorized","Name or password is incorrect.");var L=new P(400,"bad_request","Missing JSON list of 'docs'"),D=new P(404,"not_found","missing"),$=new P(409,"conflict","Document update conflict"),I=new P(400,"bad_request","_id field must contain a string"),B=new P(412,"missing_id","_id is required for puts"),N=new P(400,"bad_request","Only reserved document ids may start with underscore."),U=(new P(412,"precondition_failed","Database not open"),new P(500,"unknown_error","Database encountered an unknown error")),F=new P(500,"badarg","Some query argument is invalid"),K=(new P(400,"invalid_request","Request was invalid"),new P(400,"query_parse_error","Some query parameter is invalid")),J=new P(500,"doc_validation","Bad special document member"),z=new P(400,"bad_request","Something wrong with the request"),V=new P(400,"bad_request","Document must be a JSON object"),G=(new P(404,"not_found","Database not found"),new P(500,"indexed_db_went_bad","unknown")),Q=(new P(500,"web_sql_went_bad","unknown"),new P(500,"levelDB_went_went_bad","unknown"),new P(403,"forbidden","Forbidden by design doc validate_doc_update function"),new P(400,"bad_request","Invalid rev format")),W=(new P(412,"file_exists","The database could not be created, the file already exists."),new P(412,"missing_stub","A pre-existing attachment stub wasn't found"));new P(413,"invalid_url","Provided URL is invalid");function Y(n,e){function t(e){for(var t in n)"function"!=typeof n[t]&&(this[t]=n[t]);void 0!==e&&(this.reason=e)}return t.prototype=P.prototype,new t(e)}function H(e){if("object"!=typeof e){var t=e;(e=U).data=t}return"error"in e&&"conflict"===e.error&&(e.name="conflict",e.status=409),"name"in e||(e.name=e.error||"unknown"),"status"in e||(e.status=500),"message"in e||(e.message=e.message||e.reason),e}function X(r){var i={},o=r.filter&&"function"==typeof r.filter;return i.query=r.query_params,function(e){e.doc||(e.doc={});var t=o&&function(e,t,n){try{return!e(t,n)}catch(e){var r="Filter function threw: "+e.toString();return Y(z,r)}}(r.filter,e.doc,i);if("object"==typeof t)return t;if(t)return!1;if(r.include_docs){if(!r.attachments)for(var n in e.doc._attachments)e.doc._attachments.hasOwnProperty(n)&&(e.doc._attachments[n].stub=!0)}else delete e.doc;return!0}}function Z(e){for(var t=[],n=0,r=e.length;n<r;n++)t=t.concat(e[n]);return t}function ee(e){var t;if(e?"string"!=typeof e?t=Y(I):/^_/.test(e)&&!/^_(design|local)/.test(e)&&(t=Y(N)):t=Y(B),t)throw t}function te(e){return"boolean"==typeof e._remote?e._remote:"function"==typeof e.type&&(E("warn","db.type() is deprecated and will be removed in a future version of PouchDB"),"http"===e.type())}function ne(e){if(!e)return null;var t=e.split("/");return 2===t.length?t:1===t.length?[e,e]:null}function re(e){var t=ne(e);return t?t.join("/"):null}var ie=["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],oe="queryKey",se=/(?:^|&)([^&=]*)=?([^&]*)/g,ae=/^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/;function ue(e){for(var t=ae.exec(e),r={},n=14;n--;){var i=ie[n],o=t[n]||"",s=-1!==["user","password"].indexOf(i);r[i]=s?decodeURIComponent(o):o}return r[oe]={},r[ie[12]].replace(se,function(e,t,n){t&&(r[oe][t]=n)}),r}function ce(e,t){var n=[],r=[];for(var i in t)t.hasOwnProperty(i)&&(n.push(i),r.push(t[i]));return n.push(e),Function.apply(null,n).apply(null,r)}function fe(c,f,l){return new Promise(function(a,u){c.get(f,function(e,t){if(e){if(404!==e.status)return u(e);t={}}var n,r,i,o=t._rev,s=l(t);if(!s)return a({updated:!1,rev:o});s._id=f,s._rev=o,a((r=s,i=l,(n=c).put(r).then(function(e){return{updated:!0,rev:e.rev}},function(e){if(409!==e.status)throw e;return fe(n,r._id,i)})))})})}var le=function(e){return atob(e)},de=function(e){return btoa(e)};function he(t,n){t=t||[],n=n||{};try{return new Blob(t,n)}catch(e){if("TypeError"!==e.name)throw e;for(var r=new("undefined"!=typeof BlobBuilder?BlobBuilder:"undefined"!=typeof MSBlobBuilder?MSBlobBuilder:"undefined"!=typeof MozBlobBuilder?MozBlobBuilder:WebKitBlobBuilder),i=0;i<t.length;i+=1)r.append(t[i]);return r.getBlob(n.type)}}function pe(e,t){return he([function(e){for(var t=e.length,n=new ArrayBuffer(t),r=new Uint8Array(n),i=0;i<t;i++)r[i]=e.charCodeAt(i);return n}(e)],{type:t})}function ve(e,t){return pe(le(e),t)}function ye(e,n){var t=new FileReader,r="function"==typeof t.readAsBinaryString;t.onloadend=function(e){var t=e.target.result||"";if(r)return n(t);n(function(e){for(var t="",n=new Uint8Array(e),r=n.byteLength,i=0;i<r;i++)t+=String.fromCharCode(n[i]);return t}(t))},r?t.readAsBinaryString(e):t.readAsArrayBuffer(e)}function _e(e,t){ye(e,function(e){t(e)})}function ge(e,t){_e(e,function(e){t(de(e))})}var me=e.setImmediate||e.setTimeout,be=32768;function we(t,e,n,r,i){var o,s,a,u,c,f;(0<n||r<e.size)&&(s=n,a=r,e=(o=e).webkitSlice?o.webkitSlice(s,a):o.slice(s,a)),u=e,c=function(e){t.append(e),i()},(f=new FileReader).onloadend=function(e){var t=e.target.result||new ArrayBuffer(0);c(t)},f.readAsArrayBuffer(u)}function ke(e,t,n,r,i){(0<n||r<t.length)&&(t=t.substring(n,r)),e.appendBinary(t),i()}function je(n,r){var e="string"==typeof n,t=e?n.length:n.size,i=Math.min(be,t),o=Math.ceil(t/i),s=0,a=e?new d:new d.ArrayBuffer,u=e?ke:we;function c(){me(l)}function f(){var e=a.end(!0),t=de(e);r(t),a.destroy()}function l(){var e=s*i,t=e+i;u(a,n,e,t,++s<o?c:f)}l()}function Oe(e){return d.hash(e)}function qe(e,t){var n=R(e);return t?(delete n._rev_tree,Oe(JSON.stringify(n))):i.v4().replace(/-/g,"").toLowerCase()}var Ae=i.v4;function Ee(e){for(var t,n,r,i,o=e.rev_tree.slice();i=o.pop();){var s=i.ids,a=s[2],u=i.pos;if(a.length)for(var c=0,f=a.length;c<f;c++)o.push({pos:u+1,ids:a[c]});else{var l=!!s[1].deleted,d=s[0];t&&!(r!==l?r:n!==u?n<u:t<d)||(t=d,n=u,r=l)}}return n+"-"+t}function Se(e,t){for(var n,r=e.slice();n=r.pop();)for(var i=n.pos,o=n.ids,s=o[2],a=t(0===s.length,i,o[0],n.ctx,o[1]),u=0,c=s.length;u<c;u++)r.push({pos:i+1,ids:s[u],ctx:a})}function xe(e,t){return e.pos-t.pos}function Ce(e){var o=[];Se(e,function(e,t,n,r,i){e&&o.push({rev:t+"-"+n,pos:t,opts:i})}),o.sort(xe).reverse();for(var t=0,n=o.length;t<n;t++)delete o[t].pos;return o}function Pe(e){for(var t=Ee(e),n=Ce(e.rev_tree),r=[],i=0,o=n.length;i<o;i++){var s=n[i];s.rev===t||s.opts.deleted||r.push(s.rev)}return r}function Le(e){for(var t,n=[],r=e.slice();t=r.pop();){var i=t.pos,o=t.ids,s=o[0],a=o[1],u=o[2],c=0===u.length,f=t.history?t.history.slice():[];f.push({id:s,opts:a}),c&&n.push({pos:i+1-f.length,ids:f});for(var l=0,d=u.length;l<d;l++)r.push({pos:i+1,ids:u[l],history:f})}return n.reverse()}function De(e,t){return e.pos-t.pos}function $e(e,t){for(var n,r,i=t,o=e.length;i<o;i++){var s=e[i],a=[s.id,s.opts,[]];r?(r[2].push(a),r=a):n=r=a}return n}function Ie(e,t){return e[0]<t[0]?-1:1}function Be(e,t){for(var n,r,i,o=[{tree1:e,tree2:t}],s=!1;0<o.length;){var a=o.pop(),u=a.tree1,c=a.tree2;(u[1].status||c[1].status)&&(u[1].status="available"===u[1].status||"available"===c[1].status?"available":"missing");for(var f=0;f<c[2].length;f++)if(u[2][0]){for(var l=!1,d=0;d<u[2].length;d++)u[2][d][0]===c[2][f][0]&&(o.push({tree1:u[2][d],tree2:c[2][f]}),l=!0);l||(s="new_branch",n=u[2],r=c[2][f],void 0,i=function(e,t,n){for(var r,i=0,o=e.length;i<o;)n(e[r=i+o>>>1],t)<0?i=r+1:o=r;return i}(n,r,Ie),n.splice(i,0,r))}else s="new_leaf",u[2][0]=c[2][f]}return{conflicts:s,tree:e}}function Te(e,t,n){var r,i=[],o=!1,s=!1;if(!e.length)return{tree:[t],conflicts:"new_leaf"};for(var a=0,u=e.length;a<u;a++){var c=e[a];if(c.pos===t.pos&&c.ids[0]===t.ids[0])r=Be(c.ids,t.ids),i.push({pos:c.pos,ids:r.tree}),o=o||r.conflicts,s=!0;else if(!0!==n){var f=c.pos<t.pos?c:t,l=c.pos<t.pos?t:c,d=l.pos-f.pos,h=[],p=[];for(p.push({ids:f.ids,diff:d,parent:null,parentIdx:null});0<p.length;){var v=p.pop();if(0!==v.diff)for(var y=v.ids[2],_=0,g=y.length;_<g;_++)p.push({ids:y[_],diff:v.diff-1,parent:v.ids,parentIdx:_});else v.ids[0]===l.ids[0]&&h.push(v)}var m=h[0];m?(r=Be(m.ids,l.ids),m.parent[2][m.parentIdx]=r.tree,i.push({pos:f.pos,ids:f.ids}),o=o||r.conflicts,s=!0):i.push(c)}else i.push(c)}return s||i.push(t),i.sort(De),{tree:i,conflicts:o||"internal_node"}}function Re(e,t,n){var r=Te(e,t),i=function(e,t){for(var r,n,i=Le(e),o=0,s=i.length;o<s;o++){var a,u=i[o],c=u.ids;if(c.length>t){r||(r={});var f=c.length-t;a={pos:u.pos+f,ids:$e(c,f)};for(var l=0;l<f;l++){var d=u.pos+l+"-"+c[l].id;r[d]=!0}}else a={pos:u.pos,ids:$e(c,0)};n=n?Te(n,a,!0).tree:[a]}return r&&Se(n,function(e,t,n){delete r[t+"-"+n]}),{tree:n,revs:r?Object.keys(r):[]}}(r.tree,n);return{tree:i.tree,stemmedRevs:i.revs,conflicts:r.conflicts}}function Me(e){return e.ids}function Ne(e,t){t||(t=Ee(e));for(var n,r=t.substring(t.indexOf("-")+1),i=e.rev_tree.map(Me);n=i.pop();){if(n[0]===r)return!!n[1].deleted;i=i.concat(n[2])}}function Ue(e){return/^_local/.test(e)}function Fe(i,t,n){a.EventEmitter.call(this);var o=this;this.db=i;var r=(t=t?R(t):{}).complete=l(function(e,t){var n,r;e?(r="error",0<("listenerCount"in(n=o)?n.listenerCount(r):a.EventEmitter.listenerCount(n,r))&&o.emit("error",e)):o.emit("complete",t),o.removeAllListeners(),i.removeListener("destroyed",s)});function s(){o.cancel()}n&&(o.on("complete",function(e){n(null,e)}),o.on("error",n)),i.once("destroyed",s),t.onChange=function(e,t,n){o.isCancelled||function(e,t,n,r){try{e.emit("change",t,n,r)}catch(e){E("error",'Error in .on("change", function):',e)}}(o,e,t,n)};var e=new Promise(function(n,r){t.complete=function(e,t){e?r(e):n(t)}});o.once("cancel",function(){i.removeListener("destroyed",s),t.complete(null,{status:"cancelled"})}),this.then=e.then.bind(e),this.catch=e.catch.bind(e),this.then(function(e){r(null,e)},r),i.taskqueue.isReady?o.validateChanges(t):i.taskqueue.addTask(function(e){e?t.complete(e):o.isCancelled?o.emit("cancel"):o.validateChanges(t)})}function Ke(e,t,n){var r=[{rev:e._rev}];"all_docs"===n.style&&(r=Ce(t.rev_tree).map(function(e){return{rev:e.rev}}));var i={id:t.id,changes:r,doc:e};return Ne(t,e._rev)&&(i.deleted=!0),n.conflicts&&(i.doc._conflicts=Pe(t),i.doc._conflicts.length||delete i.doc._conflicts),i}function Je(e,t){return e<t?-1:t<e?1:0}function ze(n,r){return function(e,t){e||t[0]&&t[0].error?((e=e||t[0]).docId=r,n(e)):n(null,t.length?t[0]:t)}}function Ve(e,t){var n=Je(e._id,t._id);return 0!==n?n:Je(e._revisions?e._revisions.start:0,t._revisions?t._revisions.start:0)}function Ge(){for(var e in a.EventEmitter.call(this),Ge.prototype)"function"==typeof this[e]&&(this[e]=this[e].bind(this))}function Qe(){this.isReady=!1,this.failed=!1,this.queue=[]}function We(e,t){if(!(this instanceof We))return new We(e,t);var n=this;if(t=t||{},e&&"object"==typeof e&&(e=(t=e).name,delete t.name),void 0===t.deterministic_revs&&(t.deterministic_revs=!0),this.__opts=t=R(t),n.auto_compaction=t.auto_compaction,n.prefix=We.prefix,"string"!=typeof e)throw new Error("Missing/invalid DB name");var r=function(e,t){var n=e.match(/([a-z-]*):\/\/(.*)/);if(n)return{name:/https?/.test(n[1])?n[1]+"://"+n[2]:n[2],adapter:n[1]};var r=We.adapters,i=We.preferredAdapters,o=We.prefix,s=t.adapter;if(!s)for(var a=0;a<i.length&&"idb"===(s=i[a])&&"websql"in r&&j()&&localStorage["_pouch__websqldb_"+o+e];++a)E("log",'PouchDB is downgrading "'+e+'" to WebSQL to avoid data loss, because it was already opened with WebSQL.');var u=r[s];return{name:u&&"use_prefix"in u&&!u.use_prefix?e:o+e,adapter:s}}((t.prefix||"")+e,t);if(t.name=r.name,t.adapter=t.adapter||r.adapter,n.name=e,n._adapter=t.adapter,We.emit("debug",["adapter","Picked adapter: ",t.adapter]),!We.adapters[t.adapter]||!We.adapters[t.adapter].valid())throw new Error("Invalid Adapter: "+t.adapter);Ge.call(n),n.taskqueue=new Qe,n.adapter=t.adapter,We.adapters[t.adapter].call(n,t,function(e){if(e)return n.taskqueue.fail(e);!function(t){function e(e){t.removeListener("closed",n),e||t.constructor.emit("destroyed",t.name)}function n(){t.removeListener("destroyed",e),t.constructor.emit("unref",t)}t.once("destroyed",e),t.once("closed",n),t.constructor.emit("ref",t)}(n),n.emit("created",n),We.emit("created",n.name),n.taskqueue.ready(n)})}r(Fe,a.EventEmitter),Fe.prototype.cancel=function(){this.isCancelled=!0,this.db.taskqueue.isReady&&this.emit("cancel")},Fe.prototype.validateChanges=function(t){var n=t.complete,r=this;We._changesFilterPlugin?We._changesFilterPlugin.validate(t,function(e){if(e)return n(e);r.doChanges(t)}):r.doChanges(t)},Fe.prototype.doChanges=function(t){var n=this,r=t.complete;if("live"in(t=R(t))&&!("continuous"in t)&&(t.continuous=t.live),t.processChange=Ke,"latest"===t.since&&(t.since="now"),t.since||(t.since=0),"now"!==t.since){if(We._changesFilterPlugin){if(We._changesFilterPlugin.normalize(t),We._changesFilterPlugin.shouldFilter(this,t))return We._changesFilterPlugin.filter(this,t)}else["doc_ids","filter","selector","view"].forEach(function(e){e in t&&E("warn",'The "'+e+'" option was passed in to changes/replicate, but pouchdb-changes-filter plugin is not installed, so it was ignored. Please install the plugin to enable filtering.')});"descending"in t||(t.descending=!1),t.limit=0===t.limit?1:t.limit,t.complete=r;var i=this.db._changes(t);if(i&&"function"==typeof i.cancel){var o=n.cancel;n.cancel=O(function(e){i.cancel(),o.apply(this,e)})}}else this.db.info().then(function(e){n.isCancelled?r(null,{status:"cancelled"}):(t.since=e.update_seq,n.doChanges(t))},r)},r(Ge,a.EventEmitter),Ge.prototype.post=h("post",function(e,t,n){if("function"==typeof t&&(n=t,t={}),"object"!=typeof e||Array.isArray(e))return n(Y(V));this.bulkDocs({docs:[e]},t,ze(n,e._id))}),Ge.prototype.put=h("put",function(n,t,r){if("function"==typeof t&&(r=t,t={}),"object"!=typeof n||Array.isArray(n))return r(Y(V));if(ee(n._id),Ue(n._id)&&"function"==typeof this._putLocal)return n._deleted?this._removeLocal(n,r):this._putLocal(n,r);var e,i,o,s,a=this;function u(e){"function"==typeof a._put&&!1!==t.new_edits?a._put(n,t,e):a.bulkDocs({docs:[n]},t,ze(e,n._id))}t.force&&n._rev?(e=n._rev.split("-"),i=e[1],o=parseInt(e[0],10)+1,s=qe(),n._revisions={start:o,ids:[s,i]},n._rev=o+"-"+s,t.new_edits=!1,u(function(e){var t=e?null:{ok:!0,id:n._id,rev:n._rev};r(e,t)})):u(r)}),Ge.prototype.putAttachment=h("putAttachment",function(t,n,r,i,o){var s=this;function a(e){var t="_rev"in e?parseInt(e._rev,10):0;return e._attachments=e._attachments||{},e._attachments[n]={content_type:o,data:i,revpos:++t},s.put(e)}return"function"==typeof o&&(o=i,i=r,r=null),void 0===o&&(o=i,i=r,r=null),o||E("warn","Attachment",n,"on document",t,"is missing content_type"),s.get(t).then(function(e){if(e._rev!==r)throw Y($);return a(e)},function(e){if(e.reason===D.message)return a({_id:t});throw e})}),Ge.prototype.removeAttachment=h("removeAttachment",function(e,n,r,i){var o=this;o.get(e,function(e,t){if(e)i(e);else if(t._rev===r){if(!t._attachments)return i();delete t._attachments[n],0===Object.keys(t._attachments).length&&delete t._attachments,o.put(t,i)}else i(Y($))})}),Ge.prototype.remove=h("remove",function(e,t,n,r){var i;"string"==typeof t?(i={_id:e,_rev:t},"function"==typeof n&&(r=n,n={})):(i=e,"function"==typeof t?(r=t,n={}):(r=n,n=t)),(n=n||{}).was_delete=!0;var o={_id:i._id,_rev:i._rev||n.rev,_deleted:!0};if(Ue(o._id)&&"function"==typeof this._removeLocal)return this._removeLocal(i,r);this.bulkDocs({docs:[o]},n,ze(r,o._id))}),Ge.prototype.revsDiff=h("revsDiff",function(o,e,s){"function"==typeof e&&(s=e,e={});var c=Object.keys(o);if(!c.length)return s(null,{});var f=0,l=new x;function d(e,t){l.has(e)||l.set(e,{missing:[]}),l.get(e).missing.push(t)}c.map(function(i){this._getRevisionTree(i,function(e,t){if(e&&404===e.status&&"missing"===e.message)l.set(i,{missing:o[i]});else{if(e)return s(e);n=t,u=o[a=i].slice(0),Se(n,function(e,t,n,r,i){var o=t+"-"+n,s=u.indexOf(o);-1!==s&&(u.splice(s,1),"available"!==i.status&&d(a,o))}),u.forEach(function(e){d(a,e)})}var a,n,u;if(++f===c.length){var r={};return l.forEach(function(e,t){r[t]=e}),s(null,r)}})},this)}),Ge.prototype.bulkGet=h("bulkGet",function(e,t){k(this,e,t)}),Ge.prototype.compactDocument=h("compactDocument",function(r,i,c){var f=this;this._getRevisionTree(r,function(e,t){if(e)return c(e);var o,s,n=(o={},s=[],Se(t,function(e,t,n,r){var i=t+"-"+n;return e&&(o[i]=0),void 0!==r&&s.push({from:r,to:i}),i}),s.reverse(),s.forEach(function(e){void 0===o[e.from]?o[e.from]=1+o[e.to]:o[e.from]=Math.min(o[e.from],1+o[e.to])}),o),a=[],u=[];Object.keys(n).forEach(function(e){n[e]>i&&a.push(e)}),Se(t,function(e,t,n,r,i){var o=t+"-"+n;"available"===i.status&&-1!==a.indexOf(o)&&u.push(o)}),f._doCompaction(r,u,c)})}),Ge.prototype.compact=h("compact",function(e,t){"function"==typeof e&&(t=e,e={});var n=this;e=e||{},n._compactionQueue=n._compactionQueue||[],n._compactionQueue.push({opts:e,callback:t}),1===n._compactionQueue.length&&function n(r){var e=r._compactionQueue[0],t=e.opts,i=e.callback;r.get("_local/compaction").catch(function(){return!1}).then(function(e){e&&e.last_seq&&(t.last_seq=e.last_seq),r._compact(t,function(e,t){e?i(e):i(null,t),T(function(){r._compactionQueue.shift(),r._compactionQueue.length&&n(r)})})})}(n)}),Ge.prototype._compact=function(e,n){var r=this,t={return_docs:!1,last_seq:e.last_seq||0},i=[];r.changes(t).on("change",function(e){i.push(r.compactDocument(e.id,0))}).on("complete",function(e){var t=e.last_seq;Promise.all(i).then(function(){return fe(r,"_local/compaction",function(e){return(!e.last_seq||e.last_seq<t)&&(e.last_seq=t,e)})}).then(function(){n(null,{ok:!0})}).catch(n)}).on("error",n)},Ge.prototype.get=h("get",function(b,w,k){if("function"==typeof w&&(k=w,w={}),"string"!=typeof b)return k(Y(I));if(Ue(b)&&"function"==typeof this._getLocal)return this._getLocal(b,k);var n=[],j=this;function r(){var s=[],a=n.length;if(!a)return k(null,s);n.forEach(function(o){j.get(b,{rev:o,revs:w.revs,latest:w.latest,attachments:w.attachments,binary:w.binary},function(e,t){if(e)s.push({missing:o});else{for(var n,r=0,i=s.length;r<i;r++)if(s[r].ok&&s[r].ok._rev===t._rev){n=!0;break}n||s.push({ok:t})}--a||k(null,s)})})}if(!w.open_revs)return this._get(b,w,function(e,t){if(e)return e.docId=b,k(e);var i=t.doc,n=t.metadata,o=t.ctx;if(w.conflicts){var r=Pe(n);r.length&&(i._conflicts=r)}if(Ne(n,i._rev)&&(i._deleted=!0),w.revs||w.revs_info){for(var s=i._rev.split("-"),a=parseInt(s[0],10),u=s[1],c=Le(n.rev_tree),f=null,l=0;l<c.length;l++){var d=c[l],h=d.ids.map(function(e){return e.id}).indexOf(u);(h===a-1||!f&&-1!==h)&&(f=d)}var p=f.ids.map(function(e){return e.id}).indexOf(i._rev.split("-")[1])+1,v=f.ids.length-p;if(f.ids.splice(p,v),f.ids.reverse(),w.revs&&(i._revisions={start:f.pos+f.ids.length-1,ids:f.ids.map(function(e){return e.id})}),w.revs_info){var y=f.pos+f.ids.length;i._revs_info=f.ids.map(function(e){return{rev:--y+"-"+e.id,status:e.opts.status}})}}if(w.attachments&&i._attachments){var _=i._attachments,g=Object.keys(_).length;if(0===g)return k(null,i);Object.keys(_).forEach(function(r){this._getAttachment(i._id,r,_[r],{rev:i._rev,binary:w.binary,ctx:o},function(e,t){var n=i._attachments[r];n.data=t,delete n.stub,delete n.length,--g||k(null,i)})},j)}else{if(i._attachments)for(var m in i._attachments)i._attachments.hasOwnProperty(m)&&(i._attachments[m].stub=!0);k(null,i)}});if("all"===w.open_revs)this._getRevisionTree(b,function(e,t){if(e)return k(e);n=Ce(t).map(function(e){return e.rev}),r()});else{if(!Array.isArray(w.open_revs))return k(Y(U,"function_clause"));n=w.open_revs;for(var e=0;e<n.length;e++){var t=n[e];if("string"!=typeof t||!/^\d+-/.test(t))return k(Y(Q))}r()}}),Ge.prototype.getAttachment=h("getAttachment",function(n,r,i,o){var s=this;i instanceof Function&&(o=i,i={}),this._get(n,i,function(e,t){return e?o(e):t.doc._attachments&&t.doc._attachments[r]?(i.ctx=t.ctx,i.binary=!0,void s._getAttachment(n,r,t.doc._attachments[r],i,o)):o(Y(D))})}),Ge.prototype.allDocs=h("allDocs",function(t,e){if("function"==typeof t&&(e=t,t={}),t.skip=void 0!==t.skip?t.skip:0,t.start_key&&(t.startkey=t.start_key),t.end_key&&(t.endkey=t.end_key),"keys"in t){if(!Array.isArray(t.keys))return e(new TypeError("options.keys must be an array"));var n=["startkey","endkey","key"].filter(function(e){return e in t})[0];if(n)return void e(Y(K,"Query parameter `"+n+"` is not compatible with multi-get"));if(!te(this)&&(i="limit"in(r=t)?r.keys.slice(r.skip,r.limit+r.skip):0<r.skip?r.keys.slice(r.skip):r.keys,r.keys=i,r.skip=0,delete r.limit,r.descending&&(i.reverse(),r.descending=!1),0===t.keys.length))return this._allDocs({limit:0},e)}var r,i;return this._allDocs(t,e)}),Ge.prototype.changes=function(e,t){return"function"==typeof e&&(t=e,e={}),(e=e||{}).return_docs="return_docs"in e?e.return_docs:!e.live,new Fe(this,e,t)},Ge.prototype.close=h("close",function(e){return this._closed=!0,this.emit("closed"),this._close(e)}),Ge.prototype.info=h("info",function(n){var r=this;this._info(function(e,t){if(e)return n(e);t.db_name=t.db_name||r.name,t.auto_compaction=!(!r.auto_compaction||te(r)),t.adapter=r.adapter,n(null,t)})}),Ge.prototype.id=h("id",function(e){return this._id(e)}),Ge.prototype.type=function(){return"function"==typeof this._type?this._type():this.adapter},Ge.prototype.bulkDocs=h("bulkDocs",function(e,i,o){if("function"==typeof i&&(o=i,i={}),i=i||{},Array.isArray(e)&&(e={docs:e}),!e||!e.docs||!Array.isArray(e.docs))return o(Y(L));for(var t=0;t<e.docs.length;++t)if("object"!=typeof e.docs[t]||Array.isArray(e.docs[t]))return o(Y(V));var r;if(e.docs.forEach(function(n){n._attachments&&Object.keys(n._attachments).forEach(function(e){var t;r=r||"_"===(t=e).charAt(0)&&t+" is not a valid attachment name, attachment names cannot start with '_'",n._attachments[e].content_type||E("warn","Attachment",e,"on document",n._id,"is missing content_type")})}),r)return o(Y(z,r));"new_edits"in i||(i.new_edits=!("new_edits"in e)||e.new_edits);var s=this;i.new_edits||te(s)||e.docs.sort(Ve),function(e){for(var t=0;t<e.length;t++){var n=e[t];if(n._deleted)delete n._attachments;else if(n._attachments)for(var r=Object.keys(n._attachments),i=0;i<r.length;i++){var o=r[i];n._attachments[o]=m(n._attachments[o],["data","digest","content_type","length","revpos","stub"])}}}(e.docs);var a=e.docs.map(function(e){return e._id});return this._bulkDocs(e,i,function(e,t){if(e)return o(e);if(i.new_edits||(t=t.filter(function(e){return e.error})),!te(s))for(var n=0,r=t.length;n<r;n++)t[n].id=t[n].id||a[n];o(null,t)})}),Ge.prototype.registerDependentDatabase=h("registerDependentDatabase",function(t,e){var n=new this.constructor(t,this.__opts);fe(this,"_local/_pouch_dependentDbs",function(e){return e.dependentDbs=e.dependentDbs||{},!e.dependentDbs[t]&&(e.dependentDbs[t]=!0,e)}).then(function(){e(null,{db:n})}).catch(e)}),Ge.prototype.destroy=h("destroy",function(e,o){"function"==typeof e&&(o=e,e={});var s=this,a=!("use_prefix"in s)||s.use_prefix;function u(){s._destroy(e,function(e,t){if(e)return o(e);s._destroyed=!0,s.emit("destroyed"),o(null,t||{ok:!0})})}if(te(s))return u();s.get("_local/_pouch_dependentDbs",function(e,t){if(e)return 404!==e.status?o(e):u();var n=t.dependentDbs,r=s.constructor,i=Object.keys(n).map(function(e){var t=a?e.replace(new RegExp("^"+r.prefix),""):e;return new r(t,s.__opts).destroy()});Promise.all(i).then(u,o)})}),Qe.prototype.execute=function(){var e;if(this.failed)for(;e=this.queue.shift();)e(this.failed);else for(;e=this.queue.shift();)e()},Qe.prototype.fail=function(e){this.failed=e,this.execute()},Qe.prototype.ready=function(e){this.isReady=!0,this.db=e,this.execute()},Qe.prototype.addTask=function(e){this.queue.push(e),this.failed&&this.execute()},r(We,Ge);var Ye="undefined"!=typeof AbortController?AbortController:function(){return{abort:function(){}}},He=fetch,Xe=Headers;We.adapters={},We.preferredAdapters=[],We.prefix="_pouch_";var Ze=new a.EventEmitter;!function(t){Object.keys(a.EventEmitter.prototype).forEach(function(e){"function"==typeof a.EventEmitter.prototype[e]&&(t[e]=Ze[e].bind(Ze))});var r=t._destructionListeners=new x;t.on("ref",function(e){r.has(e.name)||r.set(e.name,[]),r.get(e.name).push(e)}),t.on("unref",function(e){if(r.has(e.name)){var t=r.get(e.name),n=t.indexOf(e);n<0||(t.splice(n,1),1<t.length?r.set(e.name,t):r.delete(e.name))}}),t.on("destroyed",function(e){if(r.has(e)){var t=r.get(e);r.delete(e),t.forEach(function(e){e.emit("destroyed",!0)})}})}(We),We.adapter=function(e,t,n){t.valid()&&(We.adapters[e]=t,n&&We.preferredAdapters.push(e))},We.plugin=function(t){if("function"==typeof t)t(We);else{if("object"!=typeof t||0===Object.keys(t).length)throw new Error('Invalid plugin: got "'+t+'", expected an object or a function');Object.keys(t).forEach(function(e){We.prototype[e]=t[e]})}return this.__defaults&&(We.__defaults=C({},this.__defaults)),We},We.defaults=function(e){function n(e,t){if(!(this instanceof n))return new n(e,t);t=t||{},e&&"object"==typeof e&&(e=(t=e).name,delete t.name),t=C({},n.__defaults,t),We.call(this,e,t)}return r(n,We),n.preferredAdapters=We.preferredAdapters.slice(),Object.keys(We).forEach(function(e){e in n||(n[e]=We[e])}),n.__defaults=C({},this.__defaults,e),n},We.fetch=function(e,t){return He(e,t)};function et(e,t){for(var n=e,r=0,i=t.length;r<i;r++){if(!(n=n[t[r]]))break}return n}function tt(e){for(var t=[],n="",r=0,i=e.length;r<i;r++){var o=e[r];"."===o?0<r&&"\\"===e[r-1]?n=n.substring(0,n.length-1)+".":(t.push(n),n=""):n+=o}return t.push(n),t}var nt=["$or","$nor","$not"];function rt(e){return-1<nt.indexOf(e)}function it(e){return Object.keys(e)[0]}function ot(e){var n={};return e.forEach(function(t){Object.keys(t).forEach(function(e){var s=t[e];if("object"!=typeof s&&(s={$eq:s}),rt(e))s instanceof Array?n[e]=s.map(function(e){return ot([e])}):n[e]=ot([s]);else{var a=n[e]=n[e]||{};Object.keys(s).forEach(function(e){var t,n,r,i,o=s[e];return"$gt"===e||"$gte"===e?function(e,t,n){if(void 0!==n.$eq)return;void 0!==n.$gte?"$gte"===e?t>n.$gte&&(n.$gte=t):t>=n.$gte&&(delete n.$gte,n.$gt=t):void 0!==n.$gt?"$gte"===e?t>n.$gt&&(delete n.$gt,n.$gte=t):t>n.$gt&&(n.$gt=t):n[e]=t}(e,o,a):"$lt"===e||"$lte"===e?function(e,t,n){if(void 0!==n.$eq)return;void 0!==n.$lte?"$lte"===e?t<n.$lte&&(n.$lte=t):t<=n.$lte&&(delete n.$lte,n.$lt=t):void 0!==n.$lt?"$lte"===e?t<n.$lt&&(delete n.$lt,n.$lte=t):t<n.$lt&&(n.$lt=t):n[e]=t}(e,o,a):"$ne"===e?(r=o,void("$ne"in(i=a)?i.$ne.push(r):i.$ne=[r])):"$eq"===e?(t=o,delete(n=a).$gt,delete n.$gte,delete n.$lt,delete n.$lte,delete n.$ne,void(n.$eq=t)):void(a[e]=o)})}})}),n}var st=-324,at=3,ut="";function ct(e,t){if(e===t)return 0;e=ft(e),t=ft(t);var n,r,i=vt(e),o=vt(t);if(i-o!=0)return i-o;switch(typeof e){case"number":return e-t;case"boolean":return e<t?-1:1;case"string":return(n=e)===(r=t)?0:r<n?1:-1}return Array.isArray(e)?function(e,t){for(var n=Math.min(e.length,t.length),r=0;r<n;r++){var i=ct(e[r],t[r]);if(0!==i)return i}return e.length===t.length?0:e.length>t.length?1:-1}(e,t):function(e,t){for(var n=Object.keys(e),r=Object.keys(t),i=Math.min(n.length,r.length),o=0;o<i;o++){var s=ct(n[o],r[o]);if(0!==s)return s;if(0!==(s=ct(e[n[o]],t[r[o]])))return s}return n.length===r.length?0:n.length>r.length?1:-1}(e,t)}function ft(e){switch(typeof e){case"undefined":return null;case"number":return e===1/0||e===-1/0||isNaN(e)?null:e;case"object":var t=e;if(Array.isArray(e)){var n=e.length;e=new Array(n);for(var r=0;r<n;r++)e[r]=ft(t[r])}else{if(e instanceof Date)return e.toJSON();if(null!==e)for(var i in e={},t)if(t.hasOwnProperty(i)){var o=t[i];void 0!==o&&(e[i]=ft(o))}}}return e}function lt(e){if(null!==e)switch(typeof e){case"boolean":return e?1:0;case"number":return function(e){if(0===e)return"1";var t=e.toExponential().split(/e\+?/),n=parseInt(t[1],10),r=e<0,i=r?"0":"2",o=(s=((r?-n:n)-st).toString(),a="0",u=at,function(e,t,n){for(var r="",i=n-e.length;r.length<i;)r+=t;return r}(s,a,u)+s);var s,a,u;i+=ut+o;var c=Math.abs(parseFloat(t[0]));r&&(c=10-c);var f=c.toFixed(20);return f=f.replace(/\.?0+$/,""),i+=ut+f}(e);case"string":return e.replace(/\u0002/g,"").replace(/\u0001/g,"").replace(/\u0000/g,"");case"object":var t=Array.isArray(e),n=t?e:Object.keys(e),r=-1,i=n.length,o="";if(t)for(;++r<i;)o+=dt(n[r]);else for(;++r<i;){var s=n[r];o+=dt(s)+dt(e[s])}return o}return""}function dt(e){return vt(e=ft(e))+ut+lt(e)+"\0"}function ht(e,t){var n,r=t;if("1"===e[t])n=0,t++;else{var i="0"===e[t];t++;var o="",s=e.substring(t,t+at),a=parseInt(s,10)+st;for(i&&(a=-a),t+=at;;){var u=e[t];if("\0"===u)break;o+=u,t++}n=1===(o=o.split(".")).length?parseInt(o,10):parseFloat(o[0]+"."+o[1]),i&&(n-=10),0!==a&&(n=parseFloat(n+"e"+a))}return{num:n,length:t-r}}function pt(e,t){var n=e.pop();if(t.length){var r=t[t.length-1];n===r.element&&(t.pop(),r=t[t.length-1]);var i=r.element,o=r.index;if(Array.isArray(i))i.push(n);else if(o===e.length-2){i[e.pop()]=n}else e.push(n)}}function vt(e){var t=["boolean","number","string","object"].indexOf(typeof e);return~t?null===e?1:Array.isArray(e)?5:t<3?t+2:t+3:Array.isArray(e)?5:void 0}function yt(e,t,n){if(e=e.filter(function(e){return _t(e.doc,t.selector,n)}),t.sort){var r=function(e){function o(n){return e.map(function(e){var t=tt(it(e));return et(n,t)})}return function(e,t){var n,r,i=ct(o(e.doc),o(t.doc));return 0!==i?i:(n=e.doc._id,r=t.doc._id,n<r?-1:r<n?1:0)}}(t.sort);e=e.sort(r),"string"!=typeof t.sort[0]&&"desc"===(i=t.sort[0])[it(i)]&&(e=e.reverse())}var i;if("limit"in t||"skip"in t){var o=t.skip||0,s=("limit"in t?t.limit:e.length)+o;e=e.slice(o,s)}return e}function _t(i,o,e){return e.every(function(e){var t=o[e],n=tt(e),r=et(i,n);return rt(e)?function(e,t,n){if("$or"===e)return t.some(function(e){return _t(n,e,Object.keys(e))});if("$not"===e)return!_t(n,t,Object.keys(t));return!t.find(function(e){return _t(n,e,Object.keys(e))})}(e,t,i):gt(t,i,n,r)})}function gt(n,r,i,o){return!n||Object.keys(n).every(function(e){var t=n[e];return function(e,t,n,r,i){if(!kt[e])throw new Error('unknown operator "'+e+'" - should be one of $eq, $lte, $lt, $gt, $gte, $exists, $ne, $in, $nin, $size, $mod, $regex, $elemMatch, $type, $allMatch or $all');return kt[e](t,n,r,i)}(e,r,t,i,o)})}function mt(e){return null!=e}function bt(e){return void 0!==e}function wt(t,e){return e.some(function(e){return t instanceof Array?-1<t.indexOf(e):t===e})}var kt={$elemMatch:function(t,n,r,e){return!!Array.isArray(e)&&(0!==e.length&&("object"==typeof e[0]?e.some(function(e){return _t(e,n,Object.keys(n))}):e.some(function(e){return gt(n,t,r,e)})))},$allMatch:function(t,n,r,e){return!!Array.isArray(e)&&(0!==e.length&&("object"==typeof e[0]?e.every(function(e){return _t(e,n,Object.keys(n))}):e.every(function(e){return gt(n,t,r,e)})))},$eq:function(e,t,n,r){return bt(r)&&0===ct(r,t)},$gte:function(e,t,n,r){return bt(r)&&0<=ct(r,t)},$gt:function(e,t,n,r){return bt(r)&&0<ct(r,t)},$lte:function(e,t,n,r){return bt(r)&&ct(r,t)<=0},$lt:function(e,t,n,r){return bt(r)&&ct(r,t)<0},$exists:function(e,t,n,r){return t?bt(r):!bt(r)},$mod:function(e,t,n,r){return mt(r)&&function(e,t){var n=t[0],r=t[1];if(0===n)throw new Error("Bad divisor, cannot divide by zero");if(parseInt(n,10)!==n)throw new Error("Divisor is not an integer");if(parseInt(r,10)!==r)throw new Error("Modulus is not an integer");return parseInt(e,10)===e&&e%n===r}(r,t)},$ne:function(e,t,n,r){return t.every(function(e){return 0!==ct(r,e)})},$in:function(e,t,n,r){return mt(r)&&wt(r,t)},$nin:function(e,t,n,r){return mt(r)&&!wt(r,t)},$size:function(e,t,n,r){return mt(r)&&(i=t,r.length===i);var i},$all:function(e,t,n,r){return Array.isArray(r)&&(i=r,t.every(function(e){return-1<i.indexOf(e)}));var i},$regex:function(e,t,n,r){return mt(r)&&(i=r,new RegExp(t).test(i));var i},$type:function(e,t,n,r){return function(e,t){switch(t){case"null":return null===e;case"boolean":return"boolean"==typeof e;case"number":return"number"==typeof e;case"string":return"string"==typeof e;case"array":return e instanceof Array;case"object":return"[object Object]"==={}.toString.call(e)}throw new Error(t+" not supported as a type.Please use one of object, string, array, number, boolean or null.")}(r,t)}};function jt(e,t){if("object"!=typeof t)throw new Error("Selector error: expected a JSON object");var n=yt([{doc:e}],{selector:t=function(e){var t=R(e),n=!1;"$and"in t&&(t=ot(t.$and),n=!0),["$or","$nor"].forEach(function(e){e in t&&t[e].forEach(function(e){for(var t=Object.keys(e),n=0;n<t.length;n++){var r=t[n],i=e[r];"object"==typeof i&&null!==i||(e[r]={$eq:i})}})}),"$not"in t&&(t.$not=ot([t.$not]));for(var r=Object.keys(t),i=0;i<r.length;i++){var o=r[i],s=t[o];"object"!=typeof s||null===s?s={$eq:s}:"$ne"in s&&!n&&(s.$ne=[s.$ne]),t[o]=s}return t}(t)},Object.keys(t));return n&&1===n.length}function Ot(e,t){if(e.selector&&e.filter&&"_selector"!==e.filter){var n="string"==typeof e.filter?e.filter:"function";return t(new Error('selector invalid for filter "'+n+'"'))}t()}function qt(e){e.view&&!e.filter&&(e.filter="_view"),e.selector&&!e.filter&&(e.filter="_selector"),e.filter&&"string"==typeof e.filter&&("_view"===e.filter?e.view=re(e.view):e.filter=re(e.filter))}function At(e,t){return t.filter&&"string"==typeof t.filter&&!t.doc_ids&&!te(e.db)}function Et(r,i){var o=i.complete;if("_view"===i.filter){if(!i.view||"string"!=typeof i.view){var e=Y(z,"`view` filter parameter not found or invalid.");return o(e)}var s=ne(i.view);r.db.get("_design/"+s[0],function(e,t){if(r.isCancelled)return o(null,{status:"cancelled"});if(e)return o(H(e));var n=t&&t.views&&t.views[s[1]]&&t.views[s[1]].map;if(!n)return o(Y(D,t.views?"missing json key: "+s[1]:"missing json key: views"));i.filter=ce(["return function(doc) {",'  "use strict";',"  var emitted = false;","  var emit = function (a, b) {","    emitted = true;","  };","  var view = "+n+";","  view(doc);","  if (emitted) {","    return true;","  }","};"].join("\n"),{}),r.doChanges(i)})}else if(i.selector)i.filter=function(e){return jt(e,i.selector)},r.doChanges(i);else{var a=ne(i.filter);r.db.get("_design/"+a[0],function(e,t){if(r.isCancelled)return o(null,{status:"cancelled"});if(e)return o(H(e));var n=t&&t.filters&&t.filters[a[1]];if(!n)return o(Y(D,t&&t.filters?"missing json key: "+a[1]:"missing json key: filters"));i.filter=ce('"use strict";\nreturn '+n+";",{}),r.doChanges(i)})}}function St(e){return e.reduce(function(e,t){return e[t]=!0,e},{})}We.plugin(function(e){e._changesFilterPlugin={validate:Ot,normalize:qt,shouldFilter:At,filter:Et}}),We.version="7.0.0";var xt=St(["_id","_rev","_attachments","_deleted","_revisions","_revs_info","_conflicts","_deleted_conflicts","_local_seq","_rev_tree","_replication_id","_replication_state","_replication_state_time","_replication_state_reason","_replication_stats","_removed"]),Ct=St(["_attachments","_replication_id","_replication_state","_replication_state_time","_replication_state_reason","_replication_stats"]);function Pt(e){if(!/^\d+-./.test(e))return Y(Q);var t=e.indexOf("-"),n=e.substring(0,t),r=e.substring(t+1);return{prefix:parseInt(n,10),id:r}}function Lt(e,t,n){var r,i,o;n||(n={deterministic_revs:!0});var s={status:"available"};if(e._deleted&&(s.deleted=!0),t)if(e._id||(e._id=Ae()),i=qe(e,n.deterministic_revs),e._rev){if((o=Pt(e._rev)).error)return o;e._rev_tree=[{pos:o.prefix,ids:[o.id,{status:"missing"},[[i,s,[]]]]}],r=o.prefix+1}else e._rev_tree=[{pos:1,ids:[i,s,[]]}],r=1;else if(e._revisions&&(e._rev_tree=function(e,t){for(var n=e.start-e.ids.length+1,r=e.ids,i=[r[0],t,[]],o=1,s=r.length;o<s;o++)i=[r[o],{status:"missing"},[i]];return[{pos:n,ids:i}]}(e._revisions,s),r=e._revisions.start,i=e._revisions.ids[0]),!e._rev_tree){if((o=Pt(e._rev)).error)return o;r=o.prefix,i=o.id,e._rev_tree=[{pos:r,ids:[i,s,[]]}]}ee(e._id),e._rev=r+"-"+i;var a={metadata:{},data:{}};for(var u in e)if(Object.prototype.hasOwnProperty.call(e,u)){var c="_"===u[0];if(c&&!xt[u]){var f=Y(J,u);throw f.message=J.message+": "+u,f}c&&!Ct[u]?a.metadata[u.slice(1)]=e[u]:a.data[u]=e[u]}return a}function Dt(t,e,n){var r=function(e){try{return le(e)}catch(e){return{error:Y(F,"Attachment is not a valid base64 string")}}}(t.data);if(r.error)return n(r.error);t.length=r.length,t.data="blob"===e?pe(r,t.content_type):"base64"===e?de(r):r,je(r,function(e){t.digest="md5-"+e,n()})}function $t(e,t,n){if(e.stub)return n();var r,i,o;"string"==typeof e.data?Dt(e,t,n):(i=t,o=n,je((r=e).data,function(e){r.digest="md5-"+e,r.length=r.data.size||r.data.length||0,"binary"===i?_e(r.data,function(e){r.data=e,o()}):"base64"===i?ge(r.data,function(e){r.data=e,o()}):o()}))}function It(e,t,n,r,i,o,s,a){if(function(e,t){for(var n,r=e.slice(),i=t.split("-"),o=parseInt(i[0],10),s=i[1];n=r.pop();){if(n.pos===o&&n.ids[0]===s)return!0;for(var a=n.ids[2],u=0,c=a.length;u<c;u++)r.push({pos:n.pos+1,ids:a[u]})}return!1}(t.rev_tree,n.metadata.rev)&&!a)return r[i]=n,o();var u=t.winningRev||Ee(t),c="deleted"in t?t.deleted:Ne(t,u),f="deleted"in n.metadata?n.metadata.deleted:Ne(n.metadata),l=/^1-/.test(n.metadata.rev);if(c&&!f&&a&&l){var d=n.data;d._rev=u,d._id=n.metadata.id,n=Lt(d,a)}var h=Re(t.rev_tree,n.metadata.rev_tree[0],e);if(a&&(c&&f&&"new_leaf"!==h.conflicts||!c&&"new_leaf"!==h.conflicts||c&&!f&&"new_branch"===h.conflicts)){var p=Y($);return r[i]=p,o()}var v=n.metadata.rev;n.metadata.rev_tree=h.tree,n.stemmedRevs=h.stemmedRevs||[],t.rev_map&&(n.metadata.rev_map=t.rev_map);var y=Ee(n.metadata),_=Ne(n.metadata,y),g=c===_?0:c<_?-1:1;s(n,y,_,v===y?_:Ne(n.metadata,v),!0,g,i,o)}function Bt(u,e,i,c,o,f,l,d,t){u=u||1e3;var h=d.new_edits,s=new x,n=0,a=e.length;function p(){++n===a&&t&&t()}e.forEach(function(e,n){if(e._id&&Ue(e._id)){var t=e._deleted?"_removeLocal":"_putLocal";i[t](e,{ctx:o},function(e,t){f[n]=e||t,p()})}else{var r=e.metadata.id;s.has(r)?(a--,s.get(r).push([e,n])):s.set(r,[[e,n]])}}),s.forEach(function(i,o){var s=0;function a(){++s<i.length?e():p()}function e(){var e=i[s],t=e[0],n=e[1];if(c.has(o))It(u,c.get(o),t,f,n,a,l,h);else{var r=Re([],t.metadata.rev_tree[0],u);t.metadata.rev_tree=r.tree,t.stemmedRevs=r.stemmedRevs||[],function(e,t,n){var r=Ee(e.metadata),i=Ne(e.metadata,r);if("was_delete"in d&&i)return f[t]=Y(D,"deleted"),n();if(h&&"missing"===e.metadata.rev_tree[0].ids[1].status){var o=Y($);return f[t]=o,n()}l(e,r,i,i,!1,i?0:1,t,n)}(t,n,a)}}e()})}var Tt=5,Rt="document-store",Mt="by-sequence",Nt="attach-store",Ut="attach-seq-store",Ft="meta-store",Kt="local-store",Jt="detect-blob-support";function zt(n){return function(e){var t="unknown_error";e.target&&e.target.error&&(t=e.target.error.name||e.target.error.message),n(Y(G,t,e.type))}}function Vt(e,t,n){return{data:function(t){try{return JSON.stringify(t)}catch(e){return o.stringify(t)}}(e),winningRev:t,deletedOrLocal:n?"1":"0",seq:e.seq,id:e.id}}function Gt(e){if(!e)return null;var t=function(t){try{return JSON.parse(t)}catch(e){return o.parse(t)}}(e.data);return t.winningRev=e.winningRev,t.deleted="1"===e.deletedOrLocal,t.seq=e.seq,t}function Qt(e){if(!e)return e;var t=e._doc_id_rev.lastIndexOf(":");return e._id=e._doc_id_rev.substring(0,t-1),e._rev=e._doc_id_rev.substring(t+1),delete e._doc_id_rev,e}function Wt(e,t,n,r){n?r(e?"string"!=typeof e?e:ve(e,t):he([""],{type:t})):e?"string"!=typeof e?ye(e,function(e){r(de(e))}):r(e):r("")}function Yt(i,o,s,e){var t=Object.keys(i._attachments||{});if(!t.length)return e&&e();var n=0;function a(){++n===t.length&&e&&e()}t.forEach(function(e){var t,n,r;o.attachments&&o.include_docs?(t=e,n=i._attachments[t],r=n.digest,s.objectStore(Nt).get(r).onsuccess=function(e){n.body=e.target.result.body,a()}):(i._attachments[e].stub=!0,a())})}function Ht(e,s){return Promise.all(e.map(function(o){if(o.doc&&o.doc._attachments){var e=Object.keys(o.doc._attachments);return Promise.all(e.map(function(n){var r=o.doc._attachments[n];if("body"in r){var e=r.body,i=r.content_type;return new Promise(function(t){Wt(e,i,s,function(e){o.doc._attachments[n]=C(m(r,["digest","content_type"]),{data:e}),t()})})}}))}}))}function Xt(e,r,t){var i=[],o=t.objectStore(Mt),s=t.objectStore(Nt),a=t.objectStore(Ut),n=e.length;function u(){--n||function(){if(!i.length)return;i.forEach(function(n){var e=a.index("digestSeq").count(IDBKeyRange.bound(n+"::",n+"::￿",!1,!1));e.onsuccess=function(e){var t=e.target.result;t||s.delete(n)}})}()}e.forEach(function(e){var t=o.index("_doc_id_rev"),n=r+"::"+e;t.getKey(n).onsuccess=function(e){var t=e.target.result;if("number"!=typeof t)return u();o.delete(t),a.index("seq").openCursor(IDBKeyRange.only(t)).onsuccess=function(e){var t=e.target.result;if(t){var n=t.value.digestSeq.split("::")[0];i.push(n),a.delete(t.primaryKey),t.continue()}else u()}}})}function Zt(e,t,n){try{return{txn:e.transaction(t,n)}}catch(e){return{error:e}}}var en=new A;function tn(a,e,u,l,t,n){for(var d,h,p,v,y,r,i,o,c=e.docs,s=0,f=c.length;s<f;s++){var _=c[s];_._id&&Ue(_._id)||(_=c[s]=Lt(_,u.new_edits,a)).error&&!i&&(i=_)}if(i)return n(i);var g=!1,m=0,b=new Array(c.length),w=new x,k=!1,j=l._meta.blobSupport?"blob":"base64";function O(){g=!0,q()}function q(){o&&g&&(o.docCount+=m,r.put(o))}function A(){k||(en.notify(l._meta.name),n(null,b))}function E(e,t,n,r,i,o,s,a){e.metadata.winningRev=t,e.metadata.deleted=n;var u=e.data;if(u._id=e.metadata.id,u._rev=e.metadata.rev,r&&(u._deleted=!0),u._attachments&&Object.keys(u._attachments).length)return function(a,u,e,t,n,r){var i=a.data,c=0,o=Object.keys(i._attachments);function f(){c===o.length&&S(a,u,e,t,n,r)}function l(){c++,f()}o.forEach(function(e){var i,o,s,t=a.data._attachments[e];if(t.stub)c++,f();else{var n=t.data;delete t.data,t.revpos=parseInt(u,10);var r=t.digest;i=r,o=n,s=l,v.count(i).onsuccess=function(e){var t=e.target.result;if(t)return s();var n={digest:i,body:o},r=v.put(n);r.onsuccess=s}}})}(e,t,n,i,s,a);m+=o,q(),S(e,t,n,i,s,a)}function S(i,s,a,u,e,t){var n=i.data,c=i.metadata;function r(e){var t,o,n=i.stemmedRevs||[];u&&l.auto_compaction&&(n=n.concat((t=i.metadata,o=[],Se(t.rev_tree,function(e,t,n,r,i){"available"!==i.status||e||(o.push(t+"-"+n),i.status="missing")}),o))),n&&n.length&&Xt(n,i.metadata.id,d),c.seq=e.target.result;var r=Vt(c,s,a);h.put(r).onsuccess=f}function f(){b[e]={ok:!0,id:c.id,rev:c.rev},w.set(i.metadata.id,i.metadata),function(r,i,e){var t=0,n=Object.keys(r.data._attachments||{});if(!n.length)return e();function o(){++t===n.length&&e()}function s(e){var t=r.data._attachments[e].digest,n=y.put({seq:i,digestSeq:t+"::"+i});n.onsuccess=o,n.onerror=function(e){e.preventDefault(),e.stopPropagation(),o()}}for(var a=0;a<n.length;a++)s(n[a])}(i,c.seq,t)}n._doc_id_rev=c.id+"::"+c.rev,delete n._id,delete n._rev;var o=p.put(n);o.onsuccess=r,o.onerror=function(e){e.preventDefault(),e.stopPropagation(),p.index("_doc_id_rev").getKey(n._doc_id_rev).onsuccess=function(e){p.put(n,e.target.result).onsuccess=r}}}!function(e,o,t){if(!e.length)return t();var s,n=0;function a(){n++,e.length===n&&(s?t(s):t())}e.forEach(function(e){var t=e.data&&e.data._attachments?Object.keys(e.data._attachments):[],n=0;if(!t.length)return a();function r(e){s=e,++n===t.length&&a()}for(var i in e.data._attachments)e.data._attachments.hasOwnProperty(i)&&$t(e.data._attachments[i],o,r)})}(c,j,function(e){if(e)return n(e);!function(){var e=Zt(t,[Rt,Mt,Nt,Kt,Ut,Ft],"readwrite");if(e.error)return n(e.error);(d=e.txn).onabort=zt(n),d.ontimeout=zt(n),d.oncomplete=A,h=d.objectStore(Rt),p=d.objectStore(Mt),v=d.objectStore(Nt),y=d.objectStore(Ut),(r=d.objectStore(Ft)).get(Ft).onsuccess=function(e){o=e.target.result,q()},function(t){var i=[];if(c.forEach(function(n){n.data&&n.data._attachments&&Object.keys(n.data._attachments).forEach(function(e){var t=n.data._attachments[e];t.stub&&i.push(t.digest)})}),!i.length)return t();var o,s=0;i.forEach(function(e){var n,r;n=e,r=function(e){e&&!o&&(o=e),++s===i.length&&t(o)},v.get(n).onsuccess=function(e){if(e.target.result)r();else{var t=Y(W,"unknown stub attachment with digest "+n);t.status=412,r(t)}}})}(function(e){if(e)return k=!0,n(e);!function(){if(!c.length)return;var e=0;function n(){++e===c.length&&Bt(a.revs_limit,c,l,w,d,b,E,u,O)}function t(e){var t=Gt(e.target.result);t&&w.set(t.id,t),n()}for(var r=0,i=c.length;r<i;r++){var o=c[r];if(o._id&&Ue(o._id))n();else{var s=h.get(o.metadata.id);s.onsuccess=t}}}()})}()})}function nn(n,r,e,i,o){var s,a,t;function u(e){a=e.target.result,s&&o(s,a,t)}function c(e){s=e.target.result,a&&o(s,a,t)}function f(e){var t=e.target.result;if(!t)return o();o([t.key],[t.value],t)}-1===i&&(i=1e3),"function"==typeof n.getAll&&"function"==typeof n.getAllKeys&&1<i&&!e?(t={continue:function(){if(!s.length)return o();var e,t=s[s.length-1];if(r&&r.upper)try{e=IDBKeyRange.bound(t,r.upper,!0,r.upperOpen)}catch(e){if("DataError"===e.name&&0===e.code)return o()}else e=IDBKeyRange.lowerBound(t,!0);r=e,a=s=null,n.getAll(r,i).onsuccess=u,n.getAllKeys(r,i).onsuccess=c}},n.getAll(r,i).onsuccess=u,n.getAllKeys(r,i).onsuccess=c):e?n.openCursor(r,"prev").onsuccess=f:n.openCursor(r).onsuccess=f}function rn(a,e,t){var n,r,i="startkey"in a&&a.startkey,o="endkey"in a&&a.endkey,s="key"in a&&a.key,u="keys"in a&&a.keys,c=a.skip||0,f="number"==typeof a.limit?a.limit:-1,l=!1!==a.inclusive_end;if(!u&&(r=(n=function(e,t,n,r,i){try{if(e&&t)return i?IDBKeyRange.bound(t,e,!n,!1):IDBKeyRange.bound(e,t,!1,!n);if(e)return i?IDBKeyRange.upperBound(e):IDBKeyRange.lowerBound(e);if(t)return i?IDBKeyRange.lowerBound(t,!n):IDBKeyRange.upperBound(t,!n);if(r)return IDBKeyRange.only(r)}catch(e){return{error:e}}return null}(i,o,l,s,a.descending))&&n.error)&&("DataError"!==r.name||0!==r.code))return t(Y(G,r.name,r.message));var d=[Rt,Mt,Ft];a.attachments&&d.push(Nt);var h=Zt(e,d,"readonly");if(h.error)return t(h.error);var p=h.txn;p.oncomplete=function(){a.attachments?Ht(E,a.binary).then(P):P()},p.onabort=zt(t);var v,y,_,g,m,b,w,k,j=p.objectStore(Rt),O=p.objectStore(Mt),q=p.objectStore(Ft),A=O.index("_doc_id_rev"),E=[];function S(e,t){var n,r,i,o,s={id:t.id,key:t.id,value:{rev:e}};t.deleted?u&&(E.push(s),s.value.deleted=!0,s.doc=null):c--<=0&&(E.push(s),a.include_docs&&(r=s,i=e,o=(n=t).id+"::"+i,A.get(o).onsuccess=function(e){if(r.doc=Qt(e.target.result)||{},a.conflicts){var t=Pe(n);t.length&&(r.doc._conflicts=t)}Yt(r.doc,a,p)}))}function x(e){for(var t=0,n=e.length;t<n&&E.length!==f;t++){var r=e[t];if(r.error&&u)E.push(r);else{var i=Gt(r);S(i.winningRev,i)}}}function C(e,t,n){n&&(x(t),E.length<f&&n.continue())}function P(){var e={total_rows:v,offset:a.skip,rows:E};a.update_seq&&void 0!==y&&(e.update_seq=y),t(null,e)}return q.get(Ft).onsuccess=function(e){v=e.target.result.docCount},a.update_seq&&(_=function(e){e.target.result&&0<e.target.result.length&&(y=e.target.result[0])},O.openCursor(null,"prev").onsuccess=function(e){var t=e.target.result,n=void 0;return t&&t.key&&(n=t.key),_({target:{result:[n]}})}),r||0===f?void 0:u?(g=a.keys,m=j,b=C,w=new Array(g.length),k=0,void g.forEach(function(t,n){m.get(t).onsuccess=function(e){e.target.result?w[n]=e.target.result:w[n]={key:t,error:"not_found"},++k===g.length&&b(g,w,{})}})):-1===f?function(e,t,n){if("function"!=typeof e.getAll){var r=[];e.openCursor(t).onsuccess=function(e){var t=e.target.result;t?(r.push(t.value),t.continue()):n({target:{result:r}})}}else e.getAll(t).onsuccess=n}(j,n,function(e){var t=e.target.result;a.descending&&(t=t.reverse()),x(t)}):void nn(j,n,a.descending,f+c,C)}var on=!1,sn=[];function an(){!on&&sn.length&&(on=!0,sn.shift()())}function un(c,e,t,n){if((c=R(c)).continuous){var r=t+":"+Ae();return en.addListener(t,r,e,c),en.notify(t),{cancel:function(){en.removeListener(t,r)}}}var f=c.doc_ids&&new q(c.doc_ids);c.since=c.since||0;var l=c.since,d="limit"in c?c.limit:-1;0===d&&(d=1);var h,i,p,o,v=[],y=0,_=X(c),g=new x;function m(e,t,n,r){if(n.seq!==t)return r();if(n.winningRev===e._rev)return r(n,e);var i=e._id+"::"+n.winningRev;o.get(i).onsuccess=function(e){r(n,Qt(e.target.result))}}function s(){c.complete(null,{results:v,last_seq:l})}var a=[Rt,Mt];c.attachments&&a.push(Nt);var u=Zt(n,a,"readonly");if(u.error)return c.complete(u.error);(h=u.txn).onabort=zt(c.complete),h.oncomplete=function(){!c.continuous&&c.attachments?Ht(v).then(s):s()},i=h.objectStore(Mt),p=h.objectStore(Rt),o=i.index("_doc_id_rev"),nn(i,c.since&&!c.descending?IDBKeyRange.lowerBound(c.since,!0):null,c.descending,d,function(r,e,o){if(o&&r.length){var s=new Array(r.length),a=new Array(r.length),i=0;e.forEach(function(e,n){!function(t,n,r){if(f&&!f.has(t._id))return r();var i=g.get(t._id);if(i)return m(t,n,i,r);p.get(t._id).onsuccess=function(e){i=Gt(e.target.result),g.set(t._id,i),m(t,n,i,r)}}(Qt(e),r[n],function(e,t){a[n]=e,s[n]=t,++i===r.length&&function(){for(var e=[],t=0,n=s.length;t<n&&y!==d;t++){var r=s[t];if(r){var i=a[t];e.push(u(i,r))}}Promise.all(e).then(function(e){for(var t=0,n=e.length;t<n;t++)e[t]&&c.onChange(e[t])}).catch(c.complete),y!==d&&o.continue()}()})})}function u(e,t){var n=c.processChange(t,e,c);l=n.seq=e.seq;var r=_(n);return"object"==typeof r?Promise.reject(r):r?(y++,c.return_docs&&v.push(n),c.attachments&&c.include_docs?new Promise(function(e){Yt(t,c,h,function(){Ht([n],c.binary).then(function(){e(n)})})}):Promise.resolve(n)):Promise.resolve()}})}var cn,fn=new x,ln=new x;function dn(t,e){var n,r,i,o=this;n=function(e){!function(l,r,d){var h=r.name,p=null;function s(e,i){var o=e.objectStore(Rt);o.createIndex("deletedOrLocal","deletedOrLocal",{unique:!1}),o.openCursor().onsuccess=function(e){var t=e.target.result;if(t){var n=t.value,r=Ne(n);n.deletedOrLocal=r?"1":"0",o.put(n),t.continue()}else i()}}function a(e,d){var h=e.objectStore(Kt),p=e.objectStore(Rt),v=e.objectStore(Mt),t=p.openCursor();t.onsuccess=function(e){var n=e.target.result;if(n){var t=n.value,r=t.id,i=Ue(r),o=Ee(t);if(i){var s=r+"::"+o,a=r+"::",u=r+"::~",c=v.index("_doc_id_rev"),f=IDBKeyRange.bound(a,u,!1,!1),l=c.openCursor(f);l.onsuccess=function(e){if(l=e.target.result){var t=l.value;t._doc_id_rev===s&&h.put(t),v.delete(l.primaryKey),l.continue()}else p.delete(n.primaryKey),n.continue()}}else n.continue()}else d&&d()}}function u(e,f){var n=e.objectStore(Mt),t=e.objectStore(Nt),l=e.objectStore(Ut),r=t.count();r.onsuccess=function(e){var t=e.target.result;if(!t)return f();n.openCursor().onsuccess=function(e){var t=e.target.result;if(!t)return f();for(var n=t.value,r=t.primaryKey,i=Object.keys(n._attachments||{}),o={},s=0;s<i.length;s++){var a=n._attachments[i[s]];o[a.digest]=!0}var u=Object.keys(o);for(s=0;s<u.length;s++){var c=u[s];l.put({seq:r,digestSeq:c+"::"+r})}t.continue()}}}function c(e){var u=e.objectStore(Mt),c=e.objectStore(Rt),t=c.openCursor();t.onsuccess=function(e){var n=e.target.result;if(n){var t,r,i,o,s=function(e){if(!e.data)return e.deleted="1"===e.deletedOrLocal,e;return Gt(e)}(n.value);if(s.winningRev=s.winningRev||Ee(s),s.seq)return a();t=s.id+"::",r=s.id+"::￿",i=u.index("_doc_id_rev").openCursor(IDBKeyRange.bound(t,r)),o=0,i.onsuccess=function(e){var t=e.target.result;if(!t)return s.seq=o,a();var n=t.primaryKey;o<n&&(o=n),t.continue()}}function a(){var e=Vt(s,s.winningRev,s.deleted),t=c.put(e);t.onsuccess=function(){n.continue()}}}}l._meta=null,l._remote=!1,l.type=function(){return"idb"},l._id=v(function(e){e(null,l._meta.instanceId)}),l._bulkDocs=function(e,t,n){tn(r,e,t,l,p,n)},l._get=function(e,o,t){var s,a,u,c=o.ctx;if(!c){var n=Zt(p,[Rt,Mt,Nt],"readonly");if(n.error)return t(n.error);c=n.txn}function f(){t(u,{doc:s,metadata:a,ctx:c})}c.objectStore(Rt).get(e).onsuccess=function(e){if(!(a=Gt(e.target.result)))return u=Y(D,"missing"),f();var t;if(o.rev)t=o.latest?function(e,t){for(var n,r=t.rev_tree.slice();n=r.pop();){var i=n.pos,o=n.ids,s=o[0],a=o[1],u=o[2],c=0===u.length,f=n.history?n.history.slice():[];if(f.push({id:s,pos:i,opts:a}),c)for(var l=0,d=f.length;l<d;l++){var h=f[l];if(h.pos+"-"+h.id===e)return i+"-"+s}for(var p=0,v=u.length;p<v;p++)r.push({pos:i+1,ids:u[p],history:f})}throw new Error("Unable to resolve latest revision for id "+t.id+", rev "+e)}(o.rev,a):o.rev;else{t=a.winningRev;var n=Ne(a);if(n)return u=Y(D,"deleted"),f()}var r=c.objectStore(Mt),i=a.id+"::"+t;r.index("_doc_id_rev").get(i).onsuccess=function(e){if((s=e.target.result)&&(s=Qt(s)),!s)return u=Y(D,"missing"),f();f()}}},l._getAttachment=function(e,t,n,r,i){var o;if(r.ctx)o=r.ctx;else{var s=Zt(p,[Rt,Mt,Nt],"readonly");if(s.error)return i(s.error);o=s.txn}var a=n.digest,u=n.content_type;o.objectStore(Nt).get(a).onsuccess=function(e){var t=e.target.result.body;Wt(t,u,r.binary,function(e){i(null,e)})}},l._info=function(e){var n,t,r=Zt(p,[Ft,Mt],"readonly");if(r.error)return e(r.error);var i=r.txn;i.objectStore(Ft).get(Ft).onsuccess=function(e){t=e.target.result.docCount},i.objectStore(Mt).openCursor(null,"prev").onsuccess=function(e){var t=e.target.result;n=t?t.key:0},i.oncomplete=function(){e(null,{doc_count:t,update_seq:n,idb_attachment_format:l._meta.blobSupport?"binary":"base64"})}},l._allDocs=function(e,t){rn(e,p,t)},l._changes=function(e){return un(e,l,h,p)},l._close=function(e){p.close(),fn.delete(h),e()},l._getRevisionTree=function(e,n){var t=Zt(p,[Rt],"readonly");if(t.error)return n(t.error);var r=t.txn,i=r.objectStore(Rt).get(e);i.onsuccess=function(e){var t=Gt(e.target.result);t?n(null,t.rev_tree):n(Y(D))}},l._doCompaction=function(i,s,e){var t=[Rt,Mt,Nt,Ut],n=Zt(p,t,"readwrite");if(n.error)return e(n.error);var o=n.txn,r=o.objectStore(Rt);r.get(i).onsuccess=function(e){var t=Gt(e.target.result);Se(t.rev_tree,function(e,t,n,r,i){var o=t+"-"+n;-1!==s.indexOf(o)&&(i.status="missing")}),Xt(s,i,o);var n=t.winningRev,r=t.deleted;o.objectStore(Rt).put(Vt(t,n,r))},o.onabort=zt(e),o.oncomplete=function(){e()}},l._getLocal=function(e,n){var t=Zt(p,[Kt],"readonly");if(t.error)return n(t.error);var r=t.txn,i=r.objectStore(Kt).get(e);i.onerror=zt(n),i.onsuccess=function(e){var t=e.target.result;t?(delete t._doc_id_rev,n(null,t)):n(Y(D))}},l._putLocal=function(r,i,o){"function"==typeof i&&(o=i,i={}),delete r._revisions;var s=r._rev,e=r._id;r._rev=s?"0-"+(parseInt(s.split("-")[1],10)+1):"0-1";var a,t=i.ctx;if(!t){var n=Zt(p,[Kt],"readwrite");if(n.error)return o(n.error);(t=n.txn).onerror=zt(o),t.oncomplete=function(){a&&o(null,a)}}var u,c=t.objectStore(Kt);s?(u=c.get(e)).onsuccess=function(e){var t=e.target.result;if(t&&t._rev===s){var n=c.put(r);n.onsuccess=function(){a={ok:!0,id:r._id,rev:r._rev},i.ctx&&o(null,a)}}else o(Y($))}:((u=c.add(r)).onerror=function(e){o(Y($)),e.preventDefault(),e.stopPropagation()},u.onsuccess=function(){a={ok:!0,id:r._id,rev:r._rev},i.ctx&&o(null,a)})},l._removeLocal=function(n,r,i){"function"==typeof r&&(i=r,r={});var o,e=r.ctx;if(!e){var t=Zt(p,[Kt],"readwrite");if(t.error)return i(t.error);(e=t.txn).oncomplete=function(){o&&i(null,o)}}var s=n._id,a=e.objectStore(Kt),u=a.get(s);u.onerror=zt(i),u.onsuccess=function(e){var t=e.target.result;t&&t._rev===n._rev?(a.delete(s),o={ok:!0,id:s,rev:"0-0"},r.ctx&&i(null,o)):i(Y(D))}},l._destroy=function(e,t){en.removeAllListeners(h);var n=ln.get(h);n&&n.result&&(n.result.close(),fn.delete(h));var r=indexedDB.deleteDatabase(h);r.onsuccess=function(){ln.delete(h),j()&&h in localStorage&&delete localStorage[h],t(null,{ok:!0})},r.onerror=zt(t)};var e=fn.get(h);if(e)return p=e.idb,l._meta=e.global,T(function(){d(null,l)});var t=indexedDB.open(h,Tt);ln.set(h,t),t.onupgradeneeded=function(e){var t=e.target.result;if(e.oldVersion<1)return function(e){var t=e.createObjectStore(Rt,{keyPath:"id"});e.createObjectStore(Mt,{autoIncrement:!0}).createIndex("_doc_id_rev","_doc_id_rev",{unique:!0}),e.createObjectStore(Nt,{keyPath:"digest"}),e.createObjectStore(Ft,{keyPath:"id",autoIncrement:!1}),e.createObjectStore(Jt),t.createIndex("deletedOrLocal","deletedOrLocal",{unique:!1}),e.createObjectStore(Kt,{keyPath:"_id"});var n=e.createObjectStore(Ut,{autoIncrement:!0});n.createIndex("seq","seq"),n.createIndex("digestSeq","digestSeq",{unique:!0})}(t);var n,r=e.currentTarget.transaction;e.oldVersion<3&&t.createObjectStore(Kt,{keyPath:"_id"}).createIndex("_doc_id_rev","_doc_id_rev",{unique:!0}),e.oldVersion<4&&((n=t.createObjectStore(Ut,{autoIncrement:!0})).createIndex("seq","seq"),n.createIndex("digestSeq","digestSeq",{unique:!0}));var i=[s,a,u,c],o=e.oldVersion;!function e(){var t=i[o-1];o++;t&&t(r,e)}()},t.onsuccess=function(e){(p=e.target.result).onversionchange=function(){p.close(),fn.delete(h)},p.onabort=function(e){E("error","Database has a global failure",e.target.error),p.close(),fn.delete(h)};var t,n,r,i,o,s,a=p.transaction([Ft,Jt,Rt],"readwrite"),u=!1;function c(){void 0!==r&&u&&(l._meta={name:h,instanceId:i,blobSupport:r},fn.set(h,{idb:p,global:l._meta}),d(null,l))}function f(){if(void 0!==n&&void 0!==t){var e=h+"_id";e in t?i=t[e]:t[e]=i=Ae(),t.docCount=n,a.objectStore(Ft).put(t)}}a.objectStore(Ft).get(Ft).onsuccess=function(e){t=e.target.result||{id:Ft},f()},o=function(e){n=e,f()},a.objectStore(Rt).index("deletedOrLocal").count(IDBKeyRange.only("0")).onsuccess=function(e){o(e.target.result)},cn||(s=a,cn=new Promise(function(n){var e=he([""]),t=s.objectStore(Jt).put(e,"key");t.onsuccess=function(){var e=navigator.userAgent.match(/Chrome\/(\d+)/),t=navigator.userAgent.match(/Edge\//);n(t||!e||43<=parseInt(e[1],10))},t.onerror=s.onabort=function(e){e.preventDefault(),e.stopPropagation(),n(!1)}}).catch(function(){return!1})),cn.then(function(e){r=e,c()}),a.oncomplete=function(){u=!0,c()},a.onabort=zt(d)},t.onerror=function(){var e="Failed to open indexedDB, are you in private browsing mode?";E("error",e),d(Y(G,e))}}(o,t,e)},r=e,i=o.constructor,sn.push(function(){n(function(e,t){!function(e,t,n,r){try{e(t,n)}catch(t){r.emit("error",t)}}(r,e,t,i),on=!1,T(function(){an()})})}),an()}dn.valid=function(){try{return"undefined"!=typeof indexedDB&&"undefined"!=typeof IDBKeyRange}catch(e){return!1}};var hn=25,pn=50,vn=5e3,yn=1e4,_n={};function gn(e){var n=(e.doc||e.ok)._attachments;n&&Object.keys(n).forEach(function(e){var t=n[e];t.data=ve(t.data,t.content_type)})}function mn(e){return/^_design/.test(e)?"_design/"+encodeURIComponent(e.slice(8)):/^_local/.test(e)?"_local/"+encodeURIComponent(e.slice(7)):encodeURIComponent(e)}function bn(n){return n._attachments&&Object.keys(n._attachments)?Promise.all(Object.keys(n._attachments).map(function(e){var t=n._attachments[e];if(t.data&&"string"!=typeof t.data)return new Promise(function(e){ge(t.data,e)}).then(function(e){t.data=e})})):Promise.resolve()}function wn(e,t){if(function(e){if(!e.prefix)return!1;var t=ue(e.prefix).protocol;return"http"===t||"https"===t}(t)){var n=t.name.substr(t.prefix.length);e=t.prefix.replace(/\/?$/,"/")+encodeURIComponent(n)}var r=ue(e);(r.user||r.password)&&(r.auth={username:r.user,password:r.password});var i=r.path.replace(/(^\/|\/$)/g,"").split("/");return r.db=i.pop(),-1===r.db.indexOf("%")&&(r.db=encodeURIComponent(r.db)),r.path=i.join("/"),r}function kn(e,t){return jn(e,e.db+"/"+t)}function jn(e,t){var n=e.path?"/":"";return e.protocol+"://"+e.host+(e.port?":"+e.port:"")+"/"+e.path+n+t}function On(t){return"?"+Object.keys(t).map(function(e){return e+"="+encodeURIComponent(t[e])}).join("&")}function qn(d,e){var t=this,y=wn(d.name,d),n=kn(y,"");d=R(d);var r,a=function(e,t){if((t=t||{}).headers=t.headers||new Xe,d.auth||y.auth){var n=d.auth||y.auth,r=n.username+":"+n.password,i=de(unescape(encodeURIComponent(r)));t.headers.set("Authorization","Basic "+i)}var o,s,a,u,c,f,l=d.headers||{};return Object.keys(l).forEach(function(e){t.headers.append(e,l[e])}),o=t,s="undefined"!=typeof navigator&&navigator.userAgent?navigator.userAgent.toLowerCase():"",a=-1!==s.indexOf("msie"),u=-1!==s.indexOf("trident"),c=-1!==s.indexOf("edge"),f=!("method"in o)||"GET"===o.method,(a||u||c)&&f&&(e+=(-1===e.indexOf("?")?"?":"&")+"_nonce="+Date.now()),(d.fetch||He)(e,t)};function i(e,n){return h(e,O(function(t){g().then(function(){return n.apply(this,t)}).catch(function(e){t.pop()(e)})})).bind(t)}function _(e,t,n){var r={};return(t=t||{}).headers=t.headers||new Xe,t.headers.get("Content-Type")||t.headers.set("Content-Type","application/json"),t.headers.get("Accept")||t.headers.set("Accept","application/json"),a(e,t).then(function(e){return r.ok=e.ok,r.status=e.status,e.json()}).then(function(e){if(r.data=e,!r.ok){r.data.status=r.status;var t=H(r.data);if(n)return n(t);throw t}if(Array.isArray(r.data)&&(r.data=r.data.map(function(e){return e.error||e.missing?H(e):e})),!n)return r;n(null,r.data)})}function g(){return d.skip_setup?Promise.resolve():r||((r=_(n).catch(function(e){return e&&e.status&&404===e.status?(S(404,"PouchDB is just detecting if the remote exists."),_(n,{method:"PUT"})):Promise.reject(e)}).catch(function(e){return!(!e||!e.status||412!==e.status)||Promise.reject(e)})).catch(function(){r=null}),r)}function c(e){return e.split("/").map(encodeURIComponent).join("/")}T(function(){e(null,t)}),t._remote=!0,t.type=function(){return"http"},t.id=i("id",function(n){a(jn(y,"")).then(function(e){return e.json()}).then(function(e){var t=e&&e.uuid?e.uuid+y.db:kn(y,"");n(null,t)}).catch(function(e){n(e)})}),t.compact=i("compact",function(r,i){"function"==typeof r&&(i=r,r={}),r=R(r),_(kn(y,"_compact"),{method:"POST"}).then(function(){!function n(){t.info(function(e,t){t&&!t.compact_running?i(null,{ok:!0}):setTimeout(n,r.interval||200)})}()})}),t.bulkGet=h("bulkGet",function(a,u){var c=this;function e(t){var e={};a.revs&&(e.revs=!0),a.attachments&&(e.attachments=!0),a.latest&&(e.latest=!0),_(kn(y,"_bulk_get"+On(e)),{method:"POST",body:JSON.stringify({docs:a.docs})}).then(function(e){a.attachments&&a.binary&&e.data.results.forEach(function(e){e.docs.forEach(gn)}),t(null,e.data)}).catch(t)}function n(){var e=pn,r=Math.ceil(a.docs.length/e),i=0,o=new Array(r);function t(n){return function(e,t){o[n]=t.results,++i===r&&u(null,{results:Z(o)})}}for(var n=0;n<r;n++){var s=m(a,["revs","attachments","binary","latest"]);s.docs=a.docs.slice(n*e,Math.min(a.docs.length,(n+1)*e)),k(c,s,t(n))}}var r=jn(y,""),t=_n[r];"boolean"!=typeof t?e(function(e,t){e?(_n[r]=!1,S(e.status,"PouchDB is just detecting if the remote supports the _bulk_get API."),n()):(_n[r]=!0,u(null,t))}):t?e(u):n()}),t._info=function(t){g().then(function(){return a(kn(y,""))}).then(function(e){return e.json()}).then(function(e){e.host=kn(y,""),t(null,e)}).catch(t)},t.fetch=function(e,t){return g().then(function(){return a(kn(y,e),t)})},t.get=i("get",function(t,s,n){"function"==typeof s&&(n=s,s={});var e={};function r(i){var o=i._attachments,e=o&&Object.keys(o);if(o&&e.length){var l,d,t=e.map(function(r){return function(){return n=o[e=r],t=mn(i._id)+"/"+c(e)+"?rev="+i._rev,a(kn(y,t)).then(function(e){return void 0===u||u.browser?e.blob():e.buffer()}).then(function(t){return s.binary?(void 0===u||u.browser||(t.type=n.content_type),t):new Promise(function(e){ge(t,e)})}).then(function(e){delete n.stub,delete n.length,n.data=e});var e,n,t}});return l=t,d=5,new Promise(function(e,t){var n,r=0,i=0,o=0,s=l.length;function a(){++o===s?n?t(n):e():f()}function u(){r--,a()}function c(e){r--,n=n||e,a()}function f(){for(;r<d&&i<s;)r++,l[i++]().then(u,c)}f()})}}(s=R(s)).revs&&(e.revs=!0),s.revs_info&&(e.revs_info=!0),s.latest&&(e.latest=!0),s.open_revs&&("all"!==s.open_revs&&(s.open_revs=JSON.stringify(s.open_revs)),e.open_revs=s.open_revs),s.rev&&(e.rev=s.rev),s.conflicts&&(e.conflicts=s.conflicts),s.update_seq&&(e.update_seq=s.update_seq),t=mn(t),_(kn(y,t+On(e))).then(function(t){return Promise.resolve().then(function(){if(s.attachments)return e=t.data,Array.isArray(e)?Promise.all(e.map(function(e){if(e.ok)return r(e.ok)})):r(e);var e}).then(function(){n(null,t.data)})}).catch(function(e){e.docId=t,n(e)})}),t.remove=i("remove",function(e,t,n,r){var i;"string"==typeof t?(i={_id:e,_rev:t},"function"==typeof n&&(r=n,n={})):(i=e,"function"==typeof t?(r=t,n={}):(r=n,n=t));var o=i._rev||n.rev;_(kn(y,mn(i._id))+"?rev="+o,{method:"DELETE"},r).catch(r)}),t.getAttachment=i("getAttachment",function(e,t,n,r){"function"==typeof n&&(r=n,n={});var i,o=n.rev?"?rev="+n.rev:"",s=kn(y,mn(e))+"/"+c(t)+o;a(s,{method:"GET"}).then(function(e){if(i=e.headers.get("content-type"),e.ok)return void 0===u||u.browser?e.blob():e.buffer();throw e}).then(function(e){void 0===u||u.browser||(e.type=i),r(null,e)}).catch(function(e){r(e)})}),t.removeAttachment=i("removeAttachment",function(e,t,n,r){_(kn(y,mn(e)+"/"+c(t))+"?rev="+n,{method:"DELETE"},r).catch(r)}),t.putAttachment=i("putAttachment",function(e,t,n,r,i,o){"function"==typeof i&&(o=i,i=r,r=n,n=null);var s=mn(e)+"/"+c(t),a=kn(y,s);if(n&&(a+="?rev="+n),"string"==typeof r){var u;try{u=le(r)}catch(e){return o(Y(F,"Attachment is not a valid base64 string"))}r=u?pe(u,i):""}_(a,{headers:new Xe({"Content-Type":i}),method:"PUT",body:r},o).catch(o)}),t._bulkDocs=function(e,t,n){e.new_edits=t.new_edits,g().then(function(){return Promise.all(e.docs.map(bn))}).then(function(){return _(kn(y,"_bulk_docs"),{method:"POST",body:JSON.stringify(e)},n)}).catch(n)},t._put=function(t,e,n){g().then(function(){return bn(t)}).then(function(){return _(kn(y,mn(t._id)),{method:"PUT",body:JSON.stringify(t)})}).then(function(e){n(null,e.data)}).catch(function(e){e.docId=t&&t._id,n(e)})},t.allDocs=i("allDocs",function(t,n){"function"==typeof t&&(n=t,t={});var e,r={},i="GET";(t=R(t)).conflicts&&(r.conflicts=!0),t.update_seq&&(r.update_seq=!0),t.descending&&(r.descending=!0),t.include_docs&&(r.include_docs=!0),t.attachments&&(r.attachments=!0),t.key&&(r.key=JSON.stringify(t.key)),t.start_key&&(t.startkey=t.start_key),t.startkey&&(r.startkey=JSON.stringify(t.startkey)),t.end_key&&(t.endkey=t.end_key),t.endkey&&(r.endkey=JSON.stringify(t.endkey)),void 0!==t.inclusive_end&&(r.inclusive_end=!!t.inclusive_end),void 0!==t.limit&&(r.limit=t.limit),void 0!==t.skip&&(r.skip=t.skip);var o=On(r);void 0!==t.keys&&(i="POST",e={keys:t.keys}),_(kn(y,"_all_docs"+o),{method:i,body:JSON.stringify(e)}).then(function(e){t.include_docs&&t.attachments&&t.binary&&e.data.rows.forEach(gn),n(null,e.data)}).catch(n)}),t._changes=function(s){var a="batch_size"in s?s.batch_size:hn;!(s=R(s)).continuous||"heartbeat"in s||(s.heartbeat=yn);var e="timeout"in s?s.timeout:3e4;"timeout"in s&&s.timeout&&e-s.timeout<vn&&(e=s.timeout+vn),"heartbeat"in s&&s.heartbeat&&e-s.heartbeat<vn&&(e=s.heartbeat+vn);var i={};"timeout"in s&&s.timeout&&(i.timeout=s.timeout);var u=void 0!==s.limit&&s.limit,c=u;if(s.style&&(i.style=s.style),(s.include_docs||s.filter&&"function"==typeof s.filter)&&(i.include_docs=!0),s.attachments&&(i.attachments=!0),s.continuous&&(i.feed="longpoll"),s.seq_interval&&(i.seq_interval=s.seq_interval),s.conflicts&&(i.conflicts=!0),s.descending&&(i.descending=!0),s.update_seq&&(i.update_seq=!0),"heartbeat"in s&&s.heartbeat&&(i.heartbeat=s.heartbeat),s.filter&&"string"==typeof s.filter&&(i.filter=s.filter),s.view&&"string"==typeof s.view&&(i.filter="_view",i.view=s.view),s.query_params&&"object"==typeof s.query_params)for(var t in s.query_params)s.query_params.hasOwnProperty(t)&&(i[t]=s.query_params[t]);var o,f="GET";s.doc_ids?(i.filter="_doc_ids",f="POST",o={doc_ids:s.doc_ids}):s.selector&&(i.filter="_selector",f="POST",o={selector:s.selector});var l,d=new Ye,h=function(e,t){if(!s.aborted){i.since=e,"object"==typeof i.since&&(i.since=JSON.stringify(i.since)),s.descending?u&&(i.limit=c):i.limit=!u||a<c?a:c;var n=kn(y,"_changes"+On(i)),r={signal:d.signal,method:f,body:JSON.stringify(o)};l=e,s.aborted||g().then(function(){return _(n,r,t)}).catch(t)}},p={results:[]},v=function(e,t){if(!s.aborted){var n=0;if(t&&t.results){n=t.results.length,p.last_seq=t.last_seq;var r=null,i=null;"number"==typeof t.pending&&(r=t.pending),"string"!=typeof p.last_seq&&"number"!=typeof p.last_seq||(i=p.last_seq);s.query_params,t.results=t.results.filter(function(e){c--;var t=X(s)(e);return t&&(s.include_docs&&s.attachments&&s.binary&&gn(e),s.return_docs&&p.results.push(e),s.onChange(e,r,i)),t})}else if(e)return s.aborted=!0,void s.complete(e);t&&t.last_seq&&(l=t.last_seq);var o=u&&c<=0||t&&n<a||s.descending;(!s.continuous||u&&c<=0)&&o?s.complete(null,p):T(function(){h(l,v)})}};return h(s.since||0,v),{cancel:function(){s.aborted=!0,d.abort()}}},t.revsDiff=i("revsDiff",function(e,t,n){"function"==typeof t&&(n=t,t={}),_(kn(y,"_revs_diff"),{method:"POST",body:JSON.stringify(e)},n).catch(n)}),t._close=function(e){e()},t._destroy=function(e,t){_(kn(y,""),{method:"DELETE"}).then(function(e){t(null,e)}).catch(function(e){404===e.status?t(null,{ok:!0}):t(e)})}}function An(e){this.status=400,this.name="query_parse_error",this.message=e,this.error=!0;try{Error.captureStackTrace(this,An)}catch(e){}}function En(e){this.status=404,this.name="not_found",this.message=e,this.error=!0;try{Error.captureStackTrace(this,En)}catch(e){}}function Sn(e){this.status=500,this.name="invalid_value",this.message=e,this.error=!0;try{Error.captureStackTrace(this,Sn)}catch(e){}}function xn(e,t){return t&&e.then(function(e){T(function(){t(null,e)})},function(e){T(function(){t(e)})}),e}function Cn(n,r){return function(){var e=arguments,t=this;return n.add(function(){return r.apply(t,e)})}}function Pn(e){var t=new q(e),n=new Array(t.size),r=-1;return t.forEach(function(e){n[++r]=e}),n}function Ln(e){var n=new Array(e.size),r=-1;return e.forEach(function(e,t){n[++r]=t}),n}function Dn(e){return new Sn("builtin "+e+" function requires map values to be numbers or number arrays")}function $n(e){for(var t=0,n=0,r=e.length;n<r;n++){var i=e[n];if("number"!=typeof i){if(!Array.isArray(i))throw Dn("_sum");t="number"==typeof t?[t]:t;for(var o=0,s=i.length;o<s;o++){var a=i[o];if("number"!=typeof a)throw Dn("_sum");void 0===t[o]?t.push(a):t[o]+=a}}else"number"==typeof t?t+=i:t[0]+=i}return t}qn.valid=function(){return!0},r(An,Error),r(En,Error),r(Sn,Error);var In=E.bind(null,"log"),Bn=Array.isArray,Tn=JSON.parse;function Rn(e,t){return ce("return ("+e.replace(/;\s*$/,"")+");",{emit:t,sum:$n,log:In,isArray:Bn,toJSON:Tn})}function Mn(){this.promise=new Promise(function(e){e()})}function Nn(e){if(!e)return"undefined";switch(typeof e){case"function":case"string":return e.toString();default:return JSON.stringify(e)}}function Un(i,o,s,a,t,n){var e,u,c=(e=a,Nn(s)+Nn(e)+"undefined");if(!t&&(u=i._cachedViews=i._cachedViews||{})[c])return u[c];var r=i.info().then(function(e){var r=e.db_name+"-mrview-"+(t?"temp":Oe(c));return fe(i,"_local/"+n,function(e){e.views=e.views||{};var t=o;-1===t.indexOf("/")&&(t=o+"/"+o);var n=e.views[t]=e.views[t]||{};if(!n[r])return n[r]=!0,e}).then(function(){return i.registerDependentDatabase(r).then(function(e){var t=e.db;t.auto_compaction=!0;var n={name:r,db:t,sourceDB:i,adapter:i.adapter,mapFun:s,reduceFun:a};return n.db.get("_local/lastSeq").catch(function(e){if(404!==e.status)throw e}).then(function(e){return n.seq=e?e.seq:0,u&&n.db.once("destroyed",function(){delete u[c]}),n})})})});return u&&(u[c]=r),r}Mn.prototype.add=function(e){return this.promise=this.promise.catch(function(){}).then(function(){return e()}),this.promise},Mn.prototype.finish=function(){return this.promise};var Fn={},Kn=new Mn,Jn=50;function zn(e){return-1===e.indexOf("/")?[e,e]:e.split("/")}function Vn(e,t){try{e.emit("error",t)}catch(e){E("error","The user's map/reduce function threw an uncaught error.\nYou can debug this error by doing:\nmyDatabase.on('error', function (err) { debugger; });\nPlease double-check your map/reduce function."),E("error",t)}}var Gn={_sum:function(e,t){return $n(t)},_count:function(e,t){return t.length},_stats:function(e,t){return{sum:$n(t),min:Math.min.apply(null,t),max:Math.max.apply(null,t),count:t.length,sumsqr:function(e){for(var t=0,n=0,r=e.length;n<r;n++){var i=e[n];t+=i*i}return t}(t)}}};var Qn=function(f,t,v,l){function d(t,e,n){try{e(n)}catch(e){Vn(t,e)}}function y(t,e,n,r,i){try{return{output:e(n,r,i)}}catch(e){return Vn(t,e),{error:e}}}function h(e,t){var n=ct(e.key,t.key);return 0!==n?n:ct(e.value,t.value)}function _(e){var t=e.value;return t&&"object"==typeof t&&t._id||e.id}function p(t){return function(e){return t.include_docs&&t.attachments&&t.binary&&e.rows.forEach(function(e){var n=e.doc&&e.doc._attachments;n&&Object.keys(n).forEach(function(e){var t=n[e];n[e].data=ve(t.data,t.content_type)})}),e}}function g(e,t,n,r){var i=t[e];void 0!==i&&(r&&(i=encodeURIComponent(JSON.stringify(i))),n.push(e+"="+i))}function s(e){if(void 0!==e){var t=Number(e);return isNaN(t)||t!==parseInt(e,10)?e:t}}function m(n,e){var t=n.descending?"endkey":"startkey",r=n.descending?"startkey":"endkey";if(void 0!==n[t]&&void 0!==n[r]&&0<ct(n[t],n[r]))throw new An("No rows can match your key range, reverse your start_key and end_key or set {descending : true}");if(e.reduce&&!1!==n.reduce){if(n.include_docs)throw new An("{include_docs:true} is invalid for reduce");if(n.keys&&1<n.keys.length&&!n.group&&!n.group_level)throw new An("Multi-key fetches for reduce views must use {group: true}")}["group_level","limit","skip"].forEach(function(e){var t=function(e){if(e){if("number"!=typeof e)return new An('Invalid value for integer: "'+e+'"');if(e<0)return new An('Invalid value for positive integer: "'+e+'"')}}(n[e]);if(t)throw t})}function b(t){return function(e){if(404===e.status)return t;throw e}}function w(e,n,t){var r,i="_local/doc_"+e,o={_id:i,keys:[]},s=t.get(e),c=s[0],a=s[1];return(1===(r=a).length&&/^1-/.test(r[0].rev)?Promise.resolve(o):n.db.get(i).catch(b(o))).then(function(t){return(e=t,e.keys.length?n.db.allDocs({keys:e.keys,include_docs:!0}):Promise.resolve({rows:[]})).then(function(e){return function(e,t){for(var r=[],i=new q,n=0,o=t.rows.length;n<o;n++){var s=t.rows[n].doc;if(s&&(r.push(s),i.add(s._id),s._deleted=!c.has(s._id),!s._deleted)){var a=c.get(s._id);"value"in a&&(s.value=a.value)}}var u=Ln(c);return u.forEach(function(e){if(!i.has(e)){var t={_id:e},n=c.get(e);"value"in n&&(t.value=n.value),r.push(t)}}),e.keys=Pn(u.concat(e.keys)),r.push(e),r}(t,e)});var e})}function r(e){var t="string"==typeof e?e:e.name,n=Fn[t];return n||(n=Fn[t]=new Mn),n}function k(e){return Cn(r(e),function(){return function(s){var a,u,c=t(s.mapFun,function(e,t){var n={id:u._id,key:ft(e)};null!=t&&(n.value=ft(t)),a.push(n)}),f=s.seq||0;function r(n,o){return function(){return t=n,i=o,e="_local/lastSeq",(r=s).db.get(e).catch(b({_id:e,seq:0})).then(function(n){var e=Ln(t);return Promise.all(e.map(function(e){return w(e,r,t)})).then(function(e){var t=Z(e);return n.seq=i,t.push(n),r.db.bulkDocs({docs:t})})});var r,t,i,e}}var i=new Mn;function o(){return s.sourceDB.changes({return_docs:!0,conflicts:!0,include_docs:!0,style:"all_docs",since:f,limit:Jn}).then(e)}function e(e){var t=e.results;if(t.length){var n=function(e){for(var t=new x,n=0,r=e.length;n<r;n++){var i=e[n];if("_"!==i.doc._id[0]){a=[],(u=i.doc)._deleted||d(s.sourceDB,c,u),a.sort(h);var o=l(a);t.set(i.doc._id,[o,i.changes])}f=i.seq}return t}(t);if(i.add(r(n,f)),!(t.length<Jn))return o()}}function l(e){for(var t,n=new x,r=0,i=e.length;r<i;r++){var o=e[r],s=[o.key,o.id];0<r&&0===ct(o.key,t)&&s.push(r),n.set(dt(s),o),t=o.key}return n}return o().then(function(){return i.finish()}).then(function(){s.seq=f})}(e)})()}function j(e,t){return Cn(r(e),function(){return function(r,i){var o,s=r.reduceFun&&!1!==i.reduce,a=i.skip||0;function n(e){return e.include_docs=!0,r.db.allDocs(e).then(function(e){return o=e.total_rows,e.rows.map(function(e){if("value"in e.doc&&"object"==typeof e.doc.value&&null!==e.doc.value){var t=Object.keys(e.doc.value).sort(),n=["id","key","value"];if(!(t<n||n<t))return e.doc.value}var r=function(e){for(var t=[],n=[],r=0;;){var i=e[r++];if("\0"!==i)switch(i){case"1":t.push(null);break;case"2":t.push("1"===e[r]),r++;break;case"3":var o=ht(e,r);t.push(o.num),r+=o.length;break;case"4":for(var s="";;){var a=e[r];if("\0"===a)break;s+=a,r++}s=s.replace(/\u0001\u0001/g,"\0").replace(/\u0001\u0002/g,"").replace(/\u0002\u0002/g,""),t.push(s);break;case"5":var u={element:[],index:t.length};t.push(u.element),n.push(u);break;case"6":var c={element:{},index:t.length};t.push(c.element),n.push(c);break;default:throw new Error("bad collationIndex or unexpectedly reached end of input: "+i)}else{if(1===t.length)return t.pop();pt(t,n)}}}(e.doc._id);return{key:r[0],id:r[1],value:"value"in e.doc?e.doc.value:null}})})}function e(t){var n;if(n=s?function(e,t,n){0===n.group_level&&delete n.group_level;var r,i,o,s=n.group||n.group_level,a=v(e.reduceFun),u=[],c=isNaN(n.group_level)?Number.POSITIVE_INFINITY:n.group_level;t.forEach(function(e){var t=u[u.length-1],n=s?e.key:null;if(s&&Array.isArray(n)&&(n=n.slice(0,c)),t&&0===ct(t.groupKey,n))return t.keys.push([e.key,e.id]),void t.values.push(e.value);u.push({keys:[[e.key,e.id]],values:[e.value],groupKey:n})}),t=[];for(var f=0,l=u.length;f<l;f++){var d=u[f],h=y(e.sourceDB,a,d.keys,d.values,!1);if(h.error&&h.error instanceof Sn)throw h.error;t.push({value:h.error?null:h.output,key:d.groupKey})}return{rows:(r=t,i=n.limit,o=n.skip,o=o||0,"number"==typeof i?r.slice(o,i+o):0<o?r.slice(o):r)}}(r,t,i):{total_rows:o,offset:a,rows:t},i.update_seq&&(n.update_seq=r.seq),i.include_docs){var e=Pn(t.map(_));return r.sourceDB.allDocs({keys:e,include_docs:!0,conflicts:i.conflicts,attachments:i.attachments,binary:i.binary}).then(function(e){var r=new x;return e.rows.forEach(function(e){r.set(e.id,e.doc)}),t.forEach(function(e){var t=_(e),n=r.get(t);n&&(e.doc=n)}),n})}return n}if(void 0===i.keys||i.keys.length||(i.limit=0,delete i.keys),void 0!==i.keys){var t=i.keys,u=t.map(function(e){var t={startkey:dt([e]),endkey:dt([e,{}])};return i.update_seq&&(t.update_seq=!0),n(t)});return Promise.all(u).then(Z).then(e)}var c,f,l={descending:i.descending};if(i.update_seq&&(l.update_seq=!0),"start_key"in i&&(c=i.start_key),"startkey"in i&&(c=i.startkey),"end_key"in i&&(f=i.end_key),"endkey"in i&&(f=i.endkey),void 0!==c&&(l.startkey=i.descending?dt([c,{}]):dt([c])),void 0!==f){var d=!1!==i.inclusive_end;i.descending&&(d=!d),l.endkey=dt(d?[f,{}]:[f])}if(void 0!==i.key){var h=dt([i.key]),p=dt([i.key,{}]);l.descending?(l.endkey=h,l.startkey=p):(l.startkey=h,l.endkey=p)}return s||("number"==typeof i.limit&&(l.limit=i.limit),l.skip=a),n(l).then(e)}(e,t)})()}function a(n,e,r){if("function"==typeof n._query)return t=n,i=e,o=r,new Promise(function(n,r){t._query(i,o,function(e,t){if(e)return r(e);n(t)})});var t,i,o;if(te(n))return function(e,t,n){var r,i,o,s=[],a="GET";if(g("reduce",n,s),g("include_docs",n,s),g("attachments",n,s),g("limit",n,s),g("descending",n,s),g("group",n,s),g("group_level",n,s),g("skip",n,s),g("stale",n,s),g("conflicts",n,s),g("startkey",n,s,!0),g("start_key",n,s,!0),g("endkey",n,s,!0),g("end_key",n,s,!0),g("inclusive_end",n,s),g("key",n,s,!0),g("update_seq",n,s),s=""===(s=s.join("&"))?"":"?"+s,void 0!==n.keys){var u="keys="+encodeURIComponent(JSON.stringify(n.keys));u.length+s.length+1<=2e3?s+=("?"===s[0]?"&":"?")+u:(a="POST","string"==typeof t?r={keys:n.keys}:t.keys=n.keys)}if("string"==typeof t){var c=zn(t);return e.fetch("_design/"+c[0]+"/_view/"+c[1]+s,{headers:new Xe({"Content-Type":"application/json"}),method:a,body:JSON.stringify(r)}).then(function(e){return i=e.ok,o=e.status,e.json()}).then(function(e){if(!i)throw e.status=o,H(e);return e.rows.forEach(function(e){if(e.value&&e.value.error&&"builtin_reduce_error"===e.value.error)throw new Error(e.reason)}),e}).then(p(n))}return r=r||{},Object.keys(t).forEach(function(e){Array.isArray(t[e])?r[e]=t[e]:r[e]=t[e].toString()}),e.fetch("_temp_view"+s,{headers:new Xe({"Content-Type":"application/json"}),method:"POST",body:JSON.stringify(r)}).then(function(e){return i=e.ok,o=e.status,e.json()}).then(function(e){if(!i)throw e.status=o,H(e);return e}).then(p(n))}(n,e,r);if("string"!=typeof e)return m(r,e),Kn.add(function(){return Un(n,"temp_view/temp_view",e.map,e.reduce,!0,f).then(function(e){return t=k(e).then(function(){return j(e,r)}),n=function(){return e.db.destroy()},t.then(function(e){return n().then(function(){return e})},function(e){return n().then(function(){throw e})});var t,n})}),Kn.finish();var s=e,a=zn(s),u=a[0],c=a[1];return n.get("_design/"+u).then(function(e){var t=e.views&&e.views[c];if(!t)throw new En("ddoc "+e._id+" has no view named "+c);return l(e,c),m(r,t),Un(n,s,t.map,t.reduce,!1,f).then(function(e){return"ok"===r.stale||"update_after"===r.stale?("update_after"===r.stale&&T(function(){k(e)}),j(e,r)):k(e).then(function(){return j(e,r)})})})}var i;return{query:function(e,t,n){var r,i=this;"function"==typeof t&&(n=t,t={}),t=t?((r=t).group_level=s(r.group_level),r.limit=s(r.limit),r.skip=s(r.skip),r):{},"function"==typeof e&&(e={map:e});var o=Promise.resolve().then(function(){return a(i,e,t)});return xn(o,n),o},viewCleanup:(i=function(){var e,n,t=this;return"function"==typeof t._viewCleanup?(e=t,new Promise(function(n,r){e._viewCleanup(function(e,t){if(e)return r(e);n(t)})})):te(t)?t.fetch("_view_cleanup",{headers:new Xe({"Content-Type":"application/json"}),method:"POST"}).then(function(e){return e.json()}):(n=t).get("_local/"+f).then(function(a){var u=new x;Object.keys(a.views).forEach(function(e){var t=zn(e),n="_design/"+t[0],r=t[1],i=u.get(n);i||(i=new q,u.set(n,i)),i.add(r)});var e={keys:Ln(u),include_docs:!0};return n.allDocs(e).then(function(e){var s={};e.rows.forEach(function(i){var o=i.key.substring(8);u.get(i.key).forEach(function(e){var t=o+"/"+e;a.views[t]||(t=e);var n=Object.keys(a.views[t]),r=i.doc&&i.doc.views&&i.doc.views[e];n.forEach(function(e){s[e]=s[e]||r})})});var t=Object.keys(s).filter(function(e){return!s[e]}).map(function(e){return Cn(r(e),function(){return new n.constructor(e,n.__opts).destroy()})()});return Promise.all(t).then(function(){return{ok:!0}})})},b({ok:!0}))},O(function(e){var t=e.pop(),n=i.apply(this,e);return"function"==typeof t&&xn(n,t),n}))}}("mrviews",function(e,t){if("function"==typeof e&&2===e.length){var n=e;return function(e){return n(e,t)}}return Rn(e.toString(),t)},function(e){var t=e.toString(),n=function(e){if(/^_sum/.test(e))return Gn._sum;if(/^_count/.test(e))return Gn._count;if(/^_stats/.test(e))return Gn._stats;if(/^_/.test(e))throw new Error(e+" is not a supported reduce function.")}(t);return n||Rn(t)},function(e,t){var n=e.views&&e.views[t];if("string"!=typeof n.map)throw new En("ddoc "+e._id+" has no string view named "+t+", instead found object of type: "+typeof n.map)});var Wn={query:function(e,t,n){return Qn.query.call(this,e,t,n)},viewCleanup:function(e){return Qn.viewCleanup.call(this,e)}};function Yn(e){return/^1-/.test(e)}function Hn(t,n){var e=Object.keys(n._attachments);return Promise.all(e.map(function(e){return t.getAttachment(n._id,e,{rev:n._rev})}))}function Xn(r,u,i,o){i=R(i);var s=[],c=!0;function t(e){return r.allDocs({keys:e,include_docs:!0,conflicts:!0}).then(function(e){if(o.cancelled)throw new Error("cancelled");e.rows.forEach(function(e){var t,n;e.deleted||!e.doc||!Yn(e.value.rev)||(n=e.doc)._attachments&&0<Object.keys(n._attachments).length||(t=e.doc)._conflicts&&0<t._conflicts.length||(e.doc._conflicts&&delete e.doc._conflicts,s.push(e.doc),delete i[e.id])})})}return Promise.resolve().then(function(){var e=Object.keys(i).filter(function(e){var t=i[e].missing;return 1===t.length&&Yn(t[0])});if(0<e.length)return t(e)}).then(function(){var e,n,t=(e=i,n=[],Object.keys(e).forEach(function(t){e[t].missing.forEach(function(e){n.push({id:t,rev:e})})}),{docs:n,revs:!0,latest:!0});if(t.docs.length)return r.bulkGet(t).then(function(e){if(o.cancelled)throw new Error("cancelled");return Promise.all(e.results.map(function(e){return Promise.all(e.docs.map(function(e){var o,s,a,t,n,i=e.ok;return e.error&&(c=!1),i&&i._attachments?(o=u,s=r,a=i,t=te(s)&&!te(o),n=Object.keys(a._attachments),t?o.get(a._id).then(function(i){return Promise.all(n.map(function(e){return n=a,r=e,(t=i)._attachments&&t._attachments[r]&&t._attachments[r].digest===n._attachments[r].digest?o.getAttachment(i._id,e):s.getAttachment(a._id,e);var t,n,r}))}).catch(function(e){if(404!==e.status)throw e;return Hn(s,a)}):Hn(s,a)).then(function(e){var r=Object.keys(i._attachments);return e.forEach(function(e,t){var n=i._attachments[r[t]];delete n.stub,delete n.length,n.data=e}),i}):i}))})).then(function(e){s=s.concat(Z(e).filter(Boolean))})})}).then(function(){return{ok:c,docs:s}})}var Zn=1,er="pouchdb",tr=5,nr=0;function rr(t,n,r,i,o){return t.get(n).catch(function(e){if(404===e.status)return"http"!==t.adapter&&"https"!==t.adapter||S(404,"PouchDB is just checking if a remote checkpoint exists."),{session_id:i,_id:n,history:[],replicator:er,version:Zn};throw e}).then(function(e){if(!o.cancelled&&e.last_seq!==r)return e.history=(e.history||[]).filter(function(e){return e.session_id!==i}),e.history.unshift({last_seq:r,session_id:i}),e.history=e.history.slice(0,tr),e.version=Zn,e.replicator=er,e.session_id=i,e.last_seq=r,t.put(e).catch(function(e){if(409===e.status)return rr(t,n,r,i,o);throw e})})}function ir(e,t,n,r,i){this.src=e,this.target=t,this.id=n,this.returnValue=r,this.opts=i||{}}ir.prototype.writeCheckpoint=function(e,t){var n=this;return this.updateTarget(e,t).then(function(){return n.updateSource(e,t)})},ir.prototype.updateTarget=function(e,t){return this.opts.writeTargetCheckpoint?rr(this.target,this.id,e,t,this.returnValue):Promise.resolve(!0)},ir.prototype.updateSource=function(e,t){if(this.opts.writeSourceCheckpoint){var n=this;return rr(this.src,this.id,e,t,this.returnValue).catch(function(e){if(ar(e))return!(n.opts.writeSourceCheckpoint=!1);throw e})}return Promise.resolve(!0)};var or={undefined:function(e,t){return 0===ct(e.last_seq,t.last_seq)?t.last_seq:0},1:function(e,t){return function(e,t){if(e.session_id===t.session_id)return{last_seq:e.last_seq,history:e.history};return function e(t,n){var r=t[0];var i=t.slice(1);var o=n[0];var s=n.slice(1);if(!r||0===n.length)return{last_seq:nr,history:[]};var a=r.session_id;if(sr(a,n))return{last_seq:r.last_seq,history:t};var u=o.session_id;if(sr(u,i))return{last_seq:o.last_seq,history:s};return e(i,s)}(e.history,t.history)}(t,e).last_seq}};function sr(e,t){var n=t[0],r=t.slice(1);return!(!e||0===t.length)&&(e===n.session_id||sr(e,r))}function ar(e){return"number"==typeof e.status&&4===Math.floor(e.status/100)}ir.prototype.getCheckpoint=function(){var t=this;return t.opts&&t.opts.writeSourceCheckpoint&&!t.opts.writeTargetCheckpoint?t.src.get(t.id).then(function(e){return e.last_seq||nr}).catch(function(e){if(404!==e.status)throw e;return nr}):t.target.get(t.id).then(function(n){return t.opts&&t.opts.writeTargetCheckpoint&&!t.opts.writeSourceCheckpoint?n.last_seq||nr:t.src.get(t.id).then(function(e){return n.version!==e.version?nr:(t=n.version?n.version.toString():"undefined")in or?or[t](n,e):nr;var t},function(e){if(404===e.status&&n.last_seq)return t.src.put({_id:t.id,last_seq:nr}).then(function(){return nr},function(e){return ar(e)?(t.opts.writeSourceCheckpoint=!1,n.last_seq):nr});throw e})}).catch(function(e){if(404!==e.status)throw e;return nr})};var ur=0;function cr(e,t,n){var r,i=n.doc_ids?n.doc_ids.sort(ct):"",o=n.filter?n.filter.toString():"",s="",a="",u="";return n.selector&&(u=JSON.stringify(n.selector)),n.filter&&n.query_params&&(s=JSON.stringify((r=n.query_params,Object.keys(r).sort(ct).reduce(function(e,t){return e[t]=r[t],e},{})))),n.filter&&"_view"===n.filter&&(a=n.view.toString()),Promise.all([e.id(),t.id()]).then(function(e){var t=e[0]+e[1]+o+a+s+i+u;return new Promise(function(e){je(t,e)})}).then(function(e){return"_local/"+(e=e.replace(/\//g,".").replace(/\+/g,"_"))})}function fr(r,i,o,s,a){var u,n,c,f=[],l={seq:0,changes:[],docs:[]},d=!1,h=!1,p=!1,v=0,y=o.continuous||o.live||!1,t=o.batch_size||100,_=o.batches_limit||10,g=!1,m=o.doc_ids,b=o.selector,w=[],k=Ae();a=a||{ok:!0,start_time:(new Date).toISOString(),docs_read:0,docs_written:0,doc_write_failures:0,errors:[]};var j={};function e(){return c?Promise.resolve():cr(r,i,o).then(function(e){n=e;var t={};t=!1===o.checkpoint?{writeSourceCheckpoint:!1,writeTargetCheckpoint:!1}:"source"===o.checkpoint?{writeSourceCheckpoint:!0,writeTargetCheckpoint:!1}:"target"===o.checkpoint?{writeSourceCheckpoint:!1,writeTargetCheckpoint:!0}:{writeSourceCheckpoint:!0,writeTargetCheckpoint:!0},c=new ir(r,i,n,s,t)})}function O(){if(w=[],0!==u.docs.length){var n=u.docs,e={timeout:o.timeout};return i.bulkDocs({docs:n,new_edits:!1},e).then(function(e){if(s.cancelled)throw C(),new Error("cancelled");var r=Object.create(null);e.forEach(function(e){e.error&&(r[e.id]=e)});var t=Object.keys(r).length;a.doc_write_failures+=t,a.docs_written+=n.length-t,n.forEach(function(e){var t=r[e._id];if(t){a.errors.push(t);var n=(t.name||"").toLowerCase();if("unauthorized"!==n&&"forbidden"!==n)throw t;s.emit("denied",R(t))}else w.push(e)})},function(e){throw a.doc_write_failures+=n.length,e})}}function q(){if(u.error)throw new Error("There was a problem getting docs.");a.last_seq=v=u.seq;var e=R(a);return w.length&&(e.docs=w,"number"==typeof u.pending&&(e.pending=u.pending,delete u.pending),s.emit("change",e)),d=!0,c.writeCheckpoint(u.seq,k).then(function(){if(d=!1,s.cancelled)throw C(),new Error("cancelled");u=void 0,$()}).catch(function(e){throw B(e),e})}function A(){return Xn(r,i,u.diffs,s).then(function(e){u.error=!e.ok,e.docs.forEach(function(e){delete u.diffs[e._id],a.docs_read++,u.docs.push(e)})})}function E(){var t;s.cancelled||u||(0!==f.length?(u=f.shift(),(t={},u.changes.forEach(function(e){"_user/"!==e.id&&(t[e.id]=e.changes.map(function(e){return e.rev}))}),i.revsDiff(t).then(function(e){if(s.cancelled)throw C(),new Error("cancelled");u.diffs=e})).then(A).then(O).then(q).then(E).catch(function(e){x("batch processing terminated with error",e)})):S(!0))}function S(e){0!==l.changes.length?(e||h||l.changes.length>=t)&&(f.push(l),l={seq:0,changes:[],docs:[]},"pending"!==s.state&&"stopped"!==s.state||(s.state="active",s.emit("active")),E()):0!==f.length||u||((y&&j.live||h)&&(s.state="pending",s.emit("paused")),h&&C())}function x(e,t){p||(t.message||(t.message=e),a.ok=!1,a.status="aborting",f=[],l={seq:0,changes:[],docs:[]},C(t))}function C(e){if(!(p||s.cancelled&&(a.status="cancelled",d)))if(a.status=a.status||"complete",a.end_time=(new Date).toISOString(),a.last_seq=v,p=!0,e){(e=Y(e)).result=a;var t=(e.name||"").toLowerCase();"unauthorized"===t||"forbidden"===t?(s.emit("error",e),s.removeAllListeners()):function(e,t,n,r){if(!1===e.retry)return t.emit("error",n),t.removeAllListeners();if("function"!=typeof e.back_off_function&&(e.back_off_function=M),t.emit("requestError",n),"active"===t.state||"pending"===t.state){t.emit("paused",n),t.state="stopped";var i=function(){e.current_back_off=ur};t.once("paused",function(){t.removeListener("active",i)}),t.once("active",i)}e.current_back_off=e.current_back_off||ur,e.current_back_off=e.back_off_function(e.current_back_off),setTimeout(r,e.current_back_off)}(o,s,e,function(){fr(r,i,o,s)})}else s.emit("complete",a),s.removeAllListeners()}function P(e,t,n){if(s.cancelled)return C();"number"==typeof t&&(l.pending=t),X(o)(e)&&(l.seq=e.seq||n,l.changes.push(e),T(function(){S(0===f.length&&j.live)}))}function L(e){if(g=!1,s.cancelled)return C();if(0<e.results.length)j.since=e.results[e.results.length-1].seq,$(),S(!0);else{var t=function(){y?(j.live=!0,$()):h=!0,S(!0)};u||0!==e.results.length?t():(d=!0,c.writeCheckpoint(e.last_seq,k).then(function(){d=!1,a.last_seq=v=e.last_seq,t()}).catch(B))}}function D(e){if(g=!1,s.cancelled)return C();x("changes rejected",e)}function $(){if(!g&&!h&&f.length<_){g=!0,s._changes&&(s.removeListener("cancel",s._abortChanges),s._changes.cancel()),s.once("cancel",t);var e=r.changes(j).on("change",P);e.then(n,n),e.then(L).catch(D),o.retry&&(s._changes=e,s._abortChanges=t)}function t(){e.cancel()}function n(){s.removeListener("cancel",t)}}function I(){e().then(function(){if(!s.cancelled)return c.getCheckpoint().then(function(e){j={since:v=e,limit:t,batch_size:t,style:"all_docs",doc_ids:m,selector:b,return_docs:!0},o.filter&&("string"!=typeof o.filter?j.include_docs=!0:j.filter=o.filter),"heartbeat"in o&&(j.heartbeat=o.heartbeat),"timeout"in o&&(j.timeout=o.timeout),o.query_params&&(j.query_params=o.query_params),o.view&&(j.view=o.view),$()});C()}).catch(function(e){x("getCheckpoint rejected with ",e)})}function B(e){d=!1,x("writeCheckpoint completed with error",e)}s.ready(r,i),s.cancelled?C():(s._addedListeners||(s.once("cancel",C),"function"==typeof o.complete&&(s.once("error",o.complete),s.once("complete",function(e){o.complete(null,e)})),s._addedListeners=!0),void 0===o.since?I():e().then(function(){return d=!0,c.writeCheckpoint(o.since,k)}).then(function(){d=!1,s.cancelled?C():(v=o.since,I())}).catch(B))}function lr(){a.EventEmitter.call(this),this.cancelled=!1,this.state="pending";var n=this,r=new Promise(function(e,t){n.once("complete",e),n.once("error",t)});n.then=function(e,t){return r.then(e,t)},n.catch=function(e){return r.catch(e)},n.catch(function(){})}function dr(e,t){var n=t.PouchConstructor;return"string"==typeof e?new n(e,t):e}function hr(e,t,n,r){if("function"==typeof n&&(r=n,n={}),void 0===n&&(n={}),n.doc_ids&&!Array.isArray(n.doc_ids))throw Y(z,"`doc_ids` filter parameter is not a list.");n.complete=r,(n=R(n)).continuous=n.continuous||n.live,n.retry="retry"in n&&n.retry,n.PouchConstructor=n.PouchConstructor||this;var i=new lr(n);return fr(dr(e,n),dr(t,n),n,i),i}function pr(e,t,n,r){return"function"==typeof n&&(r=n,n={}),void 0===n&&(n={}),(n=R(n)).PouchConstructor=n.PouchConstructor||this,new vr(e=dr(e,n),t=dr(t,n),n,r)}function vr(e,t,n,r){var i=this;this.canceled=!1;var o=n.push?C({},n,n.push):n,s=n.pull?C({},n,n.pull):n;function a(e){i.emit("change",{direction:"pull",change:e})}function u(e){i.emit("change",{direction:"push",change:e})}function c(e){i.emit("denied",{direction:"push",doc:e})}function f(e){i.emit("denied",{direction:"pull",doc:e})}function l(){i.pushPaused=!0,i.pullPaused&&i.emit("paused")}function d(){i.pullPaused=!0,i.pushPaused&&i.emit("paused")}function h(){i.pushPaused=!1,i.pullPaused&&i.emit("active",{direction:"push"})}function p(){i.pullPaused=!1,i.pushPaused&&i.emit("active",{direction:"pull"})}this.push=hr(e,t,o),this.pull=hr(t,e,s),this.pushPaused=!0,this.pullPaused=!0;var v={};function y(n){return function(e,t){("change"===e&&(t===a||t===u)||"denied"===e&&(t===f||t===c)||"paused"===e&&(t===d||t===l)||"active"===e&&(t===p||t===h))&&(e in v||(v[e]={}),v[e][n]=!0,2===Object.keys(v[e]).length&&i.removeAllListeners(e))}}function _(e,t,n){-1==e.listeners(t).indexOf(n)&&e.on(t,n)}n.live&&(this.push.on("complete",i.pull.cancel.bind(i.pull)),this.pull.on("complete",i.push.cancel.bind(i.push))),this.on("newListener",function(e){"change"===e?(_(i.pull,"change",a),_(i.push,"change",u)):"denied"===e?(_(i.pull,"denied",f),_(i.push,"denied",c)):"active"===e?(_(i.pull,"active",p),_(i.push,"active",h)):"paused"===e&&(_(i.pull,"paused",d),_(i.push,"paused",l))}),this.on("removeListener",function(e){"change"===e?(i.pull.removeListener("change",a),i.push.removeListener("change",u)):"denied"===e?(i.pull.removeListener("denied",f),i.push.removeListener("denied",c)):"active"===e?(i.pull.removeListener("active",p),i.push.removeListener("active",h)):"paused"===e&&(i.pull.removeListener("paused",d),i.push.removeListener("paused",l))}),this.pull.on("removeListener",y("pull")),this.push.on("removeListener",y("push"));var g=Promise.all([this.push,this.pull]).then(function(e){var t={push:e[0],pull:e[1]};return i.emit("complete",t),r&&r(null,t),i.removeAllListeners(),t},function(e){if(i.cancel(),r?r(e):i.emit("error",e),i.removeAllListeners(),r)throw e});this.then=function(e,t){return g.then(e,t)},this.catch=function(e){return g.catch(e)}}r(lr,a.EventEmitter),lr.prototype.cancel=function(){this.cancelled=!0,this.state="cancelled",this.emit("cancel")},lr.prototype.ready=function(e,t){var n=this;function r(){n.cancel()}n._readyCalled||(n._readyCalled=!0,e.once("destroyed",r),t.once("destroyed",r),n.once("complete",function(){e.removeListener("destroyed",r),t.removeListener("destroyed",r)}))},r(vr,a.EventEmitter),vr.prototype.cancel=function(){this.canceled||(this.canceled=!0,this.push.cancel(),this.pull.cancel())},We.plugin(function(e){e.adapter("idb",dn,!0)}).plugin(function(e){e.adapter("http",qn,!1),e.adapter("https",qn,!1)}).plugin(Wn).plugin(function(e){e.replicate=hr,e.sync=pr,Object.defineProperty(e.prototype,"replicate",{get:function(){var r=this;return void 0===this.replicateMethods&&(this.replicateMethods={from:function(e,t,n){return r.constructor.replicate(e,r,t,n)},to:function(e,t,n){return r.constructor.replicate(r,e,t,n)}}),this.replicateMethods}}),e.prototype.sync=function(e,t,n){return this.constructor.sync(this,e,t,n)}}),_r.exports=We}).call(this,yr(5),"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{1:1,12:12,2:2,3:3,4:4,5:5,6:6,7:7}]},{},[13])(13)});\ No newline at end of file

index 0000000..9025083

--- /dev/null

+"use strict";++self.importScripts('js/pouchdb-7.0.0.min.js');++// Update this when you need to force the cache to reload+const version = 70;+const staticCacheName = 'teacup-'+version;++// Set up the local database in IndexDB+var DB = new PouchDB('teacup-1', {+  adapter: 'idb'+});++console.log('Cache version: '+staticCacheName);++// Cache all the required assets when the ServiceWorker installs+addEventListener('install', function (event) {+  console.log('The service worker is installing...');+  skipWaiting();+  event.waitUntil(+    cacheAssets()+  ); // end waitUntil+});++// When the ServiceWorker is activated, update the cache if the cache name changed+addEventListener('activate', activateEvent => {+  console.log('The service worker is activated.');++  activateEvent.waitUntil(+    caches.keys()+    .then( cacheNames => {+      return Promise.all(+        cacheNames.map( cacheName => {+          if (cacheName != staticCacheName) {+            return caches.delete(cacheName);+          } // end if+        }) // end map+      ); // end return Promise.all+    }) // end keys then+    .then( () => {+      var claim = clients.claim();+      sendAlert('version-'+version);+      return claim;+    }) // end then+  );++});++addEventListener('message', messageEvent => {+  console.log('Got message', messageEvent);++  if(messageEvent.data == 'version') {+    sendAlert('version-'+version);+    return;+  }++  if(messageEvent.data.action == 'create') {+    addNewPost(messageEvent.data.post, function(){+      console.log('Saved new post');+      sendAlert('new-post');+    });+    return;+  }++});++// Intercept HTTP requests+addEventListener("fetch", fetchEvent => {+  var t = fetchEvent.request, a = new URL(t.url);++  const request = fetchEvent.request;+  fetchEvent.respondWith(++    // First look in the cache+    caches.match(request)+    .then(responseFromCache => {+      if(responseFromCache) {+        return responseFromCache;+      }++      // Otherwise fetch from the network+      return fetch(request);++    }) // end match then++  ); // end respondWith+}); // end event listener+++function cacheAssets() {+  caches.open(staticCacheName)+  .then( cache => {+    // Nice to have, won't error if these fail+    cache.addAll([+      'images/teacup-16px.png',+      'images/teacup-icon-57.png',+      'images/teacup-icon-72.png',+      'images/teacup-icon-114.png',+      'images/teacup-icon-144.png',+    ]);++    // Must have, will error if they fail to download+    return cache.addAll([+      '/',+      '/settings.html',+      '/style.css',+      '/js/jquery-3.3.1.js',+      '/js/pouchdb-7.0.0.min.js',+      '/js/script.js',+      '/images/teacup-144.png',+      '/bootstrap-4.1.3/css/bootstrap.min.css',+      '/bootstrap-4.1.3/js/bootstrap.min.js',+    ]);+  })+}++// Write a post to the local database+function addNewPost(post, callback) {+  // Generate a unique ID for PouchDB+  post['_id'] = ''+(new Date()).getTime();+  DB.put(post).then(response => {+    console.log('saved to database', response);+    callback();+  })+  .catch(err => {+    console.log('error saving to database', err);+  })+}++// Load posts from the database+function loadAllPosts(callback) {+  console.log(DB);++  DB.allDocs({+    include_docs: true+  }).then(items => {+    console.log('Loaded posts', items);+    callback(items)+  }).catch(e => {+    console.log('Error loading posts', e);+  });+}++function sendAlert(alert) {+  clients.matchAll().then(clients => {+    clients.forEach(client => {+      client.postMessage(alert);+    })+  })+}

--- a/canopy/__web__/static/stylesheets/layout.css

+++ b/canopy/__web__/static/stylesheets/layout.css

         grid-column: 1; }     body > div > article > .body > aside {         grid-column: 2; }+    body.fullwidth > div > article > .body > div {+        grid-column-start: 1;+        grid-column-end: 3; }      body > div > article + nav {         grid-column: 2;

index 0000000..5540fbd

--- /dev/null

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
 
+import importlib+import json+import os+import re+import secrets+# import signal+import socket+import sys+import time+import uuid++import pendulum+import qrcode+import requests+import sh+import web+from web import tx++from .. import cache+from ..types.resources import slugs+from ..util import digitalocean, dynadot+from ..util import get_path, if_owner, get_repo, get_root+from ..util import schedule, is_scheduled, enqueue, update_inbox+from .util import view+view = view.system++# TODO backups, third party API credentials+++app = web.application("canopy-system", mount_prefix="system", provider=r"\w+",+                      identity=r"\w+", machine=r"\d+", table=r"\w+",+                      hostslug=slugs.host, pathslug=slugs.path,+                      job_module=r"[\w.]+", job_object=r"\w+",+                      job_arghash=r"\w+", job_run_id=r"\!+")+random = secrets.SystemRandom()+++@app.route(r"")+class System:++    @if_owner+    def GET(self):+        return view.index()+++@app.route(r"security")+class Security:++    @if_owner+    def GET(self):+        return view.security.index()+++@app.route(r"security/second-factor")+class SecondFactor:++    """+    manage Time-based One Time Pad (TOTP) second factor authentication++    """++    @if_owner+    def GET(self):+        web.header("Content-Type", "image/png")+        web.header("X-Accel-Redirect", f"/X/totp.png")++    @if_owner+    def POST(self):+        action = web.form("action").action+        if action == "activate":+            key = "".join(random.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567")+                          for _ in range(16))+            tx.kv["totp"] = key+            qrcode.make(f"otpauth://totp/{tx.host.name}:me?secret={key}&"+                        f"issuer={tx.host.name}").save(get_path() / "totp.png")+            return view.security.second_factor_activated()+        elif action == "deactivate":+            tx.kv["totp"] = ""+            return view.security.second_factor_deactivated()+        raise web.BadRequest("unrecognized action")+++def prepare_clone():+    """++    """+    do = digitalocean.Client(tx.kv["providers:digitalocean:auth_token"])+    key = do_get_key(do)+    droplet = do.create_droplet("Detritus", size="1gb", ssh_keys=[key["id"]])+    do_wait(do, droplet["id"], "generating temporary droplet")+    droplet = do.get_droplet(droplet["id"])+    ip_address = droplet["networks"]["v4"][0]["ip_address"]+    root_ssh = do_get_ssh("root", ip_address, wait_for_live=True)+    print(time.time(), "spawning gaea..")+    root_ssh("wget", "https://lahacker.net/code/canopy/raw/gaea.py")+    root_ssh("python3", "gaea.py", "gaea")+    gaea_ssh = do_get_ssh("gaea", ip_address)+    print(time.time(), "spawning a canopy..")+    gaea_ssh("wget", "https://lahacker.net/code/canopy/raw/gaea.py")+    gaea_ssh("python3", "gaea.py", "canopy")+    print(time.time(), "shutting down..")+    try:+        root_ssh("shutdown", "-h", "now")+    except sh.ErrorReturnCode_255:+        time.sleep(20)+    print(time.time(), "taking snapshot..")+    snapshot = do.take_snapshot(droplet["id"], "Canopy")+    do_wait(do, droplet["id"], "taking snapshot")+    snapshot = do.get_droplet_snapshots(droplet["id"])[0]+    do.delete_droplet(droplet["id"])+    tx.kv["providers:digitalocean:clone_snapshot_id"] = snapshot["id"]+++def spawn_machine():+    """++    """+    do = digitalocean.Client(tx.kv["providers:digitalocean:auth_token"])+    key = do_get_key(do)+    snapshot_id = str(tx.kv["providers:digitalocean:clone_snapshot_id"])+    droplet_id = do.create_droplet(str(uuid.uuid4()), size="2gb",+                                   image=snapshot_id, tags=["Canopy"],+                                   ssh_keys=[key["id"]])["id"]+    do_wait(do, droplet_id, "generating droplet")+    ip = do.get_droplet(droplet_id)["networks"]["v4"][0]["ip_address"]+    tx.db.insert("machines", id=droplet_id, ip=ip)+++def spawn_identity(name, passphrase, subdomain=None, domain=None):+    """++    """+    machine = random.choice(tx.db.select("machines"))+    if domain:+        if subdomain:+            do = digitalocean.Client(tx.kv["providers", "digitalocean",+                                           "auth_token"])+            do.create_domain_record(domain, subdomain, machine["ip"])+            time.sleep(10)+            domain = f"{subdomain}.{domain}"+    ssh = do_get_ssh("gaea", machine["ip"])+    domain_args = () if domain is None else ("--domain", domain)+    uuid = str(ssh("python3", "gaea.py", "tree",+                   f'"{name}"', f'"{passphrase}"', *domain_args)).strip()+    onion = str(ssh("cat", f"canopy/var/identities/{uuid}"+                           f"/nginx/tor/hostname")).strip()+    tx.db.insert("identities", machine_id=machine["id"],+                 uuid=uuid, onion=onion, domain=domain)+++def do_get_key(do):+    """++    """+    key_path = get_path() / "sshkey.pub"+    try:+        with key_path.open() as fp:+            key_data = fp.read().strip()+    except FileNotFoundError:+        sh.ssh_keygen("-o", "-a", "100", "-t", "ed25519", "-N", "",+                      "-f", str(key_path)[:-4])+        with key_path.open() as fp:+            key_data = fp.read().strip()+        key = do.add_key("gaea", key_data)+    else:+        for key in do.get_keys()["ssh_keys"]:+            if key["public_key"] == key_data:+                break+        else:+            key = do.add_key("gaea", key_data)+    return key+++def do_get_ssh(user, ip_address, wait_for_live=False):+    """+    return a shell helper function for executing a command as user++    """+    # def process_out(line):+    #     print(line, end="")+    #     sys.stdout.flush()+    def ssh(*command, env=None):+        kwargs = {}+        if env:+            kwargs["_env"] = env+        return sh.ssh("-i", str(get_path() / "sshkey"),+                      "-o", "StrictHostKeyChecking no",+                      f"{user}@{ip_address}", *command, **kwargs)+        # _out=process_out)++    if wait_for_live:+        print("waiting up to two minutes for server to come alive .", end="")+        tries = 60+        while tries:+            try:+                ssh("pwd")+            except sh.ErrorReturnCode_255:+                print(".", end="")+                sys.stdout.flush()+            else:+                break+            time.sleep(3)+            tries -= 1+        else:+            print(" couldn't connect!")+            sys.exit(1)+        time.sleep(2)++        print(" done")+    return ssh+++def do_wait(do, droplet_id, message):+    print(f"{message}", end=" ")+    while (do.get_droplet_actions(droplet_id)[0]["status"] == "in-progress"):+        print(".", end="")+        sys.stdout.flush()+        time.sleep(1)+    print(" done")+++@app.route(r"privacy")+class Privacy:++    @if_owner+    def GET(self):+        return view.privacy(int(tx.kv["vpn:active"]))++    @if_owner+    def POST(self):+        tx.kv["vpn:active"] = web.form("active").active+        return "saved"+++@app.route(r"email")+class Email:++    @if_owner+    def GET(self):+        return view.email(is_scheduled(update_inbox))++    @if_owner+    def POST(self):+        schedule("*/3 * * * *", update_inbox)+++def update_weather():+    """+    fetches weather details from Dark Sky and stores locally++    Stores all current details in `tx.sql` and the current apparent+    temperature in `tx.kv`.++    """+    geo = tx.owner["geo"]+    response = requests.get(f"https://api.darksky.net/forecast/"+                            f"{tx.kv['providers:darksky']['secret_key']}/"+                            f"{geo['latitude']},{geo['longitude']}")+    c = json.loads(response.text)["currently"]+    tx.db.insert("weather", summary=c["summary"], icon=c["icon"],+                 precipitation=c["precipIntensity"],+                 temperature=c["temperature"],+                 apparent_temperature=c["apparentTemperature"],+                 dew_point=c["dewPoint"], humidity=c["humidity"],+                 pressure=c["pressure"], wind_speed=c["windSpeed"],+                 wind_gust=c["windGust"], wind_bearing=c["windBearing"],+                 cloud_cover=c["cloudCover"], uv_index=c["uvIndex"],+                 visibility=c["visibility"], ozone=c["ozone"])+    tx.kv["weather:apparent_temperature"] = c["apparentTemperature"]+++@app.route(r"weather")+class Weather:++    @if_owner+    def GET(self):+        return view.weather(is_scheduled(update_weather))++    @if_owner+    def POST(self):+        schedule("*/2 * * * *", update_weather)+++@app.route(r"devices")+class Devices:++    def GET(self):+        return view.devices()+++@app.route(r"hosting/snapshot")+class Snapshot:++    def POST(self):+        enqueue(prepare_clone)+        return "preparing master clone.. will take ~20 minutes"+++@app.route(r"machines")+class Machines:++    def GET(self):+        machines = tx.db.select("machines")+        # do = digitalocean.Client(tx.kv["providers:digitalocean:auth_token"])+        # for snapshot in do.get_snapshots_of_droplets()["snapshots"]:+        #     if snapshot["id"] == \+        #         tx.kv["providers:digitalocean:clone_snapshot_id"]:+        #         break+        # else:+        #     snapshot = None+        return view.machines(machines)++    def POST(self):+        enqueue(spawn_machine)+        return "spawning machine.. will take ~4 minutes"+++@app.route(r"machines/{machine}")+class Machine:++    def GET(self):+        machine = tx.db.select("machines", where="id = ?",+                               vals=[self.machine])[0]+        identities = tx.db.select("identities", where="machine_id = ?",+                                  vals=[machine["id"]])+        return view.machine(machine, identities)++    def DELETE(self):+        identities = tx.db.select("identities", what="count(uuid) as c",+                                  where="machine_id = ?",+                                  vals=[self.machine])[0]["c"]+        if identities > 0:+            return "machine contains identities. empty it first."+        do = digitalocean.Client(tx.kv["providers:digitalocean:auth_token"])+        do.delete_droplet(self.machine)+        tx.db.delete("machines", where="id = ?", vals=[self.machine])+        return "ok"+++@app.route(r"domains")+class Domains:++    def GET(self):+        dd = dynadot.Client(str(tx.kv["providers:dynadot:auth_token"]))+        return view.domains(dd)++    def POST(self):+        tx.kv["providers:dynadot:domains:hosting"] = web.form("domain").domain+        # TODO do = digitalocean.Client(tx.kv["providers", "digitalocean",+        #                                     "auth_token"])+        # TODO do.create_domain(domain, local_ip)+        # TODO ensure NS and PSL and redirect domain to /hosting+        # TODO point canopy.garden to /pages/Canopy_Garden+        return "set domain for hosting"+++@app.route(r"identities")+class Identities:++    def GET(self):+        identities = tx.db.select("identities")+        return view.identities(identities)++    def POST(self):+        form = web.form("name", "passphrase", subdomain=None)+        subdomain = None+        if form.subdomain:+            subdomain = form.subdomain+        enqueue(spawn_identity, form.name, form.passphrase, subdomain,+                str(tx.kv["providers:dynadot:host_domain"]))+        return "spawning identity.. will take ~1 minute"+++@app.route(r"identities/{identity}")+class Identity:++    def get_identity(self):+        return tx.db.select("identities", where="onion LIKE ?",+                            vals=[f"{self.identity}%"])[0]++    def GET(self):+        return view.identity(self.get_identity())++    def DELETE(self):+        # TODO automatically backup and store for period of time+        identity = self.get_identity()+        ip = tx.db.select("machines", where="machine_id = ?",+                          vals=[identity["machine_id"]])[0]["ip"]+        ssh = do_get_ssh("gaea", ip, wait_for_live=True)+        ssh("runinenv", "understory", "canopy", "deletehost", identity["uuid"],+            _env={"WEBCFG": "canopy/etc/web.conf",+                  "KVDB": "canopy/run/redis.sock"})+        return "ok"+++providers = {"email": ("E-mail", "legacy mail",+                       ["SMTP Host", "SMTP Username", "SMTP Password",+                        "IMAP Host", "IMAP Username", "IMAP Password"],+                       ["fastmail.com/settings/security"]),+             "irc": ("IRC", "legacy chat", ["Host", "Port"], []),+             "twilio": ("Twilio", "voice chat and short messaging",+                        ["Account SID", "Auth Token"],+                        ["twilio.com/console"]),+             "dynadot": ("Dynadot", "domain name registrar", ["API Token"],+                         ["dynadot.com/account/domain/setting/api.html"]),+             "privateinternetaccess": ("Private Internet Access",+                                       "virtual private networking",+                                       ["Username", "Password"],+                                       ["privateinternetaccess.com/pages"+                                        "/client-control-panel"]),+             "digitalocean": ("DigitalOcean", "physical machines for serving "+                              "applications and data", ["API Token"],+                              ["cloud.digitalocean.com/account/api/tokens"]),+             "stripe": ("Stripe", "accept payments via payment card",+                        ["Public Key", "Secret Key"],+                        ["dashboard.stripe.com/account/apikeys"]),+             "darksky": ("Dark Sky", "forecasting and historical data",+                         ["Secret Key"], ["darksky.net/dev/account"]),+             "mapbox": ("Mapbox", "geocoding and map tiles",+                        ["Public Key"], ["account.mapbox.com"])}+++@app.route(r"providers")+class Providers:++    def GET(self):+        return view.providers.index(providers)+++@app.route(r"providers/{provider}")+class RegistrarProvider:++    def GET(self):+        details = tx.kv[f"providers:{self.provider}"]+        return view.providers.provider(self.provider, details,+                                       *providers[self.provider])++    def POST(self):+        tx.kv[f"providers:{self.provider}"] = dict(web.form())+        return view.providers.provider_saved(self.provider,+                                             providers[self.provider][0])+++@app.route(r"style")+class Style:++    @if_owner+    def GET(self):+        try:+            with (get_path() / "stylesheet.css").open() as fp:+                stylesheet = fp.read()+        except FileNotFoundError:+            stylesheet = ""+        return view.style(stylesheet)++    @if_owner+    def POST(self):+        stylesheet = web.form("stylesheet").stylesheet+        with (get_path() / "stylesheet.css").open("w") as fp:+            fp.write(stylesheet)+        raise web.SeeOther("/system/style")+++@app.route(r"jobs")+class Jobs:++    @if_owner+    def GET(self):+        active = tx.db.select("job_runs AS jr",+                              join="""job_signatures AS j+                                      ON j.rowid = jr.job_signature_id""",+                              where="started IS NOT NULL AND finished IS NULL")+        finished = tx.db.select("job_runs AS jr",+                                join="""job_signatures AS j+                                        ON j.rowid = jr.job_signature_id""",+                                where="finished IS NOT NULL",+                                order="finished DESC", limit=20)+        return view.jobs.index(active, finished)+++@app.route(r"jobs/{job_module}")+class JobsByModule:++    @if_owner+    def GET(self):+        jobs = tx.db.select("job_signatures", what="rowid AS id, *",+                            where="module = ?", vals=[self.job_module])+        return view.jobs.by_module(self.job_module, jobs)+++@app.route(r"jobs/{job_module}/{job_object}")+class JobsByObject:++    @if_owner+    def GET(self):+        callable = getattr(importlib.import_module(self.job_module),+                           self.job_object)+        jobs = tx.db.select("job_signatures", what="rowid AS id, *",+                            where="module = ? AND object = ?",+                            vals=[self.job_module, self.job_object])+        return view.jobs.by_object(self.job_module, self.job_object,+                                   callable, jobs)+++@app.route(r"jobs/{job_module}/{job_object}/{job_arghash}")+class Job:++    @if_owner+    def GET(self):+        callable = getattr(importlib.import_module(self.job_module),+                           self.job_object)+        job = tx.db.select("job_signatures", what="rowid AS id, *",+                           where="""module = ? AND object = ?+                                    AND arghash LIKE ?""",+                           vals=[self.job_module, self.job_object,+                                 self.job_arghash + "%"])[0]+        runs = tx.db.select("job_runs", what="*", where="job_signature_id = ?",+                            vals=[job["id"]], order="finished DESC")+        return view.jobs.job(self.job_module, self.job_object,+                             callable, job, runs)+++@app.route(r"jobs/{job_module}/{job_object}/{job_arghash}/{job_run_id}")+class JobRun:++    @if_owner+    def GET(self):+        callable = getattr(importlib.import_module(self.job_module),+                           self.job_object)+        job = tx.db.select("job_signatures", what="rowid AS id, *",+                           where="""module = ? AND object = ?+                                    AND arghash LIKE ?""",+                           vals=[self.job_module, self.job_object,+                                 self.job_arghash + "%"])[0]+        run = tx.db.select("job_runs", what="rowid, *",+                           where="id = ?", vals=[self.job_run_id],+                           order="finished DESC")[0]+        return view.jobs.run(self.job_module, self.job_object,+                             callable, job, run)+++@app.route(r"data")+class Data:++    @if_owner+    def GET(self):+        return view.data.index()+++@app.route(r"data/repository")+class DataRepository:++    @if_owner+    def GET(self):+        return view.data.repository.index(get_repo())+++@app.route(r"data/repository/log")+class DataRepositoryLog:++    @if_owner+    def GET(self):+        return view.data.repository.log(get_repo())+++@app.route(r"data/relational")+class DataRelational:++    @if_owner+    def GET(self):+        return view.data.relational.index()+++@app.route(r"data/relational/{table}")+class DataRelationalTable:++    @if_owner+    def GET(self):+        cols = tx.db.columns(self.table)+        rows = tx.db.select(self.table)+        return view.data.relational.table(self.table, cols, rows)+++@app.route(r"data/key-value")+class DataKeyValue:++    @if_owner+    def GET(self):+        return view.data.key_value.index()+++def reverse_readline(fp, buf_size=8192):+    """+    a generator that returns the lines of a file in reverse order++    """+    segment = None+    offset = 0+    fp.seek(0, os.SEEK_END)+    file_size = remaining_size = fp.tell()+    while remaining_size > 0:+        offset = min(file_size, offset + buf_size)+        fp.seek(file_size - offset)+        buffer = fp.read(min(remaining_size, buf_size))+        remaining_size -= buf_size+        lines = buffer.decode("utf-8").split('\n')+        # the first line of the buffer is probably not a complete line so+        # we'll save it and append it to the last line of the next buffer+        # we read+        if segment is not None:+            # if the previous chunk starts right from the beginning of line+            # do not concact the segment to the last line of new chunk+            # instead, yield the segment first+            if buffer[-1] is not '\n':+                lines[-1] += segment+            else:+                yield segment+        segment = lines[0]+        for index in range(len(lines) - 1, 0, -1):+            if len(lines[index]):+                yield lines[index]+    # Don't yield None if the file was empty+    if segment is not None:+        yield segment+++@app.route(r"cache")+class Cache:++    @if_owner+    def GET(self):+        return view.cache.index(cache.get_hosts())+++@app.route(r"cache/{hostslug}(/{pathslug})?.png")+class CachedResourceScreen:++    def GET(self):+        host = cache.get_hosts()[self.hostslug]+        path = getattr(self, "pathslug", "")+        filepath = f"{host.name}/{path}".rstrip("/")+        web.header("Content-Type", "image/png")+        web.header("X-Accel-Redirect", f"/X/cache/{filepath}/.screen.png")+++@app.route(r"cache/{hostslug}(/{pathslug})?")+class CachedResource:++    @if_owner+    def GET(self):+        path = getattr(self, "pathslug", "")+        try:+            host = cache.get_hosts()[self.hostslug]+        except KeyError:+            return "wait and reload"+        else:+            try:+                metadata, _ = cache.get(host / path)+            except cache.ResourceNotFound:+                metadata = None+        return view.cache.resource(host, path, metadata)++        # try:+        #     _data = mf.parse(url=url)+        # except (requests.exceptions.SSLError,+        #         requests.exceptions.ConnectionError):+        #     url.scheme = "http"+        #     url.is_secure = False+        #     _data = mf.parse(url=url)+        # card = mf.util.representative_hcard(_data, url)+        # feed = mf.util.interpret_feed(_data, str(url))+        # entry = mf.util.interpret(_data, str(url))+        # return view.url_preview(url, _data, card, feed, entry)++    @if_owner+    def POST(self):+        url = f"https://{self.hostslug}"+        if "pathslug" in self:+            url += f"/{self.pathslug}"+        enqueue(cache.update, web.uri.parse(url).minimized, parse=True)+        return "enqueued"+++@app.route(r"logs")+class Logs:++    @if_owner+    def GET(self):+        return view.logs.index()+++@app.route(r"logs/rotate")+class RotateLogs:++    access_pattern = re.compile(rb"""+        (?P<address>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|unix\:)\W+        \[(?P<timestamp>\d{2}\/[\w]{3}\/\d{4}:\d{2}:\d{2}:\d{2}\W+            (\+|\-)\d{4})\]\W+        (("(?P<method>HEAD|GET|PUT|DELETE|POST)\W)(?P<path>.+)+            ( HTTP\/(?P<version>\d\.\d)"))\W+        (?P<status>\d{3})\W(?P<duration>[\d.]+)\W(?P<bytessent>\d+)\W+        ("(?P<referrer>.*)")\W("(?P<useragent>.+)")\W+        ("(?P<cookie>.+)")""", re.X)+    invalid_access_pattern = re.compile(rb"""+        (?P<address>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|unix\:)\W+        \[(?P<timestamp>\d{2}\/[\w]{3}\/\d{4}:\d{2}:\d{2}:\d{2}\W+            (\+|\-)\d{4})\]\W+        (?P<invalid>.+)\W+        (?P<status>\d{3})\W(?P<duration>[\d.]+)\W(?P<bytessent>\d+)\W+        ("(?P<referrer>.*)")\W("(?P<useragent>.+)")\W+        ("(?P<cookie>.+)")""", re.X)+    error_pattern = re.compile(rb"""+        (?P<timestamp>\d{4}\/[\d]{2}\/\d{2}\W\d{2}:\d{2}:\d{2})\W+        \[(?P<level>\w+)\]\W+        (?P<pid>\d+)\#(?P<tid>\d+):\W\*(?P<cid>\d+)\W+        (?P<message>.+)""", re.X)++    def GET(self):+        # with open("/home/gaea/detritus/nginx/logs") as fp:+        #     nginx_pid = fp.read().strip()+        # access_tmp = get_path() / "access.log.tmp"+        # error_tmp = get_path() / "error.log.tmp"+        # (get_path() / "access.log").rename(access_tmp)+        # (get_path() / "error.log").rename(error_tmp)+        # os.kill(nginx_pid, signal.SIGUSR1)+        # time.sleep(2)++        # with access_tmp.open("rb") as fp:+        with (get_path() / "access.log.BCK").open("rb") as fp:+            for line in fp:+                match = self.access_pattern.match(line.strip())+                if match is None:+                    match = self.invalid_access_pattern.match(line.strip())+                entry = {k: v.decode("utf-8")+                         for k, v in match.groupdict().items()}+                entry["timestamp"] = pendulum.from_format(entry["timestamp"],+                                                          "DD/MMM/YYYY:"+                                                          "HH:mm:ss ZZ")+                tx.db.insert("access_log", **entry)++        i = 0+        # with error_tmp.open("rb") as fp:+        with (get_path() / "error.log.BCK").open("rb") as fp:+            for line in fp:+                match = self.error_pattern.match(line)+                tx.db.insert("error", **match.groupdict())+                i += 1+                if i == 30:+                    break++        # access_tmp.unlink()+        # error_tmp.unlink()+        return "rotated"+++@app.route(r"logs/access")+class AccessLog:++    def GET(self):+        count = tx.db.select("access_log", what="COUNT(status) AS c")[0]["c"]+        access = tx.db.select("access_log", where="invalid IS NULL",+                              order="timestamp DESC", limit=100)+        paths = tx.db.select("access_log", what="DISTINCT path",+                             order="path ASC")+        return view.logs.access(access, count, paths)+++@app.route(r"logs/error")+class ErrorLog:++    @if_owner+    def GET(self):+        error = []+        return view.logs.error(error)+++@app.route(r"logs/current")+class CurrentLog:++    def GET(self):+        js = ""+        with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s:+            s.connect(str(get_root() / "run/web-stats.sock"))+            while True:+                data = s.recv(4096)+                if len(data) < 1:+                    break+                js += data.decode("utf-8", "ignore")+        return view.logs.current(json.loads(js))

--- a/canopy/__web__/templates/__init__.py

+++ b/canopy/__web__/templates/__init__.py

 from collections import OrderedDict import inspect-import pickle from pprint import pformat  import lxml.html as lhtml
 import pendulum import qrcode from solarized import highlight+from src.api import get_doc import uri-from web.newmath import ncencode+from web.newmath import nbencode, ncencode from web import _punct_re, get_integrity_factory  from canopy.cache import interpret from canopy.content import post_types, get_type, load_json from canopy.content.read import get_resources, get_resource_version from canopy.security import get_keyring-from canopy.util import get_path, get_hostnames+from canopy.util import get_path, get_hostnames, get_plaintext from canopy.util.readability import get_smog from canopy import webmention 
 __all__ = ["inspect", "getsourcelines", "pendulum", "highlight", "actions",            "cc_licenses", "cc_license_parts", "software_licenses", "pformat",            "pf", "post_types", "get_type", "mkdn", "audiences", "lhtml",-           "get_smog", "interpret", "ncencode", "webmention", "_punct_re",-           "get_path", "get_resources", "get_resource_version",+           "get_smog", "interpret", "nbencode", "ncencode", "webmention",+           "get_path", "get_resources", "get_resource_version", "_punct_re",            "get_integrity", "load_json", "uri", "get_hostnames", "get_keyring",-           "qrcode", "pickle"]+           "qrcode", "get_doc", "get_plaintext"]   actions = {"associate": ("", "associate", "associations", "Associate"),

index 828115b..0000000

--- a/canopy/__web__/templates/app/index.html

-<!doctype html>-<html>-<head>-<title>Into the Canopy</title>-<meta name=viewport content="user-scalable=no, width=device-width,-initial-scale=1, maximum-scale=1">-<script src=//understory.live/assets/enlivenment.js-    integrity="sha256-YiFyBK1VZTbxGJOP8KCtC4rvAXOigzzAfcSh2prU7/A="-    crossorigin=anonymous></script>-<script src=//understory.live/assets/scripts/jsotp-0.1.1.js-    integrity="sha256-AnO6+YXgkNJ3b4uRuDIXSJ3bDGyGAoHGcdrWejEHNbk="-    crossorigin=anonymous></script>-<link rel=stylesheet href=https://unpkg.com/leaflet@1.2.0/dist/leaflet.css-    integrity="sha512-M2wvCLH6DSRazYeZRIm1JnYyh22purTM+FDB5CsyxtQJYeKq83arPe5wgbNmcFXGqiSH2XR8dT/fJISVA1r/zQ=="-    crossorigin=anonymous>-<script src=https://unpkg.com/leaflet@1.2.0/dist/leaflet.js-    integrity="sha512-lInM/apFSqyy1o6s89K4iQUKg6ppXEgsVxT35HbzUupEVRh2Eu9Wdl4tHj7dZO0s1uvplcYGmt3498TtHq+log=="-    crossorigin=anonymous></script>-<style>-body {-    font-size: 16px; }-#map {-    height: 30em;-    width: 50%; }-</style>-<script>-var CDN = "//understory.live/assets/";--$$.load(function() {-    var map = L.map("map");-    if (navigator.geolocation) {-        navigator.geolocation.getCurrentPosition(function(position) {-            var coords = [position.coords.latitude, position.coords.longitude];-            map.setView(coords, 18);-            L.tileLayer("https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw", {-                maxZoom: 18,-                attribution: "Map data &copy; <a href=//openstreetmap.org>OpenStreetMap</a> contributors, <a href=//creativecommons.org/licenses/by-sa/2.0>CC-BY-SA</a>, Imagery © <a href=//mapbox.com>Mapbox</a>",-                id: 'mapbox.satellite'-            }).addTo(map);-            var marker = L.marker(coords).addTo(map);-        }, function() {-            debugSocket.send("geolocation error");-        });-    }--    var success = new Audio(CDN + "sounds/spacewave.m4a");-    $$("#play").addEventListener("click", function() {-        function play() {-            success.play();-            setTimeout(play, 1000);-        }-        setTimeout(play, 1000);-    });-    -    $# L.geoJSON(assembly_districts, {-    $#     style: function (feature) {-    $#         return {color: "#3399ff"};-    $#     }-    $# }).addTo(map);-    $# -    $# /* L.geoJSON(congressional_districts, {-    $#     style: function (feature) {-    $#         return {color: "#ff3399"};-    $#     }-    $# }).addTo(map); */--    var totp = new jsOTP.totp();-    function renderOTP() {-        $$("p#otp").innerHTML = totp.getOtp("$str(tx.kv['totp-secret'])");-    }-    function updateOTP() {-        var epoch = Math.round(new Date().getTime() / 1000.0);-        var countDown = 30 - (epoch % 30);-        $$("p#otptimer").innerHTML = countDown;-        if (epoch % 30 == 0)-            renderOTP();-        setTimeout(updateOTP, 1000);-    }-    renderOTP();-    updateOTP();-});-</script>-</head>-<body>--<p id=otp></p>-<p id=otptimer></p>--<img src=/qr>--<div id=map></div>--<button id=play>Play</button>--$# <script src=/data/maps/assembly.json></script>-$# <!--script src=/static/maps/cc2011.json></script-->--</body>-</html>

index 251243e..0000000

--- a/canopy/__web__/templates/app/install.html

-$def with ()-$var naked_title: Canopy-$def head():-    <meta name=apple-mobile-web-app-capable content=yes>-    <meta name=apple-mobile-web-app-title content=Canopy>-    <meta name=apple-mobile-web-app-status-bar-style content=black-translucent>-    $# TODO <link rel=apple-touch-startup-image href=/startup-image.png>-    $# TODO <link rel=apple-touch-icon sizes=60x60 href=/touch-icon-60.png>-    $# TODO <link rel=apple-touch-icon sizes=76x76 href=/touch-icon-76.png>-    $# TODO <link rel=apple-touch-icon sizes=120x120 href=/touch-icon-120.png>-    $# TODO <link rel=apple-touch-icon sizes=152x152 href=/touch-icon-152.png>-    $# TODO <meta name=viewport content="width=device-width, initial-scale=1">-$var head = head--<p>Touch the "share" button below and chose "Add to Home Screen".</p>

--- a/canopy/__web__/templates/debug.html

+++ b/canopy/__web__/templates/debug.html

 $var title: Debug  <script>-debugSocket.onmessage = function(ev) {-    console.log(ev);--    // var entry = JSON.parse(ev.data);-    // console.log(entry);-    // $$(".h-feed").prepend(Mustache.render('<section class=h-entry>{{content}}</section>', entry));--    // var bb = document.getElementById('blackboard')-    // var html = bb.innerHTML;-    // bb.innerHTML = html + '<br/>' + e.data;-};+$$.load(() => {+    window.debugSocket.onmessage = function(ev) {+        console.log(ev);+    +        // var entry = JSON.parse(ev.data);+        // console.log(entry);+        // $$(".h-feed").prepend(Mustache.render('<section class=h-entry>{{content}}</section>', entry));+    +        // var bb = document.getElementById('blackboard')+        // var html = bb.innerHTML;+        // bb.innerHTML = html + '<br/>' + e.data;+    }+}); </script> -<button onclick='debugSocket.send("asdasd")'>SEND</button>+<button onclick='window.debugSocket.send("asdasd")'>SEND</button>

--- a/canopy/__web__/templates/editor/draft_template.html

+++ b/canopy/__web__/templates/editor/draft_template.html

         <button accesskey=d name=action value=discard>Discard</button>     </div> -    $# XXX $if pt.s in ("contact", "note", "article", "image", "like"):+    $# XXX $if pt.s in ("identity", "note", "article", "image", "like"):     $:response_context("reply", "comment", "In reply to", "in-reply-to")     $:response_context("repost", "retweet", "Re-post of", "repost-of") 
   -$ contacts = []-$for contact in get_resources("contacts"):-    $ contacts.append((contact["name"], contact["-canopy-path"], contact["-canopy-id"]))+$ identities = []+$for identity in get_resources("identities"):+    $ identities.append((identity["name"], identity["-canopy-path"], identity["-canopy-id"]))  <script> $$.load(function() {
     $# var body_complete = new Textcomplete(body_editor);     $# // var tags_complete = new Textcomplete(tags_editor); -    contacts = {-    $for contact_name, contact_url, contact_id in contacts:-        "$contact_name": {-            "id": "$contact_id",-            "url": "$contact_url"+    identities = {+    $for identity_name, identity_url, identity_id in identities:+        "$identity_name": {+            "id": "$identity_id",+            "url": "$identity_url"         },     }; 
     $#     id: "mention",     $#     match: /(^|\s)@([a-z0-9+\-\_]*)$$/,     $#     search: function (term, callback) {-    $#         callback(Object.keys(contacts).filter(function (name) {+    $#         callback(Object.keys(identities).filter(function (name) {     $#             return name.startsWith(term);     $#         }));     $#     },     $#     template: function (name) {-    $#         return '<img src="/contacts/' + contacts[name]["uid"] +-    $#                '/avatar.jpg"> ' + name + ' ' + contacts[name]["url"];+    $#         return '<img src="/identities/' + identities[name]["uid"] ++    $#                '/avatar.jpg"> ' + name + ' ' + identities[name]["url"];     $#     },     $#     replace: function (name) {-    $#         return '$$1@' + contacts[name]["url"] + ' ';+    $#         return '$$1@' + identities[name]["url"] + ' ';     $#     }     $# };     $#
     $#                           // $$("#craete #url").focus();     $#                           $$.get("/", function(data) {     $#                               $$("#feed").html($$(data).find("#feed").html());-    $#                               // $$("#contacts input[type=checkbox]").css("display", "none");+    $#                               // $$("#identities input[type=checkbox]").css("display", "none");     $#                           });     $#                       }     $#                   }
 div section img {     float: left;     margin-right: 1em; }-/* div div.contact {+/* div div.identity {        background-color: #39f; } */  div section.selected {

--- a/canopy/__web__/templates/entry_type.html

+++ b/canopy/__web__/templates/entry_type.html

     $#                 $$(".controlgroups").append($$("#controlgroup-url"));     $#                 $$(".controlgroups").append($$("#controlgroup-vote"));     $#                 break;-    $#             case "contact":+    $#             case "identity":     $#                 $$(".controlgroups").append($$("#controlgroup-url"));     $#                 break;     $#             case "event":

--- a/canopy/__web__/templates/hosting/domains.html

+++ b/canopy/__web__/templates/hosting/domains.html

-$def with (domains)-$var breadcrumbs = ("hosting", "Hosting")+$def with (dd)+$var breadcrumbs = ("system", "System") $var title: Domains -<form action=/hosting/domains method=post>-$for name, expiration in sorted(domains):-    <p><input type=radio name=domain value=$name\-    $if name == tx.kv["dynadot_host_domain"]:-         checked\-    >-    $ days = expiration.diff(pendulum.now()).days-    $if days < 120:-        <em><strong>-    $name expires <time-    datetime=$expiration.isoformat()>$expiration.isoformat()</time>-    $if days < 120:-        </strong></em>-    </p>-<button>Set domain for hosting</button>-</form>+$# <p>Account balance: $$$float(dd.account_info().find("AccountBalance").text.lstrip("$"))</p>+$# $for domain, (available, price) in dd.search("cnpy.live").items():+$#     <p>$domain+$#     $if available:+$#         $$$price+$#     $else:+$#         unavailable+$#     </p>++$ host_domains = tx.kv["providers:dynadot:domains:hosting"]++<big><strong>$", ".join(host_domains)</strong></big>++<ul>+$for name, expiration in sorted(dd.list_domain()):+    <li>$name</li>+</ul>++$# <form action=/hosting/domains method=post>+$# $for name, expiration in sorted(dd.list_domain()):+$#     $# <p><input type=hidden name=domain value=$name\+$#     $# $if name == host_domain:+$#     $#      checked\+$#     $# >+$#     $# $ days = expiration.diff(pendulum.now()).days+$#     $# $if days < 120:+$#     $#     <em><strong>+$#     $# $name expires <time+$#     $# datetime=$expiration.isoformat()>$expiration.isoformat()</time>+$#     $# $if days < 120:+$#     $#     </strong></em>+$#     $# </p>+$# <button>Set domain for hosting</button>+$# </form>

--- a/canopy/__web__/templates/hosting/identities.html

+++ b/canopy/__web__/templates/hosting/identities.html

 $var breadcrumbs = ("hosting", "Hosting") $var title: Identities -$ token = str(tx.kv["digitalocean_auth_token"])-$ snapshot_id = str(tx.kv["digitalocean_clone_snapshot_id"])+$ token = str(tx.kv["providers:digitalocean:auth_token"])+$ snapshot_id = str(tx.kv["providers:digitalocean:clone_snapshot_id"]) $if token and snapshot_id:     <form action=/hosting/identities method=post>     <fieldset>

--- a/canopy/__web__/templates/hosting/index.html

+++ b/canopy/__web__/templates/hosting/index.html

 <p>$identities identities on $machines machines</p>  $def aside():+    <p><a href=/hosting/domains>Domains</a></p>+    <p><a href=/hosting/machines>Machines</a></p>     <p><a href=/hosting/identities>Identities</a></p>-    <p><a href=/hosting/providers>Providers</a><br>-    <a href=/hosting/machines>Machines</a><br>-    <a href=/hosting/domains>Domains</a></p>+    <p><a href=/hosting/shield>Privacy Shield</a></p>+    <p><a href=/hosting/media>Media Nodes</a> (tvs, cameras, streaming!)</p>+    <p><a href=/hosting/consensus>Consensus Node</a></p>+    <p><a href=/hosting/websites>Websites</a></p> $var aside = aside

--- a/canopy/__web__/templates/hosting/machines.html

+++ b/canopy/__web__/templates/hosting/machines.html

 $var breadcrumbs = ("hosting", "Hosting") $var title: Machines -$ token = str(tx.kv["digitalocean_auth_token"])+$ token = str(tx.kv["providers:digitalocean:auth_token"])  $def aside():     $if token:         <h3>Master Snapshot</h3>-        $ snapshot = tx.kv["digitalocean_clone_snapshot_id"]+        $ snapshot = tx.kv["providers:digitalocean:clone_snapshot_id"]         <form action=/hosting/snapshot method=post>         $if snapshot:             <button>Regenerate</button>
 $var aside = aside  $if token:-    $ snapshot_id = str(tx.kv["digitalocean_clone_snapshot_id"])+    $ snapshot_id = str(tx.kv["providers:digitalocean:clone_snapshot_id"])     $if snapshot_id:         <form action=/hosting/machines method=post>         <button>Spawn New Machine</button>

index 779a173..0000000

--- a/canopy/__web__/templates/hosting/providers/host.html

-$def with ()-$var breadcrumbs = ("hosting", "Hosting", "providers", "Providers")-$var title: Host--<p><em>Initially only Digital Ocean is supported.</em></p>--$ token = str(tx.kv["digitalocean_auth_token"])--<form action=/hosting/providers/host method=post>-$if token:-    <p>Current token on file: <strong><code>$token[:16]...</code></strong></p>-<label>API Token-<div class=bounding><input type=text name=token></div></label>-<button>Set API Token</button>-</form>

index 31c49e7..0000000

--- a/canopy/__web__/templates/hosting/providers/index.html

-$def with ()-$var breadcrumbs = ("hosting", "Hosting")-$var title: Providers--<p><em>Initially only Digital Ocean and Dynadot are supported.</em></p>--<p><a href=/hosting/providers/host>Host</a><br>-<a href=/hosting/providers/registrar>Registrar</a></p>

index c525e42..0000000

--- a/canopy/__web__/templates/hosting/providers/registrar.html

-$def with ()-$var breadcrumbs = ("hosting", "Hosting", "providers", "Providers")-$var title: Registrar--<p><em>Initially only Dynadot is supported.</em></p>--$ token = str(tx.kv["dynadot_auth_token"])--<form action=/hosting/providers/registrar method=post>-$if token:-    <p>Current token on file: <strong><code>$token[:16]...</code></strong></p>-<label>API Token-<div class=bounding><input type=text name=token></div></label>-<button>Set API Token</button>-</form>

--- a/canopy/__web__/templates/hosting/register.html

+++ b/canopy/__web__/templates/hosting/register.html

  <p>The Canopy is a decentralized social network.</p> -<h2>Register your Canopy Identity</h2>+<h2>Create a Canopy Identity</h2>  <form id=create method=post action=/hosting/register> 
 <div style="text-align: right;"><label><input type=checkbox name=agree_to_terms     title="You must agree to the terms of service" required> I agree</label></div> -<p>All identities are provided an onion address. If you <em>don't</em>-chose a domain name your site will only be accessible via Tor at the-onion address.</p>--<label>Domain Name-<small><em>increases visibility/accessibility, decreases anonymity</em></small><br>-<div class="bounding code-wrapped"><input type=text-title="Domain name must be 2 to 30 characters in length" name=domain-maxlength=30 pattern="[a-z\d-]{2,30}"><code>.cnpy.gdn</code></div></label><br>--<label>Name <small><em style=color:#dc322f;>required</em></small><br>-<div class=bounding><input type=text name=name required-title="Name must be provided"></div></label><br>-<label>Passphrase <small><em style=color:#dc322f;>required</em></small><br>-<div class=bounding><input type=password name=passphrase required title="Passphrase must be provided"></div></label><br>+<input type=hidden name=service value=private>+<p><em><strong>$$10/mo</strong></em>,+<small>20GB</small> storage, <small>1TB</small> bandwidth</p> -<!--fieldset class=services id=hosting>+<fieldset class=services id=hosting> <legend>Hosting</legend> <dl>     <label>
 </dl> </fieldset> -<fieldset class=services id=dns>+<!--fieldset class=services id=dns> <legend>Domain Name (i.e. self-hosting)</legend> <dl>     <label>
     </ul></dd>     </label> </dl>-</fieldset>+</fieldset-->  <script> $$.load(function() {
         };     }); });-</script-->--<label>E-mail address <small><em>increases security/accessibility</em></small><br>-<div class=bounding><input type=email name=email></div></label><br>-<label>Phone number <small><em>increases security/accessibility</em></small><br>-<div class=bounding><input type=tel name=phone></div></label>--<input type=hidden name=service value=private>-<p><em><strong>$$10/mo</strong></em>,-<small>20GB</small> storage, <small>1TB</small> bandwidth</p>+</script> -<label>Credit or debit card<br>+<label>Payment card <small><em style=color:#dc322f;>required</em></small><br> <div class="bounding form-row"><div id=card-element></div></div></label> <div id=card-errors role=alert></div> +<label>Passphrase <small><em style=color:#dc322f;>required</em></small><br>+<div class=bounding><input type=password name=passphrase required title="Passphrase must be provided"></div></label><br>++<label>Name<br>+<div class=bounding><input type=text name=name required+title="Name must be provided"></div></label><br>++<label>Domain Name <small><em>increases accessibility, decreases privacy</em></small><br>+<div class="bounding code-wrapped"><input type=text+title="Domain name must be 2 to 30 characters in length" name=domain+maxlength=30 pattern="[a-z\d-]{2,30}"><code>.cnpy.gdn</code></div></label><br>++<label>Email address <small><em>increases security/accessibility, decreases privacy</em></small><br>+<div class=bounding><input type=email name=email></div></label><br>++<label>Phone number <small><em>increases security/accessibility, decreases privacy</em></small><br>+<div class=bounding><input type=tel name=phone></div></label>+ $# <div class=g-recaptcha data-sitekey=$recaptcha_key data-callback=submitRegistration data-size=invisible></div>  <div class=buttons><button>create</button></div>
     var stripe = Stripe("pk_test_vny8kDMevOCMcPh8zjJiu4is");     var elements = stripe.elements(); -    // Custom styling can be passed to options when creating an Element.-    var style = {-        base: {-            // Add your base input styles here. For example:-            fontSize: '16px',-            color: "#32325d",-        }-    };+    // XXX Custom styling can be passed to options when creating an Element.+    // XXX var style = {+    // XXX     base: {+    // XXX         // Add your base input styles here. For example:+    // XXX         fontSize: '16px',+    // XXX         color: "#32325d",+    // XXX     }+    // XXX };      // Create an instance of the card Element-    var card = elements.create('card', {style: style});+    // XXX var card = elements.create('card', {style: style});+    var card = elements.create('card');      function stripeTokenHandler(token) {         // Insert the token ID into the form so it gets submitted to the server

index 0000000..6f37fb4

--- /dev/null

+{+    "lang": "en",+    "name": "Into the Canopy",+    "short_name": "Into the Canopy",+    "description": "Offline-",+    "theme_color": "#0C4349",+    "background_color": "#ffffff",+    "start_url": "/",+    "display": "standalone",+    "icons": [+        {+            "src": "/images/teacup-icon-144.png",+            "sizes": "144x144",+            "type": "image/png"+        },+        {+            "src": "/images/teacup-icon-114.png",+            "sizes": "114x114",+            "type": "image/png"+        },+        {+            "src": "/images/teacup-icon-108.png",+            "sizes": "108x108",+            "type": "image/png"+        },+        {+            "src": "/images/teacup-icon-72.png",+            "sizes": "72x72",+            "type": "image/png"+        },+        {+            "src": "/images/teacup-icon-57.png",+            "sizes": "57x57",+            "type": "image/png"+        }+    ]+}

--- a/canopy/__web__/templates/resource.html

+++ b/canopy/__web__/templates/resource.html

     $ body_classes.extend(resource_template.body_classes) $var body_classes = body_classes -$var breadcrumbs = (plural, (pt.icon, plural.capitalize()))+$var breadcrumbs = (plural, plural.capitalize()) $ p = resource["published"] $if p:     $ year = p.format("YYYY")

--- a/canopy/__web__/templates/search/results.html

+++ b/canopy/__web__/templates/search/results.html

-$def with (query, ddg_results, results, types)+$def with (query, ddg_results, tpb_results, results, types) $var breadcrumbs = ("search", ("search", "Search")) $var title: Results for &ldquo;$query&rdquo; $var query = query
         <h2>The entire web</h2>         $for ddg_url, ddg_title, ddg_desc in ddg_results:             <div>-            <h3><a href=$ddg_url>$ddg_title</a></h3>-            <p><small>$ddg_desc</small></p>-            <p style="text-align: right;"><small>$ddg_url.partition("://")[2]</small></p>+            <p><big><strong><a href=$ddg_url>$ddg_title</a></strong></big><br>+            <small>$ddg_url.partition("://")[2]</small></p>+            <p><small>$:ddg_desc.replace(query, f"<strong>{query}</strong>")</small></p>+            </div>+        <h2>The Pirate Bay</h2>+        $for tpb_magnet, tpb_title, tpb_seeders, tpb_leechers in tpb_results:+            <div>+            <p><big><strong>$tpb_title</strong> ($tpb_seeders, $tpb_leechers)</big><br>+            <form method=post action=/downloads>+            <input type=hidden name=magnet value=$tpb_magnet>+            <button>Play</button>+            </form>             </div>         </section> $var aside = aside

--- a/canopy/__web__/templates/security/authenticate.html

+++ b/canopy/__web__/templates/security/authenticate.html

 <form id=authenticate method=post> <label>Secret Passphrase<br> <div class=bounding><input name=passphrase type=password></div></label>-$if tx.kv["totp-secret"]:+$if tx.kv["totp"]:     <label>Time-based Secret<br>-    <div class=bounding><input name=totp type=password></div></label>+    <div class=bounding><input name=totp type=text></div></label> <div class=buttons>     <button>Authenticate</button> </div>

index 9d5a223..0000000

--- a/canopy/__web__/templates/settings/cache/index.html

-$def with (cache)-$var title: Cache--<ul>-$for hostname, host in cache.items():-    <li><a href=/cache/$hostname>$hostname</a>-    <ul>-    $for path in sorted(host.iterdir()):-        <li><a href=/cache/$hostname/$path.name>$path.name</a></li>-    </ul>-    </li>-</ul>

index 185688a..0000000

--- a/canopy/__web__/templates/settings/cache/resource.html

-$def with (host, base, data)--$ path = host.name-$if base:-    $ path += "/" + base--$var breadcrumbs = ("cache", "Cache")-$var title = path-$var query = data["url"]--$def render_dir(path):-    <ul>-    $for subpath in sorted(path.iterdir()):-        $if not subpath.is_dir():-            $continue-        $ current = subpath.relative_to(host)-        <li><a href=/cache/$host.name/$current>$current.name</a>-        $:render_dir(subpath)-        </li>-    </ul>--$#------------------------------------------------------------------------<img src=/cache/$(path).png>--$def aside():-    $:render_dir(host / base)-    $if data:-        $ details = interpret(data["url"])-        $if "contact" in details:-            <h2>Contact</h2>-            <p>$details["contact"]["properties"]["name"][0]-            $:webaction(*actions["associate"], data["url"], condensed=True)</p>-        $if "entry" in details:-            <h2>Entry</h2>-            $ entry = details["entry"]-            <p><big>$entry.get("name")</big>-            $:webaction(*actions["like"], data["url"], condensed=True)</p>-        $if "feed" in details:-            <h2>Feed</h2>-            <p><big>$details["feed"].get("name", "Untitled")</big>-            $:webaction(*actions["follow"], data["url"], condensed=True)-            </p>-            <ul>-            $for entry in details["feed"]["entries"]:-                <li><a href=$entry["url"]>$entry.get("name")</a>\-                $ published = entry.get("published")-                $if published:-                     <time datetime=$published.isoformat()>\-                    $published.isoformat()</time></li>-            </ul>-$var aside = aside

index c3b80f8..0000000

--- a/canopy/__web__/templates/settings/index.html

-$def with ()-$var title: Settings--<p><a href=/settings/security>Security</a><br>-<a href=/settings/jobs>Jobs</a><br>-<a href=/settings/stats>Stats (current connections)</a></p>--<p><a class=breakout href=/app?secret=fcknygx><button>Install-Mobile App</button></a></p>--<p><a href=/settings/repository>repository</a></p>--<h2><a href=/settings/data>Data</a></h2>-<p><a href=/settings/data/relational>relational</a>,-<a href=/settings/data/key-value>key/value</a></p>--<h2><a href=/settings/logs>Logs</a></h2>-<p><a href=/settings/logs/access>access</a>,-<a href=/settings/logs/error>error</a>,-<a href=/settings/logs/software>software</a></p>--$# <h2>Design</h2>-$#-$# <label>Color<br>-$# <div class=bounding><input-$#     class="jscolor {onFineChange:'update_favorite_color(this)'}" name=color-$#     value="#3399ff"></div></label>-$# <script src=/static/scripts/jscolor.js></script>-$# <script>-$# function update_favorite_color(color) {-$#     $$("body").css("border-top-color", "#" + color);-$# }-$# </script>

index e213783..0000000

--- a/canopy/__web__/templates/settings/jobs.html

-$def with (active, completed)-$var breadcrumbs = ("system", "System")-$var title: Jobs--$def render_header(metadata):-    <h3>$metadata["function"].__module__:$metadata["function"].__name__</h3>-    <p>$", ".join(metadata["args"])\-    $if metadata["kwargs"]:-        $ kwargs = metadata["kwargs"]-    </p>--<h2>Active</h2>--<ul>-$if active:-    <li>-    $for job in active:-        $:render_header(job)-        <p><small>$round(job["start"], 2)</small></p>-    </li>-$else:-    <li>no active jobs</li>-</ul>--<h2>Completed</h2>--$for job in completed:-    <li>-    $:render_header(job)-    <p><small>$round(job["end"] - job["start"], 2) seconds</small></p>-    </li>

index 1c7b081..0000000

--- a/canopy/__web__/templates/settings/logs.html

-$def with (access, error)-$var breadcrumbs = ("system", "System")-$var title: Logs--<h2>Access</h2>-<ul>-$for line in access:-    $ log = line.groupdict()-    <li>-    $log["datetime"]-    $log["method"] <a href=$log["url"]>$log["url"]</a>-    $log["version"]-    $log["status"]-    $log["duration"]-    $log["bytessent"]-    $if log["referrer"] != "-":-        <a href=$log["referrer"]>$log["referrer"]</a><br>-    $log["ipaddress"]-    $log["useragent"]-    $if log["cookie"] != "-":-        <br>$log["cookie"]-    </li>-</ul>--<h2>Error</h2>-<ul>-$for line in error:-    $ log = line.groupdict()-    <li>$log</li>-</ul>

index 1e171bb..0000000

--- a/canopy/__web__/templates/settings/style.html

-$def with (stylesheet)-$var title: Style--<form method=post>-<label>Stylesheet <small>(i.e. CSS)</small><br>-<div class=bounding><textarea class=long-name=stylesheet>$stylesheet</textarea></div></label>-<button>Update</button>-</form>

--- a/canopy/__web__/templates/sitemap/human.html

+++ b/canopy/__web__/templates/sitemap/human.html

 <ul> <li><a href=/about>About</a></li> <li><a href=/mentions>Mentions</a></li>-<li><a href=/followers>Followers</a></li>-<li><a href=/following>Following</a></li>-<li><a href=/friends>Friends</a></li>-<li><a href=/family>Family</a></li>-<li><a href=/contacts>Contacts</a></li>+<li><a href=/identities>Identities</a></li> </ul>  $ seen = set()

index 0000000..dc0ad48

--- /dev/null

+$def with (cache)+$var breadcrumbs = ("system", "System")+$var title: Cache++<ul>+$for hostname, host in cache.items():+    <li><a href=/system/cache/$hostname>$hostname</a>+    <ul>+    $for path in sorted(host.iterdir()):+        <li><a href=/system/cache/$hostname/$path.name>$path.name</a></li>+    </ul>+    </li>+</ul>

index 0000000..ed434b0

--- /dev/null

+$def with (host, base, data)+$var breadcrumbs = ("system", "System", "cache", "Cache")++$ path = host.name+$if base:+    $ path += "/" + base++$var title = path+$if data:+    $var query = uri.parse(data["url"]).minimized++$def render_dir(path):+    <ul>+    $for subpath in sorted(path.iterdir()):+        $if not subpath.is_dir():+            $continue+        $ current = subpath.relative_to(host)+        <li><a href=/system/cache/$host.name/$current>$current.name</a>+        $:render_dir(subpath)+        </li>+    </ul>++$if data:+    $ details = interpret(data["url"])+    $if "contact" in details:+        <h2>Identity</h2>+        <p>$details["contact"]["properties"]["name"][0]+        $:webaction(*actions["associate"], data["url"], condensed=True)</p>+    $if "entry" in details:+        <h2>Entry</h2>+        $ entry = details["entry"]+        <p><big>$entry.get("name")</big>+        $:webaction(*actions["like"], data["url"], condensed=True)</p>+    $if "feed" in details:+        <h2>Feed</h2>+        <p><big>$details["feed"].get("name", "Untitled")</big>+        $:webaction(*actions["follow"], data["url"], condensed=True)+        </p>+        <ul>+        $for entry in details["feed"]["entries"]:+            <li>+            <pre>$pformat(entry)</pre>+            $ url = entry.get("url")+            $ name = entry.get("name")+            $if url:+                <a href="$url">$name</a>\+            $else:+                $name\+            $ published = entry.get("published")+            $if published:+                 <time datetime=$published.isoformat()>\+                $published.isoformat()</time></li>+        </ul>++$def aside():+    <form method=post action=/system/cache/$path>+    <p>Last refreshed</p>+    <button>Refresh</button>+    </form>+    $if data:+        <img src=/system/cache/$(path).png>+    $:render_dir(host / base)+$var aside = aside

rename from canopy/__web__/templates/settings/__init__.py

rename to canopy/__web__/templates/system/data/__init__.py

rename from canopy/__web__/templates/settings/data/index.html

rename to canopy/__web__/templates/system/data/index.html

 $def with ()-$var breadcrumbs = ("system", ("leaf", "System"))+$var breadcrumbs = ("system", "System") $var title: Data +<p><a href=/system/data/repository>Git Repository</a></p>++<p><a href=/system/data/relational>relational</a>,+<a href=/system/data/key-value>key/value</a></p>+ <h2>Relational Database</h2> <ul> $for table in sorted(tx.db.tables):

rename from canopy/__web__/templates/settings/data/__init__.py

rename to canopy/__web__/templates/system/data/key_value/__init__.py

rename from canopy/__web__/templates/settings/data/key_value/index.html

rename to canopy/__web__/templates/system/data/key_value/index.html

     $if not key.startswith(f"{tx.host.identity}:".encode()):         $continue     $ key = key.decode("utf-8").partition(":")[2]-    <dt>$key</dt>-    <dd>$tx.kv[key]</dd>+    <dt><code>$key</code></dt>+    <dd><code>$tx.kv[key]</code></dd> </dl>

rename from canopy/__web__/templates/settings/data/key_value/__init__.py

rename to canopy/__web__/templates/system/data/relational/__init__.py

rename from canopy/__web__/templates/settings/data/relational/index.html

rename to canopy/__web__/templates/system/data/relational/index.html

rename from canopy/__web__/templates/settings/data/relational/table.html

rename to canopy/__web__/templates/system/data/relational/table.html

 $def with (table, cols, rows)-$var breadcrumbs = ("system", ("leaf", "System"), "data", "Data", "relational", "Relational")+$var breadcrumbs = ("system", "System", "data", "Data", "relational", "Relational") $var title: $table  <ul class=table>
     <li><dl>     $for col, data in zip(cols, row):         $ col_type = col[1]-        <dt>$col[0] <small>$col[1]</small></dt>+        <dt title=$col_type>$col[0]</dt>         <dd>         $if col_type == "INTEGER":             $data+        $elif col_type == "REAL":+            $data         $elif col_type == "DATETIME":             $data         $elif col_type == "TEXT":
     color: #808080;     font-size: .8em; } .table li {-    margin-bottom: 1em; }+    margin-bottom: 3em; } .solarized.dark .table li:nth-child(even) {     background-color: #073642; } .solarized.light .table li:nth-child(even) {
 .table dt {     clear: left;     float: left;-    width: 7.5em; }+    width: 9em; } .table dd {     float: left;     margin: 0;-    width: 30em; }+    width: 28.5em; } </style>

rename from canopy/__web__/templates/settings/repository/index.html

rename to canopy/__web__/templates/system/data/repository/index.html

rename from canopy/__web__/templates/settings/repository/log.html

rename to canopy/__web__/templates/system/data/repository/log.html

index 0000000..a5eac48

--- /dev/null

+$def with ()+$var breadcrumbs = ("system", "System")+$var title: Devices++<p id=payment-requests></p>+<p id=service-workers></p>+<p id=push-notifications></p>++<script>+$$("#payment-requests").innerHTML = "Payment requests are " + (window.PaymentRequest ? "" : "not") + " supported.";+$$("#service-workers").innerHTML = "Service workers are " + (navigator.serviceWorker ? "" : "not") + " supported.";+$$("#push-notifications").innerHTML = "Push notifications are " + (window.PushManager ? "" : "not") + " supported.";++// if ('serviceWorker' in navigator && 'PushManager' in window) {+// console.log('Service Worker and Push is supported');+// navigator.serviceWorker.register('sw.js')+// .then(function(swReg) {+// console.log('Service Worker is registered', swReg);+// +// swRegistration = swReg;+// })+// .catch(function(error) {+// console.error('Service Worker Error', error);+// });+// } else {+// console.warn('Push messaging is not supported');+// pushButton.textContent = 'Push Not Supported';+// }+</script>

index 0000000..ab9707c

--- /dev/null

+$def with (is_scheduled)+$var breadcrumbs = ("system", "System")+$var title: Email++$if tx.kv["providers:email"]:+    $if is_scheduled:+        <p>See <a href=/system/jobs/schedules>job schedules</a> to modify.</p>+    $else:+        <form method=post action=/system/email>+        <button>Activate</button>+        </form>+$else:+    <p>You must subscribe to an <a href=/system/providers/email>email provider</a>.</p>

index 0000000..d0157c8

--- /dev/null

+$def with ()+$var title: System++<p>+<a href=/system/security>Security</a><br>+<a href=/system/privacy>Privacy</a><br>+<a href=/system/style>Style</a><br>+<a href=/system/data>Data</a><br>+<a href=/system/jobs>Jobs</a><br>+<a href=/system/cache>Cache</a><br>+<a href=/system/logs>Logs</a>+</p>++<p><a href=/system/providers>Providers</a></p>++<p><a href=/system/email>Email</a><br>+<a href=/system/weather>Weather</a><br>+<a href=/system/devices>Devices</a><br>+<a href=/system/machines>Machines</a><br>+<a href=/system/domains>Domains</a><br>+<a href=/system/identities>Identities</a>+</p>

index 0000000..5fb112c

--- /dev/null

+$def with (module, jobs)+$var breadcrumbs = ("system", "System", "jobs", "Jobs")+$var title: $module++<ul>+$for job in jobs:+    <li><a href=/system/jobs/$module/$job["object"]>$job["object"]</a></li>+</ul>

index 0000000..0dda0e4

--- /dev/null

+$def with (module, object, callable, jobs)+$var breadcrumbs = ("system", "System", "jobs", "Jobs", module, module)+$var title = object++$ short_desc, long_desc = get_doc(callable)++<div class=shortdesc>+$:mkdn(short_desc)+</div>++<div class=longdesc>+$:mkdn(long_desc)+</div>++<style>+.shortdesc {+    font-size: 1.25em; }+</style>++<ul>+$for job in jobs:+    $ args = load_json(job["args"])+    $ kwargs = load_json(job["kwargs"])+    $ shorthash = job["arghash"][:16]+    <li>+    $if args:+        $", ".join(args)+    $else:+        no positional arguments+    &amp;+    $if kwargs:+        $kwargs+    $else:+        no keyword arguments+    <small><a href=/system/jobs/$module/$object/$shorthash>$shorthash</a></small></li>+</ul>

index 0000000..3e8c80e

--- /dev/null

+$def with (active, finished)+$var breadcrumbs = ("system", "System")+$var title: Jobs++$def render_job(job):+    $ args = load_json(job["args"])+    $ kwargs = load_json(job["kwargs"])+    <a href=/system/jobs/$job["module"]/$job["object"]/$job["arghash"][:16]/$job["id"]><code>\+    $job["module"]:${job["object"]}\+    $for arg in args:+        <br><span title="$type(arg)">$arg</span>\+    $for k, v in kwargs.items():+        <br><span title="$type(v)">$k=$v</span>\+    </code></a>++<h2>Finished</h2>+$if finished:+    <ul>+    $for job in finished:+        <li><p>+        $:render_job(job)<br>+        <small>$job["finished"].diff_for_humans(),+        took $job["run_time"] seconds</small>+        </p></li>+    </ul>+$else:+    <p>no finished jobs</p>++$def aside():+    <h2>Active</h2>+    $if active:+        <ul>+        $for job in active:+            <li>+            $:render_job(job)+            <p><small>$job["created"], $job["started"]</small></p>+            </li>+        </ul>+    $else:+        <p>no active jobs</p>+$var aside = aside

index 0000000..11de229

--- /dev/null

+$def with (module, object, callable, job, runs)+$var breadcrumbs = ("system", "System", "jobs", "Jobs", module, module, object, object)+$var title: $module:$object+$var subtitle: <code><strong>$job["arghash"][:16]</strong> $job["arghash"][16:]</code>++<ul>+$for run in runs:+    <li>+    <p>Created: $run["created"]+    <br>Started: $run["started"]+    $if run["finished"]:+        <br>Finished: $run["finished"]+    </p>+    $if not run["finished"]:+        <form method=post action=/system/jobs>+        <button>Cancel</button>+        </form>+    <small><a href=/system/jobs/$module/$object/\+    $job["arghash"][:16]/$run["id"]>$run["id"]</a></small>+    </li>+</ul>++$def aside():+    $ args = load_json(job["args"])+    $ kwargs = load_json(job["kwargs"])++    <ol>+    $for arg in args:+        <li>$arg</li>+    </ol>++    <dl>+    $for key, val in kwargs:+        <dt>$key</dt>+        <dd>$val</dd>+    </dl>+$var aside = aside

index 0000000..2dae254

--- /dev/null

+$def with (module, object, callable, job, run)+$ shorthash = job["arghash"][:16]+$var breadcrumbs = ("system", "System", "jobs", "Jobs", module, module, object, object, shorthash, shorthash)+$var title: $module:$object+$var subtitle: <code><strong>$shorthash</strong>$job["arghash"][16:] : $run["id"]</code>++$ args = load_json(job["args"])+$ kwargs = load_json(job["kwargs"])++<ol>+$for arg in args:+    <li>$arg</li>+</ol>++<dl>+$for key, val in kwargs:+    <dt>$key</dt>+    <dd>$val</dd>+</dl>++$if run["status"]:+    <p>Error code <code>$run["status"]</code></p>+<pre>$run["output"]</pre>++$def aside():+    <p>Created: $run["created"].diff_for_humans()+    $ duration = run["started"] - run["created"]+    <br>Started: $run["started"].diff_for_humans() <small>in $(duration.seconds).$duration.microseconds seconds</small>+    $if run["finished"]:+        $ duration = run["finished"] - run["started"]+        <br>Finished: $run["finished"].diff_for_humans() <small>in $(duration.seconds).$duration.microseconds seconds</small>+    </p>+    $if not run["finished"]:+        <form method=post action=/system/jobs>+        <button>Cancel</button>+        </form>+$var aside = aside

index 0000000..849729e

--- /dev/null

+$def with (access, count, paths)+$var breadcrumbs = ("logs", "Logs")+$var title: Access++$count++<ul>+$for path in paths:+    <li>$path["path"]</li>+</ul>++<h2 id=accesslog>Access</h2>+<ul>+$for entry in access:+    <li>+    &rarr; $entry["method"] <a href=$entry["path"]>$entry["path"]</a> <small>$entry["version"]</small><br>+    &larr; $entry["status"] <small>$entry["bytessent"]b $entry["duration"]</small><br>+    <time>$entry["timestamp"]</time>+    $ address = entry["address"]+    $if address == "unix:":+        Anonymous\+    $else:+        $address\+    <br>+    $entry["useragent"]+    $if entry["cookie"] != "-":+        <br>$entry["cookie"]+    $if entry["referrer"] != "-":+        <br><a href=$entry["referrer"]>$entry["referrer"]</a>+    </li>+</ul>++<style>+#accesslog + ul {+    list-style: none;+    padding: 0; }+#accesslog + ul li {+    margin: 1em 0; }+</style>

rename from canopy/__web__/templates/settings/stats.html

rename to canopy/__web__/templates/system/logs/current.html

index 0000000..505e92c

--- /dev/null

+$def with (error)+$var breadcrumbs = ("logs", "Logs")+$var title: Error++<h2 id=errorlog>Error</h2>+<ul>+$for line in error:+    $ log = line.groupdict()+    <li><small><time>$log["datetime"]</time></small> $log["message"]<br>+    $log["level"] $log["pid"]/$log["tid"] $log["cid"]</li>+</ul>

index 0000000..f89415d

--- /dev/null

+$def with ()+$var breadcrumbs = ("system", "System")+$var title: Logs++<p><a href=/system/logs/current>Current Connections</a></p>+<p><a href=/system/logs/access>Access Log</a>,+<a href=/system/logs/error>Error Log</a></p>

index 0000000..aeda18f

--- /dev/null

+$def with (active)+$var breadcrumbs = ("system", "System")+$var title: Privacy++<form method=post action=/system/privacy>+$if active:+    <p>Currently <em>active</em>.</p>+    <button name=active value=0>Deactivate</button>+$else:+    <p>Currently <strong>not active</strong>.</p>+    <button name=active value=1>Activate</button>+</form>

index 0000000..90dabf0

--- /dev/null

+$def with (providers)+$var breadcrumbs = ("system", "System")+$var title: Providers++<ul>+$for provider, (long_name, desc, attrs, apis) in providers.items():+    <li><a href=/system/providers/$provider>$long_name</a><br><small>$desc</small>+</ul>

index 0000000..3ce6fa7

--- /dev/null

+$def with (short_name, details, long_name, desc, attrs, apis)+$var breadcrumbs = ("system", "System", "providers", "Providers")+$var title = long_name+$var subtitle: $(desc.capitalize()).++<form method=post action=/system/providers/$short_name>+$for attr in attrs:+    $ key = attr.lower().replace(" ", "_")+    <label>$attr <div class=bounding><input type=text+    name=$key value=$details.get(key, "")></div></label>+<button>Save</button>+</form>++$if apis:+    <div style="font-size: .8em;">+    <h3>References</h3>+    <p>Use the following link(s) to retrieve credentials and abide by any+    other criteria.</p>+    <ul>+    $for api in apis:+        <li><a href=https://$api>https://$api</a>+    </ul>+    </div>

index 0000000..9e2c25f

--- /dev/null

+$def with (short_name, long_name)+$var breadcrumbs = ("system", "System", "providers", "Providers", short_name, long_name)+$var title: Settings Saved++<p>Your $long_name settings have been saved.</p>

rename from canopy/__web__/templates/settings/security/index.html

rename to canopy/__web__/templates/system/security/index.html

 <button>Update</button> </form> -<h2>Time-based One Time Pad <small>i.e. TOTP</small></h2>-<form action=/system/security/totp method=post>-$if tx.kv["totp-secret"]:-    <p>Active<br>Use the following QR code to configure a third-party-    authenticator.<br><img src=/system/security/totp></p>-    <button>Regenerate</button>+<h2>Second Factor</h2>+<form action=/system/security/second-factor method=post>+$if str(tx.kv["totp"]):+    <p><big><strong>Active</strong></big></p>+    <button name=action value=deactivate>Deactivate</button> $else:-    <p>Not yet activated.</p>-    <button>Activate</button>+    <button name=action value=activate>Activate</button> </form>  $def aside():

index 0000000..9270329

--- /dev/null

+$def with ()+$var breadcrumbs = ("system", "System", "security", "Security")+$var title: Second Factor Activated++<p>Your mobile webapp can now be used for sign in.</p>++<p>Use the following scancode if you need to configure a+third-party authenticator.</p>++<p><img src=/system/security/second-factor></p>

index 0000000..302b977

--- /dev/null

+$def with ()+$var breadcrumbs = ("system", "System", "security", "Security")+$var title: Second Factor Deactivated++<p>You are no longer being protected by second factor authentication.</p>

index 0000000..95620ec

--- /dev/null

+$def with (stylesheet)+$var breadcrumbs = ("system", "System")+$var title: Style++<form method=post>+<label>Stylesheet <small>(i.e. CSS)</small><br>+<div class=bounding><textarea class=long+name=stylesheet>$stylesheet</textarea></div></label>+<button>Save</button>+</form>++$# <h2>Design</h2>+$#+$# <label>Color<br>+$# <div class=bounding><input+$#     class="jscolor {onFineChange:'update_favorite_color(this)'}" name=color+$#     value="#3399ff"></div></label>+$# <script src=/static/scripts/jscolor.js></script>+$# <script>+$# function update_favorite_color(color) {+$#     $$("body").css("border-top-color", "#" + color);+$# }+$# </script>

index 0000000..efdba36

--- /dev/null

+$def with (is_scheduled)+$var breadcrumbs = ("system", "System")+$var title: Weather++$if "street-address" in tx.owner and "region" in tx.owner:+    $if tx.kv["providers:darksky"]:+        $if is_scheduled:+            <p>See <a href=/system/jobs/schedules>job schedules</a> to modify.</p>+        $else:+            <form method=post action=/system/weather>+            <button>Activate</button>+            </form>+    $else:+        <p>You must subscribe to a <a href=/system/providers/weather>weather provider</a>.</p>+$else:+    <p>You must add an address to your <a href=/profile>profile</a>.</p>

--- a/canopy/__web__/templates/template.html

+++ b/canopy/__web__/templates/template.html

 $def with (photo, tags, published_types, published_years, resource, article)-<!doctype html>-<html class=dark>--<head>-<meta name=viewport content="user-scalable=no, width=device-width, \-initial-scale=1, maximum-scale=1"> $if tx.owner["name"]:     $ name = tx.owner["name"] $else:     $ name = tx.owner["nicknames"]-<title>\-$if "naked_title" in resource:-    $:resource.naked_title\-$else:-    $if "title" in resource:-        $resource.title&thinsp;&mdash;&thinsp;\-    $name\-</title>+<!doctype html>+<html class=dark>+<head>+<meta name=charset content=utf-8>+<meta name=viewport content="initial-scale=1.0,user-scalable=no,maximum-scale=1,width=device-width">+<meta name=apple-mobile-web-app-capable content=yes>+<meta name=apple-mobile-web-app-title content=Canopy>+<meta name=apple-mobile-web-app-status-bar-style content=black-translucent>+<link rel=manifest href=/manifest.json>+<link rel=serviceworker href=/static/scripts/serviceworker.js>+$# TODO <link rel=apple-touch-startup-image href=/startup-image.png> <link rel=icon href=/static/images/canopy.ico>-<link rel=search href=/opensearch.xml title="$tx.owner['name']" \-type=application/opensearchdescription+xml>+$# TODO <link rel=icon type=image/png href=-16px.png>+$# TODO <link rel=apple-touch-icon sizes=57x57 href=-57.png>+$# TODO <link rel=apple-touch-icon sizes=60x60 href=-60.png>+$# TODO <link rel=apple-touch-icon sizes=72x72 href=-72.png>+$# TODO <link rel=apple-touch-icon sizes=76x76 href=-76.png>+$# TODO <link rel=apple-touch-icon sizes=114x114 href=-114.png>+$# TODO <link rel=apple-touch-icon sizes=120x120 href=-120.png>+$# TODO <link rel=apple-touch-icon sizes=144x144 href=-144.png>+$# TODO <link rel=apple-touch-icon sizes=152x152 href=-152.png>+<link rel=search href=/opensearch.xml title="$name" type=application/opensearchdescription+xml> <link rel=stylesheet href=/static/stylesheets/layout.css media=screen> <link rel=stylesheet href=/static/stylesheets/color.css media=screen> <link rel=stylesheet href=/static/stylesheets/animation.css media=screen>
 <link rel=hub href=/hub> <link rel=self href=/$tx.request.uri.path> <link rel=micropub href=/micropub>-<!-- TODO async-able pack -->++<title>\+$if "naked_title" in resource:+    $:resource.naked_title\+$else:+    $if "title" in resource:+        $resource.title&thinsp;&mdash;&thinsp;\+    $name\+</title>++<!-- TODO pack everything async-able -->+<script src=/static/scripts/pouchdb-7.0.0.js></script> <script src=/static/scripts/enliven.js></script>+$# <script>+$# if(navigator.serviceWorker)+$#     navigator.serviceWorker.register("/static/scripts/serviceworker.js");+$# // make sure this database name matches the one in serviceworker.js+$# var DB = new PouchDB("canopy-1", {adapter: "idb"});+$# </script> <script src=/static/scripts/protocolcheck-2018-05-07.js></script> <script src=/static/scripts/moment-2.21.0.js></script> <script src=/static/scripts/websocket.js></script> <script> const punct_re = /$:_punct_re/;  // used for permalink slugs-const owner = "$tx.owner['name']";+const owner = "$name"; const origin = "$tx.origin"; </script> $if "script" in resource:
         <img class=u-photo src="/images/faces/$tx.owner['-canopy-id']/\         $(tx.owner['photo'].partition("-")[2]).jpg" style="max-width: 100%;">     $else:-        $:badge(tx.owner["name"], 12)+        $:badge(name, 12)     </a>-    <p class=p-name><big><strong>$tx.owner.get("name", tx.host.name)\-    </strong></big>+    <p class=p-name><big><strong>$name</strong></big>     $if "alt-svc" in tx.owner:         <br><a class=u-alt-svc         href=https://$tx.owner["alt-svc"]>$tx.owner["alt-svc"]</a>
     $#         </li>     $#     </ul> +<p>$tx.owner["locality"], $tx.owner["region"], $tx.owner["country-name"]+<small>currently feels like $tx.kv["weather:apparent_temperature"]&deg;F</small></p>+<script>+$$.load(function() {+    if (navigator.geolocation) {+        navigator.geolocation.getCurrentPosition(function(position) {+            var xhr = new XMLHttpRequest();+            var params = "latitude=" + position.coords.latitude + "&longitude=" + position.coords.longitude;+            xhr.open("POST", "/tracker");+            xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");+            xhr.setRequestHeader("X-Chromeless", "1");+            xhr.timeout = 4000;+            xhr.ontimeout = function () {+                // TODO exponential backoff?+                console.log(`Request to tracker timed out. retrying..`);+            }+            xhr.onload = function() {+                $$("#location").innerHTML = JSON.parse(xhr.responseText).distanceFromHome;+            }+            xhr.send(params);+        });+    }+});+</script>+ $if "uri" in tx.user.session:     $if "photo" in tx.owner:         <img class=u-photo style="height: 2.5em; width: 2.5em;" \         src=/images/faces/$tx.owner["-canopy-id"]/$(tx.owner["photo"].partition("-")[2]).jpg>     $else:-        $:badge(tx.owner["name"], 2)+        $:badge(name, 2)     <p class=p-name><big><strong>\     $if tx.user.is_owner:-        <a href=/about>$tx.owner["name"]</a>\+        <a href=/about>$name</a>\     $else:         <a href=/people/$tx.user.session["uri"]>$tx.user.session["name"]</a>\     </strong></big></p>+    <div id=location></div>     $if tx.user.is_owner:-        <p><a href=/reader>Reader</a><br>-        <a href=/cache>Cache</a><br>+        <p><a href=/viewer>Viewer</a><br>         <a href=/editor>Editor</a><br>-        <a href=/settings>Settings</a></p>+        <a href=/system>System</a></p>     <form id=signout class=identification action=/deauthentication method=post>         <input name=return_to type=hidden value="/$tx.request.uri.path">         <button accesskey=o>Sign Out</button>
     style="letter-spacing:.1em;text-transform:uppercase;">powered by <a \     class="p-name u-url" href=https://lahacker.net/code/canopy>canopy</a>\     </code></small></small></p>+$# <p><small>iOS users touch the "share" button and "Add to Home Screen".</small></p> </nav> </div> <footer style=display:none; id=webaction_help></footer> <footer style=display:none; id=loading>loading</footer> </body> +$# <script src=/static/scripts/moment-2.21.0.js></script>+$# <script src=/static/scripts/jsotp-0.1.1.js></script>+$# <link rel=stylesheet href=https://unpkg.com/leaflet@1.2.0/dist/leaflet.css+$#     integrity="sha512-M2wvCLH6DSRazYeZRIm1JnYyh22purTM+FDB5CsyxtQJYeKq83arPe5wgbNmcFXGqiSH2XR8dT/fJISVA1r/zQ=="+$#     crossorigin=anonymous>+$# <script src=https://unpkg.com/leaflet@1.2.0/dist/leaflet.js+$#     integrity="sha512-lInM/apFSqyy1o6s89K4iQUKg6ppXEgsVxT35HbzUupEVRh2Eu9Wdl4tHj7dZO0s1uvplcYGmt3498TtHq+log=="+$#     crossorigin=anonymous></script>+$# <script>+$# var CDN = "/static/";+$# +$# $$.load(function() {+$# +$# var map = L.map("map");+$# if (navigator.geolocation) {+$#     navigator.geolocation.getCurrentPosition(function(position) {+$#         var coords = [position.coords.latitude, position.coords.longitude];+$#         map.setView(coords, 18);+$#         L.tileLayer("https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw", {+$#             maxZoom: 18,+$#             attribution: "Map data &copy; <a href=//openstreetmap.org>OpenStreetMap</a> contributors, <a href=//creativecommons.org/licenses/by-sa/2.0>CC-BY-SA</a>, Imagery © <a href=//mapbox.com>Mapbox</a>",+$#             id: 'mapbox.satellite'+$#         }).addTo(map);+$#         var marker = L.marker(coords).addTo(map);+$#     }, function() {+$#         window.debugSocket.send("geolocation error");+$#     });+$# }+$# +$# var success = new Audio(CDN + "sounds/spacewave.m4a");+$# $$("#play").addEventListener("click", function() {+$#     function play() {+$#         success.play();+$#         setTimeout(play, 1000);+$#     }+$#     setTimeout(play, 1000);+$# });+$# +$# $# L.geoJSON(assembly_districts, {+$# $#     style: function (feature) {+$# $#         return {color: "#3399ff"};+$# $#     }+$# $# }).addTo(map);+$# $# +$# $# /* L.geoJSON(congressional_districts, {+$# $#     style: function (feature) {+$# $#         return {color: "#ff3399"};+$# $#     }+$# $# }).addTo(map); */+$# +$# var totp = new jsOTP.totp();+$# function renderOTP() {+$#     $$("p#otp").innerHTML = totp.getOtp("$str(tx.kv['totp'])");+$# }+$# function updateOTP() {+$#     var epoch = Math.round(new Date().getTime() / 1000.0);+$#     var countDown = 30 - (epoch % 30);+$#     $$("p#otptimer").innerHTML = countDown;+$#     if (epoch % 30 == 0)+$#         renderOTP();+$#     setTimeout(updateOTP, 1000);+$# }+$# renderOTP();+$# updateOTP();+$# +$# });+$# </script>+$# $# <script src=/data/maps/assembly.json></script>+$# $# <!--script src=/static/maps/cc2011.json></script-->+$# +$# <style>+$# #map {+$#     height: 30em;+$#     width: 50%; }+$# </style>+$# +$# <p id=otp></p>+$# <p id=otptimer></p>+$# <img src=/qr>+$# <div id=map></div>+$# <button id=play>Play</button>+ </html>  <!--script async src=/static/scripts/ipfs-0.32.3.js></script>

--- a/canopy/__web__/templates/url_preview.html

+++ b/canopy/__web__/templates/url_preview.html

  $def aside():     $if card:-        <form action=/contacts method=post>+        <form action=/identities method=post>         <input type=hidden name=url value="$url">-        <button>Add contact</button>+        <button>Add identity</button>         </form> $var aside = aside

rename from canopy/__web__/templates/reader/channel.html

rename to canopy/__web__/templates/viewer/channel.html

 $def aside():     $if tx.user.is_owner:         $# $for pt in post_types:-        $#     $if pt.s in ("contact",):+        $#     $if pt.s in ("identity",):         $#         $continue         $#     <form method=post action=/$pt.p>         $#     <fieldset>

rename from canopy/__web__/templates/reader/channel_settings.html

rename to canopy/__web__/templates/viewer/channel_settings.html

rename from canopy/__web__/templates/settings/data/relational/__init__.py

rename to canopy/__web__/templates/viewer/email/__init__.py

index 0000000..aa2a957

--- /dev/null

+$def with (inbox)+$var breadcrumbs = ("viewer", "Viewer")+$var title: Email+$var body_classes = ["fullwidth"]++<table>+$for email in inbox:+    $ _from = email["from"]+    <tr>+    <td class=from><a href="/viewer/email/identities/$_from[0][1]">\+    $if _from[0][0]:+        $_from[0][0]\+    $else:+        $_from[0][1]\+    </a><br><small>$_from[0][1]</small></td>+    <td class=subject><a href="/viewer/email/$email['digest']">$email["subject"]</a><br>+    <small>$email["summary"]</small></td>+    <td class=sent><small>$email["sent"].diff_for_humans()</small></td>+    </tr>+</table>++<style>+table {+    border-spacing: 0;+    border-collapse: separate;+    table-layout: fixed;+    width: 100%; }+td.from {+    padding-right: 1em;+    width: 13em; }+td.subject {+    padding-right: 1em;+    width: 32em; }+td.sent {+    text-align: right;+    width: 6em; }+td {+    overflow: hidden;+    text-overflow: ellipsis;+    vertical-align: top;+    white-space: nowrap; }+</style>

index 0000000..19d4b1d

--- /dev/null

+$def with (message, raw_message)+$var breadcrumbs = ("viewer", "Viewer", "email", "Email")+$var title: $message["subject"]+$var body_classes = ["fullwidth"]++$if message["spam"]:+    <P><BIG><STRONG>SPAM</STRONG></BIG></P>++<div>+$ from_address = message["from"][0]+<p>From: <a href="/viewer/email/identities/$from_address[1]">$from_address[0]</a></p>+<p>To: \+$for name, address in message["to"]:+    <a href=/>$name $address</a>\+    $if not loop.last:+        , \+</p>+$if message["cc"]:+    <p>CC: \+    $for name, address in message["cc"]:+        <a href=/>$name $address</a>\+        $if not loop.last:+            , \+    </p>+</div>++<form action=/viewer/email/composer>+<input type=hidden name=reply_to value=$message["digest"]>+<button>Reply</button>+</form>++$if raw_message.is_multipart():+    $for i, item in enumerate(raw_message.get_payload()):+        $ ct = item.get_content_type()+        <h2>#$i $ct</h2>+        $if ct == "text/plain":+            <pre>$get_plaintext(item)</pre></pre>+        $elif ct == "text/html":+            <iframe src=/viewer/email/$message["digest"]/$i+            width=100% height=300px></iframe>+        $elif ct.startswith("image/"):+            <img src=/viewer/email/$message["digest"]/$i width=100%>+        $elif ct == "multipart/alternative":+            $for subitem in item.get_payload():+                <p>$subitem.get_content_type()</p>+        $else:+            <strong>unsupported $ct</strong>+$else:+    <iframe src=/viewer/email/$message["digest"]/payload width=100%+    height=400px></iframe>++<style>+iframe {+    border: 0; }+</style>

rename from canopy/__web__/templates/reader/feed.html

rename to canopy/__web__/templates/viewer/feed.html

rename from canopy/__web__/templates/reader/index.html

rename to canopy/__web__/templates/viewer/index.html

 $def with (feeds)-$var title: Reader+$var title: Viewer  $def aside():     <ul>     $for feed in feeds:         $ feed_url = uri.parse(feed).minimized-        <li><a href=/reader/feeds/$feed_url>$feed_url</a></li>+        <li><a href=/viewer/feeds/$feed_url>$feed_url</a></li>     </ul> $var aside = aside

--- a/canopy/__web__/util.py

+++ b/canopy/__web__/util.py

                        channel=r"[A-Za-z\d][A-Za-z\d-]+[A-Za-z\d]", url=r".*", -                      identity=r"\w+", machine=r"\d+", factor=r"\w+")+                      factor=r"\w+",++                      email_digest=r"[0-9a-f]{32}", email_payload=r"\d+") view = mm.templates("canopy.__web__", tx=web.tx) for _post_type in post_types:     app.mount(_post_type.app)

--- a/canopy/cache.py

+++ b/canopy/cache.py

 __all__ = ["get", "put"]  -class ResourceNotFound:+class ResourceNotFound(Exception):      """""" 
      """     try:-        with get_cache_path(url, ".canopy.data.json").open() as fp:+        with get_cache_path(url, ".canopy.data.json").open("rb") as fp:             metadata = json.load(fp)     except FileNotFoundError:         raise ResourceNotFound(url)-    with get_cache_path(url, ".canopy.data").open() as fp:+    with get_cache_path(url, ".canopy.data").open("rb") as fp:         payload = fp.read()     return metadata, payload 
     web.tx.browser.shot(get_cache_path(url, ".screen"))  +def update(url, **kwargs):+    """a job for updating the cache"""+    put(url, **kwargs)++ def put(url, parse=False) -> dict:     """     fetch and store data for given `url`
     """     url = uri.parse(url) -    enqueue(screenshot, url)+    enqueue(screenshot, url.minimized)      response = request("GET", url, stream=True)     fn = get_cache_path(url, ".canopy.data")
             if chunk:                 fp.write(chunk)     if parse:-        with fn.open() as fp:+        with fn.open("rb") as fp:             html = fp.read()         metadata = mf.parse(html, url=url)     else:

--- a/canopy/content/write.py

+++ b/canopy/content/write.py

 __all__ = ["quick_draft", "publish_draft"]  -def quick_draft(resource_type: str, data: dict, cache: bool=True,-                publish: bool=False):+def quick_draft(resource_type: str, data: dict, cache: bool = True,+                publish: bool = False):     """     a shortcut for creating a new resource and optionally publishing it 
     return resource_id  -def create_resource(resource_type: str, data: dict=None) -> str:+def create_resource(resource_type: str, data: dict = None) -> str:     """     create a new blank resource of given type and return its universal ID 
     return resource_id  -def update_draft(resource_id: str, data: dict, cache: bool=True) -> None:+def update_draft(resource_id: str, data: dict, cache: bool = True) -> None:     """     update an existing draft for given resource or create one if none exist 

--- a/canopy/types/__init__.py

+++ b/canopy/types/__init__.py

 # from . import resources  -for _pt in [("Contact", "contact", "Contacts", "contacts", "user",+for _pt in [("Identity", "identity", "Identities", "identities",              "a person, organization or group"),-            ("Follow", "follow", "Follows", "follows", "follow",+            ("Follow", "follow", "Follows", "follows",              "an often appended chronological series of documents"), -            ("Note", "note", "Notes", "notes", "sticky-note",+            ("Note", "note", "Notes", "notes",              "a short-form text document"),-            # ("Article", "article", "Articles", "articles", "newspaper",+            # ("Article", "article", "Articles", "articles",             #     "a time-sensitive, mostly static long-form text document"),-            # ("Page", "page", "Pages", "pages", "file",+            # ("Page", "page", "Pages", "pages",             #     "a time-insensitive, often dynamic long-form text document"), -            ("Image", "image", "Images", "images", "image",+            ("Image", "image", "Images", "images",              "a graphic"),-            # ("Audio", "audio", "Audio", "audio", "music",+            # ("Audio", "audio", "Audio", "audio",             #     "a sound"),-            # ("Video", "video", "Video", "video", "film",+            # ("Video", "video", "Video", "video",             #     "a moving image"), -            ("Code", "code", "Code", "code", "code",+            ("Code", "code", "Code", "code",              "a set of computer instructions"),-            ("Issue", "issue", "Issues", "issues", "exclamation-circle",+            ("Issue", "issue", "Issues", "issues",              "a question or situation to be resolved"), -            # ("Event", "event", "Events", "events", "event",+            # ("Event", "event", "Events", "events",             #  ""),-            # ("RSVP", "rsvp", "RSVPs", "rsvps", "rsvp",+            # ("RSVP", "rsvp", "RSVPs", "rsvps",             #  "an event response"), -            # ("Bookmark", "bookmark", "Bookmarks", "bookmarks", "bookmark",+            # ("Bookmark", "bookmark", "Bookmarks", "bookmarks",             #     "a tagged webpage"),-            ("Like", "like", "Likes", "likes", "heart",+            ("Like", "like", "Likes", "likes",              "an enjoyed webpage"),-            ("Vote", "vote", "Votes", "votes", "vote",+            ("Vote", "vote", "Votes", "votes",              "an act of participating in a formalized choice"),             ]:     module = import_module("canopy.types.{}".format(_pt[3]))     webapp = import_module("canopy.types.{}.__web__".format(_pt[3]))-    S, s, P, p, icon, description = _pt+    S, s, P, p, description = _pt     view = webapp.view     view._globals.update(interpret=interpret, uri=uri, web=web,                          get_resource_version=get_resource_version)
     # webapp.app.route(r"{resource}/likes")(resources.Likes)      post_types.append(SN(S=S, s=s, P=P, p=p, description=description,-                         icon=icon, module=module,-                         generate_path=module.generate_path,+                         module=module, generate_path=module.generate_path,                          save=getattr(module, "save", None),                          create=getattr(module, "create", None),                          publish=getattr(module, "publish", None),

--- a/canopy/types/code/__init__.py

+++ b/canopy/types/code/__init__.py

 import web  from ...content.read import get_resource_by_id-from ...util import get_path+from ...util import get_path, get_root from ... import webmention  
     details = {}     gpghome = get_path() / "gnupg" -    system_project_dir = source_dir / project-    details["system"] = src.git.get_repo(system_project_dir, gpghome=gpghome)+    project_dir = source_dir / project+    details["system"] = src.git.get_repo(project_dir, gpghome=gpghome)     try:         details["project"] = pkg.get_distribution(project)     except pkg.DistributionNotFound:         details["project"] = None-    project_dir = get_path() / "resources/code/repositories" / project      with (project_dir / "setup.py").open() as fp:         details["description"] = fp.read().split("\n")[0].partition(": ")[2]-    details["bare"] = src.git.get_repo(str(project_dir) + ".git",-                                       gpghome=gpghome)-    details["working"] = src.git.get_repo(project_dir, gpghome=gpghome)-    details["repo"] = details["working"]  # TODO deprecate "repo" key     test_data = project_dir / "test_results.xml"     try:         details["tests"] = src.qc._parse_junit(test_data)
         details["coverage"] = src.qc._parse_coverage(coverage_data)     except FileNotFoundError:         pass++    local_project_dir = get_path() / "resources/code/repositories" / project+    details["bare"] = src.git.get_repo(str(local_project_dir) + ".git",+                                       gpghome=gpghome)+    details["working"] = src.git.get_repo(local_project_dir, gpghome=gpghome)+    details["repo"] = details["working"]  # TODO deprecate "repo" key+     return details  
  def test(name, location):     """run tests for given project in location"""-    sh.pytest("--pep8", "--cov", name, "--cov-report",-              "xml:" + str(location / "test_coverage.xml"),-              "--junit-xml", location / "test_results.xml",-              "--ignore", location / "setup.py",-              "--doctest-modules", "--doctest-report", "cdiff",-              location / "tests.py", _env=os.environ)+    try:+        sh.pytest("--pep8", "--cov", name, "--cov-report",+                  "xml:" + str(location / "test_coverage.xml"),+                  "--junit-xml", location / "test_results.xml",+                  "--ignore", location / "setup.py",+                  "--doctest-modules", "--doctest-report", "cdiff",+                  "--disable-warnings", location / "tests.py",+                  _env=os.environ, _cwd=get_root())+        # _out="/tmp/outout", _err="/tmp/outerr")+    except sh.ErrorReturnCode_1:+        pass   def get_dir(project, path) -> list:

--- a/canopy/types/code/__web__.py

+++ b/canopy/types/code/__web__.py

  from ...content.read import get_resource from ...content.write import edit_resource, update_draft, publish_draft-from ...util import enqueue, get_path+from ...util import enqueue, get_path, get_root from ..resources import slugs, Index, Versions, Version, Resource from ..resources import MentionType, Mention from . import get_project, get_mods, get_namespace, get_dir, test, view
         return view.tests(self.textslug, project)      def POST(self):-        enqueue(test, self.textslug, get_path() / "resources/code/repositories"-                / self.textslug)+        # XXX repo_dir = get_path() / "resources/code/repositories"+        # XXX            / self.textslug+        repo_dir = get_root() / "../understory/src" / self.textslug+        enqueue(test, self.textslug, repo_dir)         raise web.Accepted("tests marked to run")  
     """      def GET(self):-        project_dir = (get_path() / "resources/code/repositories" /-                       self.textslug)+        # XXX project_dir = (get_path() / "resources/code/repositories" /+        # XXX                self.textslug)+        project_dir = get_root() / "../understory/src" / self.textslug         resource_path = project_dir / self.asset         project = get_project(self.textslug)         try:

--- a/canopy/types/code/templates/index.html

+++ b/canopy/types/code/templates/index.html

 $ committer = commit["author_name"] <p><a href=/code/$project_name/commits/$hash>$commit["message"]</a><br>\ <small>created $commit["timestamp"].diff_for_humans() by <a-href="/contacts/$committer">$committer</a></small></p>+href="/identities/$committer">$committer</a></small></p>  $if details["project"]:     $ license = details["project"].details["license"]

--- a/canopy/types/code/templates/resources/file.html

+++ b/canopy/types/code/templates/resources/file.html

     $ test_count = len(tests)     $if test_count:         $ failures = [int(c["line"]) + 1-        $             for c in tests.values() if c["failure"]]+        $             for c in tests.values() if c["outcome"]["type"] == "failure"]         $ duration = 0         $for case in tests.values():             $ duration += float(case["time"])
     $if coverage_ratio:         <p>$round(coverage_ratio)% covered</p> -$:highlight(contents, resource, coverage=coverage_lines, failures=failures)+<p><a class=breakout href=/code/$project/raw/$resource>Download raw file</a></p> -$def aside():-    <p><a href=/code/$project/raw/$resource>Download raw file</a></p>-$var aside = aside+$:highlight(contents, resource, coverage=coverage_lines, failures=failures)

rename from canopy/types/code/templates/skeleton_setup.py

rename to canopy/types/code/templates/skeleton_setup.txt

 # will be useful, but *without any warranty*; without even the implied # warranty of merchantability or fitness for a particular purpose. You # can redistribute it and/or modify it under the terms of the @@[GNU's-# Not Unix][3] [Affero General Public License][4] as published by the+# Not Unix][3] %[Affero General Public License][4] as published by the # @@[Free Software Foundation][5], either version 3 of the License, or # any later version. #

--- a/canopy/types/code/templates/tests.html

+++ b/canopy/types/code/templates/tests.html

 $var title: Tests  $if "tests" in details:-    $# <pre>$pformat(details["tests"])</pre>+    $# <pre>$pformat(details["tests"]["cases"]["tests.py"])</pre>     $ test_time = 0     $for tests in details["tests"]["cases"].values():-        $for case in tests.values():-            $if case["line"] == -1:+        $for test, case in tests.items():+            $if case["line"] == "-1":                 $continue+            <h2>$test</h2>+            <p>Line $case["line"], $case["time"]</p>             $ test_time += float(case["time"])+            $ outcome = case["outcome"]+            <p>$outcome["type"]</p>+            $if outcome["type"] == "success":+                $if "output" in outcome:+                    <pre>$outcome["output"]</pre>+            $elif outcome["type"] == "failure":+                <p>$outcome["message"]</p>+                <pre>$outcome["code"]</pre>     <p>$details["tests"]["tests"] tests ran in $round(test_time) seconds.     $if int(details["tests"]["errors"]) or int(details["tests"]["failures"]):-        <span class=failing>Tests are failing</span>\+        <span class=failing>Some tests are failing</span>\     $else:         <span class=passing>All tests are passing</span>\     .</p>

--- a/canopy/types/code/templates/type.html

+++ b/canopy/types/code/templates/type.html

 $def with (entry, resource)-$var webactions = ("like", "follow", "repost", "issue")- $# <p><small>$entry["description"]</small></p>  $ resource_id = resource["-canopy-id"] $ b = get_resource_version(resource_id, entry["version"]) $if entry["version"] == 1:-    <p>Initialized <span class=p-name>$resource["name"]</span></p>+    $var webactions = ("like", "follow", "repost", "issue")+    <p>Initialized the <code class=p-name>$resource["name"]</code> project.</p>     $# <div class=e-content>$b</div> $else:+    $var webactions = ("like",)     $ a = get_resource_version(resource_id, entry["version"] - 1)     $var permalink = f"code/{resource['name']}/commits/{resource['tip']}"     <p>\     $if a.get("tip") != b.get("tip"):         $# <div class=e-content>updated</div>-        Change committed to-    <span class=p-name>$resource["name"]</span></p>+        Committed change to the+    <a class=p-name href=/code/$resource["name"]>$resource["name"]</a>+    project.</p>     $# <p><small>$entry["description"]</small></p>

--- a/canopy/types/follows/__init__.py

+++ b/canopy/types/follows/__init__.py

         properties = author["properties"]         uid = properties["uid"]         try:-            contact = get_type("contacts").module.get_contact_by_uid(uid)+            identity = get_type("people").module.get_identity_by_uid(uid)         except ResourceNotFound:             resource_data = {"name": properties["name"][0]}             print(properties)
             if "url" in properties:                 resource_data["url"] = properties["url"]             resource_data["-canopy-audience"] = "private"-            resource_id = quick_draft("contact", resource_data, cache=False,-                                      publish="Contact added during follow.")-            contact = get_resource_by_id(resource_id)-        print(contact)+            resource_id = quick_draft("identity", resource_data, cache=False,+                                      publish="Identity added during follow.")+            identity = get_resource_by_id(resource_id)+        print(identity)     try:         self = feed_metadata["rels"]["self"][0]     except KeyError:

rename from canopy/types/contacts/__init__.py

rename to canopy/types/identities/__init__.py

 from ...content import ResourceNotFound, load_json  -def get_contact_by_uuid(uuid: str) -> tuple:+def get_identity_by_uuid(uuid: str) -> tuple:     try:         resource = web.tx.db.select("finals", where="json_extract(data, "                                                     "'$.-canopy-uuid') = ?",                                     vals=[str(uuid)])[0]["data"]     except IndexError:-        raise ResourceNotFound(f"contact with uuid #{uuid}")+        raise ResourceNotFound(f"identity with uuid #{uuid}")     return load_json(resource)  -def get_contact_by_uid(uid: str) -> tuple:+def get_identity_by_uid(uid: str) -> tuple:     try:         resource = web.tx.db.select("finals", where="json_extract(data, "                                                     "'$.uid') = ?",                                     vals=[str(uid)])[0]["data"]     except IndexError:-        raise ResourceNotFound(f"contact with uid {uid}")+        raise ResourceNotFound(f"identity with uid {uid}")     return load_json(resource)  

rename from canopy/types/contacts/__web__.py

rename to canopy/types/identities/__web__.py

 from ..resources import slugs, Index, Versions, Version from ..resources import ResourceData, ResourceSignature, Resource -from .. import contacts+from .. import identities  -app = web.application("canopy-contacts", mount_prefix="contacts",+app = web.application("canopy-identities", mount_prefix="identities",                       identifier=f"{slugs.text}|"                                  f"({slugs.host}(/{slugs.path})?)",                       version=r"\d+", card_id=r"[\w-]+")-view = web.mm.templates("canopy.types.contacts", get_hostnames=get_hostnames,+view = web.mm.templates("canopy.types.identities", get_hostnames=get_hostnames,                         tx=tx, timezones=web.pendulum.timezones) canopy_app = web.application("canopy")   def generate_vcard(uuid):-    contact = contacts.get_contact_by_uuid(uuid)+    identity = identities.get_identity_by_uuid(uuid)     card = vobject.vCard()     card.add("prodid").value = "-//Canopy//canopy 0.0.1//EN"-    card.add("uid").value = contact["-canopy-uuid"]-    card.add("fn").value = contact["name"]+    card.add("uid").value = identity["-canopy-uuid"]+    card.add("fn").value = identity["name"]     return card.serialize() -    # TODO # TODO if contact["type"] == "contact":+    # TODO # TODO if identity["type"] == "identity":     # TODO n = card.add("n")     # TODO names = {}     # TODO for name_type in ("prefix", "given", "additional",     #                        "family", "suffix"):-    # TODO     if contact[name_type]:-    # TODO         names[name_type] = contact[name_type].split(";")+    # TODO     if identity[name_type]:+    # TODO         names[name_type] = identity[name_type].split(";")     # TODO n.value = vobject.vcard.Name(**names)     # TODO # TODO else:     # TODO # TODO     card.add("n")-    # TODO # TODO     card.add("org").value = [contact["name"]]+    # TODO # TODO     card.add("org").value = [identity["name"]] -    # TODO # TODO card.add("nickname").value = contact["name"]-    # TODO card.add("sort_string").value = contact["sort_string"]+    # TODO # TODO card.add("nickname").value = identity["name"]+    # TODO card.add("sort_string").value = identity["sort_string"] -    # TODO for number, types in contact["telephones"]:+    # TODO for number, types in identity["telephones"]:     # TODO     entry = card.add("tel")     # TODO     entry.value = number     # TODO     if types:     # TODO         entry.params["TYPE"] = types -    # TODO for url, types in contact["websites"]:+    # TODO for url, types in identity["websites"]:     # TODO     entry = card.add("url")     # TODO     entry.value = url     # TODO     if types:     # TODO         entry.params["TYPE"] = types      # TODO try:-    # TODO     photo_id = contact["photos"][0]+    # TODO     photo_id = identity["photos"][0]     # TODO except IndexError:     # TODO     pass     # TODO else:
     #         if val.group:     #             item_index = int(val.group[4:]) -    # for related, types in get_relationships(contact["id"]):-    #     uri = "https://{}/contacts/{}/{}.vcf".format(tx.host.name,+    # for related, types in get_relationships(identity["id"]):+    #     uri = "https://{}/identities/{}/{}.vcf".format(tx.host.name,     #                                                related["identifier"],     #                                                related["slug"])     #     rel = card.add("related")
 class About:      def GET(self):-        return view.about(get_resource("contact", "me"))+        return view.about(get_resource("identity", "me"))   @app.route(r"")-class Contacts(Index):+class Identities(Index):      """     the root type of the canopy decentralized social web platform
         depth = int(tx.request.headers["Depth"])         etags = {"": tx.kv["carddav-lasttouch"]}         if depth == 1:-            for contact in get_resources("contacts"):-                etags[contact["-canopy-uuid"]] = \-                    contact.get("updated", contact["published"]).timestamp()+            for identity in get_resources("identities"):+                etags[identity["-canopy-uuid"]] = \+                    identity.get("updated", identity["published"]).timestamp()          props = list(tx.request.body.iterchildren())[0]         namespaces = set()
                     ok.append("<owner>/</owner>")                 if prop.tag == "{DAV:}principal-URL":                     ok.append("""<principal-URL>-                                     <href>/contacts</href>+                                     <href>/identities</href>                                  </principal-URL>""")                 if prop.tag == "{DAV:}principal-collection-set":                     ok.append("""<principal-collection-set>-                                     <href>/contacts</href>+                                     <href>/identities</href>                                  </principal-collection-set>""")                 if prop.tag == "{DAV:}current-user-principal":                     ok.append("""<current-user-principal>-                                     <href>/contacts</href>+                                     <href>/identities</href>                                  </current-user-principal>""")                 if prop.tag == "{DAV:}resourcetype":                     namespaces.add("CR")
                                 "addressbook-home-set"):                     namespaces.add("CR")                     ok.append("""<CR:addressbook-home-set>-                                     <href>/contacts</href>+                                     <href>/identities</href>                                  </CR:addressbook-home-set>""")                 if (prop.tag == "{http://calendarserver.org/ns/}"                                 "getctag"):
                         notfound.append("<CS:me-card />")                     else:                         ok.append(f"""<CS:me-card>-                                      <href>/contacts/{tx.owner["-canopy-uuid"]}.vcf</href>+                                      <href>/identities/{tx.owner["-canopy-uuid"]}.vcf</href>                                       </CS:me-card>""")                  # not supported
                                 "bulk-requests"):                     namespaces.add("ME")                     notfound.append("<ME:bulk-requests />")-            href = "/contacts"+            href = "/identities"             if uuid:                 href += f"/{uuid}.vcf"             responses.append((href, ok, notfound))
      def REPORT(self):         """-        return a full listing for each requested contact+        return a full listing for each requested identity          """         etags = {}-        for contact in get_resources("contacts"):-            etags[contact["-canopy-uuid"]] = \-                contact.get("updated", contact["published"]).timestamp()+        for identity in get_resources("identities"):+            etags[identity["-canopy-uuid"]] = \+                identity.get("updated", identity["published"]).timestamp()         children = list(tx.request.body.iterchildren())         # XXX props = children[0]  # TODO soft-code prop responses         responses = []
      def PUT(self):         """-        add or update a contact+        add or update a identity          This method provides CardDAV support.          """-        # TODO only add if "if-none-match" is found and contact isn't+        # TODO only add if "if-none-match" is found and identity isn't         try:             print("if-none-match", tx.request.headers.if_none_match)         except AttributeError:             pass         else:             try:-                contacts.get_contact_by_uuid(self.card_id)+                identities.get_identity_by_uuid(self.card_id)             except ResourceNotFound:                 pass             else:-                raise web.Conflict("contact already exists")+                raise web.Conflict("identity already exists")          # TODO only update if "if-match" matches etag on hand         try:
         except AttributeError:             pass         else:-            contact = contacts.get_contact_by_uuid(self.card_id)+            identity = identities.get_identity_by_uuid(self.card_id)             current_etag = \-                contact.get("updated", contact["published"]).timestamp()+                identity.get("updated", identity["published"]).timestamp()             print("current etag", current_etag)             if request_etag != current_etag:                 raise web.Conflict("previous edit already exists")
         explode("family")         explode("suffix") -        # TODO contact_type = "person"+        # TODO identity_type = "identity"         basic = {"name": name, "uuid": self.card_id}          # TODO organizations = [o.value[0]         # TODO                  for o in card.contents.get("org", [])]         # TODO for organization in organizations:         # TODO     if organization == name:-        # TODO         contact_type = "organization"+        # TODO         identity_type = "organization"          # TODO telephones = []         # TODO for tel in card.contents.get("tel", []):
         # extended["photos"] = [photo_id]          try:-            details = contacts.get_contact_by_uuid(self.card_id)+            details = identities.get_identity_by_uuid(self.card_id)         except ResourceNotFound:-            print("NEW CONTACT!")+            print("NEW identity!")             print(basic)             print(extended)-            quick_draft("contact", basic, cache=False,-                        publish="Contact imported from iPhone.")-            # XXX details = create_contact(access="private", uid=self.card_id,+            quick_draft("identity", basic, cache=False,+                        publish="Identity imported from iPhone.")+            # XXX details = create_identity(access="private", uid=self.card_id,             # XXX                          **basic)-            # XXX details = update_contact(identifier=details["identifier"],+            # XXX details = update_identity(identifier=details["identifier"],             # XXX                  telephones=telephones, websites=websites,             # XXX                  **extended)             print("CREATED")         else:-            print("EXISTING CONTACT!")+            print("EXISTING identity!")             print(details)             print("UPDATED")         # XXX     basic.update(extended)-        # XXX     details = update_contact(identifier=details["identifier"],+        # XXX     details = update_identity(identifier=details["identifier"],         # XXX                      telephones=telephones, websites=websites,         # XXX                      **basic)-        contact = contacts.get_contact_by_uuid(self.card_id)-        etag = contact.get("updated", contact["published"]).timestamp()+        identity = identities.get_identity_by_uuid(self.card_id)+        etag = identity.get("updated", identity["published"]).timestamp()         web.header("ETag", f'"{etag}"')         tx.response.naked = True-        raise web.Created("created contact", f"/contacts/{self.card_id}.vcf")+        raise web.Created("created identity",+                          f"/identities/{self.card_id}.vcf")      def DELETE(self):         """-        delete a contact+        delete a identity          This method provides CardDAV support. 
         return f"""<?xml version="1.0"?>                    <multistatus xmlns="DAV:">                      <response>-                       <href>/contacts/{self.card_id}.vcf</href>+                       <href>/identities/{self.card_id}.vcf</href>                        <status>HTTP/1.1 200 OK</status>                      </response>                    </multistatus>"""   @app.route(r"{identifier}/versions")-class ContactVersions(Versions):+class IdentityVersions(Versions):      view = view   @app.route(r"{identifier}/versions/{version}")-class ContactVersion(Version):+class IdentityVersion(Version):      view = view   @app.route(r"{identifier}.json")-class ContactData(ResourceData):+class IdentityData(ResourceData):      view = view   @app.route(r"{identifier}.json.asc")-class ContactSignature(ResourceSignature):+class IdentitySignature(ResourceSignature):      view = view   @app.route(r"{identifier}")-class Contact(Resource):+class Identity(Resource):      view = view 
 class Keys:      def GET(self):-        return view.keys(get_resource("contact", self.identifier),+        return view.keys(get_resource("identity", self.identifier),                          get_keyring())      def POST(self):         form = web.form("pubkey")         key = import_public_key(form.pubkey.strip())-        # tx.db.insert("person_pubkey", fingerprint=key["fingerprint"],+        # tx.db.insert("identity_pubkey", fingerprint=key["fingerprint"],         #              pubkey=key)         # fingerprint = key.fingerprints[0]         raise web.Created(key, f"/{tx.host.name}/keys/asd")  # /{fingerprint}")

rename from canopy/types/contacts/templates/__init__.py

rename to canopy/types/identities/templates/__init__.py

rename from canopy/types/contacts/templates/about.html

rename to canopy/types/identities/templates/about.html

rename from canopy/types/contacts/templates/carddav.xml

rename to canopy/types/identities/templates/carddav.xml

rename from canopy/types/contacts/templates/editor.html

rename to canopy/types/identities/templates/editor.html

      <h3>Geographic</h3> -    <p>geo(s)</p>--    <label>Timezone<br>-    <div class=bounding><select name=timezone>-    $for timezone in ("",) + timezones:-        <option value="$timezone"\-        $if timezone == resource.get("timezone"):-             checked\-        >$timezone</option>-    </select></div></label>+    <label>Street<br>+    <div class=bounding><input type=text name=street-address value="\+    $resource.get('street-address', '')"></div></label>++    <label>Locality<br>+    <div class=bounding><input type=text name=locality value="\+    $resource.get('locality', '')"></div></label>++    <label>Region<br>+    <div class=bounding><input type=text name=region value="\+    $resource.get('region', '')"></div></label>++    <label>Postal Code<br>+    <div class=bounding><input type=text name=postal-code value="\+    $resource.get('postal-code', '')"></div></label>++    <label>Country<br>+    <div class=bounding><input type=text name=country-name value="\+    $resource.get('country-name', '')"></div></label>++    $# <label>Timezone<br>+    $# <div class=bounding><select name=timezone>+    $# $for timezone in ("",) + timezones:+    $#     <option value="$timezone"\+    $#     $if timezone == resource.get("timezone"):+    $#          checked\+    $#     >$timezone</option>+    $# </select></div></label>      <h2>Relationships</h2> 

rename from canopy/types/contacts/templates/index.html

rename to canopy/types/identities/templates/index.html

 $# $ target = interpret(resource["repost-of"], resource_type="feed") $#  $# <div class="h-cite u-repost-of">-$# <p>Identified <a class=u-url href=$resource["repost-of"]>contact</a>:</p>+$# <p>Identified <a class=u-url href=$resource["repost-of"]>identity</a>:</p> $# <blockquote> $# $if "name" in target: $#     $target["name"]

rename from canopy/types/contacts/templates/keys.html

rename to canopy/types/identities/templates/keys.html

rename from canopy/types/contacts/templates/quick_editor.html

rename to canopy/types/identities/templates/quick_editor.html

         scanner.stop() }); </script>++<p class=TODO>auto install an iPhone profile for CardDAV, E-mail &amp; VPN</p>

rename from canopy/types/contacts/templates/type.html

rename to canopy/types/identities/templates/type.html

     <p>Created personal identity at <code>$resource["uid"]</code>.</p> $elif entry["version"] == 1:     <p>Identified <a class="p-name u-url"-    href=/contacts/$resource["-canopy-path"]>$resource["name"]</a></p>+    href=/identities/$resource["-canopy-path"]>$resource["name"]</a></p> $else:     <p><big><a class="p-name u-url"-    href=/contacts/$resource["-canopy-path"]>$resource["name"]</a></big>-    $resource+    href=/identities/$resource["-canopy-path"]>$resource["name"]</a></big>+    $# XXX $resource     $# TODO <br>     $# TODO <small>     $# TODO <strong>$resource["uid"][:12]</strong><br>

--- a/canopy/types/images/__init__.py

+++ b/canopy/types/images/__init__.py

         if not isinstance(data[key], list):             data[key] = [data[key]]         for face in data[key]:-            person, _, coords = face.partition(":")-            tx.kv["faces", person][f"{data['-canopy-id']}-{image}"] = coords+            identity, _, coords = face.partition(":")+            tx.kv["faces", identity][f"{data['-canopy-id']}-{image}"] = coords             left, top, width, height = [int(c) for c in coords.split(",")]             pad = width / 10             cropped = fullsize.crop((left - pad, top - pad,                                      left + width + (2 * pad),                                      top + height + (2 * pad)))-            face_dir = get_path() / "faces" / person+            face_dir = get_path() / "faces" / identity             face_dir.mkdir(parents=True, exist_ok=True)             cropped.thumbnail((280, 280))             cropped.save(str((face_dir / filename).with_suffix(".jpg")),

--- a/canopy/types/images/__web__.py

+++ b/canopy/types/images/__web__.py

 app = web.application("canopy-images", mount_prefix="images",                       resource=slugs.resource, dateslug=slugs.date,                       timeslug=slugs.time, textslug=slugs.text,-                      upload=slugs.nbid, width=r"\d+", contact=slugs.resource,+                      upload=slugs.nbid, width=r"\d+", identity=slugs.resource,                       version=r"\d+") view = mm.templates("canopy.types.images", tx=tx,                     get_resource_by_id=get_resource_by_id)
                    f"/x/images/{self.resource}_{self.upload}.jpg")  -@app.route(r"faces/{contact}/{resource}-{upload}.jpg")+@app.route(r"faces/{identity}/{resource}-{upload}.jpg") class FaceImageFile:      def GET(self):         web.header("Content-Type", "image/jpg")         web.header("X-Accel-Redirect",-                   f"/faces/{self.contact}/{self.resource}_{self.upload}.jpg")+                   f"/faces/{self.identity}/{self.resource}_{self.upload}.jpg")   # TODO handle static assets -- AudioSound, VideoClip, CodeBinary, VCF, ICS, ..
     view = view      # def generate(self, resource):-    #     # owner = get_resource("contact", tx.host.name)+    #     # owner = get_resource("identity", tx.host.name)     #     return view.index(resource)  # , owner)

--- a/canopy/types/images/templates/editor.html

+++ b/canopy/types/images/templates/editor.html

          function handleFaceClick(coords) {             return function(el) {-                _contacts = document.createElement("p");+                _identities = document.createElement("p");                 // TODO uhhh... function to dump JSON (?)-                Object.keys(contacts).forEach(function (contact) { -                    // var details = contacts[contact];-                    _contacts.innerHTML = _contacts.innerHTML + \-                        "<span class=facecontact>" + contact + "</span>";+                Object.keys(identities).forEach(function (identity) { +                    // var details = identities[identity];+                    _identities.innerHTML = _identities.innerHTML + \+                        "<span class=faceidentity>" + identity + "</span>";                 });                 var _img = $$("#i" + unique_id + " div");-                _img.parentNode.insertBefore(_contacts, _img.nextSibling);-                $$$$(".facecontact").each(function() {+                _img.parentNode.insertBefore(_identities, _img.nextSibling);+                $$$$(".faceidentity").each(function() {                     this.onclick = function() {-                        contact = contacts[this.innerHTML]["id"];+                        identity = identities[this.innerHTML]["id"];                         face = document.createElement("input");                         face.setAttribute("type", "text");                         face.setAttribute("name", unique_id + "_faces");                         face.setAttribute("value",-                                          contact + ":" + coords.join());+                                          identity + ":" + coords.join());                         // container = $$("#i" + unique_id);                         _img.parentNode.insertBefore(face, _img.nextSibling);                         $$("#i" + unique_id + " > p").remove();

--- a/canopy/types/resources.py

+++ b/canopy/types/resources.py

 class Index:      """-    a post type index (e.g. /contacts, /notes, etc)+    a post type index (e.g. /people, /notes, etc)      """ 
             raise web.BadRequest("`action` must be one of draft or publish")          # move quick editor functionality to `quick()`-        if self.type.s == "contact" and "url" in data:+        if self.type.s == "identity" and "url" in data:             url = data.pop("url")             import mf             import uri
                 key_url = mfs["rels"]["pgpkey"][0]                 cache.put(key_url)                 data["key"] = key_url-            return "TODO: quick post a contact"+            return "TODO: quick post an identity"          elif self.type.s == "follow" and "follow-of" in data:             resource_id = quick_draft("follow", data, publish=True)

--- a/canopy/util/__init__.py

+++ b/canopy/util/__init__.py

 import contextlib import email+import hashlib+import imaplib+import json import pathlib-import pickle import smtplib import time  import kv+import lxml.html.clean as clean+import pendulum import phonenumbers as pn import sql from src import git
     schemas = {"owner": "string",                "photo": "hash", -               "carddav-lasttouch": "string",-               "faces:{contactid}": "hash",+               "faces:{identityid}": "hash",                 "codes": "hash", 
                 "tags": "set", -               "jobs": "hash",-               "jobs:count": "string",-               "jobs:active": "list",-               "jobs:completed": "list",--               "app-state": "string",-               "totp-secret": "string",+               "app": "string",+               "totp": "string",                 "dblock": "string",                "git-commits": "hash", -               "digitalocean_auth_token": "string",-               "dynadot_auth_token": "string",-               "digitalocean_clone_snapshot_id": "string",-               "dynadot_host_domain": "string",+               "carddav-lasttouch": "string",++               "providers:email": "hash",+               "providers:irc": "hash",+               "providers:twilio": "hash",+               "providers:dynadot": "hash",+               "providers:privateinternetaccess": "hash",+               "providers:digitalocean": "hash",+               "providers:stripe": "hash",+               "providers:darksky": "hash",+               "providers:mapbox": "hash",++               "vpn:active": "string",+               "hosting:clone_snapshot_id": "string",+               "hosting:domains": "list",+               "weather:apparent_temperature": "string",+                "pending_trees": "list",                 "new_users": "list",                "updated_facebook_feeds": "list"}-    patterns = dict(contactid=r"\w+", path=r"[\w_.\-/]*",+    patterns = dict(identityid=r"\w+", path=r"[\w_.\-/]*",                     channel=r"[A-Za-z\d][A-Za-z\d-]+[A-Za-z\d]",                     mention_type=r"[a-z\-]+")     return kv.db(global_kv["hosts"][tx.host.name], ":", schemas, **patterns)
     except KeyError:         db = sql.db(str(get_path(tx.host.identity) / "sqlite.db"))         db.define(credentials="""created DATETIME NOT NULL DEFAULT-                                 CURRENT_TIMESTAMP, salt BLOB, hash BLOB""",+                                   CURRENT_TIMESTAMP,+                                 salt BLOB, hash BLOB""",                    resources="""id TEXT UNIQUE NOT NULL, type TEXT NOT NULL""",                   # XXX path TEXT COLLATE UNICODE""",
                                    url TEXT UNIQUE NOT NULL,                                    entry TEXT NOT NULL,                                    timestamp DATETIME NOT NULL""",++                  access_log="""timestamp DATETIME, address TEXT, version REAL,+                                method TEXT, path TEXT, status INTEGER,+                                duration REAL, bytessent INTEGER,+                                referrer TEXT, useragent TEXT, cookie TEXT,+                                invalid TEXT""",+                  error_log="""timestamp DATETIME, level TEXT, pid INTEGER,+                               tid INTEGER, cid INTEGER, message TEXT""",++                  job_signatures="""module TEXT, object TEXT, args BLOB,+                                    kwargs BLOB, arghash TEXT,+                                    UNIQUE(module, object, arghash)""",+                  job_runs="""job_signature_id INTEGER, id TEXT UNIQUE,+                              created DATETIME NOT NULL+                                DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')),+                              started DATETIME, finished DATETIME,+                              start_time REAL, run_time REAL,+                              status INTEGER, output TEXT""",+                  job_schedules="""job_signature_id INTEGER, minute TEXT,+                                   hour TEXT, day_of_month TEXT, month TEXT,+                                   day_of_week TEXT,+                                   UNIQUE(job_signature_id, minute, hour,+                                          day_of_month, month, day_of_week)""",++                  email_messages="""sent DATETIME, subject TEXT,+                                    digest TEXT UNIQUE, summary TEXT,+                                    spam INTEGER""",+                  email_identities="""name TEXT, address TEXT,+                                      UNIQUE(name, address)""",+                  email_identity_blocks="""email_message_id INTEGER,+                                           block_type TEXT,+                                           email_identity_id INTEGER""",+                   machines="""id TEXT UNIQUE NOT NULL,                               ip TEXT UNIQUE NOT NULL""",                   identities="""machine_id TEXT NOT NULL,                                 uuid TEXT UNIQUE NOT NULL,                                 onion TEXT UNIQUE NOT NULL,-                                domain TEXT UNIQUE NOT NULL""")+                                domain TEXT UNIQUE NOT NULL""",++                  weather="""timestamp DATETIME NOT NULL+                               DEFAULT CURRENT_TIMESTAMP,+                             summary TEXT, icon TEXT, precipitation REAL,+                             temperature REAL, apparent_temperature REAL,+                             dew_point REAL, humidity REAL, pressure REAL,+                             wind_speed REAL, wind_gust REAL,+                             wind_bearing REAL, cloud_cover REAL,+                             uv_index REAL, visibility REAL, ozone REAL""")         cached_sqldbs[tx.host.identity] = db     return db 
         time.sleep(0.001)  -def enqueue(handler, *args, **kwargs):+def schedule(cron, callable, *args, **kwargs):+    """+    schedule a function call to be the end of the job queue++    """+    job_signature_id = get_job_signature(callable, *args, **kwargs)+    s = cron.split()+    tx.db.insert("job_schedules", job_signature_id=job_signature_id,+                 minute=s[0], hour=s[1], day_of_month=s[2], month=s[3],+                 day_of_week=s[4])+++def is_scheduled(callable, *args, **kwargs):+    """++    """+    job_signature_id = get_job_signature(callable, *args, **kwargs)+    print(job_signature_id)+    try:+        schedule = tx.db.select("job_schedules", where="job_signature_id = ?",+                                vals=[job_signature_id])[0]+    except IndexError:+        return False+    return dict(schedule)+++def enqueue(callable, *args, **kwargs):+    """+    append a function call to the end of the job queue++    """+    job_signature_id = get_job_signature(callable, *args, **kwargs)+    while True:+        job_run_id = web.nbrandom(8)+        try:+            tx.db.insert("job_runs", job_signature_id=job_signature_id,+                         id=job_run_id)+        except tx.db.IntegrityError:+            continue+        break+    global_kv["jobqueue"].append(f"{tx.host.name}:{job_run_id}")+++def get_job_signature(callable, *args, **kwargs):     """-    push a function call to the end of the job queue+    return a job signature id creating a record if necessary      """-    global_kv["jobqueue"].append(pickle.dumps((tx.host.name, handler,-                                               args, kwargs)))+    _module = callable.__module__+    _object = callable.__name__+    _args = json.dumps(args)+    _kwargs = json.dumps(kwargs)+    arghash = hashlib.sha256((_args + _kwargs).encode("utf-8")).hexdigest()+    try:+        job_signature_id = tx.db.insert("job_signatures", module=_module,+                                        object=_object, args=_args,+                                        kwargs=_kwargs, arghash=arghash)+    except tx.db.IntegrityError:+        job_signature_id = tx.db.select("job_signatures", what="rowid, *",+                                        where="""module = ? AND object = ? AND+                                                 arghash = ?""",+                                        vals=[_module, _object,+                                              arghash])[0]["rowid"]+    return job_signature_id   def parse_email(email):
 def send_text(to, message):     client = twilio.rest.Client(twilio_account_sid, twilio_auth_token)     return client.messages.create(from_=twilio_endpoint, to=to, body=message)+++def clean_html(html):+    cleaner = clean.Cleaner()+    return cleaner.clean_html(html)+++class IMAPClient:++    def __init__(self, host, username, password):+        self.imap = imaplib.IMAP4_SSL(host)+        self.connected = False+        tries = 5+        while tries:+            try:+                self.imap.login(username, password)+                self.connected = True+                break+            except imaplib.IMAP4.abort:+                pass+            tries -= 1+            time.sleep(2)++    def get_mailbox(self, mailbox):+        if not self.connected:+            return ""+        try:+            self.imap.select(mailbox)+        except imaplib.IMAP4.abort:+            return ""+        status, (uids,) = self.imap.uid("search", None, "ALL")+        return ",".join(uids.decode("utf-8").split())++    def get(self, uids):+        if not uids:+            return []+        try:+            status, messages = self.imap.uid("fetch", uids, "(RFC822)")+        except imaplib.IMAP4.abort:+            return []+        return (message for _, message in messages[::2])++    def copy(self, uids, mailbox):+        """+        send messages to mailbox (eg. Archive)++        """+        self.imap.copy(uids, mailbox)++    def flush(self, uids):+        """+        delete messages++        """+        self.imap.store(uids, "+FLAGS", r"(\DELETED)")+        self.imap.expunge()++    def disconnect(self):+        if not self.connected:+            return+        try:+            self.imap.close()+        except imaplib.IMAP4.abort:+            return+        self.imap.logout()+++def update_inbox():+    """+    fetches the inbox from the Email provider++    """+    # TODO use UIDs+    provider = tx.kv["providers:email"]+    client = IMAPClient(provider["imap_host"], provider["imap_username"],+                        provider["imap_password"])+    uids = client.get_mailbox("INBOX")+    for message in client.get(uids):+        store_message(message)+    # TODO client.copy(uids, "Archive")+    # TODO client.flush(uids)+    client.disconnect()+++def decode_email_header(item):+    if item:+        header, encoding = email.header.decode_header(item)[0]+        if encoding:+            header = header.decode(encoding)+    else:+        header = ""+    return header+++def get_plaintext(message):+    """+    get plaintext message as a string++    """+    payload = message.get_payload(decode=True)+    try:+        return payload.decode("utf-8")+    except UnicodeDecodeError:+        return message.get_payload()+++def store_message(payload):+    """"""+    message = email.message_from_bytes(payload)+    _sent = email.utils.parsedate_to_datetime(message["date"])+    sent = pendulum.instance(_sent).in_tz(pendulum.UTC)+    subject = decode_email_header(message["subject"])+    digest = hashlib.md5(payload).hexdigest()+    summary = ""+    if message.is_multipart():+        for item in message.get_payload():+            if item.get_content_type() == "text/plain":+                summary = get_plaintext(item)[:250]+    spam = message.get("x-spam-flag", "").lower() == "yes"+    try:+        message_id = tx.db.insert("email_messages", sent=sent, subject=subject,+                                  digest=digest, summary=summary, spam=spam)+    except tx.db.IntegrityError:+        return+    for block_type in ("to", "from", "cc"):+        raw_addresses = [a.replace("\r\n", " ").replace("\n", " ")+                         for a in message.get_all(block_type, [])]+        for name, address in email.utils.getaddresses(raw_addresses):+            name = decode_email_header(name)+            try:+                identity_id = tx.db.insert("email_identities", name=name,+                                           address=address)+            except tx.db.IntegrityError:+                identity_id = tx.db.select("email_identities", what="rowid",+                                           where="name = ? AND address = ?",+                                           vals=[name, address])[0]["rowid"]+            tx.db.insert("email_identity_blocks", email_message_id=message_id,+                         block_type=block_type, email_identity_id=identity_id)+    message_dir = get_path() / "email"+    message_dir.mkdir(exist_ok=True)+    with (message_dir / digest).open("wb") as fp:+        fp.write(payload)

--- a/canopy/util/dynadot.py

+++ b/canopy/util/dynadot.py

         response = self._request(show_price="1", **domain_params)         results = {}         for result in response:-            if len(result[0]) == 5:-                results[result[0][1].text] = result[0][4].text+            # if len(result[0]) == 5:+            # data = {"price": result[0][4].text}+            # results[result[0][1].text] = data+            available = (False if result[0].find("Available").text == "no"+                         else True)+            price = result[0].find("Price")+            if price is None:+                price = 0+            else:+                if " in USD" in price.text:+                    price = float(price.text.partition(" ")[0])+                else:+                    price = "?"+            results[result[0].find("DomainName").text] = (available, price)         return results      def register(self, domain, duration=1):
         response = self._request(domain=domain, duration=duration)         return response +    def account_info(self):+        """return account information"""+        response = self._request()[1][0]+        return response+     def _request(self, **payload):         """send an API request"""         payload.update(command=inspect.stack()[1].function, key=self.key)

--- a/canopy_bot.py

+++ b/canopy_bot.py

 """ canopy job queue -`JobQueue` spawns a pool of workers (default `20`) in a-`gevent.queue.PriorityQueue`. Pickled job inputs are routed through a-list at key `queue` in the environment's Redis database-(`KVDB=./path/to/redis.sock`).--The following example enqueues a request to a resource at Alice's site.--    > canopy.enqueue(canopy.post, "https://alice.example.org")--The idea is to have all tasks piped through this queue, no matter how-trivial. Web responses should be instantaneous. Background tasks can be+`JobQueue` spawns a pool of workers in a `gevent.queue.PriorityQueue`.+JSON encoded job inputs are routed through a list at key `jobqueue` in+the environment's Redis database (eg. `KVDB=./path/to/redis.sock`).++    >>> # enqueues a job to fetch a resource at Alice's site root+    >>> canopy.enqueue(canopy.get, "https://alice.example.org")+    >>> # sets a schedule to enqueue the same job every three minutes+    >>> canopy.enqueue(canopy.get, "https://alice.example.org",+    ...                _schedule="*/3 * * * *")++The idea is to have all outgoing tasks piped through this queue, no matter+how trivial. Web responses should be instantaneous. Background tasks can be trivially restarted, reprioritized, canceled, etc.  """ -# TODO make use of priority levels # TODO log back to kv  from gevent import monkey monkey.patch_all() -# import inspect-import pickle  # noqa+from importlib import import_module  # noqa+import json  # noqa import sys  # noqa import time  # noqa  import gevent.queue  # noqa+import pendulum  # noqa import term  # noqa import web  # noqa  import canopy  # noqa---__all__ = ["work", "handle", "JobQueue"]+from canopy.util import update_inbox, IMAPClient  # noqa   main = term.application("canopy-bot", "Canopy bot")
 worker_count = 20  -def work(browser):-    while True:-        priority, job = queue.get()-        handle(job, browser)---def handle(job, browser):-    # TODO handle exceptions; offer retries=n-    host, function, args, kwargs = pickle.loads(job)-    print(f"{host}/{function.__module__}:{function.__name__}",-          *(args + tuple(f"{k}={v}" for k, v in kwargs.items())), sep="\n  ")-    canopy.tx.host.identity = canopy.global_kv["hosts"][host]-    canopy.tx.host.name = host-    canopy.tx.db = canopy.util.get_db()-    canopy.tx.kv = canopy.util.get_kv()-    canopy.tx.browser = browser--    job_id = web.nbencode(canopy.tx.kv["jobs", "count"].incr())-    metadata = {"function": function, "args": args, "kwargs": kwargs,-                "start": time.time()}-    canopy.tx.kv["jobs"][job_id] = pickle.dumps(metadata)--    canopy.tx.kv["jobs", "active"].append(job_id)-    function(*args, **kwargs)+def idle_imap(host):+    """+    connect to email provider's IMAP server and IDLE waiting for new mail++    """+    tx = canopy.contextualize(host)+    provider = tx.kv["providers:email"]+    client = IMAPClient(provider["imap_host"], provider["imap_username"],+                        provider["imap_password"]).imap+    try:+        client.select("INBOX", readonly=True)+        client.send(f"{client._new_tag()} IDLE\r\n".encode("utf-8"))+        while True:+            line = client.readline().strip()+            if line.startswith(b"* BYE ") or len(line) == 0:+                break  # connection timed out+            if line.endswith(b"EXISTS"):+                update_inbox()+    finally:+        try:+            client.close()+        except Exception:+            pass+        client.logout()+++def schedule_jobs(browser):+    """+    check all schedules every minute and enqueue any scheduled jobs++    """+    # TODO support for days of month, days of week+    while True:  # wait for the start of a new minute+        now = pendulum.now()+        if now.second == 0:+            break+        time.sleep(1)+    hosts = ["lahacker.net"]  # TODO iterate over all trees+    for host in hosts:+        tx = canopy.contextualize(host)+        jobs = tx.db.select("job_schedules AS sch",+                            join="""job_signatures AS sig ON+                                    sch.job_signature_id = sig.rowid""")+        for job in jobs:+            run = True+            minute = job["minute"]+            hour = job["hour"]+            month = job["month"]+            if minute[:2] == "*/":+                if now.minute % int(minute[2]) == 0:+                    run = True+                else:+                    run = False+            if hour[:2] == "*/":+                if now.hour % int(hour[2]) == 0 and now.minute == 0:+                    run = True+                else:+                    run = False+            if month[:2] == "*/":+                if now.month % int(month[2]) == 0 and now.hour == 0 \+                   and now.minute == 0:+                    run = True+                else:+                    run = False+            if run:+                canopy.enqueue(getattr(import_module(job["module"]),+                                       job["object"]))+    time.sleep(1)+++def handle_job(job_identifier, browser):+    """+    handle a freshly dequeued job++    """+    # TODO handle retries+    host, _, job_run_id = job_identifier.partition(":")+    tx = canopy.contextualize(host)+    tx.browser = browser+    job = tx.db.select("job_runs AS r", what="s.rowid, *",+                       join="""job_signatures AS s+                               ON s.rowid = r.job_signature_id""",+                       where="r.id = ?", vals=[job_run_id])[0]+    _module = job["module"]+    _object = job["object"]+    _args = json.loads(job["args"])+    _kwargs = json.loads(job["kwargs"])+    print(f"{host}/{_module}:{_object}",+          *(_args + list(f"{k}={v}" for k, v in _kwargs.items())), sep="\n  ")     sys.stdout.flush()-    canopy.tx.kv["jobs", "active"].remove(job_id)+    tx.db.update("job_runs", where="id = ?", vals=[job_run_id],+                 what="started = STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')")+    status = 0+    try:+        output = getattr(import_module(_module), _object)(*_args, **_kwargs)+    except Exception as err:+        status = 1+        output = str(err)+    tx.db.update("job_runs", vals=[status, output, job_run_id],+                 what="""finished = STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW'),+                         status = ?, output = ?""", where="id = ?")+    run = canopy.tx.db.select("job_runs", where="id = ?", vals=[job_run_id])[0]+    st, rt = run["started"] - run["created"], run["finished"] - run["started"]+    canopy.tx.db.update("job_runs",+                        what="start_time = ?, run_time = ?", where="id = ?",+                        vals=[f"{st.seconds}.{st.microseconds}",+                              f"{rt.seconds}.{rt.microseconds}", job_run_id])+++def run_imap_idler(host):+    while True:+        idle_imap(host) -    metadata["end"] = time.time()-    canopy.tx.kv["jobs"][job_id] = pickle.dumps(metadata)-    canopy.tx.kv["jobs", "completed"].append(job_id)++def run_scheduler(browser):+    while True:+        schedule_jobs(browser)+++def run_worker(browser):+    while True:+        priority, job = queue.get()+        handle_job(job, browser)   @main.register() class JobQueue: +    """manage the job queue"""+     def run(self, stdin, log):-        browser = web.browser()         # TODO capture supervisor's kill signal and make sure to quit browser+        for host in ("lahacker.net",):+            gevent.spawn(run_imap_idler, host)+        browser = web.browser()+        gevent.spawn(run_scheduler, browser)         for _ in range(worker_count):-            gevent.spawn(work, browser)+            gevent.spawn(run_worker, browser)         for job in canopy.global_kv["jobqueue"].keep_popping():-            queue.put((1, job))+            queue.put((1, job))  # TODO utilize priority levels         browser.quit()  

index eb76e0d..0000000

--- a/pytest.ini

-[pytest]-python_files = tests.py

--- a/setup.py

+++ b/setup.py

  from setuptools import setup -setup(requires=["gevent", "html2text", "nltk", "numpy", "opencv_python",-                "opencv_contrib_python", "phe", "phonenumbers", "pillow",-                "qrcode", "sopel", "scrypt", "stripe", "tweepy", "twilio",-                "vobject", "web"],+setup(requires=["geocoder", "geopy", "gevent", "html2text", "nltk", "numpy",+                "opencv_python", "opencv_contrib_python", "phe",+                "phonenumbers", "pillow", "qrcode", "sopel", "scrypt",+                "stripe", "tweepy", "twilio", "vobject", "web"],       provides={"term.apps": ["canopy = canopy.__main__:main",                               "canopy-bot = canopy_bot:main"]},       discover=__file__)

--- a/tests.py

+++ b/tests.py

-# import pathlib+import pathlib  import canopy   name = "Alice"-host = "alice.example.cnpy.gdn"+passphrase = "alicepassword" keysize = 1024-characters = {}-# data_dir = pathlib.Path(__file__).parent / "data"+host = "alice.dev.cnpy.gdn"  -def test_nothing():-    # fixes py.test's timing result of first test-    assert True+def setup_module(module):+    canopy.delete_host(host)+    canopy.setup_host(name, passphrase, keysize, host)   def test_setup():+    # represents module setup+    assert True+++def test_owner_details():+    """assert the owner's contact details have been created"""     resource = canopy.content.read.get_resource("contact", "me")+    print(resource)     assert resource["name"] == name  -# def test_upload_photo():-#     with (data_dir / "alice.jpg").open("rb") as fp:-#         data = {"image": fp.read()}-#   resource_id = canopy.content.write.quick_draft("image", data, publish=True)-#     resource = canopy.content.read.get_resource_by_id(resource_id)-#     assert "images" in resource-#     # TODO find and assign face to identity-#     # TODO update photo in profile+# def test_failure():+#     assert False   def test_create_note():+    """assert a note has been created"""     body = "Hello world!"-    data = {"body": body}-    resource_id = canopy.content.write.quick_draft("note", data, publish=True)+    resource_id = canopy.content.write.quick_draft("note", {"body": body},+                                                   publish=True)     resource = canopy.content.read.get_resource_by_id(resource_id)     assert resource["body"] == body  +def test_upload_photo():+    """"""+    with (data_dir / "alice.jpg").open("rb") as fp:+        data = {"image": fp.read()}+    resource_id = canopy.content.write.quick_draft("image", data, publish=True)+    resource = canopy.content.read.get_resource_by_id(resource_id)+    assert "images" in resource+    # TODO find and assign face to identity+    # TODO update photo in profile+++# characters = {}+data_dir = pathlib.Path(__file__).parent / "data"++ # def mock(): #    def note(identity, body, tags, license, datetime): #        n = dict(body=body, tags=tags.split(","), license=license)
 #    note("alice", "Happy vernal equinox! Time to prep your plots!", #         "seasons", "cc-by", "2017-03-20 10:28:00") #    note("bob", "Washed the dog!", "seasons", "cc-by", "2017-04-03 02:19:12")---def setup_module(module):-    canopy.delete_host(host)-    canopy.setup_host(name, "alice", keysize, host)