Add files via upload

We recovered Zeronet Enhanced/ZNE from here web.archive.org/https://github.com/zeronet-enhanced/ZeroNet/
This commit is contained in:
wupg98 2023-09-04 05:42:58 +02:00 committed by GitHub
parent 9aa53f848f
commit 46583c4248
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 5953 additions and 0 deletions

View File

@ -0,0 +1,399 @@
import re
import time
import copy
import os
from Plugin import PluginManager
from Translate import Translate
from util import RateLimit
from util import helper
from util.Flag import flag
from Debug import Debug
try:
import OptionalManager.UiWebsocketPlugin # To make optioanlFileInfo merger sites compatible
except Exception:
pass
if "merger_db" not in locals().keys(): # To keep merger_sites between module reloads
merger_db = {} # Sites that allowed to list other sites {address: [type1, type2...]}
merged_db = {} # Sites that allowed to be merged to other sites {address: type, ...}
merged_to_merger = {} # {address: [site1, site2, ...]} cache
site_manager = None # Site manager for merger sites
plugin_dir = os.path.dirname(__file__)
if "_" not in locals():
_ = Translate(plugin_dir + "/languages/")
# Check if the site has permission to this merger site
def checkMergerPath(address, inner_path):
merged_match = re.match("^merged-(.*?)/([A-Za-z0-9]{26,35})/", inner_path)
if merged_match:
merger_type = merged_match.group(1)
# Check if merged site is allowed to include other sites
if merger_type in merger_db.get(address, []):
# Check if included site allows to include
merged_address = merged_match.group(2)
if merged_db.get(merged_address) == merger_type:
inner_path = re.sub("^merged-(.*?)/([A-Za-z0-9]{26,35})/", "", inner_path)
return merged_address, inner_path
else:
raise Exception(
"Merger site (%s) does not have permission for merged site: %s (%s)" %
(merger_type, merged_address, merged_db.get(merged_address))
)
else:
raise Exception("No merger (%s) permission to load: <br>%s (%s not in %s)" % (
address, inner_path, merger_type, merger_db.get(address, []))
)
else:
raise Exception("Invalid merger path: %s" % inner_path)
@PluginManager.registerTo("UiWebsocket")
class UiWebsocketPlugin(object):
# Download new site
def actionMergerSiteAdd(self, to, addresses):
if type(addresses) != list:
# Single site add
addresses = [addresses]
# Check if the site has merger permission
merger_types = merger_db.get(self.site.address)
if not merger_types:
return self.response(to, {"error": "Not a merger site"})
if RateLimit.isAllowed(self.site.address + "-MergerSiteAdd", 10) and len(addresses) == 1:
# Without confirmation if only one site address and not called in last 10 sec
self.cbMergerSiteAdd(to, addresses)
else:
self.cmd(
"confirm",
[_["Add <b>%s</b> new site?"] % len(addresses), "Add"],
lambda res: self.cbMergerSiteAdd(to, addresses)
)
self.response(to, "ok")
# Callback of adding new site confirmation
def cbMergerSiteAdd(self, to, addresses):
added = 0
for address in addresses:
try:
site_manager.need(address)
added += 1
except Exception as err:
self.cmd("notification", ["error", _["Adding <b>%s</b> failed: %s"] % (address, err)])
if added:
self.cmd("notification", ["done", _["Added <b>%s</b> new site"] % added, 5000])
RateLimit.called(self.site.address + "-MergerSiteAdd")
site_manager.updateMergerSites()
# Delete a merged site
@flag.no_multiuser
def actionMergerSiteDelete(self, to, address):
site = self.server.sites.get(address)
if not site:
return self.response(to, {"error": "No site found: %s" % address})
merger_types = merger_db.get(self.site.address)
if not merger_types:
return self.response(to, {"error": "Not a merger site"})
if merged_db.get(address) not in merger_types:
return self.response(to, {"error": "Merged type (%s) not in %s" % (merged_db.get(address), merger_types)})
self.cmd("notification", ["done", _["Site deleted: <b>%s</b>"] % address, 5000])
self.response(to, "ok")
# Lists merged sites
def actionMergerSiteList(self, to, query_site_info=False):
merger_types = merger_db.get(self.site.address)
ret = {}
if not merger_types:
return self.response(to, {"error": "Not a merger site"})
for address, merged_type in merged_db.items():
if merged_type not in merger_types:
continue # Site not for us
if query_site_info:
site = self.server.sites.get(address)
ret[address] = self.formatSiteInfo(site, create_user=False)
else:
ret[address] = merged_type
self.response(to, ret)
def hasSitePermission(self, address, *args, **kwargs):
if super(UiWebsocketPlugin, self).hasSitePermission(address, *args, **kwargs):
return True
else:
if self.site.address in [merger_site.address for merger_site in merged_to_merger.get(address, [])]:
return True
else:
return False
# Add support merger sites for file commands
def mergerFuncWrapper(self, func_name, to, inner_path, *args, **kwargs):
if inner_path.startswith("merged-"):
merged_address, merged_inner_path = checkMergerPath(self.site.address, inner_path)
# Set the same cert for merged site
merger_cert = self.user.getSiteData(self.site.address).get("cert")
if merger_cert and self.user.getSiteData(merged_address).get("cert") != merger_cert:
self.user.setCert(merged_address, merger_cert)
req_self = copy.copy(self)
req_self.site = self.server.sites.get(merged_address) # Change the site to the merged one
func = getattr(super(UiWebsocketPlugin, req_self), func_name)
return func(to, merged_inner_path, *args, **kwargs)
else:
func = getattr(super(UiWebsocketPlugin, self), func_name)
return func(to, inner_path, *args, **kwargs)
def actionFileList(self, to, inner_path, *args, **kwargs):
return self.mergerFuncWrapper("actionFileList", to, inner_path, *args, **kwargs)
def actionDirList(self, to, inner_path, *args, **kwargs):
return self.mergerFuncWrapper("actionDirList", to, inner_path, *args, **kwargs)
def actionFileGet(self, to, inner_path, *args, **kwargs):
return self.mergerFuncWrapper("actionFileGet", to, inner_path, *args, **kwargs)
def actionFileWrite(self, to, inner_path, *args, **kwargs):
return self.mergerFuncWrapper("actionFileWrite", to, inner_path, *args, **kwargs)
def actionFileDelete(self, to, inner_path, *args, **kwargs):
return self.mergerFuncWrapper("actionFileDelete", to, inner_path, *args, **kwargs)
def actionFileRules(self, to, inner_path, *args, **kwargs):
return self.mergerFuncWrapper("actionFileRules", to, inner_path, *args, **kwargs)
def actionFileNeed(self, to, inner_path, *args, **kwargs):
return self.mergerFuncWrapper("actionFileNeed", to, inner_path, *args, **kwargs)
def actionOptionalFileInfo(self, to, inner_path, *args, **kwargs):
return self.mergerFuncWrapper("actionOptionalFileInfo", to, inner_path, *args, **kwargs)
def actionOptionalFileDelete(self, to, inner_path, *args, **kwargs):
return self.mergerFuncWrapper("actionOptionalFileDelete", to, inner_path, *args, **kwargs)
def actionBigfileUploadInit(self, to, inner_path, *args, **kwargs):
back = self.mergerFuncWrapper("actionBigfileUploadInit", to, inner_path, *args, **kwargs)
if inner_path.startswith("merged-"):
merged_address, merged_inner_path = checkMergerPath(self.site.address, inner_path)
back["inner_path"] = "merged-%s/%s/%s" % (merged_db[merged_address], merged_address, back["inner_path"])
return back
# Add support merger sites for file commands with privatekey parameter
def mergerFuncWrapperWithPrivatekey(self, func_name, to, privatekey, inner_path, *args, **kwargs):
func = getattr(super(UiWebsocketPlugin, self), func_name)
if inner_path.startswith("merged-"):
merged_address, merged_inner_path = checkMergerPath(self.site.address, inner_path)
merged_site = self.server.sites.get(merged_address)
# Set the same cert for merged site
merger_cert = self.user.getSiteData(self.site.address).get("cert")
if merger_cert:
self.user.setCert(merged_address, merger_cert)
site_before = self.site # Save to be able to change it back after we ran the command
self.site = merged_site # Change the site to the merged one
try:
back = func(to, privatekey, merged_inner_path, *args, **kwargs)
finally:
self.site = site_before # Change back to original site
return back
else:
return func(to, privatekey, inner_path, *args, **kwargs)
def actionSiteSign(self, to, privatekey=None, inner_path="content.json", *args, **kwargs):
return self.mergerFuncWrapperWithPrivatekey("actionSiteSign", to, privatekey, inner_path, *args, **kwargs)
def actionSitePublish(self, to, privatekey=None, inner_path="content.json", *args, **kwargs):
return self.mergerFuncWrapperWithPrivatekey("actionSitePublish", to, privatekey, inner_path, *args, **kwargs)
def actionPermissionAdd(self, to, permission):
super(UiWebsocketPlugin, self).actionPermissionAdd(to, permission)
if permission.startswith("Merger"):
self.site.storage.rebuildDb()
def actionPermissionDetails(self, to, permission):
if not permission.startswith("Merger"):
return super(UiWebsocketPlugin, self).actionPermissionDetails(to, permission)
merger_type = permission.replace("Merger:", "")
if not re.match("^[A-Za-z0-9-]+$", merger_type):
raise Exception("Invalid merger_type: %s" % merger_type)
merged_sites = []
for address, merged_type in merged_db.items():
if merged_type != merger_type:
continue
site = self.server.sites.get(address)
try:
merged_sites.append(site.content_manager.contents.get("content.json").get("title", address))
except Exception:
merged_sites.append(address)
details = _["Read and write permissions to sites with merged type of <b>%s</b> "] % merger_type
details += _["(%s sites)"] % len(merged_sites)
details += "<div style='white-space: normal; max-width: 400px'>%s</div>" % ", ".join(merged_sites)
self.response(to, details)
@PluginManager.registerTo("UiRequest")
class UiRequestPlugin(object):
# Allow to load merged site files using /merged-ZeroMe/address/file.jpg
def parsePath(self, path):
path_parts = super(UiRequestPlugin, self).parsePath(path)
if "merged-" not in path: # Optimization
return path_parts
path_parts["address"], path_parts["inner_path"] = checkMergerPath(path_parts["address"], path_parts["inner_path"])
return path_parts
@PluginManager.registerTo("SiteStorage")
class SiteStoragePlugin(object):
# Also rebuild from merged sites
def getDbFiles(self):
merger_types = merger_db.get(self.site.address)
# First return the site's own db files
for item in super(SiteStoragePlugin, self).getDbFiles():
yield item
# Not a merger site, that's all
if not merger_types:
return
merged_sites = [
site_manager.sites[address]
for address, merged_type in merged_db.items()
if merged_type in merger_types
]
found = 0
for merged_site in merged_sites:
self.log.debug("Loading merged site: %s" % merged_site)
merged_type = merged_db[merged_site.address]
for content_inner_path, content in merged_site.content_manager.contents.items():
# content.json file itself
if merged_site.storage.isFile(content_inner_path): # Missing content.json file
merged_inner_path = "merged-%s/%s/%s" % (merged_type, merged_site.address, content_inner_path)
yield merged_inner_path, merged_site.storage.getPath(content_inner_path)
else:
merged_site.log.error("[MISSING] %s" % content_inner_path)
# Data files in content.json
content_inner_path_dir = helper.getDirname(content_inner_path) # Content.json dir relative to site
for file_relative_path in list(content.get("files", {}).keys()) + list(content.get("files_optional", {}).keys()):
if not file_relative_path.endswith(".json"):
continue # We only interesed in json files
file_inner_path = content_inner_path_dir + file_relative_path # File Relative to site dir
file_inner_path = file_inner_path.strip("/") # Strip leading /
if merged_site.storage.isFile(file_inner_path):
merged_inner_path = "merged-%s/%s/%s" % (merged_type, merged_site.address, file_inner_path)
yield merged_inner_path, merged_site.storage.getPath(file_inner_path)
else:
merged_site.log.error("[MISSING] %s" % file_inner_path)
found += 1
if found % 100 == 0:
time.sleep(0.001) # Context switch to avoid UI block
# Also notice merger sites on a merged site file change
def onUpdated(self, inner_path, file=None):
super(SiteStoragePlugin, self).onUpdated(inner_path, file)
merged_type = merged_db.get(self.site.address)
for merger_site in merged_to_merger.get(self.site.address, []):
if merger_site.address == self.site.address: # Avoid infinite loop
continue
virtual_path = "merged-%s/%s/%s" % (merged_type, self.site.address, inner_path)
if inner_path.endswith(".json"):
if file is not None:
merger_site.storage.onUpdated(virtual_path, file=file)
else:
merger_site.storage.onUpdated(virtual_path, file=self.open(inner_path))
else:
merger_site.storage.onUpdated(virtual_path)
@PluginManager.registerTo("Site")
class SitePlugin(object):
def fileDone(self, inner_path):
super(SitePlugin, self).fileDone(inner_path)
for merger_site in merged_to_merger.get(self.address, []):
if merger_site.address == self.address:
continue
for ws in merger_site.websockets:
ws.event("siteChanged", self, {"event": ["file_done", inner_path]})
def fileFailed(self, inner_path):
super(SitePlugin, self).fileFailed(inner_path)
for merger_site in merged_to_merger.get(self.address, []):
if merger_site.address == self.address:
continue
for ws in merger_site.websockets:
ws.event("siteChanged", self, {"event": ["file_failed", inner_path]})
@PluginManager.registerTo("SiteManager")
class SiteManagerPlugin(object):
# Update merger site for site types
def updateMergerSites(self):
global merger_db, merged_db, merged_to_merger, site_manager
s = time.time()
merger_db_new = {}
merged_db_new = {}
merged_to_merger_new = {}
site_manager = self
if not self.sites:
return
for site in self.sites.values():
# Update merged sites
try:
merged_type = site.content_manager.contents.get("content.json", {}).get("merged_type")
except Exception as err:
self.log.error("Error loading site %s: %s" % (site.address, Debug.formatException(err)))
continue
if merged_type:
merged_db_new[site.address] = merged_type
# Update merger sites
for permission in site.settings["permissions"]:
if not permission.startswith("Merger:"):
continue
if merged_type:
self.log.error(
"Removing permission %s from %s: Merger and merged at the same time." %
(permission, site.address)
)
site.settings["permissions"].remove(permission)
continue
merger_type = permission.replace("Merger:", "")
if site.address not in merger_db_new:
merger_db_new[site.address] = []
merger_db_new[site.address].append(merger_type)
site_manager.sites[site.address] = site
# Update merged to merger
if merged_type:
for merger_site in self.sites.values():
if "Merger:" + merged_type in merger_site.settings["permissions"]:
if site.address not in merged_to_merger_new:
merged_to_merger_new[site.address] = []
merged_to_merger_new[site.address].append(merger_site)
# Update globals
merger_db = merger_db_new
merged_db = merged_db_new
merged_to_merger = merged_to_merger_new
self.log.debug("Updated merger sites in %.3fs" % (time.time() - s))
def load(self, *args, **kwags):
super(SiteManagerPlugin, self).load(*args, **kwags)
self.updateMergerSites()
def saveDelayed(self, *args, **kwags):
super(SiteManagerPlugin, self).saveDelayed(*args, **kwags)
self.updateMergerSites()

View File

@ -0,0 +1 @@
from . import MergerSitePlugin

View File

@ -0,0 +1,5 @@
{
"Add <b>%s</b> new site?": "¿Agregar <b>%s</b> nuevo sitio?",
"Added <b>%s</b> new site": "Sitio <b>%s</b> agregado",
"Site deleted: <b>%s</b>": "Sitio removido: <b>%s</b>"
}

View File

@ -0,0 +1,5 @@
{
"Add <b>%s</b> new site?": "Ajouter le site <b>%s</b> ?",
"Added <b>%s</b> new site": "Site <b>%s</b> ajouté",
"Site deleted: <b>%s</b>": "Site <b>%s</b> supprimé"
}

View File

@ -0,0 +1,5 @@
{
"Add <b>%s</b> new site?": "Új oldal hozzáadása: <b>%s</b>?",
"Added <b>%s</b> new site": "Új oldal hozzáadva: <b>%s</b>",
"Site deleted: <b>%s</b>": "Oldal törölve: <b>%s</b>"
}

View File

@ -0,0 +1,5 @@
{
"Add <b>%s</b> new site?": "Aggiungere <b>%s</b> nuovo sito ?",
"Added <b>%s</b> new site": "Sito <b>%s</b> aggiunto",
"Site deleted: <b>%s</b>": "Sito <b>%s</b> eliminato"
}

View File

@ -0,0 +1,5 @@
{
"Add <b>%s</b> new site?": "サイト: <b>%s</b> を追加しますか?",
"Added <b>%s</b> new site": "サイト: <b>%s</b> を追加しました",
"Site deleted: <b>%s</b>": "サイト: <b>%s</b> を削除しました"
}

View File

@ -0,0 +1,5 @@
{
"Add <b>%s</b> new site?": "Adicionar <b>%s</b> novo site?",
"Added <b>%s</b> new site": "Site <b>%s</b> adicionado",
"Site deleted: <b>%s</b>": "Site removido: <b>%s</b>"
}

View File

@ -0,0 +1,5 @@
{
"Add <b>%s</b> new site?": "<b>%s</b> sitesi eklensin mi?",
"Added <b>%s</b> new site": "<b>%s</b> sitesi eklendi",
"Site deleted: <b>%s</b>": "<b>%s</b> sitesi silindi"
}

View File

@ -0,0 +1,5 @@
{
"Add <b>%s</b> new site?": "添加新網站: <b>%s</b>",
"Added <b>%s</b> new site": "已添加到新網站:<b>%s</b>",
"Site deleted: <b>%s</b>": "網站已刪除:<b>%s</b>"
}

View File

@ -0,0 +1,5 @@
{
"Add <b>%s</b> new site?": "添加新站点: <b>%s</b>",
"Added <b>%s</b> new site": "已添加到新站点:<b>%s</b>",
"Site deleted: <b>%s</b>": "站点已删除:<b>%s</b>"
}

View File

