2015-01-12 02:03:45 +01:00
import json , gevent , time , sys , hashlib
from Config import config
from Site import SiteManager
2015-01-17 18:50:56 +01:00
from Debug import Debug
2015-01-12 02:03:45 +01:00
class UiWebsocket :
def __init__ ( self , ws , site , server ) :
self . ws = ws
self . site = site
2015-01-14 02:41:13 +01:00
self . log = site . log
2015-01-12 02:03:45 +01:00
self . server = server
self . next_message_id = 1
self . waiting_cb = { } # Waiting for callback. Key: message_id, Value: function pointer
self . channels = [ ] # Channels joined to
# Start listener loop
def start ( self ) :
ws = self . ws
if self . site . address == config . homepage and not self . site . page_requested : # Add open fileserver port message or closed port error to homepage at first request after start
if config . ip_external :
self . site . notifications . append ( [ " done " , " Congratulation, your port <b> " + str ( config . fileserver_port ) + " </b> is opened. <br>You are full member of ZeroNet network! " , 10000 ] )
elif config . ip_external == False :
self . site . notifications . append ( [ " error " , " Your network connection is restricted. Please, open <b> " + str ( config . fileserver_port ) + " </b> port <br>on your router to become full member of ZeroNet network. " , 0 ] )
self . site . page_requested = True # Dont add connection notification anymore
for notification in self . site . notifications : # Send pending notification messages
self . cmd ( " notification " , notification )
self . site . notifications = [ ]
while True :
try :
message = ws . receive ( )
if message :
self . handleRequest ( message )
except Exception , err :
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 ( )
2015-01-17 18:50:56 +01:00
self . log . error ( " WebSocket error: %s " % Debug . formatException ( err ) )
2015-01-12 02:03:45 +01:00
return " Bye. "
# Event in a channel
def event ( self , channel , * params ) :
if channel in self . channels : # We are joined to channel
if channel == " siteChanged " :
site = params [ 0 ] # Triggerer site
2015-01-18 22:52:19 +01:00
site_info = self . formatSiteInfo ( site )
2015-01-12 02:03:45 +01:00
if len ( params ) > 1 and params [ 1 ] : # Extra data
site_info . update ( params [ 1 ] )
self . cmd ( " setSiteInfo " , site_info )
# Send response to client (to = message.id)
def response ( self , to , result ) :
self . send ( { " cmd " : " response " , " to " : to , " result " : result } )
# Send a command
def cmd ( self , cmd , params = { } , cb = None ) :
self . send ( { " cmd " : cmd , " params " : params } , cb )
# Encode to json and send message
def send ( self , message , cb = None ) :
message [ " id " ] = self . next_message_id # Add message id to allow response
self . next_message_id + = 1
2015-01-14 02:41:13 +01:00
try :
self . ws . send ( json . dumps ( message ) )
if cb : # Callback after client responsed
self . waiting_cb [ message [ " id " ] ] = cb
except Exception , err :
2015-01-17 18:50:56 +01:00
self . log . debug ( " Websocket send error: %s " % Debug . formatException ( err ) )
2015-01-12 02:03:45 +01:00
# Handle incoming messages
def handleRequest ( self , data ) :
req = json . loads ( data )
2015-01-27 22:37:13 +01:00
cmd = req . get ( " cmd " )
params = req . get ( " params " )
2015-01-12 02:03:45 +01:00
permissions = self . site . settings [ " permissions " ]
2015-01-27 22:37:13 +01:00
if cmd == " response " : # It's a response to a command
return self . actionResponse ( req [ " to " ] , req [ " result " ] )
2015-01-12 02:03:45 +01:00
elif cmd == " ping " :
2015-01-27 22:37:13 +01:00
func = self . actionPing
2015-01-12 02:03:45 +01:00
elif cmd == " channelJoin " :
2015-01-27 22:37:13 +01:00
func = self . actionChannelJoin
2015-01-12 02:03:45 +01:00
elif cmd == " siteInfo " :
2015-01-27 22:37:13 +01:00
func = self . actionSiteInfo
2015-01-12 02:03:45 +01:00
elif cmd == " serverInfo " :
2015-01-27 22:37:13 +01:00
func = self . actionServerInfo
2015-01-12 02:03:45 +01:00
elif cmd == " siteUpdate " :
2015-01-27 22:37:13 +01:00
func = self . actionSiteUpdate
2015-01-24 19:14:29 +01:00
elif cmd == " sitePublish " :
2015-01-27 22:37:13 +01:00
func = self . actionSitePublish
2015-01-24 19:14:29 +01:00
elif cmd == " fileWrite " :
2015-01-27 22:37:13 +01:00
func = self . actionFileWrite
2015-01-12 02:03:45 +01:00
# Admin commands
elif cmd == " sitePause " and " ADMIN " in permissions :
2015-01-27 22:37:13 +01:00
func = self . actionSitePause
2015-01-12 02:03:45 +01:00
elif cmd == " siteResume " and " ADMIN " in permissions :
2015-01-27 22:37:13 +01:00
func = self . actionSiteResume
2015-01-21 12:58:26 +01:00
elif cmd == " siteDelete " and " ADMIN " in permissions :
2015-01-27 22:37:13 +01:00
func = self . actionSiteDelete
2015-01-12 02:03:45 +01:00
elif cmd == " siteList " and " ADMIN " in permissions :
2015-01-27 22:37:13 +01:00
func = self . actionSiteList
2015-01-12 02:03:45 +01:00
elif cmd == " channelJoinAllsite " and " ADMIN " in permissions :
2015-01-27 22:37:13 +01:00
func = self . actionChannelJoinAllsite
2015-01-12 02:03:45 +01:00
# Unknown command
else :
self . response ( req [ " id " ] , " Unknown command: %s " % cmd )
2015-01-27 22:37:13 +01:00
return
# Support calling as named, unnamed paramters and raw first argument too
if type ( params ) is dict :
func ( req [ " id " ] , * * params )
elif type ( params ) is list :
func ( req [ " id " ] , * params )
else :
func ( req [ " id " ] , params )
2015-01-12 02:03:45 +01:00
# - Actions -
# Do callback on response {"cmd": "response", "to": message_id, "result": result}
2015-01-27 22:37:13 +01:00
def actionResponse ( self , to , result ) :
if to in self . waiting_cb :
self . waiting_cb ( result ) # Call callback function
2015-01-12 02:03:45 +01:00
else :
2015-01-27 22:37:13 +01:00
self . log . error ( " Websocket callback not found: %s , %s " % ( to , result ) )
2015-01-12 02:03:45 +01:00
# Send a simple pong answer
def actionPing ( self , to ) :
self . response ( to , " pong " )
# Format site info
2015-01-18 22:52:19 +01:00
def formatSiteInfo ( self , site ) :
content = site . content
if content and " files " in content : # Remove unnecessary data transfer
content = site . content . copy ( )
content [ " files " ] = len ( content [ " files " ] )
del ( content [ " sign " ] )
2015-01-12 02:03:45 +01:00
ret = {
2015-01-18 22:52:19 +01:00
" auth_key " : self . site . settings [ " auth_key " ] ,
" auth_key_sha512 " : hashlib . sha512 ( self . site . settings [ " auth_key " ] ) . hexdigest ( ) [ 0 : 64 ] ,
2015-01-12 02:03:45 +01:00
" address " : site . address ,
" settings " : site . settings ,
" content_updated " : site . content_updated ,
2015-01-18 22:52:19 +01:00
" bad_files " : len ( site . bad_files ) ,
" last_downloads " : len ( site . last_downloads ) ,
2015-01-12 02:03:45 +01:00
" peers " : len ( site . peers ) ,
2015-01-18 22:52:19 +01:00
" tasks " : len ( [ task [ " inner_path " ] for task in site . worker_manager . tasks ] ) ,
" content " : content
2015-01-12 02:03:45 +01:00
}
if site . settings [ " serving " ] and site . content : ret [ " peers " ] + = 1 # Add myself if serving
return ret
# Send site details
2015-01-27 22:37:13 +01:00
def actionSiteInfo ( self , to ) :
2015-01-18 22:52:19 +01:00
ret = self . formatSiteInfo ( self . site )
2015-01-12 02:03:45 +01:00
self . response ( to , ret )
# Join to an event channel
2015-01-27 22:37:13 +01:00
def actionChannelJoin ( self , to , channel ) :
if channel not in self . channels :
self . channels . append ( channel )
2015-01-12 02:03:45 +01:00
# Server variables
2015-01-27 22:37:13 +01:00
def actionServerInfo ( self , to ) :
2015-01-12 02:03:45 +01:00
ret = {
2015-01-17 18:50:56 +01:00
" ip_external " : bool ( config . ip_external ) ,
2015-01-12 02:03:45 +01:00
" platform " : sys . platform ,
" fileserver_ip " : config . fileserver_ip ,
" fileserver_port " : config . fileserver_port ,
" ui_ip " : config . ui_ip ,
" ui_port " : config . ui_port ,
2015-01-16 11:52:42 +01:00
" version " : config . version ,
2015-01-12 02:03:45 +01:00
" debug " : config . debug
}
self . response ( to , ret )
2015-01-27 22:37:13 +01:00
def actionSitePublish ( self , to , privatekey ) :
2015-01-24 19:14:29 +01:00
site = self . site
if not site . settings [ " own " ] : return self . response ( to , " Forbidden, you can only modify your own sites " )
# Signing
site . loadContent ( True ) # Reload content.json, ignore errors to make it up-to-date
2015-01-27 22:37:13 +01:00
signed = site . signContent ( privatekey ) # Sign using private key sent by user
2015-01-24 19:14:29 +01:00
if signed :
self . cmd ( " notification " , [ " done " , " Private key correct, site signed! " , 5000 ] ) # Display message for 5 sec
else :
self . cmd ( " notification " , [ " error " , " Site sign failed: invalid private key. " ] )
self . response ( to , " Site sign failed " )
return
site . loadContent ( True ) # Load new content.json, ignore errors
# Publishing
if not site . settings [ " serving " ] : # Enable site if paused
site . settings [ " serving " ] = True
site . saveSettings ( )
site . announce ( )
published = site . publish ( 5 ) # Publish to 5 peer
if published > 0 : # Successfuly published
self . cmd ( " notification " , [ " done " , " Site published to %s peers. " % published , 5000 ] )
self . response ( to , " ok " )
site . updateWebsocket ( ) # Send updated site data to local websocket clients
else :
if len ( site . peers ) == 0 :
self . cmd ( " notification " , [ " info " , " No peers found, but your site is ready to access. " ] )
self . response ( to , " No peers found, but your site is ready to access. " )
else :
self . cmd ( " notification " , [ " error " , " Site publish failed. " ] )
self . response ( to , " Site publish failed. " )
# Write a file to disk
2015-01-27 22:37:13 +01:00
def actionFileWrite ( self , to , inner_path , content_base64 ) :
2015-01-24 19:14:29 +01:00
if not self . site . settings [ " own " ] : return self . response ( to , " Forbidden, you can only modify your own sites " )
try :
import base64
2015-01-27 22:37:13 +01:00
content = base64 . b64decode ( content_base64 )
open ( self . site . getPath ( inner_path ) , " wb " ) . write ( content )
2015-01-24 19:14:29 +01:00
except Exception , err :
return self . response ( to , " Write error: %s " % err )
2015-01-27 22:37:13 +01:00
if inner_path == " content.json " :
self . site . loadContent ( True )
2015-01-24 19:14:29 +01:00
return self . response ( to , " ok " )
2015-01-12 02:03:45 +01:00
# - Admin actions -
# List all site info
2015-01-27 22:37:13 +01:00
def actionSiteList ( self , to ) :
2015-01-12 02:03:45 +01:00
ret = [ ]
SiteManager . load ( ) # Reload sites
for site in self . server . sites . values ( ) :
if not site . content : continue # Broken site
2015-01-18 22:52:19 +01:00
ret . append ( self . formatSiteInfo ( site ) )
2015-01-12 02:03:45 +01:00
self . response ( to , ret )
# Join to an event channel on all sites
2015-01-27 22:37:13 +01:00
def actionChannelJoinAllsite ( self , to , channel ) :
if channel not in self . channels : # Add channel to channels
self . channels . append ( channel )
2015-01-12 02:03:45 +01:00
for site in self . server . sites . values ( ) : # Add websocket to every channel
if self not in site . websockets :
site . websockets . append ( self )
# Update site content.json
2015-01-27 22:37:13 +01:00
def actionSiteUpdate ( self , to , address ) :
2015-01-12 02:03:45 +01:00
site = self . server . sites . get ( address )
if site and ( site . address == self . site . address or " ADMIN " in self . site . settings [ " permissions " ] ) :
gevent . spawn ( site . update )
else :
self . response ( to , { " error " : " Unknown site: %s " % address } )
# Pause site serving
2015-01-27 22:37:13 +01:00
def actionSitePause ( self , to , address ) :
2015-01-12 02:03:45 +01:00
site = self . server . sites . get ( address )
if site :
site . settings [ " serving " ] = False
site . saveSettings ( )
site . updateWebsocket ( )
2015-01-21 12:58:26 +01:00
site . worker_manager . stopWorkers ( )
2015-01-12 02:03:45 +01:00
else :
self . response ( to , { " error " : " Unknown site: %s " % address } )
# Resume site serving
2015-01-27 22:37:13 +01:00
def actionSiteResume ( self , to , address ) :
2015-01-12 02:03:45 +01:00
site = self . server . sites . get ( address )
if site :
site . settings [ " serving " ] = True
site . saveSettings ( )
gevent . spawn ( site . update )
time . sleep ( 0.001 ) # Wait for update thread starting
site . updateWebsocket ( )
else :
self . response ( to , { " error " : " Unknown site: %s " % address } )
2015-01-21 12:58:26 +01:00
2015-01-27 22:37:13 +01:00
def actionSiteDelete ( self , to , address ) :
2015-01-21 12:58:26 +01:00
site = self . server . sites . get ( address )
if site :
site . settings [ " serving " ] = False
site . saveSettings ( )
site . worker_manager . running = False
site . worker_manager . stopWorkers ( )
site . deleteFiles ( )
SiteManager . delete ( address )
site . updateWebsocket ( )
else :
self . response ( to , { " error " : " Unknown site: %s " % address } )