version 0.1.5, install as user to readme, more debug on filerequests, if upnpc fail try without -e, announce interval from 10 to 20 min, detect computer wakeup and acts as startup, delete sites files websocket command support, pause stop all current downloads, wrapper confirmation dialog support

This commit is contained in:
HelloZeroNet 2015-01-21 12:58:26 +01:00
parent 3bec738595
commit a977feec33
13 changed files with 197 additions and 16 deletions

View File

@ -40,6 +40,11 @@ Linux (Debian):
- `pip install msgpack-python`
- start using `python zeronet.py`
Linux (Without root access):
- `wget https://bootstrap.pypa.io/get-pip.py`
- `python get-pip.py --user pyzmq gevent msgpack-python`
- start using `python zeronet.py`
## Current limitations
- No torrent-like, file splitting big file support
@ -91,4 +96,4 @@ Bitcoin: 1QDhxQ6PraUZa21ET5fYUCPgdrwBomnFgX
#### Thank you!
- More info, help, changelog, zeronet sites: http://www.reddit.com/r/zeronet/
- Come, chat with us: [#zeronet @ FreeNode](https://kiwiirc.com/client/irc.freenode.net/zeronet)
- Come, chat with us: [#zeronet @ FreeNode](https://kiwiirc.com/client/irc.freenode.net/zeronet)

View File

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

View File

@ -2,6 +2,7 @@ import os, msgpack, shutil
from Site import SiteManager
from cStringIO import StringIO
from Debug import Debug
from Config import config
FILE_BUFF = 1024*512
@ -80,13 +81,17 @@ class FileRequest:
self.send({"error": "Unknown site"})
return False
try:
file = open(site.getPath(params["inner_path"]), "rb")
file_path = site.getPath(params["inner_path"])
if config.debug_socket: self.log.debug("Opening file: %s" % file_path)
file = open(file_path, "rb")
file.seek(params["location"])
back = {}
back["body"] = file.read(FILE_BUFF)
back["location"] = file.tell()
back["size"] = os.fstat(file.fileno()).st_size
if config.debug_socket: self.log.debug("Sending file %s from position %s to %s" % (file_path, params["location"], back["location"]))
self.send(back)
if config.debug_socket: self.log.debug("File %s sent" % file_path)
except Exception, err:
self.send({"error": "File read error: %s" % Debug.formatException(err)})
return False

View File

@ -49,10 +49,14 @@ class FileServer:
self.log.info("Try to open port using upnpc...")
try:
exit = os.system("%s -e ZeroNet -r %s tcp" % (config.upnpc, self.port))
if exit == 0:
if exit == 0: # Success
upnpc_success = True
else:
upnpc_success = False
else: # Failed
exit = os.system("%s -r %s tcp" % (config.upnpc, self.port)) # Try without -e option
if exit == 0:
upnpc_success = True
else:
upnpc_success = False
except Exception, err:
self.log.error("Upnpc run error: %s" % Debug.formatException(err))
upnpc_success = False
@ -122,13 +126,25 @@ class FileServer:
# Announce sites every 10 min
def announceSites(self):
while 1:
time.sleep(10*60) # Announce sites every 10 min
time.sleep(20*60) # Announce sites every 20 min
for address, site in self.sites.items():
if site.settings["serving"]:
site.announce() # Announce site to tracker
time.sleep(2) # Prevent too quick request
# Detects if computer back from wakeup
def wakeupWatcher(self):
last_time = time.time()
while 1:
time.sleep(30)
if time.time()-last_time > 60: # If taken more than 60 second then the computer was in sleep mode
self.log.info("Wakeup detected: time wrap from %s to %s (%s sleep seconds), acting like startup..." % (last_time, time.time(), time.time()-last_time))
self.port_opened = None # Check if we still has the open port on router
self.checkSites()
last_time = time.time()
# Bind and start serving sites
def start(self, check_sites = True):
self.log = logging.getLogger(__name__)
@ -152,6 +168,7 @@ class FileServer:
gevent.spawn(self.checkSites)
gevent.spawn(self.announceSites)
gevent.spawn(self.wakeupWatcher)
while True:
try:

View File

@ -267,6 +267,32 @@ class Site:
self.bad_files[bad_file] = True
def deleteFiles(self):
self.log.debug("Deleting files from content.json...")
files = self.content["files"].keys() # Make a copy
files.append("content.json")
for inner_path in files:
path = self.getPath(inner_path)
if os.path.isfile(path): os.unlink(path)
self.log.debug("Deleting empty dirs...")
for root, dirs, files in os.walk(self.directory, topdown=False):
for dir in dirs:
path = os.path.join(root,dir)
if os.path.isdir(path) and os.listdir(path) == []:
os.removedirs(path)
self.log.debug("Removing %s" % path)
if os.path.isdir(self.directory) and os.listdir(self.directory) == []: os.removedirs(self.directory) # Remove sites directory if empty
if os.path.isdir(self.directory):
self.log.debug("Some unknown file remained in site data dir: %s..." % self.directory)
return False # Some files not deleted
else:
self.log.debug("Site data directory deleted: %s..." % self.directory)
return True # All clean
# - Events -
# Add event listeners
@ -380,11 +406,10 @@ class Site:
else:
ok = self.verifyFile(inner_path, open(file_path, "rb"))
if ok:
self.log.debug("[OK] %s" % inner_path)
else:
if not ok:
self.log.error("[ERROR] %s" % inner_path)
bad_files.append(inner_path)
self.log.debug("Site verified: %s files, quick_check: %s, bad files: %s" % (len(self.content["files"]), quick_check, bad_files))
return bad_files

View File

@ -45,12 +45,20 @@ def need(address, all_file=True):
from Site import Site
if address not in sites: # Site not exits yet
if not isAddress(address): raise Exception("Not address: %s" % address)
logging.debug("Added new site: %s" % address)
sites[address] = Site(address)
sites[address].settings["serving"] = True # Maybe it was deleted before
site = sites[address]
if all_file: site.download()
return site
def delete(address):
global sites
logging.debug("SiteManager deleted site: %s" % address)
del(sites[address])
# Lazy load sites
def list():
if sites == None: # Not loaded yet

View File

@ -96,6 +96,8 @@ class UiWebsocket:
self.actionSitePause(req["id"], req["params"])
elif cmd == "siteResume" and "ADMIN" in permissions:
self.actionSiteResume(req["id"], req["params"])
elif cmd == "siteDelete" and "ADMIN" in permissions:
self.actionSiteDelete(req["id"], req["params"])
elif cmd == "siteList" and "ADMIN" in permissions:
self.actionSiteList(req["id"], req["params"])
elif cmd == "channelJoinAllsite" and "ADMIN" in permissions:
@ -211,6 +213,7 @@ class UiWebsocket:
site.settings["serving"] = False
site.saveSettings()
site.updateWebsocket()
site.worker_manager.stopWorkers()
else:
self.response(to, {"error": "Unknown site: %s" % address})
@ -227,3 +230,18 @@ class UiWebsocket:
site.updateWebsocket()
else:
self.response(to, {"error": "Unknown site: %s" % address})
def actionSiteDelete(self, to, params):
address = params.get("address")
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})

