version 0.2.7, plugin system, multiuser plugin for zeroproxies, reworked imports, cookie parse, stats moved to plugin, usermanager class, dont generate site auth on listing, multiline notifications, allow server side prompt from user, update script keep plugins disabled status

This commit is contained in:
HelloZeroNet 2015-03-24 01:33:09 +01:00
parent 3b8d49207e
commit 78f97dcbe8
26 changed files with 789 additions and 308 deletions

View File

@ -0,0 +1,135 @@
import re, time, cgi, os
from Plugin import PluginManager
@PluginManager.registerTo("UiRequest")
class UiRequestPlugin(object):
def formatTableRow(self, row):
back = []
for format, val in row:
if val == 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>%s</tr>" % "".join(back)
def getObjSize(self, obj, hpy = None):
if hpy:
return float(hpy.iso(obj).domisize)/1024
else:
return 0
# /Stats entry point
def actionStats(self):
import gc, sys
from Ui import UiRequest
hpy = None
if self.get.get("size") == "1": # Calc obj size
try:
import guppy
hpy = guppy.hpy()
except:
pass
self.sendHeader()
s = time.time()
main = sys.modules["main"]
# Style
yield """
<style>
* { font-family: monospace }
table * { text-align: right; padding: 0px 10px }
</style>
"""
# Memory
try:
import psutil
process = psutil.Process(os.getpid())
mem = process.get_memory_info()[0] / float(2 ** 20)
yield "Memory usage: %.2fMB | " % mem
yield "Threads: %s | " % len(process.threads())
yield "CPU: usr %.2fs sys %.2fs | " % process.cpu_times()
yield "Open 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><br>"
except Exception, err:
pass
yield "Connections (%s):<br>" % len(main.file_server.connections)
yield "<table><tr> <th>id</th> <th>protocol</th> <th>type</th> <th>ip</th> <th>ping</th> <th>buff</th>"
yield "<th>idle</th> <th>open</th> <th>delay</th> <th>sent</th> <th>received</th> <th>last sent</th> <th>waiting</th> <th>version</th> <th>peerid</th> </tr>"
for connection in main.file_server.connections:
yield self.formatTableRow([
("%3d", connection.id),
("%s", connection.protocol),
("%s", connection.type),
("%s", connection.ip),
("%6.3f", connection.last_ping_delay),
("%s", connection.incomplete_buff_recv),
("since", max(connection.last_send_time, connection.last_recv_time)),
("since", connection.start_time),
("%.3f", connection.last_sent_time-connection.last_send_time),
("%.0fkB", connection.bytes_sent/1024),
("%.0fkB", connection.bytes_recv/1024),
("%s", connection.last_cmd),
("%s", connection.waiting_requests.keys()),
("%s", connection.handshake.get("version")),
("%s", connection.handshake.get("peer_id")),
])
yield "</table>"
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 " - %.3fkb: %s<br>" % (self.getObjSize(obj, hpy), cgi.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 " - %.3fkb: %s<br>" % (self.getObjSize(obj, hpy), cgi.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 " - %.3fkb: %s<br>" % (self.getObjSize(obj, hpy), cgi.escape(repr(obj)))
from 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 " - %.3fkb: %s<br>" % (self.getObjSize(obj, hpy), cgi.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 " - %.3fkb: %s<br>" % (self.getObjSize(obj, hpy), cgi.escape(repr(obj.name)))
objs = [obj for obj in gc.get_objects() if isinstance(obj, UiRequest)]
yield "<br>UiRequest (%s):<br>" % len(objs)
for obj in objs:
yield " - %.3fkb: %s<br>" % (self.getObjSize(obj, hpy), cgi.escape(repr(obj)))
objs = [(key, val) for key, val in sys.modules.iteritems() 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, cgi.escape(repr(module)))
yield "Done in %.3f" % (time.time()-s)

View File

@ -0,0 +1 @@
import StatsPlugin

View File

@ -0,0 +1,24 @@
import re
from Plugin import PluginManager
# Warning: If you modify the donation address then renmae the plugin's directory to "MyDonationMessage" to prevent the update script overwrite
@PluginManager.registerTo("UiRequest")
class UiRequestPlugin(object):
# Inject a donation message to every page top right corner
def actionWrapper(self, path):
back = super(UiRequestPlugin, self).actionWrapper(path)
if not back or not hasattr(back, "endswith"): return back # Wrapper error or not string returned, injection not possible
back = re.sub("</body>\s*</html>\s*$",
"""
<style>
#donation_message { position: absolute; bottom: 0px; right: 20px; padding: 7px; font-family: Arial; font-size: 11px }
</style>
<a id='donation_message' href='https://blockchain.info/address/1QDhxQ6PraUZa21ET5fYUCPgdrwBomnFgX' target='_blank'>Please donate to help to keep this ZeroProxy alive</a>
</body>
</html>
""", back)
return back

View File

@ -0,0 +1 @@
import DonationMessagePlugin

View File

@ -0,0 +1,169 @@
import re, time, sys
from Plugin import PluginManager
from Crypt import CryptBitcoin
@PluginManager.registerTo("UiRequest")
class UiRequestPlugin(object):
def __init__(self, server = None):
self.user_manager = sys.modules["User.UserManager"].user_manager
super(UiRequestPlugin, self).__init__(server)
# Create new user and inject user welcome message if necessary
# Return: Html body also containing the injection
def actionWrapper(self, path):
user_created = False
user = self.getCurrentUser() # Get user from cookie
if not user: # No user found by cookie
user = self.user_manager.create()
user_created = True
master_address = user.master_address
master_seed = user.master_seed
if user_created:
extra_headers = [('Set-Cookie', "master_address=%s;path=/;max-age=2592000;" % user.master_address)] # Max age = 30 days
else:
extra_headers = []
loggedin = self.get.get("login") == "done"
back = super(UiRequestPlugin, self).actionWrapper(path, extra_headers) # Get the wrapper frame output
if not user_created and not loggedin: return back # No injection necessary
if not back or not hasattr(back, "endswith"): return back # Wrapper error or not string returned, injection not possible
if user_created:
# Inject the welcome message
inject_html = """
<!-- Multiser plugin -->
<style>
.masterseed { font-size: 95%; background-color: #FFF0AD; padding: 5px 8px; margin: 9px 0px }
</style>
<script>
hello_message = "<b>Hello, welcome to ZeroProxy!</b><div style='margin-top: 8px'>A new, unique account created for you:</div>"
hello_message+= "<div class='masterseed'>{master_seed}</div> <div>This is your private key, <b>save it</b>, so you can login next time.</div><br>"
hello_message+= "<a href='#' class='button' style='margin-left: 0px'>Ok, Saved it!</a> or <a href='#Login' onclick='wrapper.ws.cmd(\\"userLoginForm\\", []); return false'>Login</a><br><br>"
hello_message+= "<small>This site is allows you to browse ZeroNet content, but if you want to secure your account <br>"
hello_message+= "and help to make a better network, then please run your own <a href='https://github.com/HelloZeroNet/ZeroNet' target='_blank'>ZeroNet client</a>.</small>"
setTimeout(function() {
wrapper.notifications.add("hello", "info", hello_message)
delete(hello_message)
}, 1000)
</script>
</body>
</html>
""".replace("\t", "")
inject_html = inject_html.replace("{master_seed}", master_seed) # Set the master seed in the message
back = re.sub("</body>\s*</html>\s*$", inject_html, back) # Replace the </body></html> tags with the injection
elif loggedin:
inject_html = """
<!-- Multiser plugin -->
<script>
setTimeout(function() {
wrapper.notifications.add("login", "done", "Hello again!<br><small>You have been logged in successfully</small>", 5000)
}, 1000)
</script>
</body>
</html>
""".replace("\t", "")
back = re.sub("</body>\s*</html>\s*$", inject_html, back) # Replace the </body></html> tags with the injection
return back
# Get the current user based on request's cookies
# Return: User object or None if no match
def getCurrentUser(self):
cookies = self.getCookies()
user_manager = self.user_manager
user = None
if "master_address" in cookies:
users = self.user_manager.list()
user = users.get(cookies["master_address"])
return user
@PluginManager.registerTo("UserManager")
class UserManagerPlugin(object):
# In multiuser mode do not load the users
def load(self):
if not self.users: self.users = {}
return self.users
# Find user by master address
# Return: User or None
def get(self, master_address=None):
users = self.list()
if master_address in users:
user = users[master_address]
else:
user = None
return user
@PluginManager.registerTo("User")
class UserPlugin(object):
# In multiuser mode users data only exits in memory, dont write to data/user.json
def save(self):
return False
@PluginManager.registerTo("UiWebsocket")
class UiWebsocketPlugin(object):
# Let the page know we running in multiuser mode
def formatServerInfo(self):
server_info = super(UiWebsocketPlugin, self).formatServerInfo()
server_info["multiuser"] = True
if "ADMIN" in self.site.settings["permissions"]:
server_info["master_address"] = self.user.master_address
return server_info
# Show current user's master seed
def actionUserShowMasterSeed(self, to):
if not "ADMIN" in self.site.settings["permissions"]: return self.response(to, "Show master seed not allowed")
message = "<b style='padding-top: 5px; display: inline-block'>Your unique private key:</b>"
message+= "<div style='font-size: 84%%; background-color: #FFF0AD; padding: 5px 8px; margin: 9px 0px'>%s</div>" % self.user.master_seed
message+= "<small>(Save it, you can access your account using this information)</small>"
self.cmd("notification", ["info", message])
# Logout user
def actionUserLogout(self, to):
if not "ADMIN" in self.site.settings["permissions"]: return self.response(to, "Logout not allowed")
message = "<b>You have been logged out.</b> <a href='#Login' class='button' onclick='wrapper.ws.cmd(\"userLoginForm\", []); return false'>Login to another account</a>"
message+= "<script>document.cookie = 'master_address=; expires=Thu, 01 Jan 1970 00:00:00 UTC'</script>"
self.cmd("notification", ["done", message, 1000000]) # 1000000 = Show ~forever :)
# Delete from user_manager
user_manager = sys.modules["User.UserManager"].user_manager
if self.user.master_address in user_manager.users:
del user_manager.users[self.user.master_address]
self.response(to, "Successful logout")
else:
self.response(to, "User not found")
# Show login form
def actionUserLoginForm(self, to):
self.cmd("prompt", ["<b>Login</b><br>Your private key:", "password", "Login"], self.responseUserLogin)
# Login form submit
def responseUserLogin(self, master_seed):
user_manager = sys.modules["User.UserManager"].user_manager
user = user_manager.create(master_seed=master_seed)
if user.master_address:
message = "Successfull login, reloading page..."
message+= "<script>document.cookie = 'master_address=%s;path=/;max-age=2592000;'</script>" % user.master_address
message+= "<script>wrapper.reload('login=done')</script>"
self.cmd("notification", ["done", message])
else:
self.cmd("notification", ["error", "Error: Invalid master seed"])
self.actionUserLoginForm(0)

View File

@ -0,0 +1 @@
import MultiuserPlugin

View File

@ -3,7 +3,7 @@ import ConfigParser
class Config(object):
def __init__(self):
self.version = "0.2.6"
self.version = "0.2.7"
self.parser = self.createArguments()
argv = sys.argv[:] # Copy command line arguments
argv = self.parseConfig(argv) # Add arguments from config file

View File

@ -1,5 +1,5 @@
from src.lib.BitcoinECC import BitcoinECC
from src.lib.pybitcointools import bitcoin as btctools
from lib.BitcoinECC import BitcoinECC
from lib.pybitcointools import bitcoin as btctools
def newPrivatekey(uncompressed=True): # Return new private key

View File

@ -6,6 +6,7 @@ if config.debug: # Only load pyfilesytem if using debug mode
try:
from fs.osfs import OSFS
pyfilesystem = OSFS("src")
pyfilesystem_plugins = OSFS("plugins")
except Exception, err:
logging.debug("%s: For autoreload please download pyfilesystem (https://code.google.com/p/pyfilesystem/)" % err)
pyfilesystem = False
@ -28,6 +29,7 @@ class DebugReloader:
try:
time.sleep(1) # Wait for .pyc compiles
pyfilesystem.add_watcher(self.changed, path=self.directory, events=None, recursive=recursive)
pyfilesystem_plugins.add_watcher(self.changed, path=self.directory, events=None, recursive=recursive)
except Exception, err:
print "File system watcher failed: %s (on linux pyinotify not gevent compatible yet :( )" % err

View File

@ -1,4 +1,4 @@
import os, logging, urllib2, urllib, re, time
import os, logging, urllib2, re, time
import gevent, msgpack
import zmq.green as zmq
from Config import config

View File

@ -12,7 +12,7 @@ class Peer:
self.site = site
self.key = "%s:%s" % (ip, port)
self.log = None
self.connection_server = sys.modules["src.main"].file_server
self.connection_server = sys.modules["main"].file_server
self.connection = None
self.last_found = None # Time of last found in the torrent tracker

View File

@ -0,0 +1,97 @@
import logging, os, sys
from Debug import Debug
from Config import config
class PluginManager:
def __init__(self):
self.log = logging.getLogger("PluginManager")
self.plugin_path = "plugins" # Plugin directory
self.plugins = {} # Registered plugins (key: class name, value: list of plugins for class)
sys.path.append(self.plugin_path)
if config.debug: # Auto reload Plugins on file change
from Debug import DebugReloader
DebugReloader(self.reloadPlugins)
# -- Load / Unload --
# Load all plugin
def loadPlugins(self):
for dir_name in os.listdir(self.plugin_path):
dir_path = os.path.join(self.plugin_path, dir_name)
if dir_name.startswith("disabled"): continue # Dont load if disabled
if not os.path.isdir(dir_path): continue # Dont load if not dir
if dir_name.startswith("Debug") and not config.debug: continue # Only load in debug mode if module name starts with Debug
self.log.debug("Loading plugin: %s" % dir_name)
try:
__import__(dir_name)
except Exception, err:
self.log.error("Plugin %s load error: %s" % (dir_name, Debug.formatException(err)))
# Reload all plugins
def reloadPlugins(self):
self.plugins = {} # Reset registered plugins
for module_name, module in sys.modules.items():
if module and "__file__" in dir(module) and self.plugin_path in module.__file__: # Module file within plugin_path
if "allow_reload" not in dir(module) or module.allow_reload: # Check if reload disabled
try:
reload(module)
except Exception, err:
self.log.error("Plugin %s reload error: %s" % (module_name, Debug.formatException(err)))
self.loadPlugins() # Load new plugins
plugin_manager = PluginManager() # Singletone
# -- Decorators --
# Accept plugin to class decorator
def acceptPlugins(base_class):
class_name = base_class.__name__
if class_name in plugin_manager.plugins: # Has plugins
classes = plugin_manager.plugins[class_name][:] # Copy the current plugins
classes.reverse()
classes.append(base_class) # Add the class itself to end of inherience line
PluginedClass = type(class_name, tuple(classes), dict()) # Create the plugined class
plugin_manager.log.debug("New class accepts plugins: %s (Loaded plugins: %s)" % (class_name, classes))
else: # No plugins just use the original
PluginedClass = base_class
return PluginedClass
# Register plugin to class name decorator
def registerTo(class_name):
plugin_manager.log.debug("New plugin registered to: %s" % class_name)
if class_name not in plugin_manager.plugins: plugin_manager.plugins[class_name] = []
def classDecorator(self):
plugin_manager.plugins[class_name].append(self)
return self
return classDecorator
# - Example usage -
if __name__ == "__main__":
@registerTo("Request")
class RequestPlugin(object):
def actionMainPage(self, path):
return "Hello MainPage!"
@accept
class Request(object):
def route(self, path):
func = getattr(self, "action"+path, None)
if func:
return func(path)
else:
return "Can't route to", path
print Request().route("MainPage")

0
src/Plugin/__init__.py Normal file
View File

View File

@ -2,6 +2,7 @@ import time, re, os, mimetypes, json, cgi
from Config import config
from Site import SiteManager
from User import UserManager
from Plugin import PluginManager
from Ui.UiWebsocket import UiWebsocket
status_texts = {
@ -12,15 +13,15 @@ status_texts = {
}
class UiRequest:
@PluginManager.acceptPlugins
class UiRequest(object):
def __init__(self, server = None):
if server:
self.server = server
self.log = server.log
self.get = {} # Get parameters
self.env = {} # Enviroment settings
self.user = UserManager.getCurrent()
self.user = None
self.start_response = None # Start response function
@ -46,16 +47,17 @@ class UiRequest:
return self.actionDebug()
elif path == "/Console" and config.debug:
return self.actionConsole()
elif path == "/Stats":
return self.actionStats()
# Test
elif path == "/Test/Websocket":
return self.actionFile("Data/temp/ws_test.html")
elif path == "/Test/Stream":
return self.actionTestStream()
# Site media wrapper
else:
return self.actionWrapper(path)
body = self.actionWrapper(path)
if body:
return body
else:
func = getattr(self, "action"+path.lstrip("/"), None) # Check if we have action+request_path function
if func:
return func()
else:
return self.error404(path)
# Get mime by filename
@ -69,6 +71,24 @@ class UiRequest:
return content_type
# Returns: <dict> Cookies based on self.env
def getCookies(self):
raw_cookies = self.env.get('HTTP_COOKIE')
if raw_cookies:
cookies = cgi.parse_qsl(raw_cookies)
return {key.strip(): val for key, val in cookies}
else:
return {}
def getCurrentUser(self):
if self.user: return self.user # Cache
self.user = UserManager.user_manager.get() # Get user
if not self.user:
self.user = UserManager.user_manager.create()
return self.user
# Send response headers
def sendHeader(self, status=200, content_type="text/html", extra_headers=[]):
if content_type == "text/html": content_type = "text/html; charset=utf-8"
@ -89,7 +109,7 @@ class UiRequest:
#template = SimpleTemplate(open(template_path), lookup=[os.path.dirname(template_path)])
#yield str(template.render(*args, **kwargs).encode("utf8"))
template = open(template_path).read().decode("utf8")
yield template.format(**kwargs).encode("utf8")
return template.format(**kwargs).encode("utf8")
# - Actions -
@ -105,7 +125,7 @@ class UiRequest:
# Render a file from media with iframe site wrapper
def actionWrapper(self, path):
def actionWrapper(self, path, extra_headers=[]):
if "." in path and not path.endswith(".html"): return self.actionSiteMedia("/media"+path) # Only serve html files with frame
if self.get.get("wrapper") == "False": return self.actionSiteMedia("/media"+path) # Only serve html files with frame
if self.env.get("HTTP_X_REQUESTED_WITH"): return self.error403() # No ajax allowed on wrapper
@ -121,9 +141,11 @@ class UiRequest:
else:
title = "Loading %s..." % match.group("site")
site = SiteManager.need(match.group("site")) # Start download site
if not site: return self.error404(path)
if not site: return False
self.sendHeader(extra_headers=[("X-Frame-Options", "DENY")])
extra_headers.append(("X-Frame-Options", "DENY"))
self.sendHeader(extra_headers=extra_headers)
# Wrapper variable inits
query_string = ""
@ -152,7 +174,7 @@ class UiRequest:
)
else: # Bad url
return self.error404(path)
return False
# Serve a media for site
@ -241,7 +263,11 @@ class UiRequest:
if site_check.settings["wrapper_key"] == wrapper_key: site = site_check
if site: # Correct wrapper key
ui_websocket = UiWebsocket(ws, site, self.server, self.user)
user = self.getCurrentUser()
if not user:
self.log.error("No user found")
return self.error403()
ui_websocket = UiWebsocket(ws, site, self.server, user)
site.websockets.append(ui_websocket) # Add to site websockets to allow notify on events
ui_websocket.start()
for site_check in self.server.sites.values(): # Remove websocket from every site (admin sites allowed to join other sites event channels)
@ -260,7 +286,7 @@ class UiRequest:
def actionDebug(self):
# Raise last error from DebugHook
import sys
last_error = sys.modules["src.main"].DebugHook.last_error
last_error = sys.modules["main"].DebugHook.last_error
if last_error:
raise last_error[0], last_error[1], last_error[2]
else:
@ -272,134 +298,10 @@ class UiRequest:
def actionConsole(self):
import sys
sites = self.server.sites
main = sys.modules["src.main"]
main = sys.modules["main"]
raise Exception("Here is your console")
def formatTableRow(self, row):
back = []
for format, val in row:
if val == 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>%s</tr>" % "".join(back)
def getObjSize(self, obj, hpy = None):
if hpy:
return float(hpy.iso(obj).domisize)/1024
else:
return 0
def actionStats(self):
import gc, sys
hpy = None
if self.get.get("size") == "1": # Calc obj size
try:
import guppy
hpy = guppy.hpy()
except:
pass
self.sendHeader()
s = time.time()
main = sys.modules["src.main"]
# Style
yield """
<style>
* { font-family: monospace }
table * { text-align: right; padding: 0px 10px }
</style>
"""
# Memory
try:
import psutil
process = psutil.Process(os.getpid())
mem = process.get_memory_info()[0] / float(2 ** 20)
yield "Memory usage: %.2fMB | " % mem
yield "Threads: %s | " % len(process.threads())
yield "CPU: usr %.2fs sys %.2fs | " % process.cpu_times()
yield "Open 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><br>"
except Exception, err:
pass
yield "Connections (%s):<br>" % len(main.file_server.connections)
yield "<table><tr> <th>id</th> <th>protocol</th> <th>type</th> <th>ip</th> <th>ping</th> <th>buff</th>"
yield "<th>idle</th> <th>open</th> <th>delay</th> <th>sent</th> <th>received</th> <th>last sent</th> <th>waiting</th> <th>version</th> <th>peerid</th> </tr>"
for connection in main.file_server.connections:
yield self.formatTableRow([
("%3d", connection.id),
("%s", connection.protocol),
("%s", connection.type),
("%s", connection.ip),
("%6.3f", connection.last_ping_delay),
("%s", connection.incomplete_buff_recv),
("since", max(connection.last_send_time, connection.last_recv_time)),
("since", connection.start_time),
("%.3f", connection.last_sent_time-connection.last_send_time),
("%.0fkB", connection.bytes_sent/1024),
("%.0fkB", connection.bytes_recv/1024),
("%s", connection.last_cmd),
("%s", connection.waiting_requests.keys()),
("%s", connection.handshake.get("version")),
("%s", connection.handshake.get("peer_id")),
])
yield "</table>"
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 " - %.3fkb: %s<br>" % (self.getObjSize(obj, hpy), cgi.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 " - %.3fkb: %s<br>" % (self.getObjSize(obj, hpy), cgi.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 " - %.3fkb: %s<br>" % (self.getObjSize(obj, hpy), cgi.escape(repr(obj)))
from 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 " - %.3fkb: %s<br>" % (self.getObjSize(obj, hpy), cgi.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 " - %.3fkb: %s<br>" % (self.getObjSize(obj, hpy), cgi.escape(repr(obj.name)))
objs = [obj for obj in gc.get_objects() if isinstance(obj, UiRequest)]
yield "<br>UiRequest (%s):<br>" % len(objs)
for obj in objs:
yield " - %.3fkb: %s<br>" % (self.getObjSize(obj, hpy), cgi.escape(repr(obj)))
yield "Done in %.3f" % (time.time()-s)
# - Tests -
def actionTestStream(self):
@ -433,8 +335,9 @@ class UiRequest:
# - Reload for eaiser developing -
def reload(self):
import imp
import imp, sys
global UiWebsocket
UiWebsocket = imp.load_source("UiWebsocket", "src/Ui/UiWebsocket.py").UiWebsocket
UserManager.reload()
self.user = UserManager.getCurrent()
#reload(sys.modules["User.UserManager"])
#UserManager.reloadModule()
#self.user = UserManager.user_manager.getCurrent()

View File

@ -3,11 +3,12 @@ import logging, time, cgi, string, random
from gevent.pywsgi import WSGIServer
from gevent.pywsgi import WSGIHandler
from lib.geventwebsocket.handler import WebSocketHandler
from Ui import UiRequest
from UiRequest import UiRequest
from Site import SiteManager
from Config import config
from Debug import Debug
# Skip websocket handler if not necessary
class UiWSGIHandler(WSGIHandler):
def __init__(self, *args, **kwargs):
@ -28,7 +29,10 @@ class UiWSGIHandler(WSGIHandler):
try:
return super(UiWSGIHandler, self).run_application()
except Exception, err:
logging.debug("UiWSGIHandler error: %s" % err)
logging.debug("UiWSGIHandler error: %s" % Debug.formatException(err))
if config.debug: # Allow websocket errors to appear on /Debug
import sys
sys.modules["main"].DebugHook.handleError()
del self.server.sockets[self.client_address]
@ -59,7 +63,8 @@ class UiServer:
# Reload the UiRequest class to prevent restarts in debug mode
def reload(self):
import imp
import imp, sys
reload(sys.modules["User.UserManager"])
self.ui_request = imp.load_source("UiRequest", "src/Ui/UiRequest.py").UiRequest(self)
self.ui_request.reload()

View File

@ -3,9 +3,10 @@ from Config import config
from Site import SiteManager
from Debug import Debug
from util import QueryJson
from Plugin import PluginManager
class UiWebsocket:
@PluginManager.acceptPlugins
class UiWebsocket(object):
def __init__(self, ws, site, server, user):
self.ws = ws
self.site = site
@ -41,7 +42,7 @@ class UiWebsocket:
if err.message != 'Connection is already closed':
if config.debug: # Allow websocket errors to appear on /Debug
import sys
sys.modules["src.main"].DebugHook.handleError()
sys.modules["main"].DebugHook.handleError()
self.log.error("WebSocket error: %s" % Debug.formatException(err))
return "Bye."
@ -133,10 +134,12 @@ class UiWebsocket:
func = self.actionChannelJoinAllsite
elif cmd == "serverUpdate" and "ADMIN" in permissions:
func = self.actionServerUpdate
# Unknown command
else:
self.response(req["id"], "Unknown command: %s" % cmd)
return
func_name = "action" + cmd[0].upper() + cmd[1:]
func = getattr(self, func_name, None)
if not func: # Unknown command
self.response(req["id"], "Unknown command: %s" % cmd)
return
# Support calling as named, unnamed paramters and raw first argument too
if type(params) is dict:
@ -152,7 +155,7 @@ class UiWebsocket:
# Do callback on response {"cmd": "response", "to": message_id, "result": result}
def actionResponse(self, to, result):
if to in self.waiting_cb:
self.waiting_cb(result) # Call callback function
self.waiting_cb[to](result) # Call callback function
else:
self.log.error("Websocket callback not found: %s, %s" % (to, result))
@ -163,7 +166,7 @@ class UiWebsocket:
# Format site info
def formatSiteInfo(self, site):
def formatSiteInfo(self, site, create_user=True):
content = site.content_manager.contents.get("content.json")
if content: # Remove unnecessary data transfer
content = content.copy()
@ -179,7 +182,7 @@ class UiWebsocket:
ret = {
"auth_key": self.site.settings["auth_key"], # Obsolete, will be removed
"auth_key_sha512": hashlib.sha512(self.site.settings["auth_key"]).hexdigest()[0:64], # Obsolete, will be removed
"auth_address": self.user.getAuthAddress(site.address),
"auth_address": self.user.getAuthAddress(site.address, create=create_user),
"address": site.address,
"settings": settings,
"content_updated": site.content_updated,
@ -208,9 +211,8 @@ class UiWebsocket:
self.channels.append(channel)
# Server variables
def actionServerInfo(self, to):
ret = {
def formatServerInfo(self):
return {
"ip_external": bool(config.ip_external),
"platform": sys.platform,
"fileserver_ip": config.fileserver_ip,
@ -220,6 +222,11 @@ class UiWebsocket:
"version": config.version,
"debug": config.debug
}
# Server variables
def actionServerInfo(self, to):
ret = self.formatServerInfo()
self.response(to, ret)
@ -323,7 +330,7 @@ class UiWebsocket:
SiteManager.load() # Reload sites
for site in self.server.sites.values():
if not site.content_manager.contents.get("content.json"): continue # Broken site
ret.append(self.formatSiteInfo(site))
ret.append(self.formatSiteInfo(site, create_user=False))
self.response(to, ret)
@ -395,7 +402,7 @@ class UiWebsocket:
def actionServerUpdate(self, to):
import sys
self.cmd("updating")
sys.modules["src.main"].update_after_shutdown = True
sys.modules["src.main"].file_server.stop()
sys.modules["src.main"].ui_server.stop()
sys.modules["main"].update_after_shutdown = True
sys.modules["main"].file_server.stop()
sys.modules["main"].ui_server.stop()

View File

@ -33,7 +33,7 @@ class Notifications
$(".notification-icon", elem).html("i")
if typeof(body) == "string"
$(".body", elem).html(body)
$(".body", elem).html("<span class='message'>"+body+"</span>")
else
$(".body", elem).html("").append(body)
@ -49,9 +49,11 @@ class Notifications
# Animate
width = elem.outerWidth()
if not timeout then width += 20 # Add space for close button
if elem.outerHeight() > 55 then elem.addClass("long")
elem.css({"width": "50px", "transform": "scale(0.01)"})
elem.animate({"scale": 1}, 800, "easeOutElastic")
elem.animate({"width": width}, 700, "easeInOutCubic")
$(".body", elem).cssLater("box-shadow", "0px 0px 5px rgba(0,0,0,0.1)", 1000)
# Close button
$(".close", elem).on "click", =>

View File

@ -42,6 +42,9 @@ class Wrapper
@sendInner message # Pass message to inner frame
else if cmd == "notification" # Display notification
@notifications.add("notification-#{message.id}", message.params[0], message.params[1], message.params[2])
else if cmd == "prompt" # Prompt input
@displayPrompt message.params[0], message.params[1], message.params[2], (res) =>
@ws.response message.id, res
else if cmd == "setSiteInfo"
@sendInner message # Pass to inner frame
if message.params.address == window.address # Current page
@ -63,18 +66,19 @@ class Wrapper
@sendInner {"cmd": "wrapperOpenedWebsocket"}
@wrapperWsInited = true
else if cmd == "wrapperNotification" # Display notification
message.params = @toHtmlSafe(message.params) # Escape html
@notifications.add("notification-#{message.id}", message.params[0], message.params[1], message.params[2])
@actionNotification(message)
else if cmd == "wrapperConfirm" # Display confirm message
@actionWrapperConfirm(message)
@actionConfirm(message)
else if cmd == "wrapperPrompt" # Prompt input
@actionWrapperPrompt(message)
@actionPrompt(message)
else if cmd == "wrapperSetViewport" # Set the viewport
@actionSetViewport(message)
else if cmd == "wrapperReload" # Reload current page
@actionReload(message)
else if cmd == "wrapperGetLocalStorage"
@actionGetLocalStorage(message)
else if cmd == "wrapperSetLocalStorage"
@actionSetLocalStorage(message)
@actionSetLocalStorage(message)
else # Send to websocket
if message.id < 1000000
@ws.send(message) # Pass message to websocket
@ -84,46 +88,58 @@ class Wrapper
# - Actions -
actionWrapperConfirm: (message, cb=false) ->
actionNotification: (message) ->
message.params = @toHtmlSafe(message.params) # Escape html
if message.params[1] then caption = message.params[1] else caption = "ok"
@wrapperConfirm message.params[0], caption, =>
@sendInner {"cmd": "response", "to": message.id, "result": "boom"} # Response to confirm
return false
body = $("<span class='message'>"+message.params[1]+"</span>")
@notifications.add("notification-#{message.id}", message.params[0], body, message.params[2])
wrapperConfirm: (message, caption, cb) ->
body = $("<span>"+message+"</span>")
displayConfirm: (message, caption, cb) ->
body = $("<span class='message'>"+message+"</span>")
button = $("<a href='##{caption}' class='button button-#{caption}'>#{caption}</a>") # Add confirm button
button.on "click", cb
body.append(button)
@notifications.add("notification-#{caption}", "ask", body)
actionWrapperPrompt: (message) ->
actionConfirm: (message, cb=false) ->
message.params = @toHtmlSafe(message.params) # Escape html
if message.params[1] then type = message.params[1] else type = "text"
caption = "OK"
if message.params[1] then caption = message.params[1] else caption = "ok"
@displayConfirm message.params[0], caption, =>
@sendInner {"cmd": "response", "to": message.id, "result": "boom"} # Response to confirm
return false
body = $("<span>"+message.params[0]+"</span>")
displayPrompt: (message, type, caption, cb) ->
body = $("<span class='message'>"+message+"</span>")
input = $("<input type='#{type}' class='input button-#{type}'/>") # Add input
input.on "keyup", (e) => # Send on enter
if e.keyCode == 13
button.trigger "click" # Response to confirm
body.append(input)
button = $("<a href='##{caption}' class='button button-#{caption}'>#{caption}</a>") # Add confirm button
button.on "click", => # Response on button click
@sendInner {"cmd": "response", "to": message.id, "result": input.val()} # Response to confirm
cb input.val()
return false
body.append(button)
@notifications.add("notification-#{message.id}", "ask", body)
actionPrompt: (message) ->
message.params = @toHtmlSafe(message.params) # Escape html
if message.params[1] then type = message.params[1] else type = "text"
caption = "OK"
@displayPrompt message.params[0], type, caption, (res) =>
@sendInner {"cmd": "response", "to": message.id, "result": res} # Response to confirm
actionSetViewport: (message) ->
@log "actionSetViewport", message
if $("#viewport").length > 0
@ -132,6 +148,16 @@ class Wrapper
$('<meta name="viewport" id="viewport">').attr("content", @toHtmlSafe message.params).appendTo("head")
reload: (url_post="") ->
if url_post
if window.location.toString().indexOf("?") > 0
window.location += "&"+url_post
else
window.location += "?"+url_post
else
window.location.reload()
actionGetLocalStorage: (message) ->
data = localStorage.getItem "site.#{window.address}"
if data then data = JSON.parse(data)

View File

@ -33,21 +33,28 @@ a { color: black }
/* Notification */
.notifications { position: absolute; top: 0px; right: 85px; display: inline-block; z-index: 999; white-space: nowrap }
.notifications { position: absolute; top: 0px; right: 80px; display: inline-block; z-index: 999; white-space: nowrap }
.notification {
position: relative; float: right; clear: both; margin: 10px; height: 50px; box-sizing: border-box; overflow: hidden; backface-visibility: hidden; perspective: 1000px;
background-color: white; color: #4F4F4F; font-family: 'Helvetica Neue', 'Segoe UI', Helvetica, Arial, sans-serif; font-size: 14px; line-height: 20px; /*border: 1px solid rgba(210, 206, 205, 0.2)*/
position: relative; float: right; clear: both; margin: 10px; box-sizing: border-box; overflow: hidden; backface-visibility: hidden; perspective: 1000px; padding-bottom: 5px;
color: #4F4F4F; font-family: 'Helvetica Neue', 'Segoe UI', Helvetica, Arial, sans-serif; font-size: 14px; line-height: 20px; /*border: 1px solid rgba(210, 206, 205, 0.2)*/
}
.notification-icon {
display: block; width: 50px; height: 50px; position: absolute; float: left; z-index: 1;
text-align: center; background-color: #e74c3c; line-height: 45px; vertical-align: bottom; font-size: 40px; color: white;
}
.notification .body { max-width: 420px; padding-left: 68px; padding-right: 17px; height: 50px; vertical-align: middle; display: table-cell }
.notification .body {
max-width: 420px; padding-left: 14px; padding-right: 60px; height: 40px; vertical-align: middle; display: table;
background-color: white; left: 50px; top: 0px; position: relative; padding-top: 5px; padding-bottom: 5px;
}
.notification.long .body { padding-top: 10px; padding-bottom: 10px }
.notification .message { display: table-cell; vertical-align: middle }
.notification.visible { max-width: 350px }
.notification .close { position: absolute; top: 0px; right: 0px; font-size: 19px; line-height: 13px; color: #DDD; padding: 7px; text-decoration: none }
.notification .close:hover { color: black }
.notification .close:active, .notification .close:focus { color: #AF3BFF }
.notification small { color: #AAA }
.body-white .notification { box-shadow: 0px 1px 9px rgba(0,0,0,0.1) }
/* Notification types */

View File

@ -38,21 +38,28 @@ a { color: black }
/* Notification */
.notifications { position: absolute; top: 0px; right: 85px; display: inline-block; z-index: 999; white-space: nowrap }
.notifications { position: absolute; top: 0px; right: 80px; display: inline-block; z-index: 999; white-space: nowrap }
.notification {
position: relative; float: right; clear: both; margin: 10px; height: 50px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; -o-box-sizing: border-box; -ms-box-sizing: border-box; box-sizing: border-box ; overflow: hidden; backface-visibility: hidden; -webkit-perspective: 1000px; -moz-perspective: 1000px; -o-perspective: 1000px; -ms-perspective: 1000px; perspective: 1000px ;
background-color: white; color: #4F4F4F; font-family: 'Helvetica Neue', 'Segoe UI', Helvetica, Arial, sans-serif; font-size: 14px; line-height: 20px; /*border: 1px solid rgba(210, 206, 205, 0.2)*/
position: relative; float: right; clear: both; margin: 10px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; -o-box-sizing: border-box; -ms-box-sizing: border-box; box-sizing: border-box ; overflow: hidden; backface-visibility: hidden; -webkit-perspective: 1000px; -moz-perspective: 1000px; -o-perspective: 1000px; -ms-perspective: 1000px; perspective: 1000px ; padding-bottom: 5px;
color: #4F4F4F; font-family: 'Helvetica Neue', 'Segoe UI', Helvetica, Arial, sans-serif; font-size: 14px; line-height: 20px; /*border: 1px solid rgba(210, 206, 205, 0.2)*/
}
.notification-icon {
display: block; width: 50px; height: 50px; position: absolute; float: left; z-index: 1;
text-align: center; background-color: #e74c3c; line-height: 45px; vertical-align: bottom; font-size: 40px; color: white;
}
.notification .body { max-width: 420px; padding-left: 68px; padding-right: 17px; height: 50px; vertical-align: middle; display: table-cell }
.notification .body {
max-width: 420px; padding-left: 14px; padding-right: 60px; height: 40px; vertical-align: middle; display: table;
background-color: white; left: 50px; top: 0px; position: relative; padding-top: 5px; padding-bottom: 5px;
}
.notification.long .body { padding-top: 10px; padding-bottom: 10px }
.notification .message { display: table-cell; vertical-align: middle }
.notification.visible { max-width: 350px }
.notification .close { position: absolute; top: 0px; right: 0px; font-size: 19px; line-height: 13px; color: #DDD; padding: 7px; text-decoration: none }
.notification .close:hover { color: black }
.notification .close:active, .notification .close:focus { color: #AF3BFF }
.notification small { color: #AAA }
.body-white .notification { -webkit-box-shadow: 0px 1px 9px rgba(0,0,0,0.1) ; -moz-box-shadow: 0px 1px 9px rgba(0,0,0,0.1) ; -o-box-shadow: 0px 1px 9px rgba(0,0,0,0.1) ; -ms-box-shadow: 0px 1px 9px rgba(0,0,0,0.1) ; box-shadow: 0px 1px 9px rgba(0,0,0,0.1) }
/* Notification types */

View File

@ -149,7 +149,6 @@
}).call(this);
/* ---- src/Ui/media/lib/jquery.cssanim.js ---- */
@ -247,7 +246,6 @@ jQuery.fx.step.scale = function(fx) {
}).call(this);
/* ---- src/Ui/media/lib/jquery.easing.1.3.js ---- */
@ -542,7 +540,6 @@ jQuery.extend( jQuery.easing,
}).call(this);
/* ---- src/Ui/media/Notifications.coffee ---- */
@ -593,7 +590,7 @@ jQuery.extend( jQuery.easing,
$(".notification-icon", elem).html("i");
}
if (typeof body === "string") {
$(".body", elem).html(body);
$(".body", elem).html("<span class='message'>" + body + "</span>");
} else {
$(".body", elem).html("").append(body);
}
@ -610,6 +607,9 @@ jQuery.extend( jQuery.easing,
if (!timeout) {
width += 20;
}
if (elem.outerHeight() > 55) {
elem.addClass("long");
}
elem.css({
"width": "50px",
"transform": "scale(0.01)"
@ -620,6 +620,7 @@ jQuery.extend( jQuery.easing,
elem.animate({
"width": width
}, 700, "easeInOutCubic");
$(".body", elem).cssLater("box-shadow", "0px 0px 5px rgba(0,0,0,0.1)", 1000);
$(".close", elem).on("click", (function(_this) {
return function() {
_this.close(elem);
@ -725,7 +726,6 @@ jQuery.extend( jQuery.easing,
}).call(this);
/* ---- src/Ui/media/Wrapper.coffee ---- */
@ -786,6 +786,12 @@ jQuery.extend( jQuery.easing,
}
} else if (cmd === "notification") {
return this.notifications.add("notification-" + message.id, message.params[0], message.params[1], message.params[2]);
} else if (cmd === "prompt") {
return this.displayPrompt(message.params[0], message.params[1], message.params[2], (function(_this) {
return function(res) {
return _this.ws.response(message.id, res);
};
})(this));
} else if (cmd === "setSiteInfo") {
this.sendInner(message);
if (message.params.address === window.address) {
@ -812,14 +818,15 @@ jQuery.extend( jQuery.easing,
return this.wrapperWsInited = true;
}
} else if (cmd === "wrapperNotification") {
message.params = this.toHtmlSafe(message.params);
return this.notifications.add("notification-" + message.id, message.params[0], message.params[1], message.params[2]);
return this.actionNotification(message);
} else if (cmd === "wrapperConfirm") {
return this.actionWrapperConfirm(message);
return this.actionConfirm(message);
} else if (cmd === "wrapperPrompt") {
return this.actionWrapperPrompt(message);
return this.actionPrompt(message);
} else if (cmd === "wrapperSetViewport") {
return this.actionSetViewport(message);
} else if (cmd === "wrapperReload") {
return this.actionReload(message);
} else if (cmd === "wrapperGetLocalStorage") {
return this.actionGetLocalStorage(message);
} else if (cmd === "wrapperSetLocalStorage") {
@ -833,7 +840,23 @@ jQuery.extend( jQuery.easing,
}
};
Wrapper.prototype.actionWrapperConfirm = function(message, cb) {
Wrapper.prototype.actionNotification = function(message) {
var body;
message.params = this.toHtmlSafe(message.params);
body = $("<span class='message'>" + message.params[1] + "</span>");
return this.notifications.add("notification-" + message.id, message.params[0], body, message.params[2]);
};
Wrapper.prototype.displayConfirm = function(message, caption, cb) {
var body, button;
body = $("<span class='message'>" + message + "</span>");
button = $("<a href='#" + caption + "' class='button button-" + caption + "'>" + caption + "</a>");
button.on("click", cb);
body.append(button);
return this.notifications.add("notification-" + caption, "ask", body);
};
Wrapper.prototype.actionConfirm = function(message, cb) {
var caption;
if (cb == null) {
cb = false;
@ -844,7 +867,7 @@ jQuery.extend( jQuery.easing,
} else {
caption = "ok";
}
return this.wrapperConfirm(message.params[0], caption, (function(_this) {
return this.displayConfirm(message.params[0], caption, (function(_this) {
return function() {
_this.sendInner({
"cmd": "response",
@ -856,25 +879,9 @@ jQuery.extend( jQuery.easing,
})(this));
};
Wrapper.prototype.wrapperConfirm = function(message, caption, cb) {
var body, button;
body = $("<span>" + message + "</span>");
button = $("<a href='#" + caption + "' class='button button-" + caption + "'>" + caption + "</a>");
button.on("click", cb);
body.append(button);
return this.notifications.add("notification-" + caption, "ask", body);
};
Wrapper.prototype.actionWrapperPrompt = function(message) {
var body, button, caption, input, type;
message.params = this.toHtmlSafe(message.params);
if (message.params[1]) {
type = message.params[1];
} else {
type = "text";
}
caption = "OK";
body = $("<span>" + message.params[0] + "</span>");
Wrapper.prototype.displayPrompt = function(message, type, caption, cb) {
var body, button, input;
body = $("<span class='message'>" + message + "</span>");
input = $("<input type='" + type + "' class='input button-" + type + "'/>");
input.on("keyup", (function(_this) {
return function(e) {
@ -887,11 +894,7 @@ jQuery.extend( jQuery.easing,
button = $("<a href='#" + caption + "' class='button button-" + caption + "'>" + caption + "</a>");
button.on("click", (function(_this) {
return function() {
_this.sendInner({
"cmd": "response",
"to": message.id,
"result": input.val()
});
cb(input.val());
return false;
};
})(this));
@ -899,6 +902,26 @@ jQuery.extend( jQuery.easing,
return this.notifications.add("notification-" + message.id, "ask", body);
};
Wrapper.prototype.actionPrompt = function(message) {
var caption, type;
message.params = this.toHtmlSafe(message.params);
if (message.params[1]) {
type = message.params[1];
} else {
type = "text";
}
caption = "OK";
return this.displayPrompt(message.params[0], type, caption, (function(_this) {
return function(res) {
return _this.sendInner({
"cmd": "response",
"to": message.id,
"result": res
});
};
})(this));
};
Wrapper.prototype.actionSetViewport = function(message) {
this.log("actionSetViewport", message);
if ($("#viewport").length > 0) {
@ -908,6 +931,21 @@ jQuery.extend( jQuery.easing,
}
};
Wrapper.prototype.reload = function(url_post) {
if (url_post == null) {
url_post = "";
}
if (url_post) {
if (window.location.toString().indexOf("?") > 0) {
return window.location += "&" + url_post;
} else {
return window.location += "?" + url_post;
}
} else {
return window.location.reload();
}
};
Wrapper.prototype.actionGetLocalStorage = function(message) {
var data;
data = localStorage.getItem("site." + window.address);
@ -1116,4 +1154,4 @@ jQuery.extend( jQuery.easing,
window.wrapper = new Wrapper(ws_url);
}).call(this);
}).call(this);

View File

@ -1,9 +1,14 @@
import logging, json, time
from Crypt import CryptBitcoin
from Plugin import PluginManager
class User:
def __init__(self, master_address=None):
if master_address:
@PluginManager.acceptPlugins
class User(object):
def __init__(self, master_address=None, master_seed=None):
if master_seed:
self.master_seed = master_seed
self.master_address = CryptBitcoin.privatekeyToAddress(self.master_seed)
elif master_address:
self.master_address = master_address
self.master_seed = None
else:
@ -27,8 +32,9 @@ class User:
# Get user site data
# Return: {"auth_address": "xxx", "auth_privatekey": "xxx"}
def getSiteData(self, address):
def getSiteData(self, address, create=True):
if not address in self.sites: # Genreate new BIP32 child key based on site address
if not create: return {"auth_address": None, "auth_privatekey": None} # Dont create user yet
s = time.time()
address_id = int(address.encode("hex"), 16) # Convert site address to int
auth_privatekey = CryptBitcoin.hdPrivatekey(self.master_seed, address_id)
@ -43,17 +49,15 @@ class User:
# Get BIP32 address from site address
# Return: BIP32 auth address
def getAuthAddress(self, address):
return self.getSiteData(address)["auth_address"]
def getAuthAddress(self, address, create=True):
return self.getSiteData(address, create)["auth_address"]
def getAuthPrivatekey(self, address):
return self.getSiteData(address)["auth_privatekey"]
def getAuthPrivatekey(self, address, create=True):
return self.getSiteData(address, create)["auth_privatekey"]
# Set user attributes from dict
def setData(self, data):
for key, val in data.items():
setattr(self, key, val)

View File

@ -1,66 +1,79 @@
import json, logging, os
from User import User
users = None
# Load all user from data/users.json
def load():
global users
if not users: users = {}
user_found = []
added = 0
# Load new users
for master_address, data in json.load(open("data/users.json")).items():
if master_address not in users:
user = User(master_address)
user.setData(data)
users[master_address] = user
added += 1
user_found.append(master_address)
# Remove deleted adresses
for master_address in users.keys():
if master_address not in user_found:
del(users[master_address])
logging.debug("Removed user: %s" % master_address)
if added: logging.debug("UserManager added %s users" % added)
from Plugin import PluginManager
# Create new user
# Return: User
def create():
user = User()
logging.debug("Created user: %s" % user.master_address)
users[user.master_address] = user
user.save()
return user
@PluginManager.acceptPlugins
class UserManager(object):
def __init__(self):
self.users = {}
# List all users from data/users.json
# Return: {"usermasteraddr": User}
def list():
if users == None: # Not loaded yet
load()
return users
# Load all user from data/users.json
def load(self):
if not self.users: self.users = {}
user_found = []
added = 0
# Load new users
for master_address, data in json.load(open("data/users.json")).items():
if master_address not in self.users:
user = User(master_address)
user.setData(data)
self.users[master_address] = user
added += 1
user_found.append(master_address)
# Remove deleted adresses
for master_address in self.users.keys():
if master_address not in user_found:
del(self.users[master_address])
logging.debug("Removed user: %s" % master_address)
if added: logging.debug("UserManager added %s users" % added)
# Get current authed user
# Return: User
def getCurrent():
users = list()
if users:
return users.values()[0]
else:
return create()
# Create new user
# Return: User
def create(self, master_address=None, master_seed=None):
user = User(master_address, master_seed)
logging.debug("Created user: %s" % user.master_address)
if user.master_address: # If successfully created
self.users[user.master_address] = user
user.save()
return user
# List all users from data/users.json
# Return: {"usermasteraddr": User}
def list(self):
if self.users == {}: # Not loaded yet
self.load()
return self.users
# Get user based on master_address
# Return: User or None
def get(self, master_address=None):
users = self.list()
if users:
return users.values()[0] # Single user mode, always return the first
else:
return None
user_manager = UserManager() # Singletone
# Debug: Reload User.py
def reload():
return False # Disabled
"""import imp
global users, User
def reloadModule():
return "Not used"
import imp
global User, UserManager, user_manager
User = imp.load_source("User", "src/User/User.py").User # Reload source
users.clear() # Remove all items
load()"""
#module = imp.load_source("UserManager", "src/User/UserManager.py") # Reload module
#UserManager = module.UserManager
#user_manager = module.user_manager
# Reload users
user_manager = UserManager()
user_manager.load()

View File

@ -1,6 +1,5 @@
import os, sys
update_after_shutdown = False
sys.path.insert(0, os.path.dirname(__file__)) # Imports relative to main.py
update_after_shutdown = False # If set True then update and restart zeronet after main loop ended
# Create necessary files and dirs
if not os.path.isdir("log"): os.mkdir("log")
@ -11,7 +10,8 @@ if not os.path.isfile("data/users.json"): open("data/users.json", "w").write("{}
# Load config
from Config import config
# Init logging
# Setup logging
import logging
if config.action == "main":
if os.path.isfile("log/debug.log"): # Simple logrotate
@ -28,24 +28,35 @@ if config.action == "main": # Add time if main action
else:
console_log.setFormatter(logging.Formatter('%(name)s %(message)s', "%H:%M:%S"))
logging.getLogger('').addHandler(console_log) # Add console logger
logging.getLogger('').name = "-" # Remove root prefix
# Debug dependent configuration
from Debug import DebugHook
if config.debug:
console_log.setLevel(logging.DEBUG)
console_log.setLevel(logging.DEBUG) # Display everything to console
from gevent import monkey; monkey.patch_all(thread=False) # thread=False because of pyfilesystem
else:
console_log.setLevel(logging.INFO)
from gevent import monkey; monkey.patch_all()
console_log.setLevel(logging.INFO) # Display only important info to console
from gevent import monkey; monkey.patch_all() # Make time, thread, socket gevent compatible
import gevent
import time
# Log current config
logging.debug("Config: %s" % config)
# Load plugins
from Plugin import PluginManager
PluginManager.plugin_manager.loadPlugins()
# -- Actions --
# Starts here when running zeronet.py
def start():
action_func = globals()[config.action] # Function reference
@ -74,7 +85,7 @@ def main():
def siteCreate():
logging.info("Generating new privatekey...")
from src.Crypt import CryptBitcoin
from Crypt import CryptBitcoin
privatekey = CryptBitcoin.newPrivatekey()
logging.info("----------------------------------------------------------------------")
logging.info("Site private key: %s" % privatekey)
@ -196,7 +207,7 @@ def sitePublish(address, peer_ip=None, peer_port=15441, inner_path="content.json
# Crypto commands
def cryptoPrivatekeyToAddress(privatekey=None):
from src.Crypt import CryptBitcoin
from Crypt import CryptBitcoin
if not privatekey: # If no privatekey in args then ask it now
import getpass
privatekey = getpass.getpass("Private key (input hidden):")

View File

@ -16,15 +16,41 @@ def update():
if not buff: break
data.write(buff)
print ".",
print "Downloaded."
# Checking plugins
plugins_enabled = []
plugins_disabled = []
if os.path.isdir("plugins"):
for dir in os.listdir("plugins"):
if dir.startswith("disabled-"):
plugins_disabled.append(dir.replace("disabled-", ""))
else:
plugins_enabled.append(dir)
print "Plugins:", plugins_enabled, plugins_disabled
print "Extracting...",
zip = zipfile.ZipFile(data)
for inner_path in zip.namelist():
inner_path = inner_path.replace("\\", "/") # Make sure we have unix path
print ".",
dest_path = inner_path.replace("ZeroNet-master/", "")
if not dest_path: continue
# Keep plugin disabled/enabled status
match = re.match("plugins/([^/]+)", dest_path)
if match:
plugin_name = match.group(1).replace("disabled-","")
if plugin_name in plugins_enabled: # Plugin was enabled
dest_path = dest_path.replace("plugins/disabled-"+plugin_name, "plugins/"+plugin_name)
else: # Plugin was disabled
dest_path = dest_path.replace("plugins/"+plugin_name, "plugins/disabled-"+plugin_name)
print "P",
dest_dir = os.path.dirname(dest_path)
if dest_dir and not os.path.isdir(dest_dir):
os.makedirs(dest_dir)

View File

@ -2,8 +2,10 @@
def main():
print " - Starging ZeroNet..."
import sys, os
try:
from src import main
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src")) # Imports relative to src
import main
main.start()
if main.update_after_shutdown: # Updater
import update, sys, os, gc