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): class Config(object):
def __init__(self): def __init__(self):
self.version = "0.2.6" self.version = "0.2.7"
self.parser = self.createArguments() self.parser = self.createArguments()
argv = sys.argv[:] # Copy command line arguments argv = sys.argv[:] # Copy command line arguments
argv = self.parseConfig(argv) # Add arguments from config file argv = self.parseConfig(argv) # Add arguments from config file

View File

@ -1,5 +1,5 @@
from src.lib.BitcoinECC import BitcoinECC from lib.BitcoinECC import BitcoinECC
from src.lib.pybitcointools import bitcoin as btctools from lib.pybitcointools import bitcoin as btctools
def newPrivatekey(uncompressed=True): # Return new private key 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: try:
from fs.osfs import OSFS from fs.osfs import OSFS
pyfilesystem = OSFS("src") pyfilesystem = OSFS("src")
pyfilesystem_plugins = OSFS("plugins")
except Exception, err: except Exception, err:
logging.debug("%s: For autoreload please download pyfilesystem (https://code.google.com/p/pyfilesystem/)" % err) logging.debug("%s: For autoreload please download pyfilesystem (https://code.google.com/p/pyfilesystem/)" % err)
pyfilesystem = False pyfilesystem = False
@ -28,6 +29,7 @@ class DebugReloader:
try: try:
time.sleep(1) # Wait for .pyc compiles time.sleep(1) # Wait for .pyc compiles
pyfilesystem.add_watcher(self.changed, path=self.directory, events=None, recursive=recursive) 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: except Exception, err:
print "File system watcher failed: %s (on linux pyinotify not gevent compatible yet :( )" % 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 gevent, msgpack
import zmq.green as zmq import zmq.green as zmq
from Config import config from Config import config

View File

@ -12,7 +12,7 @@ class Peer:
self.site = site self.site = site
self.key = "%s:%s" % (ip, port) self.key = "%s:%s" % (ip, port)
self.log = None self.log = None
self.connection_server = sys.modules["src.main"].file_server self.connection_server = sys.modules["main"].file_server
self.connection = None self.connection = None
self.last_found = None # Time of last found in the torrent tracker 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 Config import config
from Site import SiteManager from Site import SiteManager
from User import UserManager from User import UserManager
from Plugin import PluginManager
from Ui.UiWebsocket import UiWebsocket from Ui.UiWebsocket import UiWebsocket
status_texts = { status_texts = {
@ -12,15 +13,15 @@ status_texts = {
} }
@PluginManager.acceptPlugins
class UiRequest: class UiRequest(object):
def __init__(self, server = None): def __init__(self, server = None):
if server: if server:
self.server = server self.server = server
self.log = server.log self.log = server.log
self.get = {} # Get parameters self.get = {} # Get parameters
self.env = {} # Enviroment settings self.env = {} # Enviroment settings
self.user = UserManager.getCurrent() self.user = None
self.start_response = None # Start response function self.start_response = None # Start response function
@ -46,16 +47,17 @@ class UiRequest:
return self.actionDebug() return self.actionDebug()
elif path == "/Console" and config.debug: elif path == "/Console" and config.debug:
return self.actionConsole() 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 # Site media wrapper
else: 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 # Get mime by filename
@ -69,6 +71,24 @@ class UiRequest:
return content_type 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 # Send response headers
def sendHeader(self, status=200, content_type="text/html", extra_headers=[]): def sendHeader(self, status=200, content_type="text/html", extra_headers=[]):
if content_type == "text/html": content_type = "text/html; charset=utf-8" 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)]) #template = SimpleTemplate(open(template_path), lookup=[os.path.dirname(template_path)])
#yield str(template.render(*args, **kwargs).encode("utf8")) #yield str(template.render(*args, **kwargs).encode("utf8"))
template = open(template_path).read().decode("utf8") template = open(template_path).read().decode("utf8")
yield template.format(**kwargs).encode("utf8") return template.format(**kwargs).encode("utf8")
# - Actions - # - Actions -
@ -105,7 +125,7 @@ class UiRequest:
# Render a file from media with iframe site wrapper # 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 "." 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.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 if self.env.get("HTTP_X_REQUESTED_WITH"): return self.error403() # No ajax allowed on wrapper
@ -121,9 +141,11 @@ class UiRequest:
else: else:
title = "Loading %s..." % match.group("site") title = "Loading %s..." % match.group("site")
site = SiteManager.need(match.group("site")) # Start download 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 # Wrapper variable inits
query_string = "" query_string = ""
@ -152,7 +174,7 @@ class UiRequest:
) )
else: # Bad url else: # Bad url
return self.error404(path) return False
# Serve a media for site # Serve a media for site
@ -241,7 +263,11 @@ class UiRequest:
if site_check.settings["wrapper_key"] == wrapper_key: site = site_check if site_check.settings["wrapper_key"] == wrapper_key: site = site_check
if site: # Correct wrapper key 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 site.websockets.append(ui_websocket) # Add to site websockets to allow notify on events
ui_websocket.start() 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) 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): def actionDebug(self):
# Raise last error from DebugHook # Raise last error from DebugHook
import sys import sys
last_error = sys.modules["src.main"].DebugHook.last_error last_error = sys.modules["main"].DebugHook.last_error
if last_error: if last_error:
raise last_error[0], last_error[1], last_error[2] raise last_error[0], last_error[1], last_error[2]
else: else:
@ -272,134 +298,10 @@ class UiRequest:
def actionConsole(self): def actionConsole(self):
import sys import sys
sites = self.server.sites sites = self.server.sites
main = sys.modules["src.main"] main = sys.modules["main"]
raise Exception("Here is your console") 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 - # - Tests -
def actionTestStream(self): def actionTestStream(self):
@ -433,8 +335,9 @@ class UiRequest:
# - Reload for eaiser developing - # - Reload for eaiser developing -
def reload(self): def reload(self):
import imp import imp, sys
global UiWebsocket global UiWebsocket
UiWebsocket = imp.load_source("UiWebsocket", "src/Ui/UiWebsocket.py").UiWebsocket UiWebsocket = imp.load_source("UiWebsocket", "src/Ui/UiWebsocket.py").UiWebsocket
UserManager.reload() #reload(sys.modules["User.UserManager"])
self.user = UserManager.getCurrent() #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 WSGIServer
from gevent.pywsgi import WSGIHandler from gevent.pywsgi import WSGIHandler
from lib.geventwebsocket.handler import WebSocketHandler from lib.geventwebsocket.handler import WebSocketHandler
from Ui import UiRequest from UiRequest import UiRequest
from Site import SiteManager from Site import SiteManager
from Config import config from Config import config
from Debug import Debug from Debug import Debug
# Skip websocket handler if not necessary # Skip websocket handler if not necessary
class UiWSGIHandler(WSGIHandler): class UiWSGIHandler(WSGIHandler):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -28,7 +29,10 @@ class UiWSGIHandler(WSGIHandler):
try: try:
return super(UiWSGIHandler, self).run_application() return super(UiWSGIHandler, self).run_application()
except Exception, err: 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] del self.server.sockets[self.client_address]
@ -59,7 +63,8 @@ class UiServer:
# Reload the UiRequest class to prevent restarts in debug mode # Reload the UiRequest class to prevent restarts in debug mode
def reload(self): 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 = imp.load_source("UiRequest", "src/Ui/UiRequest.py").UiRequest(self)
self.ui_request.reload() self.ui_request.reload()