View File

@ -27,10 +27,15 @@ class Notifications
$(".notification-icon", elem).html("!")
else if type == "done"
$(".notification-icon", elem).html("<div class='icon-success'></div>")
else if type == "ask"
$(".notification-icon", elem).html("?")
else
$(".notification-icon", elem).html("i")
$(".body", elem).html(body)
if typeof(body) == "string"
$(".body", elem).html(body)
else
$(".body", elem).html("").append(body)
elem.appendTo(@elem)
@ -53,7 +58,10 @@ class Notifications
@close elem
return false
@
# Close on button click within body (confirm dialog)
$(".button", elem).on "click", =>
@close elem
return false
close: (elem) ->

View File

@ -55,12 +55,31 @@ class Wrapper
if @ws.ws.readyState == 1 and not @wrapperWsInited # If ws already opened
@sendInner {"cmd": "wrapperOpenedWebsocket"}
@wrapperWsInited = true
else if cmd == "wrapperNotification"
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])
else if cmd == "wrapperConfirm" # Display confirm message
@actionWrapperConfirm(message)
else # Send to websocket
@ws.send(message) # Pass message to websocket
# - Actions -
actionWrapperConfirm: (message) ->
message.params = @toHtmlSafe(message.params) # Escape html
if message.params[1] then caption = message.params[1] else caption = "ok"
body = $("<span>"+message.params[0]+"</span>")
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": "boom"} # Response to confirm
return false
body.append(button)
@notifications.add("notification-#{message.id}", "ask", body)
onOpenWebsocket: (e) =>
@ws.cmd "channelJoin", {"channel": "siteChanged"} # Get info on modifications
@log "onOpenWebsocket", @inner_ready, @wrapperWsInited
@ -125,6 +144,7 @@ class Wrapper
@loading.printLine("#{site_info.event[1]} downloaded")
if site_info.event[1] == window.inner_path # File downloaded we currently on
@loading.hideScreen()
if not @site_info then @reloadSiteInfo()
if not $(".loadingscreen").length # Loading screen already removed (loaded +2sec)
@notifications.add("modified", "info", "New version of this page has just released.<br>Reload to see the modified content.")
# File failed downloading
@ -144,6 +164,10 @@ class Wrapper
@site_info = site_info
toHtmlSafe: (unsafe) ->
return unsafe
log: (args...) ->
console.log "[Wrapper]", args...