@ -0,0 +1,627 @@
import time
import html
import os
import json
import sys
import itertools
from Plugin import PluginManager
from Config import config
from util import helper
from Debug import Debug
from Db import Db
@PluginManager.registerTo("UiRequest")
class UiRequestPlugin(object):
def formatTableRow(self, row, class_name=""):
back = []
for format, val in row:
if val is None:
formatted = "n/a"
elif format == "since":
if val:
formatted = "%.0f" % (time.time() - val)
else:
formatted = "n/a"
else:
formatted = format % val
back.append("<td>%s</td>" % formatted)
return "<tr class='%s'>%s</tr>" % (class_name, "".join(back))
def getObjSize(self, obj, hpy=None):
if hpy:
return float(hpy.iso(obj).domisize) / 1024
else:
return 0
def renderHead(self):
import main
from Crypt import CryptConnection
# Memory
yield "rev%s | " % config.rev
yield "%s | " % main.file_server.ip_external_list
yield "Port: %s | " % main.file_server.port
yield "Network: %s | " % main.file_server.supported_ip_types
yield "Opened: %s | " % main.file_server.port_opened
yield "Crypt: %s, TLSv1.3: %s | " % (CryptConnection.manager.crypt_supported, CryptConnection.ssl.HAS_TLSv1_3)
yield "In: %.2fMB, Out: %.2fMB | " % (
float(main.file_server.bytes_recv) / 1024 / 1024,
float(main.file_server.bytes_sent) / 1024 / 1024
)
yield "Peerid: %s | " % main.file_server.peer_id
yield "Time: %.2fs | " % main.file_server.getTimecorrection()
yield "Blocks: %s" % Debug.num_block
try:
import psutil
process = psutil.Process(os.getpid())
mem = process.get_memory_info()[0] / float(2 ** 20)
yield "Mem: %.2fMB | " % mem
yield "Threads: %s | " % len(process.threads())
yield "CPU: usr %.2fs sys %.2fs | " % process.cpu_times()
yield "Files: %s | " % len(process.open_files())
yield "Sockets: %s | " % len(process.connections())
yield "Calc size <a href='?size=1'>on</a> <a href='?size=0'>off</a>"
except Exception:
pass
yield "<br>"
def renderConnectionsTable(self):
import main
# Connections
yield "<b>Connections</b> (%s, total made: %s, in: %s, out: %s):<br>" % (
len(main.file_server.connections), main.file_server.last_connection_id,
main.file_server.num_incoming, main.file_server.num_outgoing
)
yield "<table class='connections'><tr> <th>id</th> <th>type</th> <th>ip</th> <th>open</th> <th>crypt</th> <th>ping</th>"
yield "<th>buff</th> <th>bad</th> <th>idle</th> <th>open</th> <th>delay</th> <th>cpu</th> <th>out</th> <th>in</th> <th>last sent</th>"
yield "<th>wait</th> <th>version</th> <th>time</th> <th>sites</th> </tr>"
for connection in main.file_server.connections:
if "cipher" in dir(connection.sock):
cipher = connection.sock.cipher()[0]
tls_version = connection.sock.version()
else:
cipher = connection.crypt
tls_version = ""
if "time" in connection.handshake and connection.last_ping_delay:
time_correction = connection.handshake["time"] - connection.handshake_time - connection.last_ping_delay
else:
time_correction = 0.0
yield self.formatTableRow([
("%3d", connection.id),
("%s", connection.type),
("%s:%s", (connection.ip, connection.port)),
("%s", connection.handshake.get("port_opened")),
("<span title='%s %s'>%s</span>", (cipher, tls_version, connection.crypt)),
("%6.3f", connection.last_ping_delay),
("%s", connection.incomplete_buff_recv),
("%s", connection.bad_actions),
("since", max(connection.last_send_time, connection.last_recv_time)),
("since", connection.start_time),
("%.3f", max(-1, connection.last_sent_time - connection.last_send_time)),
("%.3f", connection.cpu_time),
("%.0fk", connection.bytes_sent / 1024),
("%.0fk", connection.bytes_recv / 1024),
("<span title='Recv: %s'>%s</span>", (connection.last_cmd_recv, connection.last_cmd_sent)),
("%s", list(connection.waiting_requests.keys())),
("%s r%s", (connection.handshake.get("version"), connection.handshake.get("rev", "?"))),
("%.2fs", time_correction),
("%s", connection.sites)
])
yield "</table>"
def renderTrackers(self):
# Trackers
yield "<br><br><b>Trackers:</b><br>"
yield "<table class='trackers'><tr> <th>address</th> <th>request</th> <th>successive errors</th> <th>last_request</th></tr>"
from Site import SiteAnnouncer # importing at the top of the file breaks plugins
for tracker_address, tracker_stat in sorted(SiteAnnouncer.global_stats.items()):
yield self.formatTableRow([
("%s", tracker_address),
("%s", tracker_stat["num_request"]),
("%s", tracker_stat["num_error"]),
("%.0f min ago", min(999, (time.time() - tracker_stat["time_request"]) / 60))
])
yield "</table>"
if "AnnounceShare" in PluginManager.plugin_manager.plugin_names:
yield "<br><br><b>Shared trackers:</b><br>"
yield "<table class='trackers'><tr> <th>address</th> <th>added</th> <th>found</th> <th>latency</th> <th>successive errors</th> <th>last_success</th></tr>"
from AnnounceShare import AnnounceSharePlugin
for tracker_address, tracker_stat in sorted(AnnounceSharePlugin.tracker_storage.getTrackers().items()):
yield self.formatTableRow([
("%s", tracker_address),
("%.0f min ago", min(999, (time.time() - tracker_stat["time_added"]) / 60)),
("%.0f min ago", min(999, (time.time() - tracker_stat.get("time_found", 0)) / 60)),
("%.3fs", tracker_stat["latency"]),
("%s", tracker_stat["num_error"]),
("%.0f min ago", min(999, (time.time() - tracker_stat["time_success"]) / 60)),
])
yield "</table>"
def renderTor(self):
import main
yield "<br><br><b>Tor hidden services (status: %s):</b><br>" % main.file_server.tor_manager.status
for site_address, onion in list(main.file_server.tor_manager.site_onions.items()):
yield "- %-34s: %s<br>" % (site_address, onion)
def renderDbStats(self):
yield "<br><br><b>Db</b>:<br>"
for db in Db.opened_dbs:
tables = [row["name"] for row in db.execute("SELECT name FROM sqlite_master WHERE type = 'table'").fetchall()]
table_rows = {}
for table in tables:
table_rows[table] = db.execute("SELECT COUNT(*) AS c FROM %s" % table).fetchone()["c"]
db_size = os.path.getsize(db.db_path) / 1024.0 / 1024.0
yield "- %.3fs: %s %.3fMB, table rows: %s<br>" % (
time.time() - db.last_query_time, db.db_path, db_size, json.dumps(table_rows, sort_keys=True)
)
def renderSites(self):
yield "<br><br><b>Sites</b>:"
yield "<table>"
yield "<tr><th>address</th> <th>connected</th> <th title='connected/good/total'>peers</th> <th>content.json</th> <th>out</th> <th>in</th> </tr>"
for site in list(self.server.sites.values()):
yield self.formatTableRow([
(
"""<a href='#' onclick='document.getElementById("peers_%s").style.display="initial"; return false'>%s</a>""",
(site.address, site.address)
),
("%s", [peer.connection.id for peer in list(site.peers.values()) if peer.connection and peer.connection.connected]),
("%s/%s/%s", (
len([peer for peer in list(site.peers.values()) if peer.connection and peer.connection.connected]),
len(site.getConnectablePeers(100)),
len(site.peers)
)),
("%s (loaded: %s)", (
len(site.content_manager.contents),
len([key for key, val in dict(site.content_manager.contents).items() if val])
)),
("%.0fk", site.settings.get("bytes_sent", 0) / 1024),
("%.0fk", site.settings.get("bytes_recv", 0) / 1024),
], "serving-%s" % site.settings["serving"])
yield "<tr><td id='peers_%s' style='display: none; white-space: pre' colspan=6>" % site.address
for key, peer in list(site.peers.items()):
if peer.time_found:
time_found = int(time.time() - peer.time_found) / 60
else:
time_found = "--"
if peer.connection:
connection_id = peer.connection.id
else:
connection_id = None
if site.content_manager.has_optional_files:
yield "Optional files: %4s " % len(peer.hashfield)
time_added = (time.time() - peer.time_added) / (60 * 60 * 24)
yield "(#%4s, rep: %2s, err: %s, found: %.1fs min, add: %.1f day) %30s -<br>" % (connection_id, peer.reputation, peer.connection_error, time_found, time_added, key)
yield "<br></td></tr>"
yield "</table>"
def renderBigfiles(self):
yield "<br><br><b>Big files</b>:<br>"
for site in list(self.server.sites.values()):
if not site.settings.get("has_bigfile"):
continue
bigfiles = {}
yield """<a href="#" onclick='document.getElementById("bigfiles_%s").style.display="initial"; return false'>%s</a><br>""" % (site.address, site.address)
for peer in list(site.peers.values()):
if not peer.time_piecefields_updated:
continue
for sha512, piecefield in peer.piecefields.items():
if sha512 not in bigfiles:
bigfiles[sha512] = []
bigfiles[sha512].append(peer)
yield "<div id='bigfiles_%s' style='display: none'>" % site.address
for sha512, peers in bigfiles.items():
yield "<br> - " + sha512 + " (hash id: %s)<br>" % site.content_manager.hashfield.getHashId(sha512)
yield "<table>"
for peer in peers:
yield "<tr><td>" + peer.key + "</td><td>" + peer.piecefields[sha512].tostring() + "</td></tr>"
yield "</table>"
yield "</div>"
def renderRequests(self):
import main
yield "<div style='float: left'>"
yield "<br><br><b>Sent commands</b>:<br>"
yield "<table>"
for stat_key, stat in sorted(main.file_server.stat_sent.items(), key=lambda i: i[1]["bytes"], reverse=True):
yield "<tr><td>%s</td><td style='white-space: nowrap'>x %s =</td><td>%.0fkB</td></tr>" % (stat_key, stat["num"], stat["bytes"] / 1024)
yield "</table>"
yield "</div>"
yield "<div style='float: left; margin-left: 20%; max-width: 50%'>"
yield "<br><br><b>Received commands</b>:<br>"
yield "<table>"
for stat_key, stat in sorted(main.file_server.stat_recv.items(), key=lambda i: i[1]["bytes"], reverse=True):
yield "<tr><td>%s</td><td style='white-space: nowrap'>x %s =</td><td>%.0fkB</td></tr>" % (stat_key, stat["num"], stat["bytes"] / 1024)
yield "</table>"
yield "</div>"
yield "<div style='clear: both'></div>"
def renderMemory(self):
import gc
from Ui import UiRequest
hpy = None
if self.get.get("size") == "1": # Calc obj size
try:
import guppy
hpy = guppy.hpy()
except Exception:
pass
self.sendHeader()
# Object types
obj_count = {}
for obj in gc.get_objects():
obj_type = str(type(obj))
if obj_type not in obj_count:
obj_count[obj_type] = [0, 0]
obj_count[obj_type][0] += 1 # Count
obj_count[obj_type][1] += float(sys.getsizeof(obj)) / 1024 # Size
yield "<br><br><b>Objects in memory (types: %s, total: %s, %.2fkb):</b><br>" % (
len(obj_count),
sum([stat[0] for stat in list(obj_count.values())]),
sum([stat[1] for stat in list(obj_count.values())])
)
for obj, stat in sorted(list(obj_count.items()), key=lambda x: x[1][0], reverse=True): # Sorted by count
yield " - %.1fkb = %s x <a href=\"/Listobj?type=%s\">%s</a><br>" % (stat[1], stat[0], obj, html.escape(obj))
# Classes
class_count = {}
for obj in gc.get_objects():
obj_type = str(type(obj))
if obj_type != "<type 'instance'>":
continue
class_name = obj.__class__.__name__
if class_name not in class_count:
class_count[class_name] = [0, 0]
class_count[class_name][0] += 1 # Count
class_count[class_name][1] += float(sys.getsizeof(obj)) / 1024 # Size
yield "<br><br><b>Classes in memory (types: %s, total: %s, %.2fkb):</b><br>" % (
len(class_count),
sum([stat[0] for stat in list(class_count.values())]),
sum([stat[1] for stat in list(class_count.values())])
)
for obj, stat in sorted(list(class_count.items()), key=lambda x: x[1][0], reverse=True): # Sorted by count
yield " - %.1fkb = %s x <a href=\"/Dumpobj?class=%s\">%s</a><br>" % (stat[1], stat[0], obj, html.escape(obj))
from greenlet import greenlet
objs = [obj for obj in gc.get_objects() if isinstance(obj, greenlet)]
yield "<br>Greenlets (%s):<br>" % len(objs)
for obj in objs:
yield " - %.1fkb: %s<br>" % (self.getObjSize(obj, hpy), html.escape(repr(obj)))
from Worker import Worker
objs = [obj for obj in gc.get_objects() if isinstance(obj, Worker)]
yield "<br>Workers (%s):<br>" % len(objs)
for obj in objs:
yield " - %.1fkb: %s<br>" % (self.getObjSize(obj, hpy), html.escape(repr(obj)))
from Connection import Connection
objs = [obj for obj in gc.get_objects() if isinstance(obj, Connection)]
yield "<br>Connections (%s):<br>" % len(objs)
for obj in objs:
yield " - %.1fkb: %s<br>" % (self.getObjSize(obj, hpy), html.escape(repr(obj)))
from socket import socket
objs = [obj for obj in gc.get_objects() if isinstance(obj, socket)]
yield "<br>Sockets (%s):<br>" % len(objs)
for obj in objs:
yield " - %.1fkb: %s<br>" % (self.getObjSize(obj, hpy), html.escape(repr(obj)))
from msgpack import Unpacker
objs = [obj for obj in gc.get_objects() if isinstance(obj, Unpacker)]
yield "<br>Msgpack unpacker (%s):<br>" % len(objs)
for obj in objs:
yield " - %.1fkb: %s<br>" % (self.getObjSize(obj, hpy), html.escape(repr(obj)))
from Site.Site import Site
objs = [obj for obj in gc.get_objects() if isinstance(obj, Site)]
yield "<br>Sites (%s):<br>" % len(objs)
for obj in objs:
yield " - %.1fkb: %s<br>" % (self.getObjSize(obj, hpy), html.escape(repr(obj)))
objs = [obj for obj in gc.get_objects() if isinstance(obj, self.server.log.__class__)]
yield "<br>Loggers (%s):<br>" % len(objs)
for obj in objs:
yield " - %.1fkb: %s<br>" % (self.getObjSize(obj, hpy), html.escape(repr(obj.name)))
objs = [obj for obj in gc.get_objects() if isinstance(obj, UiRequest)]
yield "<br>UiRequests (%s):<br>" % len(objs)
for obj in objs:
yield " - %.1fkb: %s<br>" % (self.getObjSize(obj, hpy), html.escape(repr(obj)))
from Peer import Peer
objs = [obj for obj in gc.get_objects() if isinstance(obj, Peer)]
yield "<br>Peers (%s):<br>" % len(objs)
for obj in objs:
yield " - %.1fkb: %s<br>" % (self.getObjSize(obj, hpy), html.escape(repr(obj)))
objs = [(key, val) for key, val in sys.modules.items() if val is not None]
objs.sort()
yield "<br>Modules (%s):<br>" % len(objs)
for module_name, module in objs:
yield " - %.3fkb: %s %s<br>" % (self.getObjSize(module, hpy), module_name, html.escape(repr(module)))
# /Stats entry point
@helper.encodeResponse
def actionStats(self):
import gc
self.sendHeader()
if "Multiuser" in PluginManager.plugin_manager.plugin_names and not config.multiuser_local:
yield "This function is disabled on this proxy"
return
s = time.time()
# Style
yield """
<style>
* { font-family: monospace }
table td, table th { text-align: right; padding: 0px 10px }
.connections td { white-space: nowrap }
.serving-False { opacity: 0.3 }
</style>
"""
renderers = [
self.renderHead(),
self.renderConnectionsTable(),
self.renderTrackers(),
self.renderTor(),
self.renderDbStats(),
self.renderSites(),
self.renderBigfiles(),
self.renderRequests()
]
for part in itertools.chain(*renderers):
yield part
if config.debug:
for part in self.renderMemory():
yield part
gc.collect() # Implicit grabage collection
yield "Done in %.1f" % (time.time() - s)
@helper.encodeResponse
def actionDumpobj(self):
import gc
import sys
self.sendHeader()
if "Multiuser" in PluginManager.plugin_manager.plugin_names and not config.multiuser_local:
yield "This function is disabled on this proxy"
return
# No more if not in debug mode
if not config.debug:
yield "Not in debug mode"
return
class_filter = self.get.get("class")
yield """
<style>
* { font-family: monospace; white-space: pre }
table * { text-align: right; padding: 0px 10px }
</style>
"""
objs = gc.get_objects()
for obj in objs:
obj_type = str(type(obj))
if obj_type != "<type 'instance'>" or obj.__class__.__name__ != class_filter:
continue
yield "%.1fkb %s... " % (float(sys.getsizeof(obj)) / 1024, html.escape(str(obj)))
for attr in dir(obj):
yield "- %s: %s<br>" % (attr, html.escape(str(getattr(obj, attr))))
yield "<br>"
gc.collect() # Implicit grabage collection
@helper.encodeResponse
def actionListobj(self):
import gc
import sys
self.sendHeader()
if "Multiuser" in PluginManager.plugin_manager.plugin_names and not config.multiuser_local:
yield "This function is disabled on this proxy"
return
# No more if not in debug mode
if not config.debug:
yield "Not in debug mode"
return
type_filter = self.get.get("type")
yield """
<style>
* { font-family: monospace; white-space: pre }
table * { text-align: right; padding: 0px 10px }
</style>
"""
yield "Listing all %s objects in memory...<br>" % html.escape(type_filter)
ref_count = {}
objs = gc.get_objects()
for obj in objs:
obj_type = str(type(obj))
if obj_type != type_filter:
continue
refs = [
ref for ref in gc.get_referrers(obj)
if hasattr(ref, "__class__") and
ref.__class__.__name__ not in ["list", "dict", "function", "type", "frame", "WeakSet", "tuple"]
]
if not refs:
continue
try:
yield "%.1fkb <span title=\"%s\">%s</span>... " % (
float(sys.getsizeof(obj)) / 1024, html.escape(str(obj)), html.escape(str(obj)[0:100].ljust(100))
)
except Exception:
continue
for ref in refs:
yield " ["
if "object at" in str(ref) or len(str(ref)) > 100:
yield str(ref.__class__.__name__)
else:
yield str(ref.__class__.__name__) + ":" + html.escape(str(ref))
yield "] "
ref_type = ref.__class__.__name__
if ref_type not in ref_count:
ref_count[ref_type] = [0, 0]
ref_count[ref_type][0] += 1 # Count
ref_count[ref_type][1] += float(sys.getsizeof(obj)) / 1024 # Size
yield "<br>"
yield "<br>Object referrer (total: %s, %.2fkb):<br>" % (len(ref_count), sum([stat[1] for stat in list(ref_count.values())]))
for obj, stat in sorted(list(ref_count.items()), key=lambda x: x[1][0], reverse=True)[0:30]: # Sorted by count
yield " - %.1fkb = %s x %s<br>" % (stat[1], stat[0], html.escape(str(obj)))
gc.collect() # Implicit grabage collection
@helper.encodeResponse
def actionGcCollect(self):
import gc
self.sendHeader()
yield str(gc.collect())
# /About entry point
@helper.encodeResponse
def actionEnv(self):
import main
self.sendHeader()
yield """
<style>
* { font-family: monospace; white-space: pre; }
h2 { font-size: 100%; margin-bottom: 0px; }
small { opacity: 0.5; }
table { border-collapse: collapse; }
td { padding-right: 10px; }
</style>
"""
if "Multiuser" in PluginManager.plugin_manager.plugin_names and not config.multiuser_local:
yield "This function is disabled on this proxy"
return
yield from main.actions.testEnv(format="html")
@PluginManager.registerTo("Actions")
class ActionsPlugin:
def formatTable(self, *rows, format="text"):
if format == "html":
return self.formatTableHtml(*rows)
else:
return self.formatTableText(*rows)
def formatHead(self, title, format="text"):
if format == "html":
return "<h2>%s</h2>" % title
else:
return "\n* %s\n" % title
def formatTableHtml(self, *rows):
yield "<table>"
for row in rows:
yield "<tr>"
for col in row:
yield "<td>%s</td>" % html.escape(str(col))
yield "</tr>"
yield "</table>"
def formatTableText(self, *rows):
for row in rows:
yield " "
for col in row:
yield " " + str(col)
yield "\n"
def testEnv(self, format="text"):
import gevent
import msgpack
import pkg_resources
import importlib
import coincurve
import sqlite3
from Crypt import CryptBitcoin
yield "\n"
yield from self.formatTable(
["ZeroNet version:", "%s rev%s" % (config.version, config.rev)],
["Python:", "%s" % sys.version],
["Platform:", "%s" % sys.platform],
["Crypt verify lib:", "%s" % CryptBitcoin.lib_verify_best],
["OpenSSL:", "%s" % CryptBitcoin.sslcrypto.ecc.get_backend()],
["Libsecp256k1:", "%s" % type(coincurve._libsecp256k1.lib).__name__],
["SQLite:", "%s, API: %s" % (sqlite3.sqlite_version, sqlite3.version)],
format=format
)
yield self.formatHead("Libraries:")
rows = []
for lib_name in ["gevent", "greenlet", "msgpack", "base58", "merkletools", "rsa", "socks", "pyasn1", "gevent_ws", "websocket", "maxminddb"]:
try:
module = importlib.import_module(lib_name)
if "__version__" in dir(module):
version = module.__version__
elif "version" in dir(module):
version = module.version
else:
version = "unknown version"
if type(version) is tuple:
version = ".".join(map(str, version))
rows.append(["- %s:" % lib_name, version, "at " + module.__file__])
except Exception as err:
rows.append(["! Error importing %s:", repr(err)])
"""
try:
yield " - %s<br>" % html.escape(repr(pkg_resources.get_distribution(lib_name)))
except Exception as err:
yield " ! %s<br>" % html.escape(repr(err))
"""
yield from self.formatTable(*rows, format=format)
yield self.formatHead("Library config:", format=format)
yield from self.formatTable(
["- gevent:", gevent.config.loop.__module__],
["- msgpack unpacker:", msgpack.Unpacker.__module__],
format=format
)