View File

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

View File

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

View File

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

View File

@ -33,21 +33,28 @@ a { color: black }
/* Notification */ /* 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 { .notification {
position: relative; float: right; clear: both; margin: 10px; height: 50px; box-sizing: border-box; overflow: hidden; backface-visibility: hidden; perspective: 1000px; position: relative; float: right; clear: both; margin: 10px; box-sizing: border-box; overflow: hidden; backface-visibility: hidden; perspective: 1000px; padding-bottom: 5px;
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)*/ 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 { .notification-icon {
display: block; width: 50px; height: 50px; position: absolute; float: left; z-index: 1; 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; 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.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 { 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:hover { color: black }
.notification .close:active, .notification .close:focus { color: #AF3BFF } .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) } .body-white .notification { box-shadow: 0px 1px 9px rgba(0,0,0,0.1) }
/* Notification types */ /* Notification types */

View File

@ -38,21 +38,28 @@ a { color: black }
/* Notification */ /* 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 { .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 ; 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;
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)*/ 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 { .notification-icon {
display: block; width: 50px; height: 50px; position: absolute; float: left; z-index: 1; 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; 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.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 { 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:hover { color: black }
.notification .close:active, .notification .close:focus { color: #AF3BFF } .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) } .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 */ /* Notification types */

View File

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

View File

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

View File

@ -1,66 +1,79 @@
import json, logging, os import json, logging, os
from User import User from User import User
from Plugin import PluginManager
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)
# Create new user @PluginManager.acceptPlugins
# Return: User class UserManager(object):
def create(): def __init__(self):
user = User() self.users = {}
logging.debug("Created user: %s" % user.master_address)
users[user.master_address] = user
user.save()
return user
# List all users from data/users.json # Load all user from data/users.json
# Return: {"usermasteraddr": User} def load(self):
def list(): if not self.users: self.users = {}
if users == None: # Not loaded yet
load() user_found = []
return users 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 # Create new user
# Return: User # Return: User
def getCurrent(): def create(self, master_address=None, master_seed=None):
users = list() user = User(master_address, master_seed)
if users: logging.debug("Created user: %s" % user.master_address)
return users.values()[0] if user.master_address: # If successfully created
else: self.users[user.master_address] = user
return create() 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 # Debug: Reload User.py
def reload(): def reloadModule():
return False # Disabled return "Not used"
"""import imp
global users, User import imp
global User, UserManager, user_manager
User = imp.load_source("User", "src/User/User.py").User # Reload source User = imp.load_source("User", "src/User/User.py").User # Reload source
users.clear() # Remove all items #module = imp.load_source("UserManager", "src/User/UserManager.py") # Reload module
load()""" #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 import os, sys
update_after_shutdown = False update_after_shutdown = False # If set True then update and restart zeronet after main loop ended
sys.path.insert(0, os.path.dirname(__file__)) # Imports relative to main.py
# Create necessary files and dirs # Create necessary files and dirs
if not os.path.isdir("log"): os.mkdir("log") 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 # Load config
from Config import config from Config import config
# Init logging
# Setup logging
import logging import logging
if config.action == "main": if config.action == "main":
if os.path.isfile("log/debug.log"): # Simple logrotate if os.path.isfile("log/debug.log"): # Simple logrotate
@ -28,24 +28,35 @@ if config.action == "main": # Add time if main action
else: else:
console_log.setFormatter(logging.Formatter('%(name)s %(message)s', "%H:%M:%S")) console_log.setFormatter(logging.Formatter('%(name)s %(message)s', "%H:%M:%S"))
logging.getLogger('').addHandler(console_log) # Add console logger logging.getLogger('').addHandler(console_log) # Add console logger
logging.getLogger('').name = "-" # Remove root prefix logging.getLogger('').name = "-" # Remove root prefix
# Debug dependent configuration # Debug dependent configuration
from Debug import DebugHook from Debug import DebugHook
if config.debug: 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 from gevent import monkey; monkey.patch_all(thread=False) # thread=False because of pyfilesystem
else: else:
console_log.setLevel(logging.INFO) console_log.setLevel(logging.INFO) # Display only important info to console
from gevent import monkey; monkey.patch_all() from gevent import monkey; monkey.patch_all() # Make time, thread, socket gevent compatible
import gevent import gevent
import time import time
# Log current config
logging.debug("Config: %s" % config) logging.debug("Config: %s" % config)
# Load plugins
from Plugin import PluginManager
PluginManager.plugin_manager.loadPlugins()
# -- Actions --
# Starts here when running zeronet.py # Starts here when running zeronet.py
def start(): def start():
action_func = globals()[config.action] # Function reference action_func = globals()[config.action] # Function reference
@ -74,7 +85,7 @@ def main():
def siteCreate(): def siteCreate():
logging.info("Generating new privatekey...") logging.info("Generating new privatekey...")
from src.Crypt import CryptBitcoin from Crypt import CryptBitcoin
privatekey = CryptBitcoin.newPrivatekey() privatekey = CryptBitcoin.newPrivatekey()
logging.info("----------------------------------------------------------------------") logging.info("----------------------------------------------------------------------")
logging.info("Site private key: %s" % privatekey) 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 # Crypto commands
def cryptoPrivatekeyToAddress(privatekey=None): 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 if not privatekey: # If no privatekey in args then ask it now
import getpass import getpass
privatekey = getpass.getpass("Private key (input hidden):") privatekey = getpass.getpass("Private key (input hidden):")

View File

@ -16,15 +16,41 @@ def update():
if not buff: break if not buff: break
data.write(buff) data.write(buff)
print ".", 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...", print "Extracting...",
zip = zipfile.ZipFile(data) zip = zipfile.ZipFile(data)
for inner_path in zip.namelist(): for inner_path in zip.namelist():
inner_path = inner_path.replace("\\", "/") # Make sure we have unix path
print ".", print ".",
dest_path = inner_path.replace("ZeroNet-master/", "") dest_path = inner_path.replace("ZeroNet-master/", "")
if not dest_path: continue 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) dest_dir = os.path.dirname(dest_path)
if dest_dir and not os.path.isdir(dest_dir): if dest_dir and not os.path.isdir(dest_dir):
os.makedirs(dest_dir) os.makedirs(dest_dir)

View File

@ -2,8 +2,10 @@
def main(): def main():
print " - Starging ZeroNet..." print " - Starging ZeroNet..."
import sys, os
try: 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() main.start()
if main.update_after_shutdown: # Updater if main.update_after_shutdown: # Updater
import update, sys, os, gc import update, sys, os, gc