View File

@ -7,6 +7,12 @@ a { color: black }
#inner-iframe { width: 100%; height: 100%; position: absolute; border: 0px; transition: all 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55), opacity 0.8s ease-in-out }
#inner-iframe.back { transform: scale(0.95) translate(-300px, 0px); opacity: 0.4 }
.button { padding: 5px 10px; margin-left: 10px; background-color: #FFF85F; border-bottom: 2px solid #CDBD1E; border-radius: 2px; text-decoration: none; transition: all 0.5s; }
.button:hover { background-color: #FFF400; border-bottom: 2px solid #4D4D4C; transition: none }
.button:active { position: relative; top: 1px }
.button-Delete { background-color: #e74c3c; border-bottom-color: #c0392b; color: white }
.button-Delete:hover { background-color: #FF5442; border-bottom-color: #8E2B21 }
/* Fixbutton */
@ -43,6 +49,7 @@ a { color: black }
.notification .close:active, .notification .close:focus { color: #AF3BFF }
/* Notification types */
.notification-ask .notification-icon { background-color: #f39c12; }
.notification-info .notification-icon { font-size: 22px; font-weight: bold; background-color: #2980b9; line-height: 48px }
.notification-done .notification-icon { font-size: 22px; background-color: #27ae60 }

View File

@ -12,6 +12,12 @@ a { color: black }
#inner-iframe { width: 100%; height: 100%; position: absolute; border: 0px; -webkit-transition: all 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55), opacity 0.8s ease-in-out ; -moz-transition: all 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55), opacity 0.8s ease-in-out ; -o-transition: all 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55), opacity 0.8s ease-in-out ; -ms-transition: all 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55), opacity 0.8s ease-in-out ; transition: all 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55), opacity 0.8s ease-in-out }
#inner-iframe.back { -webkit-transform: scale(0.95) translate(-300px, 0px); -moz-transform: scale(0.95) translate(-300px, 0px); -o-transform: scale(0.95) translate(-300px, 0px); -ms-transform: scale(0.95) translate(-300px, 0px); transform: scale(0.95) translate(-300px, 0px) ; opacity: 0.4 }
.button { padding: 5px 10px; margin-left: 10px; background-color: #FFF85F; border-bottom: 2px solid #CDBD1E; -webkit-border-radius: 2px; -moz-border-radius: 2px; -o-border-radius: 2px; -ms-border-radius: 2px; border-radius: 2px ; text-decoration: none; -webkit-transition: all 0.5s; -moz-transition: all 0.5s; -o-transition: all 0.5s; -ms-transition: all 0.5s; transition: all 0.5s ; }
.button:hover { background-color: #FFF400; border-bottom: 2px solid #4D4D4C; -webkit-transition: none ; -moz-transition: none ; -o-transition: none ; -ms-transition: none ; transition: none }
.button:active { position: relative; top: 1px }
.button-Delete { background-color: #e74c3c; border-bottom-color: #c0392b; color: white }
.button-Delete:hover { background-color: #FF5442; border-bottom-color: #8E2B21 }
/* Fixbutton */
@ -48,6 +54,7 @@ a { color: black }
.notification .close:active, .notification .close:focus { color: #AF3BFF }
/* Notification types */
.notification-ask .notification-icon { background-color: #f39c12; }
.notification-info .notification-icon { font-size: 22px; font-weight: bold; background-color: #2980b9; line-height: 48px }
.notification-done .notification-icon { font-size: 22px; background-color: #27ae60 }

View File

@ -571,10 +571,16 @@ jQuery.extend( jQuery.easing,
$(".notification-icon", elem).html("!");
} else if (type === "done") {
$(".notification-icon", elem).html("<div class='icon-success'></div>");
} else if (type === "ask") {
$(".notification-icon", elem).html("?");
} else {
$(".notification-icon", elem).html("i");
}
$(".body", elem).html(body);
if (typeof body === "string") {
$(".body", elem).html(body);
} else {
$(".body", elem).html("").append(body);
}
elem.appendTo(this.elem);
if (timeout) {
$(".close", elem).remove();
@ -604,7 +610,12 @@ jQuery.extend( jQuery.easing,
return false;
};
})(this));
return this;
return $(".button", elem).on("click", (function(_this) {
return function() {
_this.close(elem);
return false;
};
})(this));
};
Notifications.prototype.close = function(elem) {
@ -771,12 +782,39 @@ 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]);
} else if (cmd === "wrapperConfirm") {
return this.actionWrapperConfirm(message);
} else {
return this.ws.send(message);
}
};
Wrapper.prototype.actionWrapperConfirm = function(message) {
var body, button, caption;
message.params = this.toHtmlSafe(message.params);
if (message.params[1]) {
caption = message.params[1];
} else {
caption = "ok";
}
body = $("<span>" + message.params[0] + "</span>");
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": "boom"
});
return false;
};
})(this));
body.append(button);
return this.notifications.add("notification-" + message.id, "ask", body);
};
Wrapper.prototype.onOpenWebsocket = function(e) {
this.ws.cmd("channelJoin", {
"channel": "siteChanged"
@ -858,6 +896,9 @@ jQuery.extend( jQuery.easing,
this.loading.printLine("" + site_info.event[1] + " downloaded");
if (site_info.event[1] === window.inner_path) {
this.loading.hideScreen();
if (!this.site_info) {
this.reloadSiteInfo();
}
if (!$(".loadingscreen").length) {
this.notifications.add("modified", "info", "New version of this page has just released.<br>Reload to see the modified content.");
}
@ -880,6 +921,10 @@ jQuery.extend( jQuery.easing,
return this.site_info = site_info;
};
Wrapper.prototype.toHtmlSafe = function(unsafe) {
return unsafe;
};
Wrapper.prototype.log = function() {
var args;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];

View File

@ -9,13 +9,14 @@ class WorkerManager:
self.site = site
self.workers = {} # Key: ip:port, Value: Worker.Worker
self.tasks = [] # {"evt": evt, "workers_num": 0, "site": self.site, "inner_path": inner_path, "done": False, "time_started": None, "time_added": time.time(), "peers": peers, "priority": 0}
self.running = True
self.log = logging.getLogger("WorkerManager:%s" % self.site.address_short)
self.process_taskchecker = gevent.spawn(self.checkTasks)
# Check expired tasks
def checkTasks(self):
while 1:
while self.running:
time.sleep(15) # Check every 15 sec
# Clean up workers
@ -42,6 +43,7 @@ class WorkerManager:
task["peers"] = []
self.startWorkers()
break # One reannounce per loop
self.log.debug("checkTasks stopped running")
@ -91,6 +93,16 @@ class WorkerManager:
if worker: self.log.debug("Added worker: %s, workers: %s/%s" % (key, len(self.workers), MAX_WORKERS))
# Stop all worker
def stopWorkers(self):
for worker in self.workers.values():
worker.stop()
tasks = self.tasks[:] # Copy
for task in tasks: # Mark all current task as failed
self.failTask(task)
# Find workers by task
def findWorkers(self, task):
workers = []