View File

@ -0,0 +1 @@
from . import StatsPlugin

View File

@ -0,0 +1,5 @@
{
"name": "Stats",
"description": "/Stats and /Benchmark pages.",
"default": "enabled"
}

View File

@ -0,0 +1,171 @@
import os
import sys
import atexit
from Plugin import PluginManager
from Config import config
from Translate import Translate
allow_reload = False # No source reload supported in this plugin
plugin_dir = os.path.dirname(__file__)
if "_" not in locals():
_ = Translate(plugin_dir + "/languages/")
@PluginManager.registerTo("Actions")
class ActionsPlugin(object):
def main(self):
global notificationicon, winfolders
from .lib import notificationicon, winfolders
import gevent.threadpool
import main
self.main = main
icon = notificationicon.NotificationIcon(
os.path.join(os.path.dirname(os.path.abspath(__file__)), 'trayicon.ico'),
"ZeroNet %s" % config.version
)
self.icon = icon
self.console = False
@atexit.register
def hideIcon():
try:
icon.die()
except Exception as err:
print("Error removing trayicon: %s" % err)
ui_ip = config.ui_ip if config.ui_ip != "*" else "127.0.0.1"
if ":" in ui_ip:
ui_ip = "[" + ui_ip + "]"
icon.items = [
(self.titleIp, False),
(self.titleConnections, False),
(self.titleTransfer, False),
(self.titleConsole, self.toggleConsole),
(self.titleAutorun, self.toggleAutorun),
"--",
(_["ZeroNet Twitter"], lambda: self.opensite("https://twitter.com/HelloZeroNet")),
(_["ZeroNet Reddit"], lambda: self.opensite("http://www.reddit.com/r/zeronet/")),
(_["ZeroNet Github"], lambda: self.opensite("https://github.com/HelloZeroNet/ZeroNet")),
(_["Report bug/request feature"], lambda: self.opensite("https://github.com/HelloZeroNet/ZeroNet/issues")),
"--",
(_["!Open ZeroNet"], lambda: self.opensite("http://%s:%s/%s" % (ui_ip, config.ui_port, config.homepage))),
"--",
(_["Quit"], self.quit),
]
if not notificationicon.hasConsole():
del icon.items[3]
icon.clicked = lambda: self.opensite("http://%s:%s/%s" % (ui_ip, config.ui_port, config.homepage))
self.quit_servers_event = gevent.threadpool.ThreadResult(
lambda res: gevent.spawn_later(0.1, self.quitServers), gevent.threadpool.get_hub(), lambda: True
) # Fix gevent thread switch error
gevent.threadpool.start_new_thread(icon._run, ()) # Start in real thread (not gevent compatible)
super(ActionsPlugin, self).main()
icon._die = True
def quit(self):
self.icon.die()
self.quit_servers_event.set(True)
def quitServers(self):
self.main.ui_server.stop()
self.main.file_server.stop()
def opensite(self, url):
import webbrowser
webbrowser.open(url, new=0)
def titleIp(self):
title = "!IP: %s " % ", ".join(self.main.file_server.ip_external_list)
if any(self.main.file_server.port_opened):
title += _["(active)"]
else:
title += _["(passive)"]
return title
def titleConnections(self):
title = _["Connections: %s"] % len(self.main.file_server.connections)
return title
def titleTransfer(self):
title = _["Received: %.2f MB | Sent: %.2f MB"] % (
float(self.main.file_server.bytes_recv) / 1024 / 1024,
float(self.main.file_server.bytes_sent) / 1024 / 1024
)
return title
def titleConsole(self):
translate = _["Show console window"]
if self.console:
return "+" + translate
else:
return translate
def toggleConsole(self):
if self.console:
notificationicon.hideConsole()
self.console = False
else:
notificationicon.showConsole()
self.console = True
def getAutorunPath(self):
return "%s\\zeronet.cmd" % winfolders.get(winfolders.STARTUP)
def formatAutorun(self):
args = sys.argv[:]
if not getattr(sys, 'frozen', False): # Not frozen
args.insert(0, sys.executable)
cwd = os.getcwd()
else:
cwd = os.path.dirname(sys.executable)
ignored_args = [
"--open_browser", "default_browser",
"--dist_type", "bundle_win64"
]
if sys.platform == 'win32':
args = ['"%s"' % arg for arg in args if arg and arg not in ignored_args]
cmd = " ".join(args)
# Dont open browser on autorun
cmd = cmd.replace("start.py", "zeronet.py").strip()
cmd += ' --open_browser ""'
return "\r\n".join([
'@echo off',
'chcp 65001 > nul',
'set PYTHONIOENCODING=utf-8',
'cd /D \"%s\"' % cwd,
'start "" %s' % cmd
])
def isAutorunEnabled(self):
path = self.getAutorunPath()
return os.path.isfile(path) and open(path, "rb").read().decode("utf8") == self.formatAutorun()
def titleAutorun(self):
translate = _["Start ZeroNet when Windows starts"]
if self.isAutorunEnabled():
return "+" + translate
else:
return translate
def toggleAutorun(self):
if self.isAutorunEnabled():
os.unlink(self.getAutorunPath())
else:
open(self.getAutorunPath(), "wb").write(self.formatAutorun().encode("utf8"))

View File

@ -0,0 +1,4 @@
import sys
if sys.platform == 'win32':
from . import TrayiconPlugin

View File

@ -0,0 +1,14 @@
{
"ZeroNet Twitter": "ZeroNet Twitter",
"ZeroNet Reddit": "ZeroNet Reddit",
"ZeroNet Github": "ZeroNet Github",
"Report bug/request feature": "Reportar fallo/sugerir característica",
"!Open ZeroNet": "!Abrir ZeroNet",
"Quit": "Sair",
"(active)": "(activo)",
"(passive)": "(pasivo)",
"Connections: %s": "Conecciones: %s",
"Received: %.2f MB | Sent: %.2f MB": "Recibido: %.2f MB | Enviado: %.2f MB",
"Show console window": "Mostrar consola",
"Start ZeroNet when Windows starts": "Iniciar Zeronet cuando inicie Windows"
}

View File

@ -0,0 +1,14 @@
{
"ZeroNet Twitter": "ZeroNet Twitter",
"ZeroNet Reddit": "ZeroNet Reddit",
"ZeroNet Github": "ZeroNet Github",
"Report bug/request feature": "Rapport d'erreur/Demander une fonctionnalité",
"!Open ZeroNet": "!Ouvrir ZeroNet",
"Quit": "Quitter",
"(active)": "(actif)",
"(passive)": "(passif)",
"Connections: %s": "Connexions: %s",
"Received: %.2f MB | Sent: %.2f MB": "Reçu: %.2f MB | Envoyé: %.2f MB",
"Show console window": "Afficher la console",
"Start ZeroNet when Windows starts": "Lancer ZeroNet au démarrage de Windows"
}

View File

@ -0,0 +1,14 @@
{
"ZeroNet Twitter": "ZeroNet Twitter",
"ZeroNet Reddit": "ZeroNet Reddit",
"ZeroNet Github": "ZeroNet Github",
"Report bug/request feature": "Hiba bejelentés/ötletek",
"!Open ZeroNet": "!ZeroNet megnyitása",
"Quit": "Kilépés",
"(active)": "(aktív)",
"(passive)": "(passive)",
"Connections: %s": "Kapcsolatok: %s",
"Received: %.2f MB | Sent: %.2f MB": "Fogadott: %.2f MB | Küldött: %.2f MB",
"Show console window": "Parancssor mutatása",
"Start ZeroNet when Windows starts": "ZeroNet indítása a Windows-al együtt"
}

View File

@ -0,0 +1,14 @@
{
"ZeroNet Twitter": "ZeroNet Twitter",
"ZeroNet Reddit": "ZeroNet Reddit",
"ZeroNet Github": "ZeroNet Github",
"Report bug/request feature": "Segnala bug/richiesta di una funzione",
"!Open ZeroNet": "!Apri ZeroNet",
"Quit": "Chiudi",
"(active)": "(attivo)",
"(passive)": "(passivo)",
"Connections: %s": "Connessioni: %s",
"Received: %.2f MB | Sent: %.2f MB": "Ricevuto: %.2f MB | Inviato: %.2f MB",
"Show console window": "Mostra finestra console",
"Start ZeroNet when Windows starts": "Avvia ZeroNet all'avvio di Windows"
}

View File

@ -0,0 +1,14 @@
{
"ZeroNet Twitter": "ZeroNet Twitter",
"ZeroNet Reddit": "ZeroNet Reddit",
"ZeroNet Github": "ZeroNet Github",
"Report bug/request feature": "バグ報告/要望",
"!Open ZeroNet": "!ZeroNetをブラウザで開く",
"Quit": "閉じる",
"(active)": "(アクティブ)",
"(passive)": "(パッシブ)",
"Connections: %s": "接続数: %s",
"Received: %.2f MB | Sent: %.2f MB": "受信: %.2f MB | 送信: %.2f MB",
"Show console window": "コンソールを表示",
"Start ZeroNet when Windows starts": "Windows起動時にZeroNetも起動する"
}

View File

@ -0,0 +1,14 @@
{
"ZeroNet Twitter": "ZeroNet Twitter",
"ZeroNet Reddit": "ZeroNet Reddit",
"ZeroNet Github": "ZeroNet Github",
"Report bug/request feature": "Zgłoś błąd / propozycję",
"!Open ZeroNet": "!Otwórz ZeroNet",
"Quit": "Zamknij",
"(active)": "(aktywny)",
"(passive)": "(pasywny)",
"Connections: %s": "Połączenia: %s",
"Received: %.2f MB | Sent: %.2f MB": "Odebrano: %.2f MB | Wysłano: %.2f MB",
"Show console window": "Pokaż okno konsoli",
"Start ZeroNet when Windows starts": "Uruchom ZeroNet podczas startu Windows"
}

View File

@ -0,0 +1,14 @@
{
"ZeroNet Twitter": "ZeroNet Twitter",
"ZeroNet Reddit": "ZeroNet Reddit",
"ZeroNet Github": "ZeroNet Github",
"Report bug/request feature": "Reportar bug/sugerir recurso",
"!Open ZeroNet": "!Abrir ZeroNet",
"Quit": "Sair",
"(active)": "(ativo)",
"(passive)": "(passivo)",
"Connections: %s": "Conexões: %s",
"Received: %.2f MB | Sent: %.2f MB": "Recebido: %.2f MB | Enviado: %.2f MB",
"Show console window": "Mostrar console",
"Start ZeroNet when Windows starts": "Iniciar o ZeroNet quando o Windows for iniciado"
}

View File

@ -0,0 +1,14 @@
{
"ZeroNet Twitter": "ZeroNet Twitter",
"ZeroNet Reddit": "ZeroNet Reddit",
"ZeroNet Github": "ZeroNet Github",
"Report bug/request feature": "Hata bildir/geliştirme taleb et",
"!Open ZeroNet": "!ZeroNet'i Aç",
"Quit": "Kapat",
"(active)": "(aktif)",
"(passive)": "(pasif)",
"Connections: %s": "Bağlantı sayısı: %s",
"Received: %.2f MB | Sent: %.2f MB": "Gelen: %.2f MB | Gönderilen: %.2f MB",
"Show console window": "Konsolu aç",
"Start ZeroNet when Windows starts": "ZeroNet'i açılışta otomatik başlat"
}

View File

@ -0,0 +1,14 @@
{
"ZeroNet Twitter": "ZeroNet Twitter",
"ZeroNet Reddit": "ZeroNet Reddit",
"ZeroNet Github": "ZeroNet Github",
"Report bug/request feature": "回饋问题/請求功能",
"!Open ZeroNet": "!開啟 ZeroNet",
"Quit": "退出",
"(active)": "(主動模式)",
"(passive)": "(被動模式)",
"Connections: %s": "連線數: %s",
"Received: %.2f MB | Sent: %.2f MB": "已收到: %.2f MB | 已傳送: %.2f MB",
"Show console window": "顯示控制臺窗體",
"Start ZeroNet when Windows starts": "在 Windows 啟動時執行 ZeroNet"
}

View File

@ -0,0 +1,14 @@
{
"ZeroNet Twitter": "ZeroNet Twitter",
"ZeroNet Reddit": "ZeroNet Reddit",
"ZeroNet Github": "ZeroNet Github",
"Report bug/request feature": "反馈问题/请求功能",
"!Open ZeroNet": "!打开 ZeroNet",
"Quit": "退出",
"(active)": "(主动模式)",
"(passive)": "(被动模式)",
"Connections: %s": "连接数: %s",
"Received: %.2f MB | Sent: %.2f MB": "已接收: %.2f MB | 已发送: %.2f MB",
"Show console window": "显示控制台窗口",
"Start ZeroNet when Windows starts": "在 Windows 启动时运行 ZeroNet"
}

View File

View File

@ -0,0 +1,730 @@
# Pure ctypes windows taskbar notification icon
# via https://gist.github.com/jasonbot/5759510
# Modified for ZeroNet
import ctypes
import ctypes.wintypes
import os
import uuid
import time
import gevent
import threading
try:
from queue import Empty as queue_Empty # Python 3
except ImportError:
from Queue import Empty as queue_Empty # Python 2
__all__ = ['NotificationIcon']
# Create popup menu
CreatePopupMenu = ctypes.windll.user32.CreatePopupMenu
CreatePopupMenu.restype = ctypes.wintypes.HMENU
CreatePopupMenu.argtypes = []
MF_BYCOMMAND = 0x0
MF_BYPOSITION = 0x400
MF_BITMAP = 0x4
MF_CHECKED = 0x8
MF_DISABLED = 0x2
MF_ENABLED = 0x0
MF_GRAYED = 0x1
MF_MENUBARBREAK = 0x20
MF_MENUBREAK = 0x40
MF_OWNERDRAW = 0x100
MF_POPUP = 0x10
MF_SEPARATOR = 0x800
MF_STRING = 0x0
MF_UNCHECKED = 0x0
InsertMenu = ctypes.windll.user32.InsertMenuW
InsertMenu.restype = ctypes.wintypes.BOOL
InsertMenu.argtypes = [ctypes.wintypes.HMENU, ctypes.wintypes.UINT, ctypes.wintypes.UINT, ctypes.wintypes.UINT, ctypes.wintypes.LPCWSTR]
AppendMenu = ctypes.windll.user32.AppendMenuW
AppendMenu.restype = ctypes.wintypes.BOOL
AppendMenu.argtypes = [ctypes.wintypes.HMENU, ctypes.wintypes.UINT, ctypes.wintypes.UINT, ctypes.wintypes.LPCWSTR]
SetMenuDefaultItem = ctypes.windll.user32.SetMenuDefaultItem
SetMenuDefaultItem.restype = ctypes.wintypes.BOOL
SetMenuDefaultItem.argtypes = [ctypes.wintypes.HMENU, ctypes.wintypes.UINT, ctypes.wintypes.UINT]
class POINT(ctypes.Structure):
_fields_ = [ ('x', ctypes.wintypes.LONG),
('y', ctypes.wintypes.LONG)]
GetCursorPos = ctypes.windll.user32.GetCursorPos
GetCursorPos.argtypes = [ctypes.POINTER(POINT)]
SetForegroundWindow = ctypes.windll.user32.SetForegroundWindow
SetForegroundWindow.argtypes = [ctypes.wintypes.HWND]
TPM_LEFTALIGN = 0x0
TPM_CENTERALIGN = 0x4
TPM_RIGHTALIGN = 0x8
TPM_TOPALIGN = 0x0
TPM_VCENTERALIGN = 0x10
TPM_BOTTOMALIGN = 0x20
TPM_NONOTIFY = 0x80
TPM_RETURNCMD = 0x100
TPM_LEFTBUTTON = 0x0
TPM_RIGHTBUTTON = 0x2
TPM_HORNEGANIMATION = 0x800
TPM_HORPOSANIMATION = 0x400
TPM_NOANIMATION = 0x4000
TPM_VERNEGANIMATION = 0x2000
TPM_VERPOSANIMATION = 0x1000
TrackPopupMenu = ctypes.windll.user32.TrackPopupMenu
TrackPopupMenu.restype = ctypes.wintypes.BOOL
TrackPopupMenu.argtypes = [ctypes.wintypes.HMENU, ctypes.wintypes.UINT, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.wintypes.HWND, ctypes.c_void_p]
PostMessage = ctypes.windll.user32.PostMessageW
PostMessage.restype = ctypes.wintypes.BOOL
PostMessage.argtypes = [ctypes.wintypes.HWND, ctypes.wintypes.UINT, ctypes.wintypes.WPARAM, ctypes.wintypes.LPARAM]
DestroyMenu = ctypes.windll.user32.DestroyMenu
DestroyMenu.restype = ctypes.wintypes.BOOL
DestroyMenu.argtypes = [ctypes.wintypes.HMENU]
# Create notification icon
GUID = ctypes.c_ubyte * 16
class TimeoutVersionUnion(ctypes.Union):
_fields_ = [('uTimeout', ctypes.wintypes.UINT),
('uVersion', ctypes.wintypes.UINT),]
NIS_HIDDEN = 0x1
NIS_SHAREDICON = 0x2
class NOTIFYICONDATA(ctypes.Structure):
def __init__(self, *args, **kwargs):
super(NOTIFYICONDATA, self).__init__(*args, **kwargs)
self.cbSize = ctypes.sizeof(self)
_fields_ = [
('cbSize', ctypes.wintypes.DWORD),
('hWnd', ctypes.wintypes.HWND),
('uID', ctypes.wintypes.UINT),
('uFlags', ctypes.wintypes.UINT),
('uCallbackMessage', ctypes.wintypes.UINT),
('hIcon', ctypes.wintypes.HICON),
('szTip', ctypes.wintypes.WCHAR * 64),
('dwState', ctypes.wintypes.DWORD),
('dwStateMask', ctypes.wintypes.DWORD),
('szInfo', ctypes.wintypes.WCHAR * 256),
('union', TimeoutVersionUnion),
('szInfoTitle', ctypes.wintypes.WCHAR * 64),
('dwInfoFlags', ctypes.wintypes.DWORD),
('guidItem', GUID),
('hBalloonIcon', ctypes.wintypes.HICON),
]
NIM_ADD = 0
NIM_MODIFY = 1
NIM_DELETE = 2
NIM_SETFOCUS = 3
NIM_SETVERSION = 4
NIF_MESSAGE = 1
NIF_ICON = 2
NIF_TIP = 4
NIF_STATE = 8
NIF_INFO = 16
NIF_GUID = 32
NIF_REALTIME = 64
NIF_SHOWTIP = 128
NIIF_NONE = 0
NIIF_INFO = 1
NIIF_WARNING = 2
NIIF_ERROR = 3
NIIF_USER = 4
NOTIFYICON_VERSION = 3
NOTIFYICON_VERSION_4 = 4
Shell_NotifyIcon = ctypes.windll.shell32.Shell_NotifyIconW
Shell_NotifyIcon.restype = ctypes.wintypes.BOOL
Shell_NotifyIcon.argtypes = [ctypes.wintypes.DWORD, ctypes.POINTER(NOTIFYICONDATA)]
# Load icon/image
IMAGE_BITMAP = 0
IMAGE_ICON = 1
IMAGE_CURSOR = 2
LR_CREATEDIBSECTION = 0x00002000
LR_DEFAULTCOLOR = 0x00000000
LR_DEFAULTSIZE = 0x00000040
LR_LOADFROMFILE = 0x00000010
LR_LOADMAP3DCOLORS = 0x00001000
LR_LOADTRANSPARENT = 0x00000020
LR_MONOCHROME = 0x00000001
LR_SHARED = 0x00008000
LR_VGACOLOR = 0x00000080
OIC_SAMPLE = 32512
OIC_HAND = 32513
OIC_QUES = 32514
OIC_BANG = 32515
OIC_NOTE = 32516
OIC_WINLOGO = 32517
OIC_WARNING = OIC_BANG
OIC_ERROR = OIC_HAND
OIC_INFORMATION = OIC_NOTE
LoadImage = ctypes.windll.user32.LoadImageW
LoadImage.restype = ctypes.wintypes.HANDLE
LoadImage.argtypes = [ctypes.wintypes.HINSTANCE, ctypes.wintypes.LPCWSTR, ctypes.wintypes.UINT, ctypes.c_int, ctypes.c_int, ctypes.wintypes.UINT]
# CreateWindow call
WNDPROC = ctypes.WINFUNCTYPE(ctypes.c_int, ctypes.wintypes.HWND, ctypes.c_uint, ctypes.wintypes.WPARAM, ctypes.wintypes.LPARAM)
DefWindowProc = ctypes.windll.user32.DefWindowProcW
DefWindowProc.restype = ctypes.c_int
DefWindowProc.argtypes = [ctypes.wintypes.HWND, ctypes.c_uint, ctypes.wintypes.WPARAM, ctypes.wintypes.LPARAM]
WS_OVERLAPPED = 0x00000000
WS_POPUP = 0x80000000
WS_CHILD = 0x40000000
WS_MINIMIZE = 0x20000000
WS_VISIBLE = 0x10000000
WS_DISABLED = 0x08000000
WS_CLIPSIBLINGS = 0x04000000
WS_CLIPCHILDREN = 0x02000000
WS_MAXIMIZE = 0x01000000
WS_CAPTION = 0x00C00000
WS_BORDER = 0x00800000
WS_DLGFRAME = 0x00400000
WS_VSCROLL = 0x00200000
WS_HSCROLL = 0x00100000
WS_SYSMENU = 0x00080000
WS_THICKFRAME = 0x00040000
WS_GROUP = 0x00020000
WS_TABSTOP = 0x00010000
WS_MINIMIZEBOX = 0x00020000
WS_MAXIMIZEBOX = 0x00010000
WS_OVERLAPPEDWINDOW = (WS_OVERLAPPED |
WS_CAPTION |
WS_SYSMENU |
WS_THICKFRAME |
WS_MINIMIZEBOX |
WS_MAXIMIZEBOX)
SM_XVIRTUALSCREEN = 76
SM_YVIRTUALSCREEN = 77
SM_CXVIRTUALSCREEN = 78
SM_CYVIRTUALSCREEN = 79
SM_CMONITORS = 80
SM_SAMEDISPLAYFORMAT = 81
WM_NULL = 0x0000
WM_CREATE = 0x0001
WM_DESTROY = 0x0002
WM_MOVE = 0x0003
WM_SIZE = 0x0005
WM_ACTIVATE = 0x0006
WM_SETFOCUS = 0x0007
WM_KILLFOCUS = 0x0008
WM_ENABLE = 0x000A
WM_SETREDRAW = 0x000B
WM_SETTEXT = 0x000C
WM_GETTEXT = 0x000D
WM_GETTEXTLENGTH = 0x000E
WM_PAINT = 0x000F
WM_CLOSE = 0x0010
WM_QUERYENDSESSION = 0x0011
WM_QUIT = 0x0012
WM_QUERYOPEN = 0x0013
WM_ERASEBKGND = 0x0014
WM_SYSCOLORCHANGE = 0x0015
WM_ENDSESSION = 0x0016
WM_SHOWWINDOW = 0x0018
WM_CTLCOLOR = 0x0019
WM_WININICHANGE = 0x001A
WM_SETTINGCHANGE = 0x001A
WM_DEVMODECHANGE = 0x001B
WM_ACTIVATEAPP = 0x001C
WM_FONTCHANGE = 0x001D
WM_TIMECHANGE = 0x001E
WM_CANCELMODE = 0x001F
WM_SETCURSOR = 0x0020
WM_MOUSEACTIVATE = 0x0021
WM_CHILDACTIVATE = 0x0022
WM_QUEUESYNC = 0x0023
WM_GETMINMAXINFO = 0x0024
WM_PAINTICON = 0x0026
WM_ICONERASEBKGND = 0x0027
WM_NEXTDLGCTL = 0x0028
WM_SPOOLERSTATUS = 0x002A
WM_DRAWITEM = 0x002B
WM_MEASUREITEM = 0x002C
WM_DELETEITEM = 0x002D
WM_VKEYTOITEM = 0x002E
WM_CHARTOITEM = 0x002F
WM_SETFONT = 0x0030
WM_GETFONT = 0x0031
WM_SETHOTKEY = 0x0032
WM_GETHOTKEY = 0x0033
WM_QUERYDRAGICON = 0x0037
WM_COMPAREITEM = 0x0039
WM_GETOBJECT = 0x003D
WM_COMPACTING = 0x0041
WM_COMMNOTIFY = 0x0044
WM_WINDOWPOSCHANGING = 0x0046
WM_WINDOWPOSCHANGED = 0x0047
WM_POWER = 0x0048
WM_COPYDATA = 0x004A
WM_CANCELJOURNAL = 0x004B
WM_NOTIFY = 0x004E
WM_INPUTLANGCHANGEREQUEST = 0x0050
WM_INPUTLANGCHANGE = 0x0051
WM_TCARD = 0x0052
WM_HELP = 0x0053
WM_USERCHANGED = 0x0054
WM_NOTIFYFORMAT = 0x0055
WM_CONTEXTMENU = 0x007B
WM_STYLECHANGING = 0x007C
WM_STYLECHANGED = 0x007D
WM_DISPLAYCHANGE = 0x007E
WM_GETICON = 0x007F
WM_SETICON = 0x0080
WM_NCCREATE = 0x0081
WM_NCDESTROY = 0x0082
WM_NCCALCSIZE = 0x0083
WM_NCHITTEST = 0x0084
WM_NCPAINT = 0x0085
WM_NCACTIVATE = 0x0086
WM_GETDLGCODE = 0x0087
WM_SYNCPAINT = 0x0088
WM_NCMOUSEMOVE = 0x00A0
WM_NCLBUTTONDOWN = 0x00A1
WM_NCLBUTTONUP = 0x00A2
WM_NCLBUTTONDBLCLK = 0x00A3
WM_NCRBUTTONDOWN = 0x00A4
WM_NCRBUTTONUP = 0x00A5
WM_NCRBUTTONDBLCLK = 0x00A6
WM_NCMBUTTONDOWN = 0x00A7
WM_NCMBUTTONUP = 0x00A8
WM_NCMBUTTONDBLCLK = 0x00A9
WM_KEYDOWN = 0x0100
WM_KEYUP = 0x0101
WM_CHAR = 0x0102
WM_DEADCHAR = 0x0103
WM_SYSKEYDOWN = 0x0104
WM_SYSKEYUP = 0x0105
WM_SYSCHAR = 0x0106
WM_SYSDEADCHAR = 0x0107
WM_KEYLAST = 0x0108
WM_IME_STARTCOMPOSITION = 0x010D
WM_IME_ENDCOMPOSITION = 0x010E
WM_IME_COMPOSITION = 0x010F
WM_IME_KEYLAST = 0x010F
WM_INITDIALOG = 0x0110
WM_COMMAND = 0x0111
WM_SYSCOMMAND = 0x0112
WM_TIMER = 0x0113
WM_HSCROLL = 0x0114
WM_VSCROLL = 0x0115
WM_INITMENU = 0x0116
WM_INITMENUPOPUP = 0x0117
WM_MENUSELECT = 0x011F
WM_MENUCHAR = 0x0120
WM_ENTERIDLE = 0x0121
WM_MENURBUTTONUP = 0x0122
WM_MENUDRAG = 0x0123
WM_MENUGETOBJECT = 0x0124
WM_UNINITMENUPOPUP = 0x0125
WM_MENUCOMMAND = 0x0126
WM_CTLCOLORMSGBOX = 0x0132
WM_CTLCOLOREDIT = 0x0133
WM_CTLCOLORLISTBOX = 0x0134
WM_CTLCOLORBTN = 0x0135
WM_CTLCOLORDLG = 0x0136
WM_CTLCOLORSCROLLBAR = 0x0137
WM_CTLCOLORSTATIC = 0x0138
WM_MOUSEMOVE = 0x0200
WM_LBUTTONDOWN = 0x0201
WM_LBUTTONUP = 0x0202
WM_LBUTTONDBLCLK = 0x0203
WM_RBUTTONDOWN = 0x0204
WM_RBUTTONUP = 0x0205
WM_RBUTTONDBLCLK = 0x0206
WM_MBUTTONDOWN = 0x0207
WM_MBUTTONUP = 0x0208
WM_MBUTTONDBLCLK = 0x0209
WM_MOUSEWHEEL = 0x020A
WM_PARENTNOTIFY = 0x0210
WM_ENTERMENULOOP = 0x0211
WM_EXITMENULOOP = 0x0212
WM_NEXTMENU = 0x0213
WM_SIZING = 0x0214
WM_CAPTURECHANGED = 0x0215
WM_MOVING = 0x0216
WM_DEVICECHANGE = 0x0219
WM_MDICREATE = 0x0220
WM_MDIDESTROY = 0x0221
WM_MDIACTIVATE = 0x0222
WM_MDIRESTORE = 0x0223
WM_MDINEXT = 0x0224
WM_MDIMAXIMIZE = 0x0225
WM_MDITILE = 0x0226
WM_MDICASCADE = 0x0227
WM_MDIICONARRANGE = 0x0228
WM_MDIGETACTIVE = 0x0229
WM_MDISETMENU = 0x0230
WM_ENTERSIZEMOVE = 0x0231
WM_EXITSIZEMOVE = 0x0232
WM_DROPFILES = 0x0233
WM_MDIREFRESHMENU = 0x0234
WM_IME_SETCONTEXT = 0x0281
WM_IME_NOTIFY = 0x0282
WM_IME_CONTROL = 0x0283
WM_IME_COMPOSITIONFULL = 0x0284
WM_IME_SELECT = 0x0285
WM_IME_CHAR = 0x0286
WM_IME_REQUEST = 0x0288
WM_IME_KEYDOWN = 0x0290
WM_IME_KEYUP = 0x0291
WM_MOUSEHOVER = 0x02A1
WM_MOUSELEAVE = 0x02A3
WM_CUT = 0x0300
WM_COPY = 0x0301
WM_PASTE = 0x0302
WM_CLEAR = 0x0303
WM_UNDO = 0x0304
WM_RENDERFORMAT = 0x0305
WM_RENDERALLFORMATS = 0x0306
WM_DESTROYCLIPBOARD = 0x0307
WM_DRAWCLIPBOARD = 0x0308
WM_PAINTCLIPBOARD = 0x0309
WM_VSCROLLCLIPBOARD = 0x030A
WM_SIZECLIPBOARD = 0x030B
WM_ASKCBFORMATNAME = 0x030C
WM_CHANGECBCHAIN = 0x030D
WM_HSCROLLCLIPBOARD = 0x030E
WM_QUERYNEWPALETTE = 0x030F
WM_PALETTEISCHANGING = 0x0310
WM_PALETTECHANGED = 0x0311
WM_HOTKEY = 0x0312
WM_PRINT = 0x0317
WM_PRINTCLIENT = 0x0318
WM_HANDHELDFIRST = 0x0358
WM_HANDHELDLAST = 0x035F
WM_AFXFIRST = 0x0360
WM_AFXLAST = 0x037F
WM_PENWINFIRST = 0x0380
WM_PENWINLAST = 0x038F
WM_APP = 0x8000
WM_USER = 0x0400
WM_REFLECT = WM_USER + 0x1c00
class WNDCLASSEX(ctypes.Structure):
def __init__(self, *args, **kwargs):
super(WNDCLASSEX, self).__init__(*args, **kwargs)
self.cbSize = ctypes.sizeof(self)
_fields_ = [("cbSize", ctypes.c_uint),
("style", ctypes.c_uint),
("lpfnWndProc", WNDPROC),
("cbClsExtra", ctypes.c_int),
("cbWndExtra", ctypes.c_int),
("hInstance", ctypes.wintypes.HANDLE),
("hIcon", ctypes.wintypes.HANDLE),
("hCursor", ctypes.wintypes.HANDLE),
("hBrush", ctypes.wintypes.HANDLE),
("lpszMenuName", ctypes.wintypes.LPCWSTR),
("lpszClassName", ctypes.wintypes.LPCWSTR),
("hIconSm", ctypes.wintypes.HANDLE)]
ShowWindow = ctypes.windll.user32.ShowWindow
ShowWindow.argtypes = [ctypes.wintypes.HWND, ctypes.c_int]
def GenerateDummyWindow(callback, uid):
newclass = WNDCLASSEX()
newclass.lpfnWndProc = callback
newclass.lpszClassName = uid.replace("-", "")
ATOM = ctypes.windll.user32.RegisterClassExW(ctypes.byref(newclass))
hwnd = ctypes.windll.user32.CreateWindowExW(0, newclass.lpszClassName, None, WS_POPUP, 0, 0, 0, 0, 0, 0, 0, 0)
return hwnd
# Message loop calls
TIMERCALLBACK = ctypes.WINFUNCTYPE(None,
ctypes.wintypes.HWND,
ctypes.wintypes.UINT,
ctypes.POINTER(ctypes.wintypes.UINT),
ctypes.wintypes.DWORD)
SetTimer = ctypes.windll.user32.SetTimer
SetTimer.restype = ctypes.POINTER(ctypes.wintypes.UINT)
SetTimer.argtypes = [ctypes.wintypes.HWND,
ctypes.POINTER(ctypes.wintypes.UINT),
ctypes.wintypes.UINT,
TIMERCALLBACK]
KillTimer = ctypes.windll.user32.KillTimer
KillTimer.restype = ctypes.wintypes.BOOL
KillTimer.argtypes = [ctypes.wintypes.HWND,
ctypes.POINTER(ctypes.wintypes.UINT)]
class MSG(ctypes.Structure):
_fields_ = [ ('HWND', ctypes.wintypes.HWND),
('message', ctypes.wintypes.UINT),
('wParam', ctypes.wintypes.WPARAM),
('lParam', ctypes.wintypes.LPARAM),
('time', ctypes.wintypes.DWORD),
('pt', POINT)]
GetMessage = ctypes.windll.user32.GetMessageW
GetMessage.restype = ctypes.wintypes.BOOL
GetMessage.argtypes = [ctypes.POINTER(MSG), ctypes.wintypes.HWND, ctypes.wintypes.UINT, ctypes.wintypes.UINT]
TranslateMessage = ctypes.windll.user32.TranslateMessage
TranslateMessage.restype = ctypes.wintypes.ULONG
TranslateMessage.argtypes = [ctypes.POINTER(MSG)]
DispatchMessage = ctypes.windll.user32.DispatchMessageW
DispatchMessage.restype = ctypes.wintypes.ULONG
DispatchMessage.argtypes = [ctypes.POINTER(MSG)]
def LoadIcon(iconfilename, small=False):
return LoadImage(0,
str(iconfilename),
IMAGE_ICON,
16 if small else 0,
16 if small else 0,
LR_LOADFROMFILE)
class NotificationIcon(object):
def __init__(self, iconfilename, tooltip=None):
assert os.path.isfile(str(iconfilename)), "{} doesn't exist".format(iconfilename)
self._iconfile = str(iconfilename)
self._hicon = LoadIcon(self._iconfile, True)
assert self._hicon, "Failed to load {}".format(iconfilename)
#self._pumpqueue = Queue.Queue()
self._die = False
self._timerid = None
self._uid = uuid.uuid4()
self._tooltip = str(tooltip) if tooltip else ''
#self._thread = threading.Thread(target=self._run)
#self._thread.start()
self._info_bubble = None
self.items = []
def _bubble(self, iconinfo):
if self._info_bubble:
info_bubble = self._info_bubble
self._info_bubble = None
message = str(self._info_bubble)
iconinfo.uFlags |= NIF_INFO
iconinfo.szInfo = message
iconinfo.szInfoTitle = message
iconinfo.dwInfoFlags = NIIF_INFO
iconinfo.union.uTimeout = 10000
Shell_NotifyIcon(NIM_MODIFY, ctypes.pointer(iconinfo))
def _run(self):
self.WM_TASKBARCREATED = ctypes.windll.user32.RegisterWindowMessageW('TaskbarCreated')
self._windowproc = WNDPROC(self._callback)
self._hwnd = GenerateDummyWindow(self._windowproc, str(self._uid))
iconinfo = NOTIFYICONDATA()
iconinfo.hWnd = self._hwnd
iconinfo.uID = 100
iconinfo.uFlags = NIF_ICON | NIF_SHOWTIP | NIF_MESSAGE | (NIF_TIP if self._tooltip else 0)
iconinfo.uCallbackMessage = WM_MENUCOMMAND
iconinfo.hIcon = self._hicon
iconinfo.szTip = self._tooltip
Shell_NotifyIcon(NIM_ADD, ctypes.pointer(iconinfo))
self.iconinfo = iconinfo
PostMessage(self._hwnd, WM_NULL, 0, 0)
message = MSG()
last_time = -1
ret = None
while not self._die:
try:
ret = GetMessage(ctypes.pointer(message), 0, 0, 0)
TranslateMessage(ctypes.pointer(message))
DispatchMessage(ctypes.pointer(message))
except Exception as err:
# print "NotificationIcon error", err, message
message = MSG()
time.sleep(0.125)
print("Icon thread stopped, removing icon (hicon: %s, hwnd: %s)..." % (self._hicon, self._hwnd))
Shell_NotifyIcon(NIM_DELETE, ctypes.cast(ctypes.pointer(iconinfo), ctypes.POINTER(NOTIFYICONDATA)))
ctypes.windll.user32.DestroyWindow(self._hwnd)
ctypes.windll.user32.DestroyIcon.argtypes = [ctypes.wintypes.HICON]
ctypes.windll.user32.DestroyIcon(self._hicon)
def _menu(self):
if not hasattr(self, 'items'):
return
menu = CreatePopupMenu()
func = None
try:
iidx = 1000
defaultitem = -1
item_map = {}
for fs in self.items:
iidx += 1
if isinstance(fs, str):
if fs and not fs.strip('-_='):
AppendMenu(menu, MF_SEPARATOR, iidx, fs)
else:
AppendMenu(menu, MF_STRING | MF_GRAYED, iidx, fs)
elif isinstance(fs, tuple):
if callable(fs[0]):
itemstring = fs[0]()
else:
itemstring = str(fs[0])
flags = MF_STRING
if itemstring.startswith("!"):
itemstring = itemstring[1:]
defaultitem = iidx
if itemstring.startswith("+"):
itemstring = itemstring[1:]
flags = flags | MF_CHECKED
itemcallable = fs[1]
item_map[iidx] = itemcallable
if itemcallable is False:
flags = flags | MF_DISABLED
elif not callable(itemcallable):
flags = flags | MF_GRAYED
AppendMenu(menu, flags, iidx, itemstring)
if defaultitem != -1:
SetMenuDefaultItem(menu, defaultitem, 0)
pos = POINT()
GetCursorPos(ctypes.pointer(pos))
PostMessage(self._hwnd, WM_NULL, 0, 0)
SetForegroundWindow(self._hwnd)
ti = TrackPopupMenu(menu, TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY, pos.x, pos.y, 0, self._hwnd, None)
if ti in item_map:
func = item_map[ti]
PostMessage(self._hwnd, WM_NULL, 0, 0)
finally:
DestroyMenu(menu)
if func: func()
def clicked(self):
self._menu()
def _callback(self, hWnd, msg, wParam, lParam):
# Check if the main thread is still alive
if msg == WM_TIMER:
if not any(thread.getName() == 'MainThread' and thread.isAlive()
for thread in threading.enumerate()):
self._die = True
elif msg == WM_MENUCOMMAND and lParam == WM_LBUTTONUP:
self.clicked()
elif msg == WM_MENUCOMMAND and lParam == WM_RBUTTONUP:
self._menu()
elif msg == self.WM_TASKBARCREATED: # Explorer restarted, add the icon again.
Shell_NotifyIcon(NIM_ADD, ctypes.pointer(self.iconinfo))
else:
return DefWindowProc(hWnd, msg, wParam, lParam)
return 1
def die(self):
self._die = True
PostMessage(self._hwnd, WM_NULL, 0, 0)
time.sleep(0.2)
try:
Shell_NotifyIcon(NIM_DELETE, self.iconinfo)
except Exception as err:
print("Icon remove error", err)
ctypes.windll.user32.DestroyWindow(self._hwnd)
ctypes.windll.user32.DestroyIcon(self._hicon)
def pump(self):
try:
while not self._pumpqueue.empty():
callable = self._pumpqueue.get(False)
callable()
except queue_Empty:
pass
def announce(self, text):
self._info_bubble = text
def hideConsole():
ctypes.windll.user32.ShowWindow(ctypes.windll.kernel32.GetConsoleWindow(), 0)
def showConsole():
ctypes.windll.user32.ShowWindow(ctypes.windll.kernel32.GetConsoleWindow(), 1)
def hasConsole():
return ctypes.windll.kernel32.GetConsoleWindow() != 0
if __name__ == "__main__":
import time
def greet():
ctypes.windll.user32.ShowWindow(ctypes.windll.kernel32.GetConsoleWindow(), 0)
print("Hello")
def quit():
ni._die = True
def announce():
ctypes.windll.user32.ShowWindow(ctypes.windll.kernel32.GetConsoleWindow(), 1)
ni.announce("Hello there")
def clicked():
ni.announce("Hello")
def dynamicTitle():
return "!The time is: %s" % time.time()
ni = NotificationIcon(os.path.join(os.path.dirname(os.path.abspath(__file__)), '../trayicon.ico'), "ZeroNet 0.2.9")
ni.items = [
(dynamicTitle, False),
('Hello', greet),
('Title', False),
('!Default', greet),
('+Popup bubble', announce),
'Nothing',
'--',
('Quit', quit)
]
ni.clicked = clicked
import atexit
@atexit.register
def goodbye():
print("You are now leaving the Python sector.")
ni._run()

View File

@ -0,0 +1,54 @@
''' Get windows special folders without pythonwin
Example:
import specialfolders
start_programs = specialfolders.get(specialfolders.PROGRAMS)
Code is public domain, do with it what you will.
Luke Pinner - Environment.gov.au, 2010 February 10
'''
#Imports use _syntax to mask them from autocomplete IDE's
import ctypes as _ctypes
from ctypes import create_unicode_buffer as _cub
from ctypes.wintypes import HWND as _HWND, HANDLE as _HANDLE,DWORD as _DWORD,LPCWSTR as _LPCWSTR,MAX_PATH as _MAX_PATH
_SHGetFolderPath = _ctypes.windll.shell32.SHGetFolderPathW
#public special folder constants
DESKTOP= 0
PROGRAMS= 2
MYDOCUMENTS= 5
FAVORITES= 6
STARTUP= 7
RECENT= 8
SENDTO= 9
STARTMENU= 11
MYMUSIC= 13
MYVIDEOS= 14
NETHOOD= 19
FONTS= 20
TEMPLATES= 21
ALLUSERSSTARTMENU= 22
ALLUSERSPROGRAMS= 23
ALLUSERSSTARTUP= 24
ALLUSERSDESKTOP= 25
APPLICATIONDATA= 26
PRINTHOOD= 27
LOCALSETTINGSAPPLICATIONDATA= 28
ALLUSERSFAVORITES= 31
LOCALSETTINGSTEMPORARYINTERNETFILES=32
COOKIES= 33
LOCALSETTINGSHISTORY= 34
ALLUSERSAPPLICATIONDATA= 35
def get(intFolder):
_SHGetFolderPath.argtypes = [_HWND, _ctypes.c_int, _HANDLE, _DWORD, _LPCWSTR]
auPathBuffer = _cub(_MAX_PATH)
exit_code=_SHGetFolderPath(0, intFolder, 0, 0, auPathBuffer)
return auPathBuffer.value
if __name__ == "__main__":
import os
print(get(STARTUP))
open(get(STARTUP)+"\\zeronet.cmd", "w").write("cd /D %s\r\nzeronet.py" % os.getcwd())

View File

@ -0,0 +1,5 @@
{
"name": "Trayicon",
"description": "Icon for system tray. (Windows only)",
"default": "enabled"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,221 @@
import io
import os
import json
import shutil
import time
from Plugin import PluginManager
from Config import config
from Debug import Debug
from Translate import Translate
from util.Flag import flag
plugin_dir = os.path.dirname(__file__)
if "_" not in locals():
_ = Translate(plugin_dir + "/languages/")
# Convert non-str,int,float values to str in a dict
def restrictDictValues(input_dict):
allowed_types = (int, str, float)
return {
key: val if type(val) in allowed_types else str(val)
for key, val in input_dict.items()
}
@PluginManager.registerTo("UiRequest")
class UiRequestPlugin(object):
def actionWrapper(self, path, extra_headers=None):
if path.strip("/") != "Plugins":
return super(UiRequestPlugin, self).actionWrapper(path, extra_headers)
if not extra_headers:
extra_headers = {}
script_nonce = self.getScriptNonce()
self.sendHeader(extra_headers=extra_headers, script_nonce=script_nonce)
site = self.server.site_manager.get(config.homepage)
return iter([super(UiRequestPlugin, self).renderWrapper(
site, path, "uimedia/plugins/plugin_manager/plugin_manager.html",
"Plugin Manager", extra_headers, show_loadingscreen=False, script_nonce=script_nonce
)])
def actionUiMedia(self, path, *args, **kwargs):
if path.startswith("/uimedia/plugins/plugin_manager/"):
file_path = path.replace("/uimedia/plugins/plugin_manager/", plugin_dir + "/media/")
if config.debug and (file_path.endswith("all.js") or file_path.endswith("all.css")):
# If debugging merge *.css to all.css and *.js to all.js
from Debug import DebugMedia
DebugMedia.merge(file_path)
if file_path.endswith("js"):
data = _.translateData(open(file_path).read(), mode="js").encode("utf8")
elif file_path.endswith("html"):
data = _.translateData(open(file_path).read(), mode="html").encode("utf8")
else:
data = open(file_path, "rb").read()
return self.actionFile(file_path, file_obj=io.BytesIO(data), file_size=len(data))
else:
return super(UiRequestPlugin, self).actionUiMedia(path)
@PluginManager.registerTo("UiWebsocket")
class UiWebsocketPlugin(object):
@flag.admin
def actionPluginList(self, to):
plugins = []
for plugin in PluginManager.plugin_manager.listPlugins(list_disabled=True):
plugin_info_path = plugin["dir_path"] + "/plugin_info.json"
plugin_info = {}
if os.path.isfile(plugin_info_path):
try:
plugin_info = json.load(open(plugin_info_path))
except Exception as err:
self.log.error(
"Error loading plugin info for %s: %s" %
(plugin["name"], Debug.formatException(err))
)
if plugin_info:
plugin_info = restrictDictValues(plugin_info) # For security reasons don't allow complex values
plugin["info"] = plugin_info
if plugin["source"] != "builtin":
plugin_site = self.server.sites.get(plugin["source"])
if plugin_site:
try:
plugin_site_info = plugin_site.storage.loadJson(plugin["inner_path"] + "/plugin_info.json")
plugin_site_info = restrictDictValues(plugin_site_info)
plugin["site_info"] = plugin_site_info
plugin["site_title"] = plugin_site.content_manager.contents["content.json"].get("title")
plugin_key = "%s/%s" % (plugin["source"], plugin["inner_path"])
plugin["updated"] = plugin_key in PluginManager.plugin_manager.plugins_updated
except Exception:
pass
plugins.append(plugin)
return {"plugins": plugins}
@flag.admin
@flag.no_multiuser
def actionPluginConfigSet(self, to, source, inner_path, key, value):
plugin_manager = PluginManager.plugin_manager
plugins = plugin_manager.listPlugins(list_disabled=True)
plugin = None
for item in plugins:
if item["source"] == source and item["inner_path"] in (inner_path, "disabled-" + inner_path):
plugin = item
break
if not plugin:
return {"error": "Plugin not found"}
config_source = plugin_manager.config.setdefault(source, {})
config_plugin = config_source.setdefault(inner_path, {})
if key in config_plugin and value is None:
del config_plugin[key]
else:
config_plugin[key] = value
plugin_manager.saveConfig()
return "ok"
def pluginAction(self, action, address, inner_path):
site = self.server.sites.get(address)
plugin_manager = PluginManager.plugin_manager
# Install/update path should exists
if action in ("add", "update", "add_request"):
if not site:
raise Exception("Site not found")
if not site.storage.isDir(inner_path):
raise Exception("Directory not found on the site")
try:
plugin_info = site.storage.loadJson(inner_path + "/plugin_info.json")
plugin_data = (plugin_info["rev"], plugin_info["description"], plugin_info["name"])
except Exception as err:
raise Exception("Invalid plugin_info.json: %s" % Debug.formatExceptionMessage(err))
source_path = site.storage.getPath(inner_path)
target_path = plugin_manager.path_installed_plugins + "/" + address + "/" + inner_path
plugin_config = plugin_manager.config.setdefault(site.address, {}).setdefault(inner_path, {})
# Make sure plugin (not)installed
if action in ("add", "add_request") and os.path.isdir(target_path):
raise Exception("Plugin already installed")
if action in ("update", "remove") and not os.path.isdir(target_path):
raise Exception("Plugin not installed")
# Do actions
if action == "add":
shutil.copytree(source_path, target_path)
plugin_config["date_added"] = int(time.time())
plugin_config["rev"] = plugin_info["rev"]
plugin_config["enabled"] = True
if action == "update":
shutil.rmtree(target_path)
shutil.copytree(source_path, target_path)
plugin_config["rev"] = plugin_info["rev"]
plugin_config["date_updated"] = time.time()
if action == "remove":
del plugin_manager.config[address][inner_path]
shutil.rmtree(target_path)
def doPluginAdd(self, to, inner_path, res):
if not res:
return None
self.pluginAction("add", self.site.address, inner_path)
PluginManager.plugin_manager.saveConfig()
self.cmd(
"confirm",
["Plugin installed!<br>You have to restart the client to load the plugin", "Restart"],
lambda res: self.actionServerShutdown(to, restart=True)
)
self.response(to, "ok")
@flag.no_multiuser
def actionPluginAddRequest(self, to, inner_path):
self.pluginAction("add_request", self.site.address, inner_path)
plugin_info = self.site.storage.loadJson(inner_path + "/plugin_info.json")
warning = "<b>Warning!<br/>Plugins has the same permissions as the ZeroNet client.<br/>"
warning += "Do not install it if you don't trust the developer.</b>"
self.cmd(
"confirm",
["Install new plugin: %s?<br>%s" % (plugin_info["name"], warning), "Trust & Install"],
lambda res: self.doPluginAdd(to, inner_path, res)
)
@flag.admin
@flag.no_multiuser
def actionPluginRemove(self, to, address, inner_path):
self.pluginAction("remove", address, inner_path)
PluginManager.plugin_manager.saveConfig()
return "ok"
@flag.admin
@flag.no_multiuser
def actionPluginUpdate(self, to, address, inner_path):
self.pluginAction("update", address, inner_path)
PluginManager.plugin_manager.saveConfig()
PluginManager.plugin_manager.plugins_updated["%s/%s" % (address, inner_path)] = True
return "ok"

View File

@ -0,0 +1 @@
from . import UiPluginManagerPlugin

View File

@ -0,0 +1,75 @@
body { background-color: #EDF2F5; font-family: Roboto, 'Segoe UI', Arial, 'Helvetica Neue'; margin: 0px; padding: 0px; backface-visibility: hidden; }
h1, h2, h3, h4 { font-family: 'Roboto', Arial, sans-serif; font-weight: 200; font-size: 30px; margin: 0px; padding: 0px }
h1 { background: linear-gradient(33deg,#af3bff,#0d99c9); color: white; padding: 16px 30px; }
h2 { margin-top: 10px; }
h3 { font-weight: normal }
h4 { font-size: 19px; font-weight: lighter; margin-right: 100px; margin-top: 30px; }
a { color: #9760F9 }
a:hover { text-decoration: none }
.link { background-color: transparent; outline: 5px solid transparent; transition: all 0.3s }
.link:active { background-color: #EFEFEF; outline: 5px solid #EFEFEF; transition: none }
.content { max-width: 800px; margin: auto; background-color: white; padding: 60px 20px; box-sizing: border-box; padding-bottom: 150px; }
.section { margin: 0px 10%; }
.plugins { font-size: 19px; margin-top: 25px; margin-bottom: 75px; }
.plugin { transition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1); position: relative; padding-bottom: 20px; padding-top: 10px; }
.plugin.hidden { opacity: 0; height: 0px; padding: 0px; }
.plugin .title { display: inline-block; line-height: 36px; }
.plugin .title h3 { font-size: 20px; font-weight: lighter; margin-right: 100px; }
.plugin .title .version { font-size: 70%; margin-left: 5px; }
.plugin .title .version .version-latest { color: #2ecc71; font-weight: normal; }
.plugin .title .version .version-missing { color: #ffa200; font-weight: normal; }
.plugin .title .version .version-update { padding: 0px 15px; margin-left: 5px; line-height: 28px; }
.plugin .description { font-size: 14px; color: #666; line-height: 24px; }
.plugin .description .source { color: #999; font-size: 90%; }
.plugin .description .source a { color: #666; }
.plugin .value { display: inline-block; white-space: nowrap; }
.plugin .value-right { right: 0px; position: absolute; }
.plugin .value-fullwidth { width: 100% }
.plugin .marker {
font-weight: bold; text-decoration: none; font-size: 25px; position: absolute; padding: 2px 15px; line-height: 32px;
opacity: 0; pointer-events: none; transition: all 0.6s; transform: scale(2); color: #9760F9;
}
.plugin .marker.visible { opacity: 1; pointer-events: all; transform: scale(1); }
.plugin .marker.changed { color: #2ecc71; }
.plugin .marker.pending { color: #ffa200; }
.input-text, .input-select { padding: 8px 18px; border: 1px solid #CCC; border-radius: 3px; font-size: 17px; box-sizing: border-box; }
.input-text:focus, .input-select:focus { border: 1px solid #3396ff; outline: none; }
.input-textarea { overflow-x: auto; overflow-y: hidden; white-space: pre; line-height: 22px; }
.input-select { width: initial; font-size: 14px; padding-right: 10px; padding-left: 10px; }
.value-right .input-text { text-align: right; width: 100px; }
.value-fullwidth .input-text { width: 100%; font-size: 14px; font-family: 'Segoe UI', Arial, 'Helvetica Neue'; }
.value-fullwidth { margin-top: 10px; }
/* Checkbox */
.checkbox-skin { background-color: #CCC; width: 50px; height: 24px; border-radius: 15px; transition: all 0.3s ease-in-out; display: inline-block; }
.checkbox-skin:before {
content: ""; position: relative; width: 20px; background-color: white; height: 20px; display: block; border-radius: 100%; margin-top: 2px; margin-left: 2px;
transition: all 0.5s cubic-bezier(0.785, 0.135, 0.15, 0.86);
}
.checkbox { font-size: 14px; font-weight: normal; display: inline-block; cursor: pointer; margin-top: 5px; }
.checkbox .title { display: inline; line-height: 30px; vertical-align: 4px; margin-left: 11px }
.checkbox.checked .checkbox-skin:before { margin-left: 27px; }
.checkbox.checked .checkbox-skin { background-color: #2ECC71 }
/* Bottom */
.bottom {
width: 100%; text-align: center; background-color: #ffffffde; padding: 25px; bottom: -120px; -webkit-backdrop-filter: blur(5px); backdrop-filter: blur(5px);
transition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1); position: fixed; backface-visibility: hidden; box-sizing: border-box;
}
.bottom-content { max-width: 750px; width: 100%; margin: 0px auto; }
.bottom .button { float: right; }
.bottom.visible { bottom: 0px; box-shadow: 0px 0px 35px #dcdcdc; }
.bottom .title { padding: 10px 10px; color: #363636; float: left; text-transform: uppercase; letter-spacing: 1px; }
.bottom .title:before { content: "•"; display: inline-block; color: #2ecc71; font-size: 31px; vertical-align: -7px; margin-right: 8px; line-height: 25px; }
.bottom-restart .title:before { color: #ffa200; }
.animate { transition: all 0.3s ease-out !important; }
.animate-back { transition: all 1s cubic-bezier(0.175, 0.885, 0.32, 1.275) !important; }
.animate-inout { transition: all 0.6s cubic-bezier(0.77, 0, 0.175, 1) !important; }

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,12 @@
/* Button */
.button {
background-color: #FFDC00; color: black; padding: 10px 20px; display: inline-block; background-position: left center;
border-radius: 2px; border-bottom: 2px solid #E8BE29; transition: all 0.5s ease-out; text-decoration: none;
}
.button:hover { border-color: white; border-bottom: 2px solid #BD960C; transition: none ; background-color: #FDEB07 }
.button:active { position: relative; top: 1px }
.button.loading {
color: rgba(0,0,0,0); background: #999 url(../img/loading.gif) no-repeat center center;
transition: all 0.5s ease-out ; pointer-events: none; border-bottom: 2px solid #666
}
.button.disabled { color: #DDD; background-color: #999; pointer-events: none; border-bottom: 2px solid #666 }

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 723 B

View File

@ -0,0 +1,132 @@
class PluginList extends Class
constructor: (plugins) ->
@plugins = plugins
savePluginStatus: (plugin, is_enabled) =>
Page.cmd "pluginConfigSet", [plugin.source, plugin.inner_path, "enabled", is_enabled], (res) =>
if res == "ok"
Page.updatePlugins()
else
Page.cmd "wrapperNotification", ["error", res.error]
Page.projector.scheduleRender()
handleCheckboxChange: (e) =>
node = e.currentTarget
plugin = node["data-plugin"]
node.classList.toggle("checked")
value = node.classList.contains("checked")
@savePluginStatus(plugin, value)
handleResetClick: (e) =>
node = e.currentTarget
plugin = node["data-plugin"]
@savePluginStatus(plugin, null)
handleUpdateClick: (e) =>
node = e.currentTarget
plugin = node["data-plugin"]
node.classList.add("loading")
Page.cmd "pluginUpdate", [plugin.source, plugin.inner_path], (res) =>
if res == "ok"
Page.cmd "wrapperNotification", ["done", "Plugin #{plugin.name} updated to latest version"]
Page.updatePlugins()
else
Page.cmd "wrapperNotification", ["error", res.error]
node.classList.remove("loading")
return false
handleDeleteClick: (e) =>
node = e.currentTarget
plugin = node["data-plugin"]
if plugin.loaded
Page.cmd "wrapperNotification", ["info", "You can only delete plugin that are not currently active"]
return false
node.classList.add("loading")
Page.cmd "wrapperConfirm", ["Delete #{plugin.name} plugin?", "Delete"], (res) =>
if not res
node.classList.remove("loading")
return false
Page.cmd "pluginRemove", [plugin.source, plugin.inner_path], (res) =>
if res == "ok"
Page.cmd "wrapperNotification", ["done", "Plugin #{plugin.name} deleted"]
Page.updatePlugins()
else
Page.cmd "wrapperNotification", ["error", res.error]
node.classList.remove("loading")
return false
render: ->
h("div.plugins", @plugins.map (plugin) =>
if not plugin.info
return
descr = plugin.info.description
plugin.info.default ?= "enabled"
if plugin.info.default
descr += " (default: #{plugin.info.default})"
tag_version = ""
tag_source = ""
tag_delete = ""
if plugin.source != "builtin"
tag_update = ""
if plugin.site_info?.rev
if plugin.site_info.rev > plugin.info.rev
tag_update = h("a.version-update.button",
{href: "#Update+plugin", onclick: @handleUpdateClick, "data-plugin": plugin},
"Update to rev#{plugin.site_info.rev}"
)
else
tag_update = h("span.version-missing", "(unable to get latest vesion: update site missing)")
tag_version = h("span.version",[
"rev#{plugin.info.rev} ",
tag_update,
])
tag_source = h("div.source",[
"Source: ",
h("a", {"href": "/#{plugin.source}", "target": "_top"}, if plugin.site_title then plugin.site_title else plugin.source),
" /" + plugin.inner_path
])
tag_delete = h("a.delete", {"href": "#Delete+plugin", onclick: @handleDeleteClick, "data-plugin": plugin}, "Delete plugin")
enabled_default = plugin.info.default == "enabled"
if plugin.enabled != plugin.loaded or plugin.updated
marker_title = "Change pending"
is_pending = true
else
marker_title = "Changed from default status (click to reset to #{plugin.info.default})"
is_pending = false
is_changed = plugin.enabled != enabled_default and plugin.owner == "builtin"
h("div.plugin", {key: plugin.name}, [
h("div.title", [
h("h3", [plugin.name, tag_version]),
h("div.description", [descr, tag_source, tag_delete]),
])
h("div.value.value-right",
h("div.checkbox", {onclick: @handleCheckboxChange, "data-plugin": plugin, classes: {checked: plugin.enabled}}, h("div.checkbox-skin"))
h("a.marker", {
href: "#Reset", title: marker_title,
onclick: @handleResetClick, "data-plugin": plugin,
classes: {visible: is_pending or is_changed, pending: is_pending}
}, "\u2022")
)
])
)
window.PluginList = PluginList

View File

@ -0,0 +1,71 @@
window.h = maquette.h
class UiPluginManager extends ZeroFrame
init: ->
@plugin_list_builtin = new PluginList()
@plugin_list_custom = new PluginList()
@plugins_changed = null
@need_restart = null
@
onOpenWebsocket: =>
@cmd("wrapperSetTitle", "Plugin manager - ZeroNet")
@cmd "serverInfo", {}, (server_info) =>
@server_info = server_info
@updatePlugins()
updatePlugins: (cb) =>
@cmd "pluginList", [], (res) =>
@plugins_changed = (item for item in res.plugins when item.enabled != item.loaded or item.updated)
plugins_builtin = (item for item in res.plugins when item.source == "builtin")
@plugin_list_builtin.plugins = plugins_builtin.sort (a, b) ->
return a.name.localeCompare(b.name)
plugins_custom = (item for item in res.plugins when item.source != "builtin")
@plugin_list_custom.plugins = plugins_custom.sort (a, b) ->
return a.name.localeCompare(b.name)
@projector.scheduleRender()
cb?()
createProjector: =>
@projector = maquette.createProjector()
@projector.replace($("#content"), @render)
@projector.replace($("#bottom-restart"), @renderBottomRestart)
render: =>
if not @plugin_list_builtin.plugins
return h("div.content")
h("div.content", [
h("div.section", [
if @plugin_list_custom.plugins?.length
[
h("h2", "Installed third-party plugins"),
@plugin_list_custom.render()
]
h("h2", "Built-in plugins")
@plugin_list_builtin.render()
])
])
handleRestartClick: =>
@restart_loading = true
setTimeout ( =>
Page.cmd("serverShutdown", {restart: true})
), 300
Page.projector.scheduleRender()
return false
renderBottomRestart: =>
h("div.bottom.bottom-restart", {classes: {visible: @plugins_changed?.length}}, h("div.bottom-content", [
h("div.title", "Some plugins status has been changed"),
h("a.button.button-submit.button-restart",
{href: "#Restart", classes: {loading: @restart_loading}, onclick: @handleRestartClick},
"Restart ZeroNet client"
)
]))
window.Page = new UiPluginManager()
window.Page.createProjector()

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,23 @@
class Class
trace: true
log: (args...) ->
return unless @trace
return if typeof console is 'undefined'
args.unshift("[#{@.constructor.name}]")
console.log(args...)
@
logStart: (name, args...) ->
return unless @trace
@logtimers or= {}
@logtimers[name] = +(new Date)
@log "#{name}", args..., "(started)" if args.length > 0
@
logEnd: (name, args...) ->
ms = +(new Date)-@logtimers[name]
@log "#{name}", args..., "(Done in #{ms}ms)"
@
window.Class = Class

View File

@ -0,0 +1,74 @@
# From: http://dev.bizo.com/2011/12/promises-in-javascriptcoffeescript.html
class Promise
@when: (tasks...) ->
num_uncompleted = tasks.length
args = new Array(num_uncompleted)
promise = new Promise()
for task, task_id in tasks
((task_id) ->
task.then(() ->
args[task_id] = Array.prototype.slice.call(arguments)
num_uncompleted--
promise.complete.apply(promise, args) if num_uncompleted == 0
)
)(task_id)
return promise
constructor: ->
@resolved = false
@end_promise = null
@result = null
@callbacks = []
resolve: ->
if @resolved
return false
@resolved = true
@data = arguments
if not arguments.length
@data = [true]
@result = @data[0]
for callback in @callbacks
back = callback.apply callback, @data
if @end_promise
@end_promise.resolve(back)
fail: ->
@resolve(false)
then: (callback) ->
if @resolved == true
callback.apply callback, @data
return
@callbacks.push callback
@end_promise = new Promise()
window.Promise = Promise
###
s = Date.now()
log = (text) ->
console.log Date.now()-s, Array.prototype.slice.call(arguments).join(", ")
log "Started"
cmd = (query) ->
p = new Promise()
setTimeout ( ->
p.resolve query+" Result"
), 100
return p
back = cmd("SELECT * FROM message").then (res) ->
log res
return "Return from query"
.then (res) ->
log "Back then", res
log "Query started", back
###

View File

@ -0,0 +1,8 @@
String::startsWith = (s) -> @[...s.length] is s
String::endsWith = (s) -> s is '' or @[-s.length..] is s
String::repeat = (count) -> new Array( count + 1 ).join(@)
window.isEmpty = (obj) ->
for key of obj
return false
return true

View File

@ -0,0 +1,770 @@
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports'], factory);
} else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
// CommonJS
factory(exports);
} else {
// Browser globals
factory(root.maquette = {});
}
}(this, function (exports) {
'use strict';
;
;
;
;
var NAMESPACE_W3 = 'http://www.w3.org/';
var NAMESPACE_SVG = NAMESPACE_W3 + '2000/svg';
var NAMESPACE_XLINK = NAMESPACE_W3 + '1999/xlink';
// Utilities
var emptyArray = [];
var extend = function (base, overrides) {
var result = {};
Object.keys(base).forEach(function (key) {
result[key] = base[key];
});
if (overrides) {
Object.keys(overrides).forEach(function (key) {
result[key] = overrides[key];
});
}
return result;
};
// Hyperscript helper functions
var same = function (vnode1, vnode2) {
if (vnode1.vnodeSelector !== vnode2.vnodeSelector) {
return false;
}
if (vnode1.properties && vnode2.properties) {
if (vnode1.properties.key !== vnode2.properties.key) {
return false;
}
return vnode1.properties.bind === vnode2.properties.bind;
}
return !vnode1.properties && !vnode2.properties;
};
var toTextVNode = function (data) {
return {
vnodeSelector: '',
properties: undefined,
children: undefined,
text: data.toString(),
domNode: null
};
};
var appendChildren = function (parentSelector, insertions, main) {
for (var i = 0; i < insertions.length; i++) {
var item = insertions[i];
if (Array.isArray(item)) {
appendChildren(parentSelector, item, main);
} else {
if (item !== null && item !== undefined) {
if (!item.hasOwnProperty('vnodeSelector')) {
item = toTextVNode(item);
}
main.push(item);
}
}
}
};
// Render helper functions
var missingTransition = function () {
throw new Error('Provide a transitions object to the projectionOptions to do animations');
};
var DEFAULT_PROJECTION_OPTIONS = {
namespace: undefined,
eventHandlerInterceptor: undefined,
styleApplyer: function (domNode, styleName, value) {
// Provides a hook to add vendor prefixes for browsers that still need it.
domNode.style[styleName] = value;
},
transitions: {
enter: missingTransition,
exit: missingTransition
}
};
var applyDefaultProjectionOptions = function (projectorOptions) {
return extend(DEFAULT_PROJECTION_OPTIONS, projectorOptions);
};
var checkStyleValue = function (styleValue) {
if (typeof styleValue !== 'string') {
throw new Error('Style values must be strings');
}
};
var setProperties = function (domNode, properties, projectionOptions) {
if (!properties) {
return;
}
var eventHandlerInterceptor = projectionOptions.eventHandlerInterceptor;
var propNames = Object.keys(properties);
var propCount = propNames.length;
for (var i = 0; i < propCount; i++) {
var propName = propNames[i];
/* tslint:disable:no-var-keyword: edge case */
var propValue = properties[propName];
/* tslint:enable:no-var-keyword */
if (propName === 'className') {
throw new Error('Property "className" is not supported, use "class".');
} else if (propName === 'class') {
if (domNode.className) {
// May happen if classes is specified before class
domNode.className += ' ' + propValue;
} else {
domNode.className = propValue;
}
} else if (propName === 'classes') {
// object with string keys and boolean values
var classNames = Object.keys(propValue);
var classNameCount = classNames.length;
for (var j = 0; j < classNameCount; j++) {
var className = classNames[j];
if (propValue[className]) {
domNode.classList.add(className);
}
}
} else if (propName === 'styles') {
// object with string keys and string (!) values
var styleNames = Object.keys(propValue);
var styleCount = styleNames.length;
for (var j = 0; j < styleCount; j++) {
var styleName = styleNames[j];
var styleValue = propValue[styleName];
if (styleValue) {
checkStyleValue(styleValue);
projectionOptions.styleApplyer(domNode, styleName, styleValue);
}
}
} else if (propName === 'key') {
continue;
} else if (propValue === null || propValue === undefined) {
continue;
} else {
var type = typeof propValue;
if (type === 'function') {
if (propName.lastIndexOf('on', 0) === 0) {
if (eventHandlerInterceptor) {
propValue = eventHandlerInterceptor(propName, propValue, domNode, properties); // intercept eventhandlers
}
if (propName === 'oninput') {
(function () {
// record the evt.target.value, because IE and Edge sometimes do a requestAnimationFrame between changing value and running oninput
var oldPropValue = propValue;
propValue = function (evt) {
evt.target['oninput-value'] = evt.target.value;
// may be HTMLTextAreaElement as well
oldPropValue.apply(this, [evt]);
};
}());
}
domNode[propName] = propValue;
}
} else if (type === 'string' && propName !== 'value' && propName !== 'innerHTML') {
if (projectionOptions.namespace === NAMESPACE_SVG && propName === 'href') {
domNode.setAttributeNS(NAMESPACE_XLINK, propName, propValue);
} else {
domNode.setAttribute(propName, propValue);
}
} else {
domNode[propName] = propValue;
}
}
}
};
var updateProperties = function (domNode, previousProperties, properties, projectionOptions) {
if (!properties) {
return;
}
var propertiesUpdated = false;
var propNames = Object.keys(properties);
var propCount = propNames.length;
for (var i = 0; i < propCount; i++) {
var propName = propNames[i];
// assuming that properties will be nullified instead of missing is by design
var propValue = properties[propName];
var previousValue = previousProperties[propName];
if (propName === 'class') {
if (previousValue !== propValue) {
throw new Error('"class" property may not be updated. Use the "classes" property for conditional css classes.');
}
} else if (propName === 'classes') {
var classList = domNode.classList;
var classNames = Object.keys(propValue);
var classNameCount = classNames.length;
for (var j = 0; j < classNameCount; j++) {
var className = classNames[j];
var on = !!propValue[className];
var previousOn = !!previousValue[className];
if (on === previousOn) {
continue;
}
propertiesUpdated = true;
if (on) {
classList.add(className);
} else {
classList.remove(className);
}
}
} else if (propName === 'styles') {
var styleNames = Object.keys(propValue);
var styleCount = styleNames.length;
for (var j = 0; j < styleCount; j++) {
var styleName = styleNames[j];
var newStyleValue = propValue[styleName];
var oldStyleValue = previousValue[styleName];
if (newStyleValue === oldStyleValue) {
continue;
}
propertiesUpdated = true;
if (newStyleValue) {
checkStyleValue(newStyleValue);
projectionOptions.styleApplyer(domNode, styleName, newStyleValue);
} else {
projectionOptions.styleApplyer(domNode, styleName, '');
}
}
} else {
if (!propValue && typeof previousValue === 'string') {
propValue = '';
}
if (propName === 'value') {
if (domNode[propName] !== propValue && domNode['oninput-value'] !== propValue) {
domNode[propName] = propValue;
// Reset the value, even if the virtual DOM did not change
domNode['oninput-value'] = undefined;
}
// else do not update the domNode, otherwise the cursor position would be changed
if (propValue !== previousValue) {
propertiesUpdated = true;
}
} else if (propValue !== previousValue) {
var type = typeof propValue;
if (type === 'function') {
throw new Error('Functions may not be updated on subsequent renders (property: ' + propName + '). Hint: declare event handler functions outside the render() function.');
}
if (type === 'string' && propName !== 'innerHTML') {
if (projectionOptions.namespace === NAMESPACE_SVG && propName === 'href') {
domNode.setAttributeNS(NAMESPACE_XLINK, propName, propValue);
} else {
domNode.setAttribute(propName, propValue);
}
} else {
if (domNode[propName] !== propValue) {
domNode[propName] = propValue;
}
}
propertiesUpdated = true;
}
}
}
return propertiesUpdated;
};
var findIndexOfChild = function (children, sameAs, start) {
if (sameAs.vnodeSelector !== '') {
// Never scan for text-nodes
for (var i = start; i < children.length; i++) {
if (same(children[i], sameAs)) {
return i;
}
}
}
return -1;
};
var nodeAdded = function (vNode, transitions) {
if (vNode.properties) {
var enterAnimation = vNode.properties.enterAnimation;
if (enterAnimation) {
if (typeof enterAnimation === 'function') {
enterAnimation(vNode.domNode, vNode.properties);
} else {
transitions.enter(vNode.domNode, vNode.properties, enterAnimation);
}
}
}
};
var nodeToRemove = function (vNode, transitions) {
var domNode = vNode.domNode;
if (vNode.properties) {
var exitAnimation = vNode.properties.exitAnimation;
if (exitAnimation) {
domNode.style.pointerEvents = 'none';
var removeDomNode = function () {
if (domNode.parentNode) {
domNode.parentNode.removeChild(domNode);
}
};
if (typeof exitAnimation === 'function') {
exitAnimation(domNode, removeDomNode, vNode.properties);
return;
} else {
transitions.exit(vNode.domNode, vNode.properties, exitAnimation, removeDomNode);
return;
}
}
}
if (domNode.parentNode) {
domNode.parentNode.removeChild(domNode);
}
};
var checkDistinguishable = function (childNodes, indexToCheck, parentVNode, operation) {
var childNode = childNodes[indexToCheck];
if (childNode.vnodeSelector === '') {
return; // Text nodes need not be distinguishable
}
var properties = childNode.properties;
var key = properties ? properties.key === undefined ? properties.bind : properties.key : undefined;
if (!key) {
for (var i = 0; i < childNodes.length; i++) {
if (i !== indexToCheck) {
var node = childNodes[i];
if (same(node, childNode)) {
if (operation === 'added') {
throw new Error(parentVNode.vnodeSelector + ' had a ' + childNode.vnodeSelector + ' child ' + 'added, but there is now more than one. You must add unique key properties to make them distinguishable.');
} else {
throw new Error(parentVNode.vnodeSelector + ' had a ' + childNode.vnodeSelector + ' child ' + 'removed, but there were more than one. You must add unique key properties to make them distinguishable.');
}
}
}
}
}
};
var createDom;
var updateDom;
var updateChildren = function (vnode, domNode, oldChildren, newChildren, projectionOptions) {
if (oldChildren === newChildren) {
return false;
}
oldChildren = oldChildren || emptyArray;
newChildren = newChildren || emptyArray;
var oldChildrenLength = oldChildren.length;
var newChildrenLength = newChildren.length;
var transitions = projectionOptions.transitions;
var oldIndex = 0;
var newIndex = 0;
var i;
var textUpdated = false;
while (newIndex < newChildrenLength) {
var oldChild = oldIndex < oldChildrenLength ? oldChildren[oldIndex] : undefined;
var newChild = newChildren[newIndex];
if (oldChild !== undefined && same(oldChild, newChild)) {
textUpdated = updateDom(oldChild, newChild, projectionOptions) || textUpdated;
oldIndex++;
} else {
var findOldIndex = findIndexOfChild(oldChildren, newChild, oldIndex + 1);
if (findOldIndex >= 0) {
// Remove preceding missing children
for (i = oldIndex; i < findOldIndex; i++) {
nodeToRemove(oldChildren[i], transitions);
checkDistinguishable(oldChildren, i, vnode, 'removed');
}
textUpdated = updateDom(oldChildren[findOldIndex], newChild, projectionOptions) || textUpdated;
oldIndex = findOldIndex + 1;
} else {
// New child
createDom(newChild, domNode, oldIndex < oldChildrenLength ? oldChildren[oldIndex].domNode : undefined, projectionOptions);
nodeAdded(newChild, transitions);
checkDistinguishable(newChildren, newIndex, vnode, 'added');
}
}
newIndex++;
}
if (oldChildrenLength > oldIndex) {
// Remove child fragments
for (i = oldIndex; i < oldChildrenLength; i++) {
nodeToRemove(oldChildren[i], transitions);
checkDistinguishable(oldChildren, i, vnode, 'removed');
}
}
return textUpdated;
};
var addChildren = function (domNode, children, projectionOptions) {
if (!children) {
return;
}
for (var i = 0; i < children.length; i++) {
createDom(children[i], domNode, undefined, projectionOptions);
}
};
var initPropertiesAndChildren = function (domNode, vnode, projectionOptions) {
addChildren(domNode, vnode.children, projectionOptions);
// children before properties, needed for value property of <select>.
if (vnode.text) {
domNode.textContent = vnode.text;
}
setProperties(domNode, vnode.properties, projectionOptions);
if (vnode.properties && vnode.properties.afterCreate) {
vnode.properties.afterCreate(domNode, projectionOptions, vnode.vnodeSelector, vnode.properties, vnode.children);
}
};
createDom = function (vnode, parentNode, insertBefore, projectionOptions) {
var domNode, i, c, start = 0, type, found;
var vnodeSelector = vnode.vnodeSelector;
if (vnodeSelector === '') {
domNode = vnode.domNode = document.createTextNode(vnode.text);
if (insertBefore !== undefined) {
parentNode.insertBefore(domNode, insertBefore);
} else {
parentNode.appendChild(domNode);
}
} else {
for (i = 0; i <= vnodeSelector.length; ++i) {
c = vnodeSelector.charAt(i);
if (i === vnodeSelector.length || c === '.' || c === '#') {
type = vnodeSelector.charAt(start - 1);
found = vnodeSelector.slice(start, i);
if (type === '.') {
domNode.classList.add(found);
} else if (type === '#') {
domNode.id = found;
} else {
if (found === 'svg') {
projectionOptions = extend(projectionOptions, { namespace: NAMESPACE_SVG });
}
if (projectionOptions.namespace !== undefined) {
domNode = vnode.domNode = document.createElementNS(projectionOptions.namespace, found);
} else {
domNode = vnode.domNode = document.createElement(found);
}
if (insertBefore !== undefined) {
parentNode.insertBefore(domNode, insertBefore);
} else {
parentNode.appendChild(domNode);
}
}
start = i + 1;
}
}
initPropertiesAndChildren(domNode, vnode, projectionOptions);
}
};
updateDom = function (previous, vnode, projectionOptions) {
var domNode = previous.domNode;
var textUpdated = false;
if (previous === vnode) {
return false; // By contract, VNode objects may not be modified anymore after passing them to maquette
}
var updated = false;
if (vnode.vnodeSelector === '') {
if (vnode.text !== previous.text) {
var newVNode = document.createTextNode(vnode.text);
domNode.parentNode.replaceChild(newVNode, domNode);
vnode.domNode = newVNode;
textUpdated = true;
return textUpdated;
}
} else {
if (vnode.vnodeSelector.lastIndexOf('svg', 0) === 0) {
projectionOptions = extend(projectionOptions, { namespace: NAMESPACE_SVG });
}
if (previous.text !== vnode.text) {
updated = true;
if (vnode.text === undefined) {
domNode.removeChild(domNode.firstChild); // the only textnode presumably
} else {
domNode.textContent = vnode.text;
}
}
updated = updateChildren(vnode, domNode, previous.children, vnode.children, projectionOptions) || updated;
updated = updateProperties(domNode, previous.properties, vnode.properties, projectionOptions) || updated;
if (vnode.properties && vnode.properties.afterUpdate) {
vnode.properties.afterUpdate(domNode, projectionOptions, vnode.vnodeSelector, vnode.properties, vnode.children);
}
}
if (updated && vnode.properties && vnode.properties.updateAnimation) {
vnode.properties.updateAnimation(domNode, vnode.properties, previous.properties);
}
vnode.domNode = previous.domNode;
return textUpdated;
};
var createProjection = function (vnode, projectionOptions) {
return {
update: function (updatedVnode) {
if (vnode.vnodeSelector !== updatedVnode.vnodeSelector) {
throw new Error('The selector for the root VNode may not be changed. (consider using dom.merge and add one extra level to the virtual DOM)');
}
updateDom(vnode, updatedVnode, projectionOptions);
vnode = updatedVnode;
},
domNode: vnode.domNode
};
};
;
// The other two parameters are not added here, because the Typescript compiler creates surrogate code for desctructuring 'children'.
exports.h = function (selector) {
var properties = arguments[1];
if (typeof selector !== 'string') {
throw new Error();
}
var childIndex = 1;
if (properties && !properties.hasOwnProperty('vnodeSelector') && !Array.isArray(properties) && typeof properties === 'object') {
childIndex = 2;
} else {
// Optional properties argument was omitted
properties = undefined;
}
var text = undefined;
var children = undefined;
var argsLength = arguments.length;
// Recognize a common special case where there is only a single text node
if (argsLength === childIndex + 1) {
var onlyChild = arguments[childIndex];
if (typeof onlyChild === 'string') {
text = onlyChild;
} else if (onlyChild !== undefined && onlyChild.length === 1 && typeof onlyChild[0] === 'string') {
text = onlyChild[0];
}
}
if (text === undefined) {
children = [];
for (; childIndex < arguments.length; childIndex++) {
var child = arguments[childIndex];
if (child === null || child === undefined) {
continue;
} else if (Array.isArray(child)) {
appendChildren(selector, child, children);
} else if (child.hasOwnProperty('vnodeSelector')) {
children.push(child);
} else {
children.push(toTextVNode(child));
}
}
}
return {
vnodeSelector: selector,
properties: properties,
children: children,
text: text === '' ? undefined : text,
domNode: null
};
};
/**
* Contains simple low-level utility functions to manipulate the real DOM.
*/
exports.dom = {
/**
* Creates a real DOM tree from `vnode`. The [[Projection]] object returned will contain the resulting DOM Node in
* its [[Projection.domNode|domNode]] property.
* This is a low-level method. Users wil typically use a [[Projector]] instead.
* @param vnode - The root of the virtual DOM tree that was created using the [[h]] function. NOTE: [[VNode]]
* objects may only be rendered once.
* @param projectionOptions - Options to be used to create and update the projection.
* @returns The [[Projection]] which also contains the DOM Node that was created.
*/
create: function (vnode, projectionOptions) {
projectionOptions = applyDefaultProjectionOptions(projectionOptions);
createDom(vnode, document.createElement('div'), undefined, projectionOptions);
return createProjection(vnode, projectionOptions);
},
/**
* Appends a new childnode to the DOM which is generated from a [[VNode]].
* This is a low-level method. Users wil typically use a [[Projector]] instead.
* @param parentNode - The parent node for the new childNode.
* @param vnode - The root of the virtual DOM tree that was created using the [[h]] function. NOTE: [[VNode]]
* objects may only be rendered once.
* @param projectionOptions - Options to be used to create and update the [[Projection]].
* @returns The [[Projection]] that was created.
*/
append: function (parentNode, vnode, projectionOptions) {
projectionOptions = applyDefaultProjectionOptions(projectionOptions);
createDom(vnode, parentNode, undefined, projectionOptions);
return createProjection(vnode, projectionOptions);
},
/**
* Inserts a new DOM node which is generated from a [[VNode]].
* This is a low-level method. Users wil typically use a [[Projector]] instead.
* @param beforeNode - The node that the DOM Node is inserted before.
* @param vnode - The root of the virtual DOM tree that was created using the [[h]] function.
* NOTE: [[VNode]] objects may only be rendered once.
* @param projectionOptions - Options to be used to create and update the projection, see [[createProjector]].
* @returns The [[Projection]] that was created.
*/
insertBefore: function (beforeNode, vnode, projectionOptions) {
projectionOptions = applyDefaultProjectionOptions(projectionOptions);
createDom(vnode, beforeNode.parentNode, beforeNode, projectionOptions);
return createProjection(vnode, projectionOptions);
},
/**
* Merges a new DOM node which is generated from a [[VNode]] with an existing DOM Node.
* This means that the virtual DOM and the real DOM will have one overlapping element.
* Therefore the selector for the root [[VNode]] will be ignored, but its properties and children will be applied to the Element provided.
* This is a low-level method. Users wil typically use a [[Projector]] instead.
* @param domNode - The existing element to adopt as the root of the new virtual DOM. Existing attributes and childnodes are preserved.
* @param vnode - The root of the virtual DOM tree that was created using the [[h]] function. NOTE: [[VNode]] objects
* may only be rendered once.
* @param projectionOptions - Options to be used to create and update the projection, see [[createProjector]].
* @returns The [[Projection]] that was created.
*/
merge: function (element, vnode, projectionOptions) {
projectionOptions = applyDefaultProjectionOptions(projectionOptions);
vnode.domNode = element;
initPropertiesAndChildren(element, vnode, projectionOptions);
return createProjection(vnode, projectionOptions);
}
};
/**
* Creates a [[CalculationCache]] object, useful for caching [[VNode]] trees.
* In practice, caching of [[VNode]] trees is not needed, because achieving 60 frames per second is almost never a problem.
* For more information, see [[CalculationCache]].
*
* @param <Result> The type of the value that is cached.
*/
exports.createCache = function () {
var cachedInputs = undefined;
var cachedOutcome = undefined;
var result = {
invalidate: function () {
cachedOutcome = undefined;
cachedInputs = undefined;
},
result: function (inputs, calculation) {
if (cachedInputs) {
for (var i = 0; i < inputs.length; i++) {
if (cachedInputs[i] !== inputs[i]) {
cachedOutcome = undefined;
}
}
}
if (!cachedOutcome) {
cachedOutcome = calculation();
cachedInputs = inputs;
}
return cachedOutcome;
}
};
return result;
};
/**
* Creates a {@link Mapping} instance that keeps an array of result objects synchronized with an array of source objects.
* See {@link http://maquettejs.org/docs/arrays.html|Working with arrays}.
*
* @param <Source> The type of source items. A database-record for instance.
* @param <Target> The type of target items. A [[Component]] for instance.
* @param getSourceKey `function(source)` that must return a key to identify each source object. The result must either be a string or a number.
* @param createResult `function(source, index)` that must create a new result object from a given source. This function is identical
* to the `callback` argument in `Array.map(callback)`.
* @param updateResult `function(source, target, index)` that updates a result to an updated source.
*/
exports.createMapping = function (getSourceKey, createResult, updateResult) {
var keys = [];
var results = [];
return {
results: results,
map: function (newSources) {
var newKeys = newSources.map(getSourceKey);
var oldTargets = results.slice();
var oldIndex = 0;
for (var i = 0; i < newSources.length; i++) {
var source = newSources[i];
var sourceKey = newKeys[i];
if (sourceKey === keys[oldIndex]) {
results[i] = oldTargets[oldIndex];
updateResult(source, oldTargets[oldIndex], i);
oldIndex++;
} else {
var found = false;
for (var j = 1; j < keys.length; j++) {
var searchIndex = (oldIndex + j) % keys.length;
if (keys[searchIndex] === sourceKey) {
results[i] = oldTargets[searchIndex];
updateResult(newSources[i], oldTargets[searchIndex], i);
oldIndex = searchIndex + 1;
found = true;
break;
}
}
if (!found) {
results[i] = createResult(source, i);
}
}
}
results.length = newSources.length;
keys = newKeys;
}
};
};
/**
* Creates a [[Projector]] instance using the provided projectionOptions.
*
* For more information, see [[Projector]].
*
* @param projectionOptions Options that influence how the DOM is rendered and updated.
*/
exports.createProjector = function (projectorOptions) {
var projector;
var projectionOptions = applyDefaultProjectionOptions(projectorOptions);
projectionOptions.eventHandlerInterceptor = function (propertyName, eventHandler, domNode, properties) {
return function () {
// intercept function calls (event handlers) to do a render afterwards.
projector.scheduleRender();
return eventHandler.apply(properties.bind || this, arguments);
};
};
var renderCompleted = true;
var scheduled;
var stopped = false;
var projections = [];
var renderFunctions = [];
// matches the projections array
var doRender = function () {
scheduled = undefined;
if (!renderCompleted) {
return; // The last render threw an error, it should be logged in the browser console.
}
renderCompleted = false;
for (var i = 0; i < projections.length; i++) {
var updatedVnode = renderFunctions[i]();
projections[i].update(updatedVnode);
}
renderCompleted = true;
};
projector = {
scheduleRender: function () {
if (!scheduled && !stopped) {
scheduled = requestAnimationFrame(doRender);
}
},
stop: function () {
if (scheduled) {
cancelAnimationFrame(scheduled);
scheduled = undefined;
}
stopped = true;
},
resume: function () {
stopped = false;
renderCompleted = true;
projector.scheduleRender();
},
append: function (parentNode, renderMaquetteFunction) {
projections.push(exports.dom.append(parentNode, renderMaquetteFunction(), projectionOptions));
renderFunctions.push(renderMaquetteFunction);
},
insertBefore: function (beforeNode, renderMaquetteFunction) {
projections.push(exports.dom.insertBefore(beforeNode, renderMaquetteFunction(), projectionOptions));
renderFunctions.push(renderMaquetteFunction);
},
merge: function (domNode, renderMaquetteFunction) {
projections.push(exports.dom.merge(domNode, renderMaquetteFunction(), projectionOptions));
renderFunctions.push(renderMaquetteFunction);
},
replace: function (domNode, renderMaquetteFunction) {
var vnode = renderMaquetteFunction();
createDom(vnode, domNode.parentNode, domNode, projectionOptions);
domNode.parentNode.removeChild(domNode);
projections.push(createProjection(vnode, projectionOptions));
renderFunctions.push(renderMaquetteFunction);
},
detach: function (renderMaquetteFunction) {
for (var i = 0; i < renderFunctions.length; i++) {
if (renderFunctions[i] === renderMaquetteFunction) {
renderFunctions.splice(i, 1);
return projections.splice(i, 1)[0];
}
}
throw new Error('renderMaquetteFunction was not found');
}
};
return projector;
};
}));

View File

@ -0,0 +1,138 @@
class Animation
slideDown: (elem, props) ->
if elem.offsetTop > 2000
return
h = elem.offsetHeight
cstyle = window.getComputedStyle(elem)
margin_top = cstyle.marginTop
margin_bottom = cstyle.marginBottom
padding_top = cstyle.paddingTop
padding_bottom = cstyle.paddingBottom
transition = cstyle.transition
elem.style.boxSizing = "border-box"
elem.style.overflow = "hidden"
elem.style.transform = "scale(0.6)"
elem.style.opacity = "0"
elem.style.height = "0px"
elem.style.marginTop = "0px"
elem.style.marginBottom = "0px"
elem.style.paddingTop = "0px"
elem.style.paddingBottom = "0px"
elem.style.transition = "none"
setTimeout (->
elem.className += " animate-inout"
elem.style.height = h+"px"
elem.style.transform = "scale(1)"
elem.style.opacity = "1"
elem.style.marginTop = margin_top
elem.style.marginBottom = margin_bottom
elem.style.paddingTop = padding_top
elem.style.paddingBottom = padding_bottom
), 1
elem.addEventListener "transitionend", ->
elem.classList.remove("animate-inout")
elem.style.transition = elem.style.transform = elem.style.opacity = elem.style.height = null
elem.style.boxSizing = elem.style.marginTop = elem.style.marginBottom = null
elem.style.paddingTop = elem.style.paddingBottom = elem.style.overflow = null
elem.removeEventListener "transitionend", arguments.callee, false
slideUp: (elem, remove_func, props) ->
if elem.offsetTop > 1000
return remove_func()
elem.className += " animate-back"
elem.style.boxSizing = "border-box"
elem.style.height = elem.offsetHeight+"px"
elem.style.overflow = "hidden"
elem.style.transform = "scale(1)"
elem.style.opacity = "1"
elem.style.pointerEvents = "none"
setTimeout (->
elem.style.height = "0px"
elem.style.marginTop = "0px"
elem.style.marginBottom = "0px"
elem.style.paddingTop = "0px"
elem.style.paddingBottom = "0px"
elem.style.transform = "scale(0.8)"
elem.style.borderTopWidth = "0px"
elem.style.borderBottomWidth = "0px"
elem.style.opacity = "0"
), 1
elem.addEventListener "transitionend", (e) ->
if e.propertyName == "opacity" or e.elapsedTime >= 0.6
elem.removeEventListener "transitionend", arguments.callee, false
remove_func()
slideUpInout: (elem, remove_func, props) ->
elem.className += " animate-inout"
elem.style.boxSizing = "border-box"
elem.style.height = elem.offsetHeight+"px"
elem.style.overflow = "hidden"
elem.style.transform = "scale(1)"
elem.style.opacity = "1"
elem.style.pointerEvents = "none"
setTimeout (->
elem.style.height = "0px"
elem.style.marginTop = "0px"
elem.style.marginBottom = "0px"
elem.style.paddingTop = "0px"
elem.style.paddingBottom = "0px"
elem.style.transform = "scale(0.8)"
elem.style.borderTopWidth = "0px"
elem.style.borderBottomWidth = "0px"
elem.style.opacity = "0"
), 1
elem.addEventListener "transitionend", (e) ->
if e.propertyName == "opacity" or e.elapsedTime >= 0.6
elem.removeEventListener "transitionend", arguments.callee, false
remove_func()
showRight: (elem, props) ->
elem.className += " animate"
elem.style.opacity = 0
elem.style.transform = "TranslateX(-20px) Scale(1.01)"
setTimeout (->
elem.style.opacity = 1
elem.style.transform = "TranslateX(0px) Scale(1)"
), 1
elem.addEventListener "transitionend", ->
elem.classList.remove("animate")
elem.style.transform = elem.style.opacity = null
show: (elem, props) ->
delay = arguments[arguments.length-2]?.delay*1000 or 1
elem.style.opacity = 0
setTimeout (->
elem.className += " animate"
), 1
setTimeout (->
elem.style.opacity = 1
), delay
elem.addEventListener "transitionend", ->
elem.classList.remove("animate")
elem.style.opacity = null
elem.removeEventListener "transitionend", arguments.callee, false
hide: (elem, remove_func, props) ->
delay = arguments[arguments.length-2]?.delay*1000 or 1
elem.className += " animate"
setTimeout (->
elem.style.opacity = 0
), delay
elem.addEventListener "transitionend", (e) ->
if e.propertyName == "opacity"
remove_func()
addVisibleClass: (elem, props) ->
setTimeout ->
elem.classList.add("visible")
window.Animation = new Animation()

View File

@ -0,0 +1,3 @@
window.$ = (selector) ->
if selector.startsWith("#")
return document.getElementById(selector.replace("#", ""))

View File

@ -0,0 +1,85 @@
class ZeroFrame extends Class
constructor: (url) ->
@url = url
@waiting_cb = {}
@wrapper_nonce = document.location.href.replace(/.*wrapper_nonce=([A-Za-z0-9]+).*/, "$1")
@connect()
@next_message_id = 1
@history_state = {}
@init()
init: ->
@
connect: ->
@target = window.parent
window.addEventListener("message", @onMessage, false)
@cmd("innerReady")
# Save scrollTop
window.addEventListener "beforeunload", (e) =>
@log "save scrollTop", window.pageYOffset
@history_state["scrollTop"] = window.pageYOffset
@cmd "wrapperReplaceState", [@history_state, null]
# Restore scrollTop
@cmd "wrapperGetState", [], (state) =>
@history_state = state if state?
@log "restore scrollTop", state, window.pageYOffset
if window.pageYOffset == 0 and state
window.scroll(window.pageXOffset, state.scrollTop)
onMessage: (e) =>
message = e.data
cmd = message.cmd
if cmd == "response"
if @waiting_cb[message.to]?
@waiting_cb[message.to](message.result)
else
@log "Websocket callback not found:", message
else if cmd == "wrapperReady" # Wrapper inited later
@cmd("innerReady")
else if cmd == "ping"
@response message.id, "pong"
else if cmd == "wrapperOpenedWebsocket"
@onOpenWebsocket()
else if cmd == "wrapperClosedWebsocket"
@onCloseWebsocket()
else
@onRequest cmd, message.params
onRequest: (cmd, message) =>
@log "Unknown request", message
response: (to, result) ->
@send {"cmd": "response", "to": to, "result": result}
cmd: (cmd, params={}, cb=null) ->
@send {"cmd": cmd, "params": params}, cb
send: (message, cb=null) ->
message.wrapper_nonce = @wrapper_nonce
message.id = @next_message_id
@next_message_id += 1
@target.postMessage(message, "*")
if cb
@waiting_cb[message.id] = cb
onOpenWebsocket: =>
@log "Websocket open"
onCloseWebsocket: =>
@log "Websocket close"
window.ZeroFrame = ZeroFrame

View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<title>Settings - ZeroNet</title>
<meta charset="utf-8" />
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<link rel="stylesheet" href="css/all.css?rev={rev}" />
</head>
<h1>ZeroNet plugin manager</h1>
<div class="content" id="content"></div>
<div class="bottom" id="bottom-restart"></div>
<script type="text/javascript" src="js/all.js?rev={rev}&lang={lang}"></script>
</body>
</html>

View File

@ -0,0 +1,55 @@
# ZeroName
Zeroname plugin to connect Namecoin and register all the .bit domain name.
## Start
You can create your own Zeroname.
### Namecoin node
You need to run a namecoin node.
[Namecoin](https://namecoin.org/download/)
You will need to start it as a RPC server.
Example of `~/.namecoin/namecoin.conf` minimal setup:
```
daemon=1
rpcuser=your-name
rpcpassword=your-password
rpcport=8336
server=1
txindex=1
valueencoding=utf8
```
Don't forget to change the `rpcuser` value and `rpcpassword` value!
You can start your node : `./namecoind`
### Create a Zeroname site
You will also need to create a site `python zeronet.py createSite` and regitser the info.
In the site you will need to create a file `./data/<your-site>/data/names.json` with this is it:
```
{}
```
### `zeroname_config.json` file
In `~/.namecoin/zeroname_config.json`
```
{
"lastprocessed": 223910,
"zeronet_path": "/root/ZeroNet", # Update with your path
"privatekey": "", # Update with your private key of your site
"site": "" # Update with the address of your site
}
```
### Run updater
You can now run the script : `updater/zeroname_updater.py` and wait until it is fully sync (it might take a while).

View File

@ -0,0 +1,69 @@
import logging
import re
import time
from Config import config
from Plugin import PluginManager
allow_reload = False # No reload supported
log = logging.getLogger("ZeronamePlugin")
@PluginManager.registerTo("SiteManager")
class SiteManagerPlugin(object):
site_zeroname = None
db_domains = {}
db_domains_modified = None
def load(self, *args, **kwargs):
super(SiteManagerPlugin, self).load(*args, **kwargs)
if not self.get(config.bit_resolver):
self.need(config.bit_resolver) # Need ZeroName site
# Return: True if the address is .bit domain
def isBitDomain(self, address):
return re.match(r"(.*?)([A-Za-z0-9_-]+\.bit)$", address)
# Resolve domain
# Return: The address or None
def resolveBitDomain(self, domain):
domain = domain.lower()
if not self.site_zeroname:
self.site_zeroname = self.need(config.bit_resolver)
site_zeroname_modified = self.site_zeroname.content_manager.contents.get("content.json", {}).get("modified", 0)
if not self.db_domains or self.db_domains_modified != site_zeroname_modified:
self.site_zeroname.needFile("data/names.json", priority=10)
s = time.time()
try:
self.db_domains = self.site_zeroname.storage.loadJson("data/names.json")
except Exception as err:
log.error("Error loading names.json: %s" % err)
log.debug(
"Domain db with %s entries loaded in %.3fs (modification: %s -> %s)" %
(len(self.db_domains), time.time() - s, self.db_domains_modified, site_zeroname_modified)
)
self.db_domains_modified = site_zeroname_modified
return self.db_domains.get(domain)
# Turn domain into address
def resolveDomain(self, domain):
return self.resolveBitDomain(domain) or super(SiteManagerPlugin, self).resolveDomain(domain)
# Return: True if the address is domain
def isDomain(self, address):
return self.isBitDomain(address) or super(SiteManagerPlugin, self).isDomain(address)
@PluginManager.registerTo("ConfigPlugin")
class ConfigPlugin(object):
def createArguments(self):
group = self.parser.add_argument_group("Zeroname plugin")
group.add_argument(
"--bit_resolver", help="ZeroNet site to resolve .bit domains",
default="1Name2NXVi1RDPDgf5617UoW7xA6YrhM9F", metavar="address"
)
return super(ConfigPlugin, self).createArguments()

View File

@ -0,0 +1 @@
from . import SiteManagerPlugin

View File

@ -0,0 +1,249 @@
from __future__ import print_function
import time
import json
import os
import sys
import re
import socket
from six import string_types
from subprocess import call
from bitcoinrpc.authproxy import AuthServiceProxy
def publish():
print("* Signing and Publishing...")
call(" ".join(command_sign_publish), shell=True)
def processNameOp(domain, value, test=False):
if not value.strip().startswith("{"):
return False
try:
data = json.loads(value)
except Exception as err:
print("Json load error: %s" % err)
return False
if "zeronet" not in data and "map" not in data:
# Namecoin standard use {"map": { "blog": {"zeronet": "1D..."} }}
print("No zeronet and no map in ", data.keys())
return False
if "map" in data:
# If subdomains using the Namecoin standard is present, just re-write in the Zeronet way
# and call the function again
data_map = data["map"]
new_value = {}
for subdomain in data_map:
if "zeronet" in data_map[subdomain]:
new_value[subdomain] = data_map[subdomain]["zeronet"]
if "zeronet" in data and isinstance(data["zeronet"], string_types):
# {
# "zeronet":"19rXKeKptSdQ9qt7omwN82smehzTuuq6S9",
# ....
# }
new_value[""] = data["zeronet"]
if len(new_value) > 0:
return processNameOp(domain, json.dumps({"zeronet": new_value}), test)
else:
return False
if "zeronet" in data and isinstance(data["zeronet"], string_types):
# {
# "zeronet":"19rXKeKptSdQ9qt7omwN82smehzTuuq6S9"
# } is valid
return processNameOp(domain, json.dumps({"zeronet": { "": data["zeronet"]}}), test)
if not isinstance(data["zeronet"], dict):
print("Not dict: ", data["zeronet"])
return False
if not re.match("^[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?$", domain):
print("Invalid domain: ", domain)
return False
if test:
return True
if "slave" in sys.argv:
print("Waiting for master update arrive")
time.sleep(30) # Wait 30 sec to allow master updater
# Note: Requires the file data/names.json to exist and contain "{}" to work
names_raw = open(names_path, "rb").read()
names = json.loads(names_raw)
for subdomain, address in data["zeronet"].items():
subdomain = subdomain.lower()
address = re.sub("[^A-Za-z0-9]", "", address)
print(subdomain, domain, "->", address)
if subdomain:
if re.match("^[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?$", subdomain):
names["%s.%s.bit" % (subdomain, domain)] = address
else:
print("Invalid subdomain:", domain, subdomain)
else:
names["%s.bit" % domain] = address
new_names_raw = json.dumps(names, indent=2, sort_keys=True)
if new_names_raw != names_raw:
open(names_path, "wb").write(new_names_raw)
print("-", domain, "Changed")
return True
else:
print("-", domain, "Not changed")
return False
def processBlock(block_id, test=False):
print("Processing block #%s..." % block_id)
s = time.time()
block_hash = rpc.getblockhash(block_id)
block = rpc.getblock(block_hash)
print("Checking %s tx" % len(block["tx"]))
updated = 0
for tx in block["tx"]:
try:
transaction = rpc.getrawtransaction(tx, 1)
for vout in transaction.get("vout", []):
if "scriptPubKey" in vout and "nameOp" in vout["scriptPubKey"] and "name" in vout["scriptPubKey"]["nameOp"]:
name_op = vout["scriptPubKey"]["nameOp"]
updated += processNameOp(name_op["name"].replace("d/", ""), name_op["value"], test)
except Exception as err:
print("Error processing tx #%s %s" % (tx, err))
print("Done in %.3fs (updated %s)." % (time.time() - s, updated))
return updated
# Connecting to RPC
def initRpc(config):
"""Initialize Namecoin RPC"""
rpc_data = {
'connect': '127.0.0.1',
'port': '8336',
'user': 'PLACEHOLDER',
'password': 'PLACEHOLDER',
'clienttimeout': '900'
}
try:
fptr = open(config, 'r')
lines = fptr.readlines()
fptr.close()
except:
return None # Or take some other appropriate action
for line in lines:
if not line.startswith('rpc'):
continue
key_val = line.split(None, 1)[0]
(key, val) = key_val.split('=', 1)
if not key or not val:
continue
rpc_data[key[3:]] = val
url = 'http://%(user)s:%(password)s@%(connect)s:%(port)s' % rpc_data
return url, int(rpc_data['clienttimeout'])
# Loading config...
# Check whether platform is on windows or linux
# On linux namecoin is installed under ~/.namecoin, while on on windows it is in %appdata%/Namecoin
if sys.platform == "win32":
namecoin_location = os.getenv('APPDATA') + "/Namecoin/"
else:
namecoin_location = os.path.expanduser("~/.namecoin/")
config_path = namecoin_location + 'zeroname_config.json'
if not os.path.isfile(config_path): # Create sample config
open(config_path, "w").write(
json.dumps({'site': 'site', 'zeronet_path': '/home/zeronet', 'privatekey': '', 'lastprocessed': 223910}, indent=2)
)
print("* Example config written to %s" % config_path)
sys.exit(0)
config = json.load(open(config_path))
names_path = "%s/data/%s/data/names.json" % (config["zeronet_path"], config["site"])
os.chdir(config["zeronet_path"]) # Change working dir - tells script where Zeronet install is.
# Parameters to sign and publish
command_sign_publish = [sys.executable, "zeronet.py", "siteSign", config["site"], config["privatekey"], "--publish"]
if sys.platform == 'win32':
command_sign_publish = ['"%s"' % param for param in command_sign_publish]
# Initialize rpc connection
rpc_auth, rpc_timeout = initRpc(namecoin_location + "namecoin.conf")
rpc = AuthServiceProxy(rpc_auth, timeout=rpc_timeout)
node_version = rpc.getnetworkinfo()['version']
while 1:
try:
time.sleep(1)
if node_version < 160000 :
last_block = int(rpc.getinfo()["blocks"])
else:
last_block = int(rpc.getblockchaininfo()["blocks"])
break # Connection succeeded
except socket.timeout: # Timeout
print(".", end=' ')
sys.stdout.flush()
except Exception as err:
print("Exception", err.__class__, err)
time.sleep(5)
rpc = AuthServiceProxy(rpc_auth, timeout=rpc_timeout)
if not config["lastprocessed"]: # First startup: Start processing from last block
config["lastprocessed"] = last_block
print("- Testing domain parsing...")
assert processBlock(223911, test=True) # Testing zeronetwork.bit
assert processBlock(227052, test=True) # Testing brainwallets.bit
assert not processBlock(236824, test=True) # Utf8 domain name (invalid should skip)
assert not processBlock(236752, test=True) # Uppercase domain (invalid should skip)
assert processBlock(236870, test=True) # Encoded domain (should pass)
assert processBlock(438317, test=True) # Testing namecoin standard artifaxradio.bit (should pass)
# sys.exit(0)
print("- Parsing skipped blocks...")
should_publish = False
for block_id in range(config["lastprocessed"], last_block + 1):
if processBlock(block_id):
should_publish = True
config["lastprocessed"] = last_block
if should_publish:
publish()
while 1:
print("- Waiting for new block")
sys.stdout.flush()
while 1:
try:
time.sleep(1)
if node_version < 160000 :
rpc.waitforblock()
else:
rpc.waitfornewblock()
print("Found")
break # Block found
except socket.timeout: # Timeout
print(".", end=' ')
sys.stdout.flush()
except Exception as err:
print("Exception", err.__class__, err)
time.sleep(5)
rpc = AuthServiceProxy(rpc_auth, timeout=rpc_timeout)
if node_version < 160000 :
last_block = int(rpc.getinfo()["blocks"])
else:
last_block = int(rpc.getblockchaininfo()["blocks"])
should_publish = False
for block_id in range(config["lastprocessed"] + 1, last_block + 1):
if processBlock(block_id):
should_publish = True
config["lastprocessed"] = last_block
open(config_path, "w").write(json.dumps(config, indent=2))
if should_publish:
publish()

0
plugins/__init__.py Normal file
View File