Merge remote-tracking branch 'refs/remotes/HelloZeroNet/master'
This commit is contained in:
commit
a7f21f0680
57 changed files with 917 additions and 184 deletions
|
@ -70,6 +70,7 @@ Decentralized websites using Bitcoin crypto and the BitTorrent network - https:/
|
|||
* [Linux 32bit](https://github.com/HelloZeroNet/ZeroBundle/raw/master/dist/ZeroBundle-linux32.tar.gz)
|
||||
* Unpack anywhere
|
||||
* Run `ZeroNet.cmd` (win), `ZeroNet(.app)` (osx), `ZeroNet.sh` (linux)
|
||||
* On OSX you may need to make the application executable via `chmod +x ZeroNet.app`
|
||||
|
||||
If you get "classic environment no longer supported" error on OS X: Open a Terminal window and drop ZeroNet.app on it
|
||||
|
||||
|
@ -187,4 +188,4 @@ Site:13DNDk..bhC2 Successfuly published to 3 peers
|
|||
|
||||
* More info, help, changelog, zeronet sites: https://www.reddit.com/r/zeronet/
|
||||
* Come, chat with us: [#zeronet @ FreeNode](https://kiwiirc.com/client/irc.freenode.net/zeronet) or on [gitter](https://gitter.im/HelloZeroNet/ZeroNet)
|
||||
* Email: hello@noloop.me
|
||||
* Email: hello@zeronet.io (PGP: CB9613AE)
|
||||
|
|
|
@ -5,6 +5,7 @@ from Plugin import PluginManager
|
|||
from Translate import Translate
|
||||
from util import RateLimit
|
||||
from util import helper
|
||||
from Debug import Debug
|
||||
try:
|
||||
import OptionalManager.UiWebsocketPlugin # To make optioanlFileInfo merger sites compatible
|
||||
except Exception:
|
||||
|
@ -19,6 +20,7 @@ if "merger_db" not in locals().keys(): # To keep merger_sites between module re
|
|||
if "_" not in locals():
|
||||
_ = Translate("plugins/MergerSite/languages/")
|
||||
|
||||
|
||||
# Check if the site has permission to this merger site
|
||||
def checkMergerPath(address, inner_path):
|
||||
merged_match = re.match("^merged-(.*?)/([A-Za-z0-9]{26,35})/", inner_path)
|
||||
|
@ -32,7 +34,10 @@ def checkMergerPath(address, inner_path):
|
|||
inner_path = re.sub("^merged-(.*?)/([A-Za-z0-9]{26,35})/", "", inner_path)
|
||||
return merged_address, inner_path
|
||||
else:
|
||||
raise Exception("Merger site (%s) does not have permission for merged site: %s" % (merger_type, merged_address))
|
||||
raise Exception(
|
||||
"Merger site (%s) does not have permission for merged site: %s (%s)" %
|
||||
(merger_type, merged_address, merged_db.get(merged_address))
|
||||
)
|
||||
else:
|
||||
raise Exception("No merger (%s) permission to load: <br>%s (%s not in %s)" % (
|
||||
address, inner_path, merger_type, merger_db.get(address, []))
|
||||
|
@ -184,7 +189,8 @@ class UiWebsocketPlugin(object):
|
|||
|
||||
def actionPermissionAdd(self, to, permission):
|
||||
super(UiWebsocketPlugin, self).actionPermissionAdd(to, permission)
|
||||
self.site.storage.rebuildDb()
|
||||
if permission.startswith("Merger"):
|
||||
self.site.storage.rebuildDb()
|
||||
|
||||
|
||||
@PluginManager.registerTo("UiRequest")
|
||||
|
@ -269,7 +275,6 @@ class SitePlugin(object):
|
|||
for ws in merger_site.websockets:
|
||||
ws.event("siteChanged", self, {"event": ["file_done", inner_path]})
|
||||
|
||||
|
||||
def fileFailed(self, inner_path):
|
||||
super(SitePlugin, self).fileFailed(inner_path)
|
||||
|
||||
|
@ -294,7 +299,11 @@ class SiteManagerPlugin(object):
|
|||
return
|
||||
for site in self.sites.itervalues():
|
||||
# Update merged sites
|
||||
merged_type = site.content_manager.contents.get("content.json", {}).get("merged_type")
|
||||
try:
|
||||
merged_type = site.content_manager.contents.get("content.json", {}).get("merged_type")
|
||||
except Exception, err:
|
||||
self.log.error("Error loading site %s: %s" % (site.address, Debug.formatException(err)))
|
||||
continue
|
||||
if merged_type:
|
||||
merged_db[site.address] = merged_type
|
||||
|
||||
|
@ -303,7 +312,10 @@ class SiteManagerPlugin(object):
|
|||
if not permission.startswith("Merger:"):
|
||||
continue
|
||||
if merged_type:
|
||||
self.log.error("Removing permission %s from %s: Merger and merged at the same time." % (permission, site.address))
|
||||
self.log.error(
|
||||
"Removing permission %s from %s: Merger and merged at the same time." %
|
||||
(permission, site.address)
|
||||
)
|
||||
site.settings["permissions"].remove(permission)
|
||||
continue
|
||||
merger_type = permission.replace("Merger:", "")
|
||||
|
|
5
plugins/MergerSite/languages/fr.json
Normal file
5
plugins/MergerSite/languages/fr.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"Add <b>%s</b> new site?": "Ajouter le site <b>%s</b> ?",
|
||||
"Added <b>%s</b> new site": "Site <b>%s</b> ajouté",
|
||||
"Site deleted: <b>%s</b>": "Site <b>%s</b> supprimé"
|
||||
}
|
5
plugins/MergerSite/languages/tr.json
Normal file
5
plugins/MergerSite/languages/tr.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"Add <b>%s</b> new site?": "<b>%s</b> sitesi eklensin mi?",
|
||||
"Added <b>%s</b> new site": "<b>%s</b> sitesi eklendi",
|
||||
"Site deleted: <b>%s</b>": "<b>%s</b> sitesi silindi"
|
||||
}
|
|
@ -157,7 +157,10 @@ class ContentDbPlugin(object):
|
|||
def setContentFilesOptional(self, site, content_inner_path, content, cur=None):
|
||||
if not cur:
|
||||
cur = self
|
||||
cur.execute("BEGIN")
|
||||
try:
|
||||
cur.execute("BEGIN")
|
||||
except Exception, err:
|
||||
self.log.warning("Transaction begin error %s %s: %s" % (site, content_inner_path, Debug.formatException(err)))
|
||||
|
||||
num = 0
|
||||
site_id = self.site_ids[site.address]
|
||||
|
@ -190,8 +193,10 @@ class ContentDbPlugin(object):
|
|||
num += 1
|
||||
|
||||
if cur == self:
|
||||
cur.execute("END")
|
||||
|
||||
try:
|
||||
cur.execute("END")
|
||||
except Exception, err:
|
||||
self.log.warning("Transaction end error %s %s: %s" % (site, content_inner_path, Debug.formatException(err)))
|
||||
return num
|
||||
|
||||
def setContent(self, site, inner_path, content, size=0):
|
||||
|
|
7
plugins/OptionalManager/languages/fr.json
Normal file
7
plugins/OptionalManager/languages/fr.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"Pinned %s files": "Fichiers %s épinglés",
|
||||
"Removed pin from %s files": "Fichiers %s ne sont plus épinglés",
|
||||
"You started to help distribute <b>%s</b>.<br><small>Directory: %s</small>": "Vous avez commencé à aider à distribuer <b>%s</b>.<br><small>Dossier : %s</small>",
|
||||
"Help distribute all new optional files on site <b>%s</b>": "Aider à distribuer tous les fichiers optionnels du site <b>%s</b>",
|
||||
"Yes, I want to help!": "Oui, je veux aider !"
|
||||
}
|
|
@ -191,7 +191,7 @@ class UiWebsocketPlugin(object):
|
|||
size = max(0, size_other)
|
||||
elif extension == "Image":
|
||||
size = size_filetypes.get("jpg", 0) + size_filetypes.get("png", 0) + size_filetypes.get("gif", 0)
|
||||
elif extension == "total":
|
||||
elif extension == "Total":
|
||||
size = size_total
|
||||
else:
|
||||
size = size_filetypes.get(extension, 0)
|
||||
|
@ -411,12 +411,11 @@ class UiWebsocketPlugin(object):
|
|||
# Choose content you want to sign
|
||||
contents = ["content.json"]
|
||||
contents += site.content_manager.contents.get("content.json", {}).get("includes", {}).keys()
|
||||
if len(contents) > 1:
|
||||
body.append(_(u"<div class='contents'>{_[Choose]}: "))
|
||||
for content in contents:
|
||||
content = cgi.escape(content, True)
|
||||
body.append(_("<a href='#{content}' onclick='$(\"#input-contents\").val(\"{content}\"); return false'>{content}</a> "))
|
||||
body.append("</div>")
|
||||
body.append(_(u"<div class='contents'>{_[Choose]}: "))
|
||||
for content in contents:
|
||||
content = cgi.escape(content, True)
|
||||
body.append(_("<a href='#{content}' onclick='$(\"#input-contents\").val(\"{content}\"); return false'>{content}</a> "))
|
||||
body.append("</div>")
|
||||
|
||||
body.append(_(u"""
|
||||
<div class='flex'>
|
||||
|
@ -561,7 +560,7 @@ class UiWebsocketPlugin(object):
|
|||
globe_data += (lat, lon, ping)
|
||||
# Append myself
|
||||
loc = geodb.get(config.ip_external)
|
||||
if loc:
|
||||
if loc and loc.get("location"):
|
||||
lat, lon = (loc["location"]["latitude"], loc["location"]["longitude"])
|
||||
globe_data += (lat, lon, -0.135)
|
||||
|
||||
|
|
79
plugins/Sidebar/languages/es.json
Normal file
79
plugins/Sidebar/languages/es.json
Normal file
|
@ -0,0 +1,79 @@
|
|||
{
|
||||
"Peers": "Pares",
|
||||
"Connected": "Conectados",
|
||||
"Connectable": "Conectables",
|
||||
"Connectable peers": "Pares conectables",
|
||||
|
||||
"Data transfer": "Transferencia de datos",
|
||||
"Received": "Recibidos",
|
||||
"Received bytes": "Bytes recibidos",
|
||||
"Sent": "Enviados",
|
||||
"Sent bytes": "Bytes envidados",
|
||||
|
||||
"Files": "Ficheros",
|
||||
"Total": "Total",
|
||||
"Image": "Imagen",
|
||||
"Other": "Otro",
|
||||
"User data": "Datos del usuario",
|
||||
|
||||
"Size limit": "Límite de tamaño",
|
||||
"limit used": "Límite utilizado",
|
||||
"free space": "Espacio libre",
|
||||
"Set": "Establecer",
|
||||
|
||||
"Optional files": "Ficheros opcionales",
|
||||
"Downloaded": "Descargado",
|
||||
"Download and help distribute all files": "Descargar y ayudar a distribuir todos los ficheros",
|
||||
"Total size": "Tamaño total",
|
||||
"Downloaded files": "Ficheros descargados",
|
||||
|
||||
"Database": "Base de datos",
|
||||
"search feeds": "Fuentes de búsqueda",
|
||||
"{feeds} query": "{feeds} consulta",
|
||||
"Reload": "Recargar",
|
||||
"Rebuild": "Reconstruir",
|
||||
"No database found": "No se ha encontrado la base de datos",
|
||||
|
||||
"Identity address": "Dirección de la identidad",
|
||||
"Change": "Cambiar",
|
||||
|
||||
"Update": "Actualizar",
|
||||
"Pause": "Pausar",
|
||||
"Resume": "Reanudar",
|
||||
"Delete": "Borrar",
|
||||
|
||||
"Site address": "Dirección del sitio",
|
||||
"Donate": "Donar",
|
||||
|
||||
"Missing files": "Ficheros perdidos",
|
||||
"{} try": "{} intento",
|
||||
"{} tries": "{} intentos",
|
||||
"+ {num_bad_files} more": "+ {num_bad_files} más",
|
||||
|
||||
"This is my site": "Este es mi sitio",
|
||||
"Site title": "Título del sitio",
|
||||
"Site description": "Descripción del sitio",
|
||||
"Save site settings": "Guardar la configuración del sitio",
|
||||
|
||||
"Content publishing": "Publicación del contenido",
|
||||
"Choose": "Elegir",
|
||||
"Sign": "Firmar",
|
||||
"Publish": "Publicar",
|
||||
"This function is disabled on this proxy": "Esta función está deshabilitada en este proxy",
|
||||
"GeoLite2 City database download error: {}!<br>Please download manually and unpack to data dir:<br>{}": "¡Error de la base de datos GeoLite2: {}!<br>Por favor, descárgalo manualmente y descomprime al directorio de datos:<br>{}",
|
||||
"Downloading GeoLite2 City database (one time only, ~20MB)...": "Descargando la base de datos de GeoLite2 (una única vez, ~20MB)...",
|
||||
"GeoLite2 City database downloaded!": "¡Base de datos de GeoLite2 descargada!",
|
||||
|
||||
"Are you sure?": "¿Estás seguro?",
|
||||
"Site storage limit modified!": "¡Límite de almacenamiento del sitio modificado!",
|
||||
"Database schema reloaded!": "¡Esquema de la base de datos recargado!",
|
||||
"Database rebuilding....": "Reconstruyendo la base de datos...",
|
||||
"Database rebuilt!": "¡Base de datos reconstruida!",
|
||||
"Site updated!": "¡Sitio actualizado!",
|
||||
"Delete this site": "Borrar este sitio",
|
||||
"File write error: ": "Error de escritura de fichero:",
|
||||
"Site settings saved!": "¡Configuración del sitio guardada!",
|
||||
"Enter your private key:": "Introduce tu clave privada:",
|
||||
" Signed!": " ¡firmado!",
|
||||
"WebGL not supported": "WebGL no está soportado"
|
||||
}
|
82
plugins/Sidebar/languages/pl.json
Normal file
82
plugins/Sidebar/languages/pl.json
Normal file
|
@ -0,0 +1,82 @@
|
|||
{
|
||||
"Peers": "Użytkownicy równorzędni",
|
||||
"Connected": "Połączony",
|
||||
"Connectable": "Możliwy do podłączenia",
|
||||
"Connectable peers": "Połączeni użytkownicy równorzędni",
|
||||
|
||||
"Data transfer": "Transfer danych",
|
||||
"Received": "Odebrane",
|
||||
"Received bytes": "Odebrany bajty",
|
||||
"Sent": "Wysłane",
|
||||
"Sent bytes": "Wysłane bajty",
|
||||
|
||||
"Files": "Pliki",
|
||||
"Total": "Sumarycznie",
|
||||
"Image": "Obraz",
|
||||
"Other": "Inne",
|
||||
"User data": "Dane użytkownika",
|
||||
|
||||
"Size limit": "Rozmiar limitu",
|
||||
"limit used": "zużyty limit",
|
||||
"free space": "wolna przestrzeń",
|
||||
"Set": "Ustaw",
|
||||
|
||||
"Optional files": "Pliki opcjonalne",
|
||||
"Downloaded": "Ściągnięte",
|
||||
"Download and help distribute all files": "Ściągnij i pomóż rozpowszechniać wszystkie pliki",
|
||||
"Total size": "Rozmiar sumaryczny",
|
||||
"Downloaded files": "Ściągnięte pliki",
|
||||
|
||||
"Database": "Baza danych",
|
||||
"search feeds": "przeszukaj zasoby",
|
||||
"{feeds} query": "{feeds} pytanie",
|
||||
"Reload": "Odśwież",
|
||||
"Rebuild": "Odbuduj",
|
||||
"No database found": "Nie odnaleziono bazy danych",
|
||||
|
||||
"Identity address": "Adres identyfikacyjny",
|
||||
"Change": "Zmień",
|
||||
|
||||
"Site control": "Kontrola strony",
|
||||
"Update": "Zaktualizuj",
|
||||
"Pause": "Wstrzymaj",
|
||||
"Resume": "Wznów",
|
||||
"Delete": "Skasuj",
|
||||
"Are you sure?": "Jesteś pewien?",
|
||||
|
||||
"Site address": "Adres strony",
|
||||
"Donate": "Wspomóż",
|
||||
|
||||
"Missing files": "Brakujące pliki",
|
||||
"{} try": "{} próba",
|
||||
"{} tries": "{} próby",
|
||||
"+ {num_bad_files} more": "+ {num_bad_files} więcej",
|
||||
|
||||
"This is my site": "To moja strona",
|
||||
"Site title": "Tytuł strony",
|
||||
"Site description": "Opis strony",
|
||||
"Save site settings": "Zapisz ustawienia strony",
|
||||
|
||||
"Content publishing": "Publikowanie treści",
|
||||
"Choose": "Wybierz",
|
||||
"Sign": "Podpisz",
|
||||
"Publish": "Opublikuj",
|
||||
|
||||
"This function is disabled on this proxy": "Ta funkcja jest zablokowana w tym proxy",
|
||||
"GeoLite2 City database download error: {}!<br>Please download manually and unpack to data dir:<br>{}": "Błąd ściągania bazy danych GeoLite2 City: {}!<br>Proszę ściągnąć ją recznie i wypakować do katalogu danych:<br>{}",
|
||||
"Downloading GeoLite2 City database (one time only, ~20MB)...": "Ściąganie bazy danych GeoLite2 City (tylko jednorazowo, ok. 20MB)...",
|
||||
"GeoLite2 City database downloaded!": "Baza danych GeoLite2 City ściagnięta!",
|
||||
|
||||
"Are you sure?": "Jesteś pewien?",
|
||||
"Site storage limit modified!": "Limit pamięci strony zmodyfikowany!",
|
||||
"Database schema reloaded!": "Schemat bazy danych załadowany ponownie!",
|
||||
"Database rebuilding....": "Przebudowywanie bazy danych...",
|
||||
"Database rebuilt!": "Baza danych przebudowana!",
|
||||
"Site updated!": "Strona zaktualizowana!",
|
||||
"Delete this site": "Usuń tę stronę",
|
||||
"File write error: ": "Błąd zapisu pliku: ",
|
||||
"Site settings saved!": "Ustawienia strony zapisane!",
|
||||
"Enter your private key:": "Wpisz swój prywatny klucz:",
|
||||
" Signed!": " Podpisane!",
|
||||
"WebGL not supported": "WebGL nie jest obsługiwany"
|
||||
}
|
|
@ -37,6 +37,8 @@ class Sidebar extends Class
|
|||
|
||||
# Detect dragging
|
||||
@fixbutton.on "mousedown touchstart", (e) =>
|
||||
if e.button > 0 # Right or middle click
|
||||
return
|
||||
e.preventDefault()
|
||||
|
||||
# Disable previous listeners
|
||||
|
@ -330,7 +332,7 @@ class Sidebar extends Class
|
|||
data["title"] = $("#settings-title").val()
|
||||
data["description"] = $("#settings-description").val()
|
||||
json_raw = unescape(encodeURIComponent(JSON.stringify(data, undefined, '\t')))
|
||||
wrapper.ws.cmd "fileWrite", ["content.json", btoa(json_raw)], (res) =>
|
||||
wrapper.ws.cmd "fileWrite", ["content.json", btoa(json_raw), true], (res) =>
|
||||
if res != "ok" # fileWrite failed
|
||||
wrapper.notifications.add "file-write", "error", "File write error: #{res}"
|
||||
else
|
||||
|
|
|
@ -237,6 +237,9 @@ window.initScrollable = function () {
|
|||
*/
|
||||
this.fixbutton.on("mousedown touchstart", (function(_this) {
|
||||
return function(e) {
|
||||
if (e.button > 0) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
_this.fixbutton.off("click touchstop touchcancel");
|
||||
_this.fixbutton.off("mousemove touchmove");
|
||||
|
@ -569,7 +572,7 @@ window.initScrollable = function () {
|
|||
data["title"] = $("#settings-title").val();
|
||||
data["description"] = $("#settings-description").val();
|
||||
json_raw = unescape(encodeURIComponent(JSON.stringify(data, void 0, '\t')));
|
||||
return wrapper.ws.cmd("fileWrite", ["content.json", btoa(json_raw)], function(res) {
|
||||
return wrapper.ws.cmd("fileWrite", ["content.json", btoa(json_raw), true], function(res) {
|
||||
if (res !== "ok") {
|
||||
return wrapper.notifications.add("file-write", "error", "File write error: " + res);
|
||||
} else {
|
||||
|
|
|
@ -130,7 +130,7 @@ class UiRequestPlugin(object):
|
|||
# Db
|
||||
yield "<br><br><b>Db</b>:<br>"
|
||||
for db in sys.modules["Db.Db"].opened_dbs:
|
||||
yield "- %.3fs: %s<br>" % (time.time() - db.last_query_time, db.db_path)
|
||||
yield "- %.3fs: %s<br>" % (time.time() - db.last_query_time, db.db_path.encode("utf8"))
|
||||
|
||||
# Sites
|
||||
yield "<br><br><b>Sites</b>:"
|
||||
|
@ -220,7 +220,7 @@ class UiRequestPlugin(object):
|
|||
objs = [obj for obj in gc.get_objects() if isinstance(obj, greenlet)]
|
||||
yield "<br>Greenlets (%s):<br>" % len(objs)
|
||||
for obj in objs:
|
||||
yield " - %.1fkb: %s<br>" % (self.getObjSize(obj, hpy), cgi.escape(repr(obj)))
|
||||
yield " - %.1fkb: %s<br>" % (self.getObjSize(obj, hpy), cgi.escape(repr(obj).encode("utf8")))
|
||||
|
||||
from Worker import Worker
|
||||
objs = [obj for obj in gc.get_objects() if isinstance(obj, Worker)]
|
||||
|
@ -401,7 +401,10 @@ class UiRequestPlugin(object):
|
|||
except Exception, err:
|
||||
output("<br><b>! Error: %s</b><br>" % err)
|
||||
taken = time.time() - s
|
||||
multipler = standard / taken
|
||||
if taken > 0:
|
||||
multipler = standard / taken
|
||||
else:
|
||||
multipler = 99
|
||||
if multipler < 0.3:
|
||||
speed = "Sloooow"
|
||||
elif multipler < 0.5:
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import time
|
||||
import os
|
||||
import sys
|
||||
import atexit
|
||||
|
@ -12,6 +11,7 @@ allow_reload = False # No source reload supported in this plugin
|
|||
if "_" not in locals():
|
||||
_ = Translate("plugins/Trayicon/languages/")
|
||||
|
||||
|
||||
@PluginManager.registerTo("Actions")
|
||||
class ActionsPlugin(object):
|
||||
|
||||
|
@ -54,23 +54,26 @@ class ActionsPlugin(object):
|
|||
(_["ZeroNet Github"], lambda: self.opensite("https://github.com/HelloZeroNet/ZeroNet")),
|
||||
(_["Report bug/request feature"], lambda: self.opensite("https://github.com/HelloZeroNet/ZeroNet/issues")),
|
||||
"--",
|
||||
(_["!Open ZeroNet"], lambda: self.opensite("http://%s:%s/%s" % (ui_ip, config.ui_port, config.homepage) )),
|
||||
(_["!Open ZeroNet"], lambda: self.opensite("http://%s:%s/%s" % (ui_ip, config.ui_port, config.homepage))),
|
||||
"--",
|
||||
(_["Quit"], self.quit),
|
||||
|
||||
)
|
||||
|
||||
icon.clicked = lambda: self.opensite("http://%s:%s/%s" % (ui_ip, config.ui_port, config.homepage) )
|
||||
icon.clicked = lambda: self.opensite("http://%s:%s/%s" % (ui_ip, config.ui_port, config.homepage))
|
||||
self.quit_servers_event = gevent.threadpool.ThreadResult(
|
||||
lambda res: gevent.spawn_later(0.1, self.quitServers)
|
||||
) # Fix gevent thread switch error
|
||||
gevent.threadpool.start_new_thread(icon._run, ()) # Start in real thread (not gevent compatible)
|
||||
super(ActionsPlugin, self).main()
|
||||
icon._die = True
|
||||
|
||||
def quit(self):
|
||||
self.icon.die()
|
||||
time.sleep(0.1)
|
||||
sys.exit()
|
||||
# self.main.ui_server.stop()
|
||||
# self.main.file_server.stop()
|
||||
self.quit_servers_event.set(True)
|
||||
|
||||
def quitServers(self):
|
||||
self.main.ui_server.stop()
|
||||
self.main.file_server.stop()
|
||||
|
||||
def opensite(self, url):
|
||||
import webbrowser
|
||||
|
@ -115,19 +118,33 @@ class ActionsPlugin(object):
|
|||
|
||||
def formatAutorun(self):
|
||||
args = sys.argv[:]
|
||||
args.insert(0, sys.executable)
|
||||
|
||||
if not getattr(sys, 'frozen', False): # Not frozen
|
||||
args.insert(0, sys.executable)
|
||||
cwd = os.getcwd().decode(sys.getfilesystemencoding())
|
||||
else:
|
||||
cwd = os.path.dirname(sys.executable).decode(sys.getfilesystemencoding())
|
||||
|
||||
if sys.platform == 'win32':
|
||||
args = ['"%s"' % arg for arg in args]
|
||||
args = ['"%s"' % arg for arg in args if arg]
|
||||
cmd = " ".join(args)
|
||||
|
||||
# Dont open browser on autorun
|
||||
cmd = cmd.replace("start.py", "zeronet.py").replace('"--open_browser"', "").replace('"default_browser"', "").strip()
|
||||
cmd += ' --open_browser ""'
|
||||
cmd = cmd.decode(sys.getfilesystemencoding())
|
||||
|
||||
return "@echo off\ncd /D %s\n%s" % (os.getcwd(), cmd)
|
||||
return u"""
|
||||
@echo off
|
||||
chcp 65001
|
||||
set PYTHONIOENCODING=utf-8
|
||||
cd /D \"%s\"
|
||||
%s
|
||||
""" % (cwd, cmd)
|
||||
|
||||
def isAutorunEnabled(self):
|
||||
path = self.getAutorunPath()
|
||||
return os.path.isfile(path) and open(path).read() == self.formatAutorun()
|
||||
return os.path.isfile(path) and open(path).read().decode("utf8") == self.formatAutorun()
|
||||
|
||||
def titleAutorun(self):
|
||||
translate = _["Start ZeroNet when Windows starts"]
|
||||
|
@ -140,4 +157,4 @@ class ActionsPlugin(object):
|
|||
if self.isAutorunEnabled():
|
||||
os.unlink(self.getAutorunPath())
|
||||
else:
|
||||
open(self.getAutorunPath(), "w").write(self.formatAutorun())
|
||||
open(self.getAutorunPath(), "w").write(self.formatAutorun().encode("utf8"))
|
||||
|
|
14
plugins/Trayicon/languages/fr.json
Normal file
14
plugins/Trayicon/languages/fr.json
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"ZeroNet Twitter": "ZeroNet Twitter",
|
||||
"ZeroNet Reddit": "ZeroNet Reddit",
|
||||
"ZeroNet Github": "ZeroNet Github",
|
||||
"Report bug/request feature": "Rapport d'erreur/Demanger une fonctionnalité",
|
||||
"!Open ZeroNet": "!Ouvrir ZeroNet",
|
||||
"Quit": "Quitter",
|
||||
"(active)": "(actif)",
|
||||
"(passive)": "(passif)",
|
||||
"Connections: %s": "Connexions: %s",
|
||||
"Received: %.2f MB | Sent: %.2f MB": "Reçu: %.2f MB | Envoyé: %.2f MB",
|
||||
"Show console window": "Afficher la console",
|
||||
"Start ZeroNet when Windows starts": "Lancer ZeroNet au démarrage de Windows"
|
||||
}
|
14
plugins/Trayicon/languages/tr.json
Normal file
14
plugins/Trayicon/languages/tr.json
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"ZeroNet Twitter": "ZeroNet Twitter",
|
||||
"ZeroNet Reddit": "ZeroNet Reddit",
|
||||
"ZeroNet Github": "ZeroNet Github",
|
||||
"Report bug/request feature": "Hata bildir/geliştirme taleb et",
|
||||
"!Open ZeroNet": "!ZeroNet'i Aç",
|
||||
"Quit": "Kapat",
|
||||
"(active)": "(aktif)",
|
||||
"(passive)": "(pasif)",
|
||||
"Connections: %s": "Bağlantı sayısı: %s",
|
||||
"Received: %.2f MB | Sent: %.2f MB": "Gelen: %.2f MB | Gönderilen: %.2f MB",
|
||||
"Show console window": "Konsolu aç",
|
||||
"Start ZeroNet when Windows starts": "ZeroNet'i açılışta otomatik başlat"
|
||||
}
|
|
@ -130,7 +130,7 @@ class BootstrapperDb(Db):
|
|||
|
||||
where = "hash_id = :hashid"
|
||||
if onions:
|
||||
onions_escaped = ["'%s'" % re.sub("[^a-z0-9,]", "", onion) for onion in onions]
|
||||
onions_escaped = ["'%s'" % re.sub("[^a-z0-9,]", "", onion) for onion in onions if type(onion) is str]
|
||||
where += " AND (onion NOT IN (%s) OR onion IS NULL)" % ",".join(onions_escaped)
|
||||
elif ip4:
|
||||
where += " AND (NOT (ip4 = :ip4 AND port = :port) OR ip4 IS NULL)"
|
||||
|
|
|
@ -2,6 +2,7 @@ import argparse
|
|||
import sys
|
||||
import os
|
||||
import locale
|
||||
import re
|
||||
import ConfigParser
|
||||
|
||||
|
||||
|
@ -9,7 +10,7 @@ class Config(object):
|
|||
|
||||
def __init__(self, argv):
|
||||
self.version = "0.5.1"
|
||||
self.rev = 1766
|
||||
self.rev = 1848
|
||||
self.argv = argv
|
||||
self.action = None
|
||||
self.config_file = "zeronet.conf"
|
||||
|
@ -37,7 +38,7 @@ class Config(object):
|
|||
"udp://tracker.coppersurfer.tk:6969",
|
||||
"udp://tracker.leechers-paradise.org:6969",
|
||||
"udp://9.rarbg.com:2710",
|
||||
"http://tracker.tordb.ml:6881/announce",
|
||||
"http://tracker.opentrackr.org:1337/announce",
|
||||
"http://explodie.org:6969/announce",
|
||||
"http://tracker1.wasabii.com.tw:6969/announce"
|
||||
]
|
||||
|
@ -55,6 +56,35 @@ class Config(object):
|
|||
|
||||
use_openssl = True
|
||||
|
||||
if repr(1483108852.565) != "1483108852.565":
|
||||
fix_float_decimals = True
|
||||
else:
|
||||
fix_float_decimals = False
|
||||
|
||||
this_file = os.path.abspath(__file__).replace("\\", "/")
|
||||
|
||||
if this_file.endswith("/Contents/Resources/core/src/Config.py"):
|
||||
# Running as ZeroNet.app
|
||||
if this_file.startswith("/Application") or this_file.startswith("/private") or this_file.startswith(os.path.expanduser("~/Library")):
|
||||
# Runnig from non-writeable directory, put data to Application Support
|
||||
start_dir = os.path.expanduser("~/Library/Application Support/ZeroNet").decode(sys.getfilesystemencoding())
|
||||
else:
|
||||
# Running from writeable directory put data next to .app
|
||||
start_dir = re.sub("/[^/]+/Contents/Resources/core/src/Config.py", "", this_file).decode(sys.getfilesystemencoding())
|
||||
config_file = start_dir + "/zeronet.conf"
|
||||
data_dir = start_dir + "/data"
|
||||
log_dir = start_dir + "/log"
|
||||
elif this_file.endswith("/core/src/Config.py"):
|
||||
# Running as exe or source is at Application Support directory, put var files to outside of core dir
|
||||
start_dir = this_file.replace("/core/src/Config.py", "").decode(sys.getfilesystemencoding())
|
||||
config_file = start_dir + "/zeronet.conf"
|
||||
data_dir = start_dir + "/data"
|
||||
log_dir = start_dir + "/log"
|
||||
else:
|
||||
config_file = "zeronet.conf"
|
||||
data_dir = "data"
|
||||
log_dir = "log"
|
||||
|
||||
# Main
|
||||
action = self.subparsers.add_parser("main", help='Start UiServer and FileServer (default)')
|
||||
|
||||
|
@ -76,6 +106,7 @@ class Config(object):
|
|||
action.add_argument('privatekey', help='Private key (default: ask on execute)', nargs='?')
|
||||
action.add_argument('--inner_path', help='File you want to sign (default: content.json)',
|
||||
default="content.json", metavar="inner_path")
|
||||
action.add_argument('--remove_missing_optional', help='Remove optional files that is not present in the directory', action='store_true')
|
||||
action.add_argument('--publish', help='Publish site after the signing', action='store_true')
|
||||
|
||||
# SitePublish
|
||||
|
@ -134,9 +165,9 @@ class Config(object):
|
|||
|
||||
self.parser.add_argument('--batch', help="Batch mode (No interactive input for commands)", action='store_true')
|
||||
|
||||
self.parser.add_argument('--config_file', help='Path of config file', default="zeronet.conf", metavar="path")
|
||||
self.parser.add_argument('--data_dir', help='Path of data directory', default="data", metavar="path")
|
||||
self.parser.add_argument('--log_dir', help='Path of logging directory', default="log", metavar="path")
|
||||
self.parser.add_argument('--config_file', help='Path of config file', default=config_file, metavar="path")
|
||||
self.parser.add_argument('--data_dir', help='Path of data directory', default=data_dir, metavar="path")
|
||||
self.parser.add_argument('--log_dir', help='Path of logging directory', default=log_dir, metavar="path")
|
||||
|
||||
self.parser.add_argument('--language', help='Web interface language', default=language, metavar='language')
|
||||
self.parser.add_argument('--ui_ip', help='Web interface bind address', default="127.0.0.1", metavar='ip')
|
||||
|
@ -149,7 +180,7 @@ class Config(object):
|
|||
self.parser.add_argument('--updatesite', help='Source code update site', default='1UPDatEDxnvHDo7TXvq6AEBARfNkyfxsp',
|
||||
metavar='address')
|
||||
self.parser.add_argument('--size_limit', help='Default site size limit in MB', default=10, type=int, metavar='size')
|
||||
self.parser.add_argument('--connected_limit', help='Max connected peer per site', default=10, type=int, metavar='connected_limit')
|
||||
self.parser.add_argument('--connected_limit', help='Max connected peer per site', default=6, type=int, metavar='connected_limit')
|
||||
|
||||
self.parser.add_argument('--fileserver_ip', help='FileServer bind address', default="*", metavar='ip')
|
||||
self.parser.add_argument('--fileserver_port', help='FileServer bind port', default=15441, type=int, metavar='port')
|
||||
|
@ -173,6 +204,8 @@ class Config(object):
|
|||
type='bool', choices=[True, False], default=False)
|
||||
self.parser.add_argument("--msgpack_purepython", help='Use less memory, but a bit more CPU power',
|
||||
type='bool', choices=[True, False], default=True)
|
||||
self.parser.add_argument("--fix_float_decimals", help='Fix content.json modification date float precision on verification',
|
||||
type='bool', choices=[True, False], default=fix_float_decimals)
|
||||
|
||||
self.parser.add_argument('--coffeescript_compiler', help='Coffeescript compiler for developing', default=coffeescript,
|
||||
metavar='executable_path')
|
||||
|
@ -180,6 +213,7 @@ class Config(object):
|
|||
self.parser.add_argument('--tor', help='enable: Use only for Tor peers, always: Use Tor for every connection', choices=["disable", "enable", "always"], default='enable')
|
||||
self.parser.add_argument('--tor_controller', help='Tor controller address', metavar='ip:port', default='127.0.0.1:9051')
|
||||
self.parser.add_argument('--tor_proxy', help='Tor proxy address', metavar='ip:port', default='127.0.0.1:9050')
|
||||
self.parser.add_argument('--tor_password', help='Tor controller password', metavar='password')
|
||||
|
||||
self.parser.add_argument('--version', action='version', version='ZeroNet %s r%s' % (self.version, self.rev))
|
||||
|
||||
|
|
|
@ -78,6 +78,11 @@ class Connection(object):
|
|||
|
||||
def badAction(self, weight=1):
|
||||
self.bad_actions += weight
|
||||
if self.bad_actions > 40:
|
||||
self.close()
|
||||
elif self.bad_actions > 20:
|
||||
time.sleep(5)
|
||||
|
||||
|
||||
def goodAction(self):
|
||||
self.bad_actions = 0
|
||||
|
|
|
@ -55,7 +55,7 @@ class ConnectionServer:
|
|||
if port: # Listen server on a port
|
||||
self.pool = Pool(1000) # do not accept more than 1000 connections
|
||||
self.stream_server = StreamServer(
|
||||
(ip.replace("*", ""), port), self.handleIncomingConnection, spawn=self.pool, backlog=500
|
||||
(ip.replace("*", "0.0.0.0"), port), self.handleIncomingConnection, spawn=self.pool, backlog=500
|
||||
)
|
||||
if request_handler:
|
||||
self.handleRequest = request_handler
|
||||
|
|
|
@ -212,7 +212,7 @@ class ContentManager(object):
|
|||
# Update the content
|
||||
self.contents[content_inner_path] = new_content
|
||||
except Exception, err:
|
||||
self.log.warning("Content.json parse error: %s" % Debug.formatException(err))
|
||||
self.log.warning("%s parse error: %s" % (content_inner_path, Debug.formatException(err)))
|
||||
return [], [] # Content.json parse error
|
||||
|
||||
# Add changed files to bad files
|
||||
|
@ -315,6 +315,7 @@ class ContentManager(object):
|
|||
if back:
|
||||
back["content_inner_path"] = content_inner_path
|
||||
back["optional"] = False
|
||||
back["relative_path"] = "/".join(inner_path_parts)
|
||||
return back
|
||||
|
||||
# Check in optional files
|
||||
|
@ -323,6 +324,7 @@ class ContentManager(object):
|
|||
if back:
|
||||
back["content_inner_path"] = content_inner_path
|
||||
back["optional"] = True
|
||||
back["relative_path"] = "/".join(inner_path_parts)
|
||||
return back
|
||||
|
||||
# Return the rules if user dir
|
||||
|
@ -424,7 +426,7 @@ class ContentManager(object):
|
|||
# Get diffs for changed files
|
||||
def getDiffs(self, inner_path, limit=30 * 1024, update_files=True):
|
||||
if inner_path not in self.contents:
|
||||
return None
|
||||
return {}
|
||||
diffs = {}
|
||||
content_inner_path_dir = helper.getDirname(inner_path)
|
||||
for file_relative_path in self.contents[inner_path].get("files", {}):
|
||||
|
@ -491,7 +493,7 @@ class ContentManager(object):
|
|||
|
||||
# Create and sign a content.json
|
||||
# Return: The new content if filewrite = False
|
||||
def sign(self, inner_path="content.json", privatekey=None, filewrite=True, update_changed_files=False, extend=None):
|
||||
def sign(self, inner_path="content.json", privatekey=None, filewrite=True, update_changed_files=False, extend=None, remove_missing_optional=False):
|
||||
if inner_path in self.contents:
|
||||
content = self.contents[inner_path]
|
||||
if self.contents[inner_path].get("cert_sign", False) is None and self.site.storage.isFile(inner_path):
|
||||
|
@ -523,6 +525,11 @@ class ContentManager(object):
|
|||
helper.getDirname(inner_path), content.get("ignore"), content.get("optional")
|
||||
)
|
||||
|
||||
if not remove_missing_optional:
|
||||
for file_inner_path, file_details in content.get("files_optional", {}).iteritems():
|
||||
if file_inner_path not in files_optional_node:
|
||||
files_optional_node[file_inner_path] = file_details
|
||||
|
||||
# Find changed files
|
||||
files_merged = files_node.copy()
|
||||
files_merged.update(files_optional_node)
|
||||
|
@ -547,7 +554,7 @@ class ContentManager(object):
|
|||
elif "files_optional" in new_content:
|
||||
del new_content["files_optional"]
|
||||
|
||||
new_content["modified"] = time.time() # Add timestamp
|
||||
new_content["modified"] = int(time.time()) # Add timestamp
|
||||
if inner_path == "content.json":
|
||||
new_content["zeronet_version"] = config.version
|
||||
new_content["signs_required"] = content.get("signs_required", 1)
|
||||
|
@ -768,8 +775,18 @@ class ContentManager(object):
|
|||
del(new_content["sign"]) # The file signed without the sign
|
||||
if "signs" in new_content:
|
||||
del(new_content["signs"]) # The file signed without the signs
|
||||
|
||||
sign_content = json.dumps(new_content, sort_keys=True) # Dump the json to string to remove whitepsace
|
||||
|
||||
# Fix float representation error on Android
|
||||
modified = new_content["modified"]
|
||||
if config.fix_float_decimals and type(modified) is float and not str(modified).endswith(".0"):
|
||||
modified_fixed = "{:.6f}".format(modified).strip("0.")
|
||||
sign_content = sign_content.replace(
|
||||
'"modified": %s' % repr(modified),
|
||||
'"modified": %s' % modified_fixed
|
||||
)
|
||||
|
||||
if not self.verifyContent(inner_path, new_content):
|
||||
return False # Content not valid (files too large, invalid files)
|
||||
|
||||
|
|
|
@ -70,13 +70,14 @@ class CryptConnectionManager:
|
|||
return True # Files already exits
|
||||
|
||||
import subprocess
|
||||
cmd = "%s req -x509 -newkey rsa:2048 -sha256 -batch -keyout %s -out %s -nodes -config %s" % helper.shellquote(
|
||||
self.openssl_bin,
|
||||
config.data_dir+"/key-rsa.pem",
|
||||
config.data_dir+"/cert-rsa.pem",
|
||||
self.openssl_env["OPENSSL_CONF"]
|
||||
)
|
||||
proc = subprocess.Popen(
|
||||
"%s req -x509 -newkey rsa:2048 -sha256 -batch -keyout %s -out %s -nodes -config %s" % helper.shellquote(
|
||||
self.openssl_bin,
|
||||
config.data_dir+"/key-rsa.pem",
|
||||
config.data_dir+"/cert-rsa.pem",
|
||||
self.openssl_env["OPENSSL_CONF"]
|
||||
),
|
||||
cmd.encode(sys.getfilesystemencoding()),
|
||||
shell=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=self.openssl_env
|
||||
)
|
||||
back = proc.stdout.read().strip()
|
||||
|
|
|
@ -55,7 +55,7 @@ class Db(object):
|
|||
self.log.debug("Created Db path: %s" % self.db_dir)
|
||||
if not os.path.isfile(self.db_path):
|
||||
self.log.debug("Db file not exist yet: %s" % self.db_path)
|
||||
self.conn = sqlite3.connect(self.db_path)
|
||||
self.conn = sqlite3.connect(self.db_path, check_same_thread=False)
|
||||
self.conn.row_factory = sqlite3.Row
|
||||
self.conn.isolation_level = None
|
||||
self.cur = self.getCursor()
|
||||
|
|
|
@ -40,9 +40,11 @@ class DebugReloader:
|
|||
if (
|
||||
not evt.path or "%s/" % config.data_dir in evt.path or
|
||||
(not evt.path.endswith("py") and not evt.path.endswith("json")) or
|
||||
"Test" in evt.path or
|
||||
time.time() - self.last_chaged < 1
|
||||
):
|
||||
return False # Ignore *.pyc changes and no reload within 1 sec
|
||||
time.sleep(0.1) # Wait for lock release
|
||||
logging.debug("Changed: %s, reloading source." % evt.path)
|
||||
self.callback()
|
||||
self.last_chaged = time.time()
|
||||
|
|
|
@ -173,7 +173,9 @@ class FileRequest(object):
|
|||
file.seek(params["location"])
|
||||
file.read_bytes = FILE_BUFF
|
||||
file_size = os.fstat(file.fileno()).st_size
|
||||
assert params["location"] <= file_size, "Bad file location"
|
||||
if params["location"] > file_size:
|
||||
self.connection.badAction(5)
|
||||
raise Exception("Bad file location")
|
||||
|
||||
back = {
|
||||
"body": file,
|
||||
|
@ -212,7 +214,9 @@ class FileRequest(object):
|
|||
file.seek(params["location"])
|
||||
file_size = os.fstat(file.fileno()).st_size
|
||||
stream_bytes = min(FILE_BUFF, file_size - params["location"])
|
||||
assert stream_bytes >= 0, "Stream bytes out of range"
|
||||
if stream_bytes < 0:
|
||||
self.connection.badAction(5)
|
||||
raise Exception("Bad file location")
|
||||
|
||||
back = {
|
||||
"size": file_size,
|
||||
|
|
|
@ -398,7 +398,7 @@ class Site(object):
|
|||
gevent.joinall(content_threads)
|
||||
|
||||
# Publish worker
|
||||
def publisher(self, inner_path, peers, published, limit, event_done=None, diffs={}):
|
||||
def publisher(self, inner_path, peers, published, limit, diffs={}, event_done=None, cb_progress=None):
|
||||
file_size = self.storage.getSize(inner_path)
|
||||
content_json_modified = self.content_manager.contents[inner_path]["modified"]
|
||||
body = self.storage.read(inner_path)
|
||||
|
@ -457,6 +457,8 @@ class Site(object):
|
|||
|
||||
if result and "ok" in result:
|
||||
published.append(peer)
|
||||
if cb_progress and len(published) <= limit:
|
||||
cb_progress(len(published), limit)
|
||||
self.log.info("[OK] %s: %s %s/%s" % (peer.key, result["ok"], len(published), limit))
|
||||
else:
|
||||
if result == {"exception": "Timeout"}:
|
||||
|
@ -466,7 +468,7 @@ class Site(object):
|
|||
|
||||
# Update content.json on peers
|
||||
@util.Noparallel()
|
||||
def publish(self, limit="default", inner_path="content.json", diffs={}):
|
||||
def publish(self, limit="default", inner_path="content.json", diffs={}, cb_progress=None):
|
||||
published = [] # Successfully published (Peer)
|
||||
publishers = [] # Publisher threads
|
||||
|
||||
|
@ -498,7 +500,7 @@ class Site(object):
|
|||
|
||||
event_done = gevent.event.AsyncResult()
|
||||
for i in range(min(len(peers), limit, threads)):
|
||||
publisher = gevent.spawn(self.publisher, inner_path, peers, published, limit, event_done, diffs)
|
||||
publisher = gevent.spawn(self.publisher, inner_path, peers, published, limit, diffs, event_done, cb_progress)
|
||||
publishers.append(publisher)
|
||||
|
||||
event_done.get() # Wait for done
|
||||
|
@ -522,7 +524,7 @@ class Site(object):
|
|||
return len(published)
|
||||
|
||||
# Copy this site
|
||||
def clone(self, address, privatekey=None, address_index=None, overwrite=False):
|
||||
def clone(self, address, privatekey=None, address_index=None, root_inner_path="", overwrite=False):
|
||||
import shutil
|
||||
new_site = SiteManager.site_manager.need(address, all_file=False)
|
||||
default_dirs = [] # Dont copy these directories (has -default version)
|
||||
|
@ -530,16 +532,20 @@ class Site(object):
|
|||
if "-default" in dir_name:
|
||||
default_dirs.append(dir_name.replace("-default", ""))
|
||||
|
||||
self.log.debug("Cloning to %s, ignore dirs: %s" % (address, default_dirs))
|
||||
self.log.debug("Cloning to %s, ignore dirs: %s, root: %s" % (address, default_dirs, root_inner_path))
|
||||
|
||||
# Copy root content.json
|
||||
if not new_site.storage.isFile("content.json") and not overwrite:
|
||||
# Content.json not exist yet, create a new one from source site
|
||||
content_json = self.storage.loadJson("content.json")
|
||||
if self.storage.isFile(root_inner_path + "/content.json-default"):
|
||||
content_json = self.storage.loadJson(root_inner_path + "/content.json-default")
|
||||
else:
|
||||
content_json = self.storage.loadJson("content.json")
|
||||
if "domain" in content_json:
|
||||
del content_json["domain"]
|
||||
content_json["title"] = "my" + content_json["title"]
|
||||
content_json["cloned_from"] = self.address
|
||||
content_json["clone_root"] = root_inner_path
|
||||
content_json["files"] = {}
|
||||
if address_index:
|
||||
content_json["address_index"] = address_index # Site owner's BIP32 index
|
||||
|
@ -553,17 +559,29 @@ class Site(object):
|
|||
for file_relative_path in sorted(content["files"].keys()):
|
||||
file_inner_path = helper.getDirname(content_inner_path) + file_relative_path # Relative to content.json
|
||||
file_inner_path = file_inner_path.strip("/") # Strip leading /
|
||||
if not file_inner_path.startswith(root_inner_path):
|
||||
self.log.debug("[SKIP] %s (not in clone root)" % file_inner_path)
|
||||
continue
|
||||
if file_inner_path.split("/")[0] in default_dirs: # Dont copy directories that has -default postfixed alternative
|
||||
self.log.debug("[SKIP] %s (has default alternative)" % file_inner_path)
|
||||
continue
|
||||
file_path = self.storage.getPath(file_inner_path)
|
||||
|
||||
# Copy the file normally to keep the -default postfixed dir and file to allow cloning later
|
||||
file_path_dest = new_site.storage.getPath(file_inner_path)
|
||||
if root_inner_path:
|
||||
file_inner_path_dest = re.sub("^%s/" % re.escape(root_inner_path), "", file_inner_path)
|
||||
file_path_dest = new_site.storage.getPath(file_inner_path_dest)
|
||||
else:
|
||||
file_inner_path_dest = file_inner_path
|
||||
file_path_dest = new_site.storage.getPath(file_inner_path)
|
||||
|
||||
self.log.debug("[COPY] %s to %s..." % (file_inner_path, file_path_dest))
|
||||
dest_dir = os.path.dirname(file_path_dest)
|
||||
if not os.path.isdir(dest_dir):
|
||||
os.makedirs(dest_dir)
|
||||
if file_inner_path_dest == "content.json-default": # Don't copy root content.json-default
|
||||
continue
|
||||
|
||||
shutil.copy(file_path, file_path_dest)
|
||||
|
||||
# If -default in path, create a -default less copy of the file
|
||||
|
|
|
@ -36,7 +36,13 @@ class SiteManager(object):
|
|||
for address, settings in json.load(open("%s/sites.json" % config.data_dir)).iteritems():
|
||||
if address not in self.sites and os.path.isfile("%s/%s/content.json" % (config.data_dir, address)):
|
||||
s = time.time()
|
||||
self.sites[address] = Site(address, settings=settings)
|
||||
try:
|
||||
site = Site(address, settings=settings)
|
||||
site.content_manager.contents.get("content.json")
|
||||
except Exception, err:
|
||||
self.log.debug("Error loading site %s: %s" % (address, err))
|
||||
continue
|
||||
self.sites[address] = site
|
||||
self.log.debug("Loaded site %s in %.3fs" % (address, time.time() - s))
|
||||
added += 1
|
||||
address_found.append(address)
|
||||
|
|
|
@ -19,8 +19,8 @@ from Plugin import PluginManager
|
|||
class SiteStorage(object):
|
||||
def __init__(self, site, allow_create=True):
|
||||
self.site = site
|
||||
self.directory = "%s/%s" % (config.data_dir, self.site.address) # Site data diretory
|
||||
self.allowed_dir = os.path.abspath(self.directory.decode(sys.getfilesystemencoding())) # Only serve file within this dir
|
||||
self.directory = u"%s/%s" % (config.data_dir, self.site.address) # Site data diretory
|
||||
self.allowed_dir = os.path.abspath(self.directory) # Only serve file within this dir
|
||||
self.log = site.log
|
||||
self.db = None # Db class
|
||||
self.db_checked = False # Checked db tables since startup
|
||||
|
@ -166,8 +166,11 @@ class SiteStorage(object):
|
|||
with open(file_path, "wb") as file:
|
||||
shutil.copyfileobj(content, file) # Write buff to disk
|
||||
else: # Simple string
|
||||
with open(file_path, "wb") as file:
|
||||
file.write(content)
|
||||
if inner_path == "content.json" and os.path.isfile(file_path):
|
||||
helper.atomicWrite(file_path, content)
|
||||
else:
|
||||
with open(file_path, "wb") as file:
|
||||
file.write(content)
|
||||
del content
|
||||
self.onUpdated(inner_path)
|
||||
|
||||
|
@ -275,11 +278,10 @@ class SiteStorage(object):
|
|||
if not inner_path:
|
||||
return self.directory
|
||||
|
||||
file_path = u"%s/%s" % (self.directory, inner_path)
|
||||
if ".." in inner_path:
|
||||
raise Exception(u"File not allowed: %s" % inner_path)
|
||||
|
||||
if ".." in file_path:
|
||||
raise Exception(u"File not allowed: %s" % file_path)
|
||||
return file_path
|
||||
return u"%s/%s" % (self.directory, inner_path)
|
||||
|
||||
# Get site dir relative path
|
||||
def getInnerPath(self, path):
|
||||
|
@ -415,8 +417,8 @@ class SiteStorage(object):
|
|||
os.unlink(path)
|
||||
break
|
||||
except Exception, err:
|
||||
self.log.error("Error removing %s: %s, try #%s" % (path, err, retry))
|
||||
time.sleep(float(retry)/10)
|
||||
self.log.error("Error removing %s: %s, try #%s" % (path, err, retry))
|
||||
time.sleep(float(retry) / 10)
|
||||
self.onUpdated(inner_path, False)
|
||||
|
||||
self.log.debug("Deleting empty dirs...")
|
||||
|
|
|
@ -108,10 +108,10 @@ class TestContent:
|
|||
assert len(site.content_manager.hashfield) == 0
|
||||
|
||||
site.content_manager.contents["content.json"]["optional"] = "((data/img/zero.*))"
|
||||
content_optional = site.content_manager.sign(privatekey="5KUh3PvNm5HUWoCfSUfcYvfQ2g3PrRNJWr6Q9eqdBGu23mtMntv", filewrite=False)
|
||||
content_optional = site.content_manager.sign(privatekey="5KUh3PvNm5HUWoCfSUfcYvfQ2g3PrRNJWr6Q9eqdBGu23mtMntv", filewrite=False, remove_missing_optional=True)
|
||||
|
||||
del site.content_manager.contents["content.json"]["optional"]
|
||||
content_nooptional = site.content_manager.sign(privatekey="5KUh3PvNm5HUWoCfSUfcYvfQ2g3PrRNJWr6Q9eqdBGu23mtMntv", filewrite=False)
|
||||
content_nooptional = site.content_manager.sign(privatekey="5KUh3PvNm5HUWoCfSUfcYvfQ2g3PrRNJWr6Q9eqdBGu23mtMntv", filewrite=False, remove_missing_optional=True)
|
||||
|
||||
assert len(content_nooptional.get("files_optional", {})) == 0 # No optional files if no pattern
|
||||
assert len(content_optional["files_optional"]) > 0
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
import time
|
||||
|
||||
import gevent
|
||||
from gevent import monkey
|
||||
monkey.patch_all()
|
||||
|
||||
import util
|
||||
import gevent
|
||||
|
||||
class ExampleClass(object):
|
||||
def __init__(self):
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import time
|
||||
|
||||
import gevent
|
||||
from gevent import monkey
|
||||
monkey.patch_all()
|
||||
|
||||
from util import RateLimit
|
||||
|
||||
|
|
|
@ -18,14 +18,14 @@ class WaitForPageLoad(object):
|
|||
self.old_page = self.browser.find_element_by_tag_name('html')
|
||||
|
||||
def __exit__(self, *args):
|
||||
WebDriverWait(self.browser, 20).until(staleness_of(self.old_page))
|
||||
WebDriverWait(self.browser, 5).until(staleness_of(self.old_page))
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("resetSettings")
|
||||
@pytest.mark.webtest
|
||||
class TestWeb:
|
||||
def testFileSecurity(self, site_url):
|
||||
assert "Forbidden" in urllib.urlopen("%s/media/./sites.json" % site_url).read()
|
||||
assert "Not Found" in urllib.urlopen("%s/media/./sites.json" % site_url).read()
|
||||
assert "Forbidden" in urllib.urlopen("%s/media/../config.py" % site_url).read()
|
||||
assert "Forbidden" in urllib.urlopen("%s/media/1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr/../sites.json" % site_url).read()
|
||||
assert "Forbidden" in urllib.urlopen("%s/media/1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr/..//sites.json" % site_url).read()
|
||||
|
@ -34,10 +34,6 @@ class TestWeb:
|
|||
assert "Forbidden" in urllib.urlopen("%s/1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr/..//sites.json" % site_url).read()
|
||||
assert "Forbidden" in urllib.urlopen("%s/1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr/../../zeronet.py" % site_url).read()
|
||||
|
||||
def testHomepage(self, browser, site_url):
|
||||
browser.get("%s/1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr" % site_url)
|
||||
assert browser.title == "ZeroHello - ZeroNet"
|
||||
|
||||
def testLinkSecurity(self, browser, site_url):
|
||||
browser.get("%s/1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr/test/security.html" % site_url)
|
||||
assert browser.title == "ZeroHello - ZeroNet"
|
||||
|
|
|
@ -49,7 +49,7 @@ if os.path.isfile("%s-temp/content.db" % config.data_dir):
|
|||
|
||||
import gevent
|
||||
from gevent import monkey
|
||||
monkey.patch_all(thread=False)
|
||||
monkey.patch_all(thread=False, subprocess=False)
|
||||
|
||||
from Site import Site
|
||||
from Site import SiteManager
|
||||
|
@ -164,7 +164,9 @@ def user():
|
|||
def browser():
|
||||
try:
|
||||
from selenium import webdriver
|
||||
print "Starting phantomjs..."
|
||||
browser = webdriver.PhantomJS(executable_path=PHANTOMJS_PATH, service_log_path=os.path.devnull)
|
||||
print "Set window size..."
|
||||
browser.set_window_size(1400, 1000)
|
||||
except Exception, err:
|
||||
raise pytest.skip("Test requires selenium + phantomjs: %s" % err)
|
||||
|
|
|
@ -14,7 +14,10 @@ from Config import config
|
|||
from Crypt import CryptRsa
|
||||
from Site import SiteManager
|
||||
from lib.PySocks import socks
|
||||
from gevent.coros import RLock
|
||||
try:
|
||||
from gevent.coros import RLock
|
||||
except:
|
||||
from gevent.lock import RLock
|
||||
from util import helper
|
||||
from Debug import Debug
|
||||
|
||||
|
@ -75,7 +78,9 @@ class TorManager:
|
|||
|
||||
self.log.info("Starting Tor client %s..." % self.tor_exe)
|
||||
tor_dir = os.path.dirname(self.tor_exe)
|
||||
self.tor_process = subprocess.Popen(r"%s -f torrc" % self.tor_exe, cwd=tor_dir, close_fds=True)
|
||||
startupinfo = subprocess.STARTUPINFO()
|
||||
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
||||
self.tor_process = subprocess.Popen(r"%s -f torrc" % self.tor_exe, cwd=tor_dir, close_fds=True, startupinfo=startupinfo)
|
||||
for wait in range(1,10): # Wait for startup
|
||||
time.sleep(wait * 0.5)
|
||||
self.enabled = True
|
||||
|
@ -152,22 +157,26 @@ class TorManager:
|
|||
try:
|
||||
with self.lock:
|
||||
conn.connect((self.ip, self.port))
|
||||
res_protocol = self.send("PROTOCOLINFO", conn)
|
||||
|
||||
version = re.search('Tor="([0-9\.]+)', res_protocol).group(1)
|
||||
# Version 0.2.7.5 required because ADD_ONION support
|
||||
assert float(version.replace(".", "0", 2)) >= 207.5, "Tor version >=0.2.7.5 required, found: %s" % version
|
||||
|
||||
# Auth cookie file
|
||||
res_protocol = self.send("PROTOCOLINFO", conn)
|
||||
cookie_match = re.search('COOKIEFILE="(.*?)"', res_protocol)
|
||||
if cookie_match:
|
||||
cookie_file = cookie_match.group(1)
|
||||
cookie_file = cookie_match.group(1).decode("string-escape")
|
||||
auth_hex = binascii.b2a_hex(open(cookie_file, "rb").read())
|
||||
res_auth = self.send("AUTHENTICATE %s" % auth_hex, conn)
|
||||
elif config.tor_password:
|
||||
res_auth = self.send('AUTHENTICATE "%s"' % config.tor_password, conn)
|
||||
else:
|
||||
res_auth = self.send("AUTHENTICATE", conn)
|
||||
|
||||
assert "250 OK" in res_auth, "Authenticate error %s" % res_auth
|
||||
|
||||
# Version 0.2.7.5 required because ADD_ONION support
|
||||
res_version = self.send("GETINFO version", conn)
|
||||
version = re.search('version=([0-9\.]+)', res_version).group(1)
|
||||
assert float(version.replace(".", "0", 2)) >= 207.5, "Tor version >=0.2.7.5 required, found: %s" % version
|
||||
|
||||
self.status = u"Connected (%s)" % res_auth
|
||||
self.conn = conn
|
||||
except Exception, err:
|
||||
|
@ -232,10 +241,12 @@ class TorManager:
|
|||
if not conn:
|
||||
conn = self.conn
|
||||
self.log.debug("> %s" % cmd)
|
||||
back = ""
|
||||
for retry in range(2):
|
||||
try:
|
||||
conn.send("%s\r\n" % cmd)
|
||||
back = conn.recv(1024 * 64).decode("utf8", "ignore")
|
||||
conn.sendall("%s\r\n" % cmd)
|
||||
while not back.endswith("250 OK\r\n"):
|
||||
back += conn.recv(1024 * 64).decode("utf8", "ignore")
|
||||
break
|
||||
except Exception, err:
|
||||
self.log.error("Tor send error: %s, reconnecting..." % err)
|
||||
|
|
51
src/Translate/languages/es.json
Normal file
51
src/Translate/languages/es.json
Normal file
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"Congratulation, your port <b>{0}</b> is opened.<br>You are full member of ZeroNet network!": "¡Felicidades! tu puerto <b>{0}</b> está abierto.<br>¡Eres un miembro completo de la red Zeronet!",
|
||||
"Tor mode active, every connection using Onion route.": "Modo Tor activado, cada conexión usa una ruta Onion.",
|
||||
"Successfully started Tor onion hidden services.": "Tor ha iniciado satisfactoriamente la ocultación de los servicios onion.",
|
||||
"Unable to start hidden services, please check your config.": "No se puedo iniciar los servicios ocultos, por favor comprueba tu configuración.",
|
||||
"For faster connections open <b>{0}</b> port on your router.": "Para conexiones más rápidas abre el puerto <b>{0}</b> en tu router.",
|
||||
"Your connection is restricted. Please, open <b>{0}</b> port on your router": "Tu conexión está limitada. Por favor, abre el puerto <b>{0}</b> en tu router",
|
||||
"or configure Tor to become full member of ZeroNet network.": "o configura Tor para convertirte en un miembro completo de la red ZeroNet.",
|
||||
|
||||
"Select account you want to use in this site:": "Selecciona la cuenta que quieres utilizar en este sitio:",
|
||||
"currently selected": "actualmente seleccionada",
|
||||
"Unique to site": "Única para el sitio",
|
||||
|
||||
"Content signing failed": "Firma del contenido fallida",
|
||||
"Content publish queued for {0:.0f} seconds.": "Publicación de contenido en cola durante {0:.0f} segundos.",
|
||||
"Content published to {0} peers.": "Contenido publicado para {0} pares.",
|
||||
"No peers found, but your content is ready to access.": "No se ha encontrado pares, pero tu contenido está listo para ser accedido.",
|
||||
"Your network connection is restricted. Please, open <b>{0}</b> port": "Tu conexión de red está restringida. Por favor, abre el puerto<b>{0}</b>",
|
||||
"on your router to make your site accessible for everyone.": "en tu router para hacer tu sitio accesible a todo el mundo.",
|
||||
"Content publish failed.": "Publicación de contenido fallida.",
|
||||
"This file still in sync, if you write it now, then the previous content may be lost.": "Este archivo está aún sincronizado, si le escribes ahora el contenido previo podría perderse.",
|
||||
"Write content anyway": "Escribir el contenido de todas formas",
|
||||
"New certificate added:": "Nuevo certificado añadido:",
|
||||
"You current certificate:": "Tu certificado actual:",
|
||||
"Change it to {auth_type}/{auth_user_name}@{domain}": "Cambia esto a {auth_type}/{auth_user_name}@{domain}",
|
||||
"Certificate changed to: <b>{auth_type}/{auth_user_name}@{domain}</b>.": "Certificado cambiado a: <b>{auth_type}/{auth_user_name}@{domain}</b>.",
|
||||
"Site cloned": "Sitio clonado",
|
||||
|
||||
"You have successfully changed the web interface's language!": "¡Has cambiado con éxito el idioma de la interfaz web!",
|
||||
"Due to the browser's caching, the full transformation could take some minute.": "Debido a la caché del navegador, la transformación completa podría llevar unos minutos.",
|
||||
|
||||
"Connection with <b>UiServer Websocket</b> was lost. Reconnecting...": "Se perdió la conexión con <b>UiServer Websocket</b>. Reconectando...",
|
||||
"Connection with <b>UiServer Websocket</b> recovered.": "Conexión con <b>UiServer Websocket</b> recuperada.",
|
||||
"UiServer Websocket error, please reload the page.": "Error de UiServer Websocket, por favor recarga la página.",
|
||||
" Connecting...": " Conectando...",
|
||||
"Site size: <b>": "Tamaño del sitio: <b>",
|
||||
"MB</b> is larger than default allowed ": "MB</b> es más grande de lo permitido por defecto",
|
||||
"Open site and set size limit to \" + site_info.next_size_limit + \"MB": "Abre tu sitio and establece el límite de tamaño a \" + site_info.next_size_limit + \"MBs",
|
||||
" files needs to be downloaded": " Los archivos necesitan ser descargados",
|
||||
" downloaded": " descargados",
|
||||
" download failed": " descarga fallida",
|
||||
"Peers found: ": "Pares encontrados: ",
|
||||
"No peers found": "No se han encontrado pares",
|
||||
"Running out of size limit (": "Superando el tamaño límite (",
|
||||
"Set limit to \" + site_info.next_size_limit + \"MB": "Establece ellímite a \" + site_info.next_size_limit + \"MB ändern",
|
||||
"Site size limit changed to {0}MB": "Límite de tamaño del sitio cambiado a {0}MBs",
|
||||
" New version of this page has just released.<br>Reload to see the modified content.": " Se ha publicado una nueva versión de esta página .<br>Recarga para ver el contenido modificado.",
|
||||
"This site requests permission:": "Este sitio solicita permiso:",
|
||||
"Grant": "Conceder"
|
||||
|
||||
}
|
51
src/Translate/languages/pl.json
Normal file
51
src/Translate/languages/pl.json
Normal file
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"Congratulation, your port <b>{0}</b> is opened.<br>You are full member of ZeroNet network!": "Gratulacje, twój port <b>{0}</b> jest otwarty.<br>Jesteś pełnoprawnym użytkownikiem sieci ZeroNet!",
|
||||
"Tor mode active, every connection using Onion route.": "Tryb Tor aktywny, każde połączenie przy użyciu trasy Cebulowej.",
|
||||
"Successfully started Tor onion hidden services.": "Pomyślnie zainicjowano ukryte usługi cebulowe Tor.",
|
||||
"Unable to start hidden services, please check your config.": "Niezdolny do uruchomienia ukrytych usług, proszę sprawdź swoją konfigurację.",
|
||||
"For faster connections open <b>{0}</b> port on your router.": "Dla szybszego połączenia otwórz <b>{0}</b> port w swoim routerze.",
|
||||
"Your connection is restricted. Please, open <b>{0}</b> port on your router": "Połączenie jest ograniczone. Proszę, otwórz port <b>{0}</b> w swoim routerze",
|
||||
"or configure Tor to become full member of ZeroNet network.": "bądź skonfiguruj Tora by stać się pełnoprawnym użytkownikiem sieci ZeroNet.",
|
||||
|
||||
"Select account you want to use in this site:": "Wybierz konto którego chcesz użyć na tej stronie:",
|
||||
"currently selected": "aktualnie wybrany",
|
||||
"Unique to site": "Unikatowy dla strony",
|
||||
|
||||
"Content signing failed": "Podpisanie treści zawiodło",
|
||||
"Content publish queued for {0:.0f} seconds.": "Publikacja treści wstrzymana na {0:.0f} sekund(y).",
|
||||
"Content published to {0} peers.": "Treść opublikowana do {0} uzytkowników równorzednych.",
|
||||
"No peers found, but your content is ready to access.": "Nie odnaleziono użytkowników równorzędnych, ale twoja treść jest dostępna.",
|
||||
"Your network connection is restricted. Please, open <b>{0}</b> port": "Twoje połączenie sieciowe jest ograniczone. Proszę, otwórz port <b>{0}</b>",
|
||||
"on your router to make your site accessible for everyone.": "w swoim routerze, by twoja strona mogłabyć dostępna dla wszystkich.",
|
||||
"Content publish failed.": "Publikacja treści zawiodła.",
|
||||
"This file still in sync, if you write it now, then the previous content may be lost.": "Ten plik wciąż się synchronizuje, jeśli zapiszesz go teraz, poprzednia treść może zostać utracona.",
|
||||
"Write content anyway": "Zapisz treść mimo wszystko",
|
||||
"New certificate added:": "Nowy certyfikat dodany:",
|
||||
"You current certificate:": "Twój aktualny certyfikat: ",
|
||||
"Change it to {auth_type}/{auth_user_name}@{domain}": "Zmień na {auth_type}/{auth_user_name}@{domain}-ra",
|
||||
"Certificate changed to: <b>{auth_type}/{auth_user_name}@{domain}</b>.": "Certyfikat zmieniony na <b>{auth_type}/{auth_user_name}@{domain}</b>-ra.",
|
||||
"Site cloned": "Strona sklonowana",
|
||||
|
||||
"You have successfully changed the web interface's language!": "Pomyślnie zmieniono język interfejsu stron!",
|
||||
"Due to the browser's caching, the full transformation could take some minute.": "Ze względu na buforowanie przeglądarki, pełna zmiana może zająć parę minutę.",
|
||||
|
||||
"Connection with <b>UiServer Websocket</b> was lost. Reconnecting...": "Połączenie z <b>UiServer Websocket</b> zostało przerwane. Ponowne łączenie...",
|
||||
"Connection with <b>UiServer Websocket</b> recovered.": "Połączenie z <b>UiServer Websocket</b> przywrócone.",
|
||||
"UiServer Websocket error, please reload the page.": "Błąd UiServer Websocket, prosze odświeżyć stronę.",
|
||||
" Connecting...": " Łączenie...",
|
||||
"Site size: <b>": "Rozmiar strony: <b>",
|
||||
"MB</b> is larger than default allowed ": "MB</b> jest większy niż domyślnie dozwolony ",
|
||||
"Open site and set size limit to \" + site_info.next_size_limit + \"MB": "Otwórz stronę i ustaw limit na \" + site_info.next_size_limit + \"MBów",
|
||||
" files needs to be downloaded": " pliki muszą zostać ściągnięte",
|
||||
" downloaded": " ściągnięte",
|
||||
" download failed": " ściąganie nie powiodło się",
|
||||
"Peers found: ": "Odnaleziono użytkowników równorzednych: ",
|
||||
"No peers found": "Nie odnaleziono użytkowników równorzędnych",
|
||||
"Running out of size limit (": "Limit rozmiaru na wyczerpaniu (",
|
||||
"Set limit to \" + site_info.next_size_limit + \"MB": "Ustaw limit na \" + site_info.next_size_limit + \"MBów",
|
||||
"Site size limit changed to {0}MB": "Rozmiar limitu strony zmieniony na {0}MBów",
|
||||
" New version of this page has just released.<br>Reload to see the modified content.": "Nowa wersja tej strony właśnie została wydana.<br>Odśwież by zobaczyć nową, zmodyfikowaną treść strony.",
|
||||
"This site requests permission:": "Ta strona wymaga uprawnień:",
|
||||
"Grant": "Przyznaj uprawnienia"
|
||||
|
||||
}
|
|
@ -49,7 +49,10 @@ class UiRequest(object):
|
|||
path = re.sub("^http://", "/", path) # Remove begining http for chrome extension .bit access
|
||||
|
||||
if self.env["REQUEST_METHOD"] == "OPTIONS":
|
||||
content_type = self.getContentType(path)
|
||||
if "/" not in path.strip("/"):
|
||||
content_type = self.getContentType("index.html")
|
||||
else:
|
||||
content_type = self.getContentType(path)
|
||||
self.sendHeader(content_type=content_type)
|
||||
return ""
|
||||
|
||||
|
@ -64,8 +67,6 @@ class UiRequest(object):
|
|||
# uimedia within site dir (for chrome extension)
|
||||
path = re.sub(".*?/uimedia/", "/uimedia/", path)
|
||||
return self.actionUiMedia(path)
|
||||
elif path.startswith("/media"):
|
||||
return self.actionSiteMedia(path)
|
||||
# Websocket
|
||||
elif path == "/Websocket":
|
||||
return self.actionWebsocket()
|
||||
|
@ -93,14 +94,21 @@ class UiRequest(object):
|
|||
def isProxyRequest(self):
|
||||
return self.env["PATH_INFO"].startswith("http://")
|
||||
|
||||
def isWebSocketRequest(self):
|
||||
return self.env.get("HTTP_UPGRADE") == "websocket"
|
||||
|
||||
def isAjaxRequest(self):
|
||||
return self.env.get("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest"
|
||||
|
||||
# Get mime by filename
|
||||
def getContentType(self, file_name):
|
||||
content_type = mimetypes.guess_type(file_name)[0]
|
||||
|
||||
if file_name.endswith(".css"): # Force correct css content type
|
||||
content_type = "text/css"
|
||||
|
||||
if not content_type:
|
||||
if file_name.endswith("json"): # Correct json header
|
||||
if file_name.endswith(".json"): # Correct json header
|
||||
content_type = "application/json"
|
||||
else:
|
||||
content_type = "application/octet-stream"
|
||||
|
@ -140,6 +148,7 @@ class UiRequest(object):
|
|||
headers.append(("Keep-Alive", "max=25, timeout=30"))
|
||||
if content_type != "text/html":
|
||||
headers.append(("Access-Control-Allow-Origin", "*")) # Allow json access on non-html files
|
||||
headers.append(("X-Frame-Options", "SAMEORIGIN"))
|
||||
# headers.append(("Content-Security-Policy", "default-src 'self' data: 'unsafe-inline' ws://127.0.0.1:* http://127.0.0.1:* wss://tracker.webtorrent.io; sandbox allow-same-origin allow-top-navigation allow-scripts")) # Only local connections
|
||||
if self.env["REQUEST_METHOD"] == "OPTIONS":
|
||||
# Allow json access
|
||||
|
@ -191,6 +200,14 @@ class UiRequest(object):
|
|||
if self.isAjaxRequest():
|
||||
return self.error403("Ajax request not allowed to load wrapper") # No ajax allowed on wrapper
|
||||
|
||||
if self.isWebSocketRequest():
|
||||
return self.error403("WebSocket request not allowed to load wrapper") # No websocket
|
||||
|
||||
if "text/html" not in self.env.get("HTTP_ACCEPT", ""):
|
||||
return self.error403("Invalid Accept header to load wrapper")
|
||||
if "prefetch" in self.env.get("HTTP_X_MOZ", "") or "prefetch" in self.env.get("HTTP_PURPOSE", ""):
|
||||
return self.error403("Prefetch not allowed to load wrapper")
|
||||
|
||||
site = SiteManager.site_manager.get(address)
|
||||
|
||||
if (
|
||||
|
@ -339,14 +356,8 @@ class UiRequest(object):
|
|||
if path_parts: # Looks like a valid path
|
||||
address = path_parts["address"]
|
||||
file_path = "%s/%s/%s" % (config.data_dir, address, path_parts["inner_path"])
|
||||
allowed_dir = os.path.abspath("%s/%s" % (config.data_dir, address)) # Only files within data/sitehash allowed
|
||||
data_dir = os.path.abspath(config.data_dir) # No files from data/ allowed
|
||||
if (
|
||||
".." in file_path or
|
||||
not os.path.dirname(os.path.abspath(file_path)).startswith(allowed_dir) or
|
||||
allowed_dir == data_dir
|
||||
): # File not in allowed path
|
||||
return self.error403()
|
||||
if ".." in path_parts["inner_path"]: # File not in allowed path
|
||||
return self.error403("Invalid file path")
|
||||
else:
|
||||
if config.debug and file_path.split("/")[-1].startswith("all."):
|
||||
# If debugging merge *.css to all.css and *.js to all.js
|
||||
|
@ -359,7 +370,10 @@ class UiRequest(object):
|
|||
elif os.path.isdir(file_path): # If this is actually a folder, add "/" and redirect
|
||||
return self.actionRedirect("./{0}/".format(path_parts["inner_path"].split("/")[-1]))
|
||||
else: # File not exists, try to download
|
||||
site = SiteManager.site_manager.need(address, all_file=False)
|
||||
if address not in SiteManager.site_manager.sites: # Only in case if site already started downloading
|
||||
return self.error404(path_parts["inner_path"])
|
||||
|
||||
site = SiteManager.site_manager.need(address)
|
||||
|
||||
if path_parts["inner_path"].endswith("favicon.ico"): # Default favicon for all sites
|
||||
return self.actionFile("src/Ui/media/img/favicon.ico")
|
||||
|
|
|
@ -3,6 +3,7 @@ import time
|
|||
import cgi
|
||||
import socket
|
||||
import sys
|
||||
import gevent
|
||||
|
||||
from gevent.pywsgi import WSGIServer
|
||||
from gevent.pywsgi import WSGIHandler
|
||||
|
@ -121,7 +122,8 @@ class UiServer:
|
|||
browser = webbrowser.get()
|
||||
else:
|
||||
browser = webbrowser.get(config.open_browser)
|
||||
browser.open("http://%s:%s/%s" % (config.ui_ip if config.ui_ip != "*" else "127.0.0.1", config.ui_port, config.homepage), new=2)
|
||||
url = "http://%s:%s/%s" % (config.ui_ip if config.ui_ip != "*" else "127.0.0.1", config.ui_port, config.homepage)
|
||||
gevent.spawn_later(0.3, browser.open, url, new=2)
|
||||
|
||||
self.server = WSGIServer((self.ip.replace("*", ""), self.port), handler, handler_class=UiWSGIHandler, log=self.log)
|
||||
self.server.sockets = {}
|
||||
|
|
|
@ -32,6 +32,12 @@ class UiWebsocket(object):
|
|||
self.channels = [] # Channels joined to
|
||||
self.sending = False # Currently sending to client
|
||||
self.send_queue = [] # Messages to send to client
|
||||
self.admin_commands = (
|
||||
"sitePause", "siteResume", "siteDelete", "siteList", "siteSetLimit", "siteClone",
|
||||
"channelJoinAllsite", "serverUpdate", "serverPortcheck", "serverShutdown", "certSet", "configSet",
|
||||
"actionPermissionAdd", "actionPermissionRemove"
|
||||
)
|
||||
self.async_commands = ("fileGet", "fileList")
|
||||
|
||||
# Start listener loop
|
||||
def start(self):
|
||||
|
@ -152,6 +158,11 @@ class UiWebsocket(object):
|
|||
permissions.append("ADMIN")
|
||||
return permissions
|
||||
|
||||
def asyncWrapper(self, func):
|
||||
def wrapper(*args, **kwargs):
|
||||
gevent.spawn(func, *args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
# Handle incoming messages
|
||||
def handleRequest(self, data):
|
||||
req = json.loads(data)
|
||||
|
@ -160,16 +171,10 @@ class UiWebsocket(object):
|
|||
params = req.get("params")
|
||||
self.permissions = self.getPermissions(req["id"])
|
||||
|
||||
admin_commands = (
|
||||
"sitePause", "siteResume", "siteDelete", "siteList", "siteSetLimit", "siteClone",
|
||||
"channelJoinAllsite", "serverUpdate", "serverPortcheck", "serverShutdown", "certSet", "configSet",
|
||||
"actionPermissionAdd", "actionPermissionRemove"
|
||||
)
|
||||
|
||||
if cmd == "response": # It's a response to a command
|
||||
return self.actionResponse(req["to"], req["result"])
|
||||
elif cmd in admin_commands and "ADMIN" not in self.permissions: # Admin commands
|
||||
return self.response(req["id"], {"error:", "You don't have permission to run %s" % cmd})
|
||||
elif cmd in self.admin_commands and "ADMIN" not in self.permissions: # Admin commands
|
||||
return self.response(req["id"], {"error": "You don't have permission to run %s" % cmd})
|
||||
else: # Normal command
|
||||
func_name = "action" + cmd[0].upper() + cmd[1:]
|
||||
func = getattr(self, func_name, None)
|
||||
|
@ -177,6 +182,10 @@ class UiWebsocket(object):
|
|||
self.response(req["id"], {"error": "Unknown command: %s" % cmd})
|
||||
return
|
||||
|
||||
# Execute in parallel
|
||||
if cmd in self.async_commands:
|
||||
func = self.asyncWrapper(func)
|
||||
|
||||
# Support calling as named, unnamed parameters and raw first argument too
|
||||
if type(params) is dict:
|
||||
func(req["id"], **params)
|
||||
|
@ -278,7 +287,7 @@ class UiWebsocket(object):
|
|||
self.response(to, ret)
|
||||
|
||||
# Sign content.json
|
||||
def actionSiteSign(self, to, privatekey=None, inner_path="content.json", response_ok=True, update_changed_files=False):
|
||||
def actionSiteSign(self, to, privatekey=None, inner_path="content.json", response_ok=True, update_changed_files=False, remove_missing_optional=False):
|
||||
self.log.debug("Signing: %s" % inner_path)
|
||||
site = self.site
|
||||
extend = {} # Extended info for signing
|
||||
|
@ -311,7 +320,7 @@ class UiWebsocket(object):
|
|||
# Reload content.json, ignore errors to make it up-to-date
|
||||
site.content_manager.loadContent(inner_path, add_bad_files=False, force=True)
|
||||
# Sign using private key sent by user
|
||||
signed = site.content_manager.sign(inner_path, privatekey, extend=extend, update_changed_files=update_changed_files)
|
||||
signed = site.content_manager.sign(inner_path, privatekey, extend=extend, update_changed_files=update_changed_files, remove_missing_optional=remove_missing_optional)
|
||||
if not signed:
|
||||
self.cmd("notification", ["error", _["Content signing failed"]])
|
||||
self.response(to, {"error": "Site sign failed"})
|
||||
|
@ -346,6 +355,7 @@ class UiWebsocket(object):
|
|||
thread.linked = True
|
||||
if called_instantly: # Allowed to call instantly
|
||||
# At the end callback with request id and thread
|
||||
self.cmd("progress", ["publish", _["Content published to {0}/{1} peers."].format(0, 5), 0])
|
||||
thread.link(lambda thread: self.cbSitePublish(to, self.site, thread, notification, callback=notification))
|
||||
else:
|
||||
self.cmd(
|
||||
|
@ -357,15 +367,27 @@ class UiWebsocket(object):
|
|||
thread.link(lambda thread: self.cbSitePublish(to, self.site, thread, notification, callback=False))
|
||||
|
||||
def doSitePublish(self, site, inner_path):
|
||||
def cbProgress(published, limit):
|
||||
progress = int(float(published) / limit * 100)
|
||||
self.cmd("progress", [
|
||||
"publish",
|
||||
_["Content published to {0}/{1} peers."].format(published, limit),
|
||||
progress
|
||||
])
|
||||
diffs = site.content_manager.getDiffs(inner_path)
|
||||
return site.publish(limit=5, inner_path=inner_path, diffs=diffs)
|
||||
back = site.publish(limit=5, inner_path=inner_path, diffs=diffs, cb_progress=cbProgress)
|
||||
if back == 0: # Failed to publish to anyone
|
||||
self.cmd("progress", ["publish", _["Content publish failed."], -100])
|
||||
else:
|
||||
cbProgress(back, back)
|
||||
return back
|
||||
|
||||
# Callback of site publish
|
||||
def cbSitePublish(self, to, site, thread, notification=True, callback=True):
|
||||
published = thread.value
|
||||
if published > 0: # Successfully published
|
||||
if notification:
|
||||
self.cmd("notification", ["done", _["Content published to {0} peers."].format(published), 5000])
|
||||
# self.cmd("notification", ["done", _["Content published to {0} peers."].format(published), 5000])
|
||||
site.updateWebsocket() # Send updated site data to local websocket clients
|
||||
if callback:
|
||||
self.response(to, "ok")
|
||||
|
@ -388,7 +410,6 @@ class UiWebsocket(object):
|
|||
|
||||
else:
|
||||
if notification:
|
||||
self.cmd("notification", ["error", _["Content publish failed."]])
|
||||
self.response(to, {"error": "Content publish failed."})
|
||||
|
||||
# Write a file to disk
|
||||
|
@ -448,6 +469,16 @@ class UiWebsocket(object):
|
|||
):
|
||||
return self.response(to, {"error": "Forbidden, you can only modify your own files"})
|
||||
|
||||
file_info = self.site.content_manager.getFileInfo(inner_path)
|
||||
if file_info.get("optional"):
|
||||
self.log.debug("Deleting optional file: %s" % inner_path)
|
||||
relative_path = file_info["relative_path"]
|
||||
content_json = self.site.storage.loadJson(file_info["content_inner_path"])
|
||||
if relative_path in content_json.get("files_optional", {}):
|
||||
del content_json["files_optional"][relative_path]
|
||||
self.site.storage.writeJson(file_info["content_inner_path"], content_json)
|
||||
self.site.content_manager.loadContent(file_info["content_inner_path"], add_bad_files=False, force=True)
|
||||
|
||||
try:
|
||||
self.site.storage.delete(inner_path)
|
||||
except Exception, err:
|
||||
|
@ -468,6 +499,10 @@ class UiWebsocket(object):
|
|||
# self.log.debug("FileQuery %s %s done in %s" % (dir_inner_path, query, time.time()-s))
|
||||
return self.response(to, rows)
|
||||
|
||||
# List files in directory
|
||||
def actionFileList(self, to, inner_path):
|
||||
return self.response(to, list(self.site.storage.list(inner_path)))
|
||||
|
||||
# Sql query
|
||||
def actionDbQuery(self, to, query, params=None, wait_for=None):
|
||||
if config.debug:
|
||||
|
@ -487,14 +522,18 @@ class UiWebsocket(object):
|
|||
return self.response(to, rows)
|
||||
|
||||
# Return file content
|
||||
def actionFileGet(self, to, inner_path, required=True):
|
||||
def actionFileGet(self, to, inner_path, required=True, format="text", timeout=300):
|
||||
try:
|
||||
if required or inner_path in self.site.bad_files:
|
||||
self.site.needFile(inner_path, priority=6)
|
||||
with gevent.Timeout(timeout):
|
||||
self.site.needFile(inner_path, priority=6)
|
||||
body = self.site.storage.read(inner_path)
|
||||
except Exception, err:
|
||||
self.log.debug("%s fileGet error: %s" % (inner_path, err))
|
||||
body = None
|
||||
if body and format == "base64":
|
||||
import base64
|
||||
body = base64.b64encode(body)
|
||||
return self.response(to, body)
|
||||
|
||||
def actionFileRules(self, to, inner_path):
|
||||
|
@ -514,13 +553,13 @@ class UiWebsocket(object):
|
|||
if res is True:
|
||||
self.cmd(
|
||||
"notification",
|
||||
["done", _("{_[New certificate added:]} <b>{auth_type}/{auth_user_name}@{domain}</b>.")]
|
||||
["done", _("{_[New certificate added]:} <b>{auth_type}/{auth_user_name}@{domain}</b>.")]
|
||||
)
|
||||
self.response(to, "ok")
|
||||
elif res is False:
|
||||
# Display confirmation of change
|
||||
cert_current = self.user.certs[domain]
|
||||
body = _("{_[You current certificate:]} <b>{cert_current[auth_type]}/{cert_current[auth_user_name]}@{domain}</b>")
|
||||
body = _("{_[Your current certificate]:} <b>{cert_current[auth_type]}/{cert_current[auth_user_name]}@{domain}</b>")
|
||||
self.cmd(
|
||||
"confirm",
|
||||
[body, _("Change it to {auth_type}/{auth_user_name}@{domain}")],
|
||||
|
@ -599,11 +638,13 @@ class UiWebsocket(object):
|
|||
if permission not in self.site.settings["permissions"]:
|
||||
self.site.settings["permissions"].append(permission)
|
||||
self.site.saveSettings()
|
||||
self.site.updateWebsocket(permission_added=permission)
|
||||
self.response(to, "ok")
|
||||
|
||||
def actionPermissionRemove(self, to, permission):
|
||||
self.site.settings["permissions"].remove(permission)
|
||||
self.site.saveSettings()
|
||||
self.site.updateWebsocket(permission_removed=permission)
|
||||
self.response(to, "ok")
|
||||
|
||||
# Set certificate that used for authenticate user for site
|
||||
|
@ -681,12 +722,12 @@ class UiWebsocket(object):
|
|||
else:
|
||||
self.response(to, {"error": "Unknown site: %s" % address})
|
||||
|
||||
def actionSiteClone(self, to, address):
|
||||
def actionSiteClone(self, to, address, root_inner_path=""):
|
||||
self.cmd("notification", ["info", "Cloning site..."])
|
||||
site = self.server.sites.get(address)
|
||||
# Generate a new site from user's bip32 seed
|
||||
new_address, new_address_index, new_site_data = self.user.getNewSiteData()
|
||||
new_site = site.clone(new_address, new_site_data["privatekey"], address_index=new_address_index)
|
||||
new_site = site.clone(new_address, new_site_data["privatekey"], address_index=new_address_index, root_inner_path=root_inner_path)
|
||||
new_site.settings["own"] = True
|
||||
new_site.saveSettings()
|
||||
self.cmd("notification", ["done", _["Site cloned"] + "<script>window.top.location = '/%s'</script>" % new_address])
|
||||
|
|
|
@ -21,12 +21,16 @@ class Notifications
|
|||
# Create element
|
||||
elem = $(".notification.template", @elem).clone().removeClass("template")
|
||||
elem.addClass("notification-#{type}").addClass("notification-#{id}")
|
||||
if type == "progress"
|
||||
elem.addClass("notification-done")
|
||||
|
||||
# Update text
|
||||
if type == "error"
|
||||
$(".notification-icon", elem).html("!")
|
||||
else if type == "done"
|
||||
$(".notification-icon", elem).html("<div class='icon-success'></div>")
|
||||
else if type == "progress"
|
||||
$(".notification-icon", elem).html("<div class='icon-success'></div>")
|
||||
else if type == "ask"
|
||||
$(".notification-icon", elem).html("?")
|
||||
else
|
||||
|
@ -64,6 +68,8 @@ class Notifications
|
|||
$(".select", elem).on "click", =>
|
||||
@close elem
|
||||
|
||||
return elem
|
||||
|
||||
|
||||
close: (elem) ->
|
||||
elem.stop().animate {"width": 0, "opacity": 0}, 700, "easeInOutCubic"
|
||||
|
|
|
@ -53,6 +53,8 @@ class Wrapper
|
|||
if "-" in message.params[0] # - in first param: message id defined
|
||||
[id, type] = message.params[0].split("-")
|
||||
@notifications.add(id, type, message.params[1], message.params[2])
|
||||
else if cmd == "progress" # Display notification
|
||||
@actionProgress(message)
|
||||
else if cmd == "prompt" # Prompt input
|
||||
@displayPrompt message.params[0], message.params[1], message.params[2], (res) =>
|
||||
@ws.response message.id, res
|
||||
|
@ -109,6 +111,8 @@ class Wrapper
|
|||
@actionConfirm(message)
|
||||
else if cmd == "wrapperPrompt" # Prompt input
|
||||
@actionPrompt(message)
|
||||
else if cmd == "wrapperProgress" # Progress bar
|
||||
@actionProgress(message)
|
||||
else if cmd == "wrapperSetViewport" # Set the viewport
|
||||
@actionSetViewport(message)
|
||||
else if cmd == "wrapperSetTitle"
|
||||
|
@ -131,6 +135,8 @@ class Wrapper
|
|||
@actionOpenWindow(message.params)
|
||||
else if cmd == "wrapperPermissionAdd"
|
||||
@actionPermissionAdd(message)
|
||||
else if cmd == "wrapperRequestFullscreen"
|
||||
@actionRequestFullscreen()
|
||||
else # Send to websocket
|
||||
if message.id < 1000000
|
||||
@ws.send(message) # Pass message to websocket
|
||||
|
@ -141,7 +147,7 @@ class Wrapper
|
|||
if query == null
|
||||
query = window.location.search
|
||||
back = window.location.pathname
|
||||
if back.slice(-1) != "/"
|
||||
if back.match /^\/[^\/]+$/ # Add / after site address if called without it
|
||||
back += "/"
|
||||
if query.replace("?", "")
|
||||
back += "?"+query.replace("?", "")
|
||||
|
@ -168,6 +174,21 @@ class Wrapper
|
|||
w.opener = null
|
||||
w.location = params[0]
|
||||
|
||||
actionRequestFullscreen: ->
|
||||
if "Fullscreen" in @site_info.settings.permissions
|
||||
elem = document.getElementById("inner-iframe")
|
||||
request_fullscreen = elem.requestFullScreen || elem.webkitRequestFullscreen || elem.mozRequestFullScreen || elem.msRequestFullScreen
|
||||
request_fullscreen.call(elem)
|
||||
setTimeout ( =>
|
||||
if window.innerHeight != screen.height # Fullscreen failed, probably only allowed on click
|
||||
@displayConfirm "This site requests permission:" + " <b>Fullscreen</b>", "Grant", =>
|
||||
request_fullscreen.call(elem)
|
||||
), 100
|
||||
else
|
||||
@displayConfirm "This site requests permission:" + " <b>Fullscreen</b>", "Grant", =>
|
||||
@site_info.settings.permissions.push("Fullscreen")
|
||||
@actionRequestFullscreen()
|
||||
@ws.cmd "permissionAdd", "Fullscreen"
|
||||
|
||||
actionPermissionAdd: (message) ->
|
||||
permission = message.params
|
||||
|
@ -180,8 +201,6 @@ class Wrapper
|
|||
body = $("<span class='message'>"+message.params[1]+"</span>")
|
||||
@notifications.add("notification-#{message.id}", message.params[0], body, message.params[2])
|
||||
|
||||
|
||||
|
||||
displayConfirm: (message, caption, cb) ->
|
||||
body = $("<span class='message'>"+message+"</span>")
|
||||
button = $("<a href='##{caption}' class='button button-#{caption}'>#{caption}</a>") # Add confirm button
|
||||
|
@ -232,6 +251,49 @@ class Wrapper
|
|||
@displayPrompt message.params[0], type, caption, (res) =>
|
||||
@sendInner {"cmd": "response", "to": message.id, "result": res} # Response to confirm
|
||||
|
||||
actionProgress: (message) ->
|
||||
message.params = @toHtmlSafe(message.params) # Escape html
|
||||
percent = Math.min(100, message.params[2])/100
|
||||
offset = 75-(percent*75)
|
||||
circle = """
|
||||
<div class="circle"><svg class="circle-svg" width="30" height="30" viewport="0 0 30 30" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle r="12" cx="15" cy="15" fill="transparent" class="circle-bg"></circle>
|
||||
<circle r="12" cx="15" cy="15" fill="transparent" class="circle-fg" style="stroke-dashoffset: #{offset}"></circle>
|
||||
</svg></div>
|
||||
"""
|
||||
body = "<span class='message'>"+message.params[1]+"</span>" + circle
|
||||
elem = $(".notification-#{message.params[0]}")
|
||||
if elem.length
|
||||
width = $(".body .message", elem).outerWidth()
|
||||
$(".body .message", elem).html(message.params[1])
|
||||
if $(".body .message", elem).css("width") == ""
|
||||
$(".body .message", elem).css("width", width)
|
||||
$(".body .circle-fg", elem).css("stroke-dashoffset", offset)
|
||||
else
|
||||
elem = @notifications.add(message.params[0], "progress", $(body))
|
||||
if percent > 0
|
||||
$(".body .circle-bg", elem).css {"animation-play-state": "paused", "stroke-dasharray": "180px"}
|
||||
|
||||
if $(".notification-icon", elem).data("done")
|
||||
return false
|
||||
else if message.params[2] >= 100 # Done
|
||||
$(".circle-fg", elem).css("transition", "all 0.3s ease-in-out")
|
||||
setTimeout (->
|
||||
$(".notification-icon", elem).css {transform: "scale(1)", opacity: 1}
|
||||
$(".notification-icon .icon-success", elem).css {transform: "rotate(45deg) scale(1)"}
|
||||
), 300
|
||||
setTimeout (=>
|
||||
@notifications.close elem
|
||||
), 3000
|
||||
$(".notification-icon", elem).data("done", true)
|
||||
else if message.params[2] < 0 # Error
|
||||
$(".body .circle-fg", elem).css("stroke", "#ec6f47").css("transition", "transition: all 0.3s ease-in-out")
|
||||
setTimeout (=>
|
||||
$(".notification-icon", elem).css {transform: "scale(1)", opacity: 1}
|
||||
elem.removeClass("notification-done").addClass("notification-error")
|
||||
$(".notification-icon .icon-success", elem).removeClass("icon-success").html("!")
|
||||
), 300
|
||||
$(".notification-icon", elem).data("done", true)
|
||||
|
||||
|
||||
actionSetViewport: (message) ->
|
||||
|
@ -362,7 +424,7 @@ class Wrapper
|
|||
if site_info.content
|
||||
window.document.title = site_info.content.title+" - ZeroNet"
|
||||
@log "Required file done, setting title to", window.document.title
|
||||
if not $(".loadingscreen").length # Loading screen already removed (loaded +2sec)
|
||||
if not window.show_loadingscreen
|
||||
@notifications.add("modified", "info", "New version of this page has just released.<br>Reload to see the modified content.")
|
||||
# File failed downloading
|
||||
else if site_info.event[0] == "file_failed"
|
||||
|
|
|
@ -45,7 +45,7 @@ a { color: black }
|
|||
color: #4F4F4F; font-family: 'Lucida Grande', 'Segoe UI', Helvetica, Arial, sans-serif; font-size: 14px; line-height: 20px; /*border: 1px solid rgba(210, 206, 205, 0.2)*/
|
||||
}
|
||||
.notification-icon {
|
||||
display: block; width: 50px; height: 50px; position: absolute; float: left; z-index: 1;
|
||||
display: block; width: 50px; height: 50px; position: absolute; float: left; z-index: 2;
|
||||
text-align: center; background-color: #e74c3c; line-height: 45px; vertical-align: bottom; font-size: 40px; color: white;
|
||||
}
|
||||
.notification .body {
|
||||
|
@ -82,6 +82,18 @@ a { color: black }
|
|||
.notification .input { padding: 6px; border: 1px solid #DDD; margin-left: 10px; border-bottom: 2px solid #DDD; border-radius: 1px; margin-right: -11px; transition: all 0.3s }
|
||||
.notification .input:focus { border-color: #95a5a6; outline: none }
|
||||
|
||||
/* Notification progress */
|
||||
.notification .circle { width: 50px; height: 50px; position: absolute; left: -50px; top: 0px; background-color: #e2e9ec; z-index: 1; background: linear-gradient(405deg, rgba(226, 233, 236, 0.8), #efefef); }
|
||||
.notification .circle-svg { margin-left: 10px; margin-top: 10px; transform: rotateZ(-90deg); }
|
||||
.notification .circle-bg { stroke: #FFF; stroke-width: 2px; animation: rolling 0.4s infinite linear; stroke-dasharray: 40px; transition: all 1s }
|
||||
.notification .circle-fg { stroke-dashoffset: 200; stroke: #2ecc71; stroke-width: 2px; stroke-dasharray: 75px; transition: all 5s cubic-bezier(0.19, 1, 0.22, 1); }
|
||||
.notification-progress .notification-icon { opacity: 0; transform: scale(0); transition: all 0.3s ease-in-out }
|
||||
.notification-progress .icon-success { transform: rotate(45deg) scale(0); transition: all 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55); }
|
||||
@keyframes rolling {
|
||||
0% { stroke-dashoffset: 80px }
|
||||
100% { stroke-dashoffset: 0px }
|
||||
}
|
||||
|
||||
/* Icons (based on http://nicolasgallagher.com/pure-css-gui-icons/demo/) */
|
||||
|
||||
.icon-success { left:6px; width:5px; height:12px; border-width:0 5px 5px 0; border-style:solid; border-color:white; margin-left: 20px; margin-top: 15px; transform:rotate(45deg) }
|
||||
|
|
|
@ -50,7 +50,7 @@ a { color: black }
|
|||
color: #4F4F4F; font-family: 'Lucida Grande', 'Segoe UI', Helvetica, Arial, sans-serif; font-size: 14px; line-height: 20px; /*border: 1px solid rgba(210, 206, 205, 0.2)*/
|
||||
}
|
||||
.notification-icon {
|
||||
display: block; width: 50px; height: 50px; position: absolute; float: left; z-index: 1;
|
||||
display: block; width: 50px; height: 50px; position: absolute; float: left; z-index: 2;
|
||||
text-align: center; background-color: #e74c3c; line-height: 45px; vertical-align: bottom; font-size: 40px; color: white;
|
||||
}
|
||||
.notification .body {
|
||||
|
@ -87,6 +87,27 @@ a { color: black }
|
|||
.notification .input { padding: 6px; border: 1px solid #DDD; margin-left: 10px; border-bottom: 2px solid #DDD; -webkit-border-radius: 1px; -moz-border-radius: 1px; -o-border-radius: 1px; -ms-border-radius: 1px; border-radius: 1px ; margin-right: -11px; -webkit-transition: all 0.3s ; -moz-transition: all 0.3s ; -o-transition: all 0.3s ; -ms-transition: all 0.3s ; transition: all 0.3s }
|
||||
.notification .input:focus { border-color: #95a5a6; outline: none }
|
||||
|
||||
/* Notification progress */
|
||||
.notification .circle { width: 50px; height: 50px; position: absolute; left: -50px; top: 0px; background-color: #e2e9ec; z-index: 1; background: -webkit-linear-gradient(405deg, rgba(226, 233, 236, 0.8), #efefef);background: -moz-linear-gradient(405deg, rgba(226, 233, 236, 0.8), #efefef);background: -o-linear-gradient(405deg, rgba(226, 233, 236, 0.8), #efefef);background: -ms-linear-gradient(405deg, rgba(226, 233, 236, 0.8), #efefef);background: linear-gradient(405deg, rgba(226, 233, 236, 0.8), #efefef); }
|
||||
.notification .circle-svg { margin-left: 10px; margin-top: 10px; -webkit-transform: rotateZ(-90deg); -moz-transform: rotateZ(-90deg); -o-transform: rotateZ(-90deg); -ms-transform: rotateZ(-90deg); transform: rotateZ(-90deg) ; }
|
||||
.notification .circle-bg { stroke: #FFF; stroke-width: 2px; -webkit-animation: rolling 0.4s infinite linear; -moz-animation: rolling 0.4s infinite linear; -o-animation: rolling 0.4s infinite linear; -ms-animation: rolling 0.4s infinite linear; animation: rolling 0.4s infinite linear ; stroke-dasharray: 40px; -webkit-transition: all 1s ; -moz-transition: all 1s ; -o-transition: all 1s ; -ms-transition: all 1s ; transition: all 1s }
|
||||
.notification .circle-fg { stroke-dashoffset: 200; stroke: #2ecc71; stroke-width: 2px; stroke-dasharray: 75px; -webkit-transition: all 5s cubic-bezier(0.19, 1, 0.22, 1); -moz-transition: all 5s cubic-bezier(0.19, 1, 0.22, 1); -o-transition: all 5s cubic-bezier(0.19, 1, 0.22, 1); -ms-transition: all 5s cubic-bezier(0.19, 1, 0.22, 1); transition: all 5s cubic-bezier(0.19, 1, 0.22, 1) ; }
|
||||
.notification-progress .notification-icon { opacity: 0; -webkit-transform: scale(0); -moz-transform: scale(0); -o-transform: scale(0); -ms-transform: scale(0); transform: scale(0) ; -webkit-transition: all 0.3s ease-in-out ; -moz-transition: all 0.3s ease-in-out ; -o-transition: all 0.3s ease-in-out ; -ms-transition: all 0.3s ease-in-out ; transition: all 0.3s ease-in-out }
|
||||
.notification-progress .icon-success { -webkit-transform: rotate(45deg) scale(0); -moz-transform: rotate(45deg) scale(0); -o-transform: rotate(45deg) scale(0); -ms-transform: rotate(45deg) scale(0); transform: rotate(45deg) scale(0) ; -webkit-transition: all 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55); -moz-transition: all 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55); -o-transition: all 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55); -ms-transition: all 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55); transition: all 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55) ; }
|
||||
@keyframes rolling {
|
||||
0% { stroke-dashoffset: 80px }
|
||||
100% { stroke-dashoffset: 0px }
|
||||
}
|
||||
@-webkit-keyframes rolling {
|
||||
0% { stroke-dashoffset: 80px }
|
||||
100% { stroke-dashoffset: 0px }
|
||||
}
|
||||
@-moz-keyframes rolling {
|
||||
0% { stroke-dashoffset: 80px }
|
||||
100% { stroke-dashoffset: 0px }
|
||||
}
|
||||
|
||||
|
||||
/* Icons (based on http://nicolasgallagher.com/pure-css-gui-icons/demo/) */
|
||||
|
||||
.icon-success { left:6px; width:5px; height:12px; border-width:0 5px 5px 0; border-style:solid; border-color:white; margin-left: 20px; margin-top: 15px; transform:rotate(45deg) }
|
||||
|
|
|
@ -688,10 +688,15 @@ jQuery.extend( jQuery.easing,
|
|||
}
|
||||
elem = $(".notification.template", this.elem).clone().removeClass("template");
|
||||
elem.addClass("notification-" + type).addClass("notification-" + id);
|
||||
if (type === "progress") {
|
||||
elem.addClass("notification-done");
|
||||
}
|
||||
if (type === "error") {
|
||||
$(".notification-icon", elem).html("!");
|
||||
} else if (type === "done") {
|
||||
$(".notification-icon", elem).html("<div class='icon-success'></div>");
|
||||
} else if (type === "progress") {
|
||||
$(".notification-icon", elem).html("<div class='icon-success'></div>");
|
||||
} else if (type === "ask") {
|
||||
$(".notification-icon", elem).html("?");
|
||||
} else {
|
||||
|
@ -735,11 +740,12 @@ jQuery.extend( jQuery.easing,
|
|||
return false;
|
||||
};
|
||||
})(this));
|
||||
return $(".select", elem).on("click", (function(_this) {
|
||||
$(".select", elem).on("click", (function(_this) {
|
||||
return function() {
|
||||
return _this.close(elem);
|
||||
};
|
||||
})(this));
|
||||
return elem;
|
||||
};
|
||||
|
||||
Notifications.prototype.close = function(elem) {
|
||||
|
@ -848,6 +854,8 @@ jQuery.extend( jQuery.easing,
|
|||
_ref = message.params[0].split("-"), id = _ref[0], type = _ref[1];
|
||||
}
|
||||
return this.notifications.add(id, type, message.params[1], message.params[2]);
|
||||
} else if (cmd === "progress") {
|
||||
return this.actionProgress(message);
|
||||
} else if (cmd === "prompt") {
|
||||
return this.displayPrompt(message.params[0], message.params[1], message.params[2], (function(_this) {
|
||||
return function(res) {
|
||||
|
@ -915,6 +923,8 @@ jQuery.extend( jQuery.easing,
|
|||
return this.actionConfirm(message);
|
||||
} else if (cmd === "wrapperPrompt") {
|
||||
return this.actionPrompt(message);
|
||||
} else if (cmd === "wrapperProgress") {
|
||||
return this.actionProgress(message);
|
||||
} else if (cmd === "wrapperSetViewport") {
|
||||
return this.actionSetViewport(message);
|
||||
} else if (cmd === "wrapperSetTitle") {
|
||||
|
@ -941,6 +951,8 @@ jQuery.extend( jQuery.easing,
|
|||
return this.actionOpenWindow(message.params);
|
||||
} else if (cmd === "wrapperPermissionAdd") {
|
||||
return this.actionPermissionAdd(message);
|
||||
} else if (cmd === "wrapperRequestFullscreen") {
|
||||
return this.actionRequestFullscreen();
|
||||
} else {
|
||||
if (message.id < 1000000) {
|
||||
return this.ws.send(message);
|
||||
|
@ -959,7 +971,7 @@ jQuery.extend( jQuery.easing,
|
|||
query = window.location.search;
|
||||
}
|
||||
back = window.location.pathname;
|
||||
if (back.slice(-1) !== "/") {
|
||||
if (back.match(/^\/[^\/]+$/)) {
|
||||
back += "/";
|
||||
}
|
||||
if (query.replace("?", "")) {
|
||||
|
@ -992,6 +1004,32 @@ jQuery.extend( jQuery.easing,
|
|||
}
|
||||
};
|
||||
|
||||
Wrapper.prototype.actionRequestFullscreen = function() {
|
||||
var elem, request_fullscreen;
|
||||
if (__indexOf.call(this.site_info.settings.permissions, "Fullscreen") >= 0) {
|
||||
elem = document.getElementById("inner-iframe");
|
||||
request_fullscreen = elem.requestFullScreen || elem.webkitRequestFullscreen || elem.mozRequestFullScreen || elem.msRequestFullScreen;
|
||||
request_fullscreen.call(elem);
|
||||
return setTimeout(((function(_this) {
|
||||
return function() {
|
||||
if (window.innerHeight !== screen.height) {
|
||||
return _this.displayConfirm("This site requests permission:" + " <b>Fullscreen</b>", "Grant", function() {
|
||||
return request_fullscreen.call(elem);
|
||||
});
|
||||
}
|
||||
};
|
||||
})(this)), 100);
|
||||
} else {
|
||||
return this.displayConfirm("This site requests permission:" + " <b>Fullscreen</b>", "Grant", (function(_this) {
|
||||
return function() {
|
||||
_this.site_info.settings.permissions.push("Fullscreen");
|
||||
_this.actionRequestFullscreen();
|
||||
return _this.ws.cmd("permissionAdd", "Fullscreen");
|
||||
};
|
||||
})(this));
|
||||
}
|
||||
};
|
||||
|
||||
Wrapper.prototype.actionPermissionAdd = function(message) {
|
||||
var permission;
|
||||
permission = message.params;
|
||||
|
@ -1099,6 +1137,65 @@ jQuery.extend( jQuery.easing,
|
|||
})(this));
|
||||
};
|
||||
|
||||
Wrapper.prototype.actionProgress = function(message) {
|
||||
var body, circle, elem, offset, percent, width;
|
||||
message.params = this.toHtmlSafe(message.params);
|
||||
percent = Math.min(100, message.params[2]) / 100;
|
||||
offset = 75 - (percent * 75);
|
||||
circle = "<div class=\"circle\"><svg class=\"circle-svg\" width=\"30\" height=\"30\" viewport=\"0 0 30 30\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\">\n <circle r=\"12\" cx=\"15\" cy=\"15\" fill=\"transparent\" class=\"circle-bg\"></circle>\n <circle r=\"12\" cx=\"15\" cy=\"15\" fill=\"transparent\" class=\"circle-fg\" style=\"stroke-dashoffset: " + offset + "\"></circle>\n</svg></div>";
|
||||
body = "<span class='message'>" + message.params[1] + "</span>" + circle;
|
||||
elem = $(".notification-" + message.params[0]);
|
||||
if (elem.length) {
|
||||
width = $(".body .message", elem).outerWidth();
|
||||
$(".body .message", elem).html(message.params[1]);
|
||||
if ($(".body .message", elem).css("width") === "") {
|
||||
$(".body .message", elem).css("width", width);
|
||||
}
|
||||
$(".body .circle-fg", elem).css("stroke-dashoffset", offset);
|
||||
} else {
|
||||
elem = this.notifications.add(message.params[0], "progress", $(body));
|
||||
}
|
||||
if (percent > 0) {
|
||||
$(".body .circle-bg", elem).css({
|
||||
"animation-play-state": "paused",
|
||||
"stroke-dasharray": "180px"
|
||||
});
|
||||
}
|
||||
if ($(".notification-icon", elem).data("done")) {
|
||||
return false;
|
||||
} else if (message.params[2] >= 100) {
|
||||
$(".circle-fg", elem).css("transition", "all 0.3s ease-in-out");
|
||||
setTimeout((function() {
|
||||
$(".notification-icon", elem).css({
|
||||
transform: "scale(1)",
|
||||
opacity: 1
|
||||
});
|
||||
return $(".notification-icon .icon-success", elem).css({
|
||||
transform: "rotate(45deg) scale(1)"
|
||||
});
|
||||
}), 300);
|
||||
setTimeout(((function(_this) {
|
||||
return function() {
|
||||
return _this.notifications.close(elem);
|
||||
};
|
||||
})(this)), 3000);
|
||||
return $(".notification-icon", elem).data("done", true);
|
||||
} else if (message.params[2] < 0) {
|
||||
$(".body .circle-fg", elem).css("stroke", "#ec6f47").css("transition", "transition: all 0.3s ease-in-out");
|
||||
setTimeout(((function(_this) {
|
||||
return function() {
|
||||
$(".notification-icon", elem).css({
|
||||
transform: "scale(1)",
|
||||
opacity: 1
|
||||
});
|
||||
elem.removeClass("notification-done").addClass("notification-error");
|
||||
return $(".notification-icon .icon-success", elem).removeClass("icon-success").html("!");
|
||||
};
|
||||
})(this)), 300);
|
||||
return $(".notification-icon", elem).data("done", true);
|
||||
}
|
||||
};
|
||||
|
||||
Wrapper.prototype.actionSetViewport = function(message) {
|
||||
this.log("actionSetViewport", message);
|
||||
if ($("#viewport").length > 0) {
|
||||
|
@ -1273,7 +1370,7 @@ jQuery.extend( jQuery.easing,
|
|||
window.document.title = site_info.content.title + " - ZeroNet";
|
||||
this.log("Required file done, setting title to", window.document.title);
|
||||
}
|
||||
if (!$(".loadingscreen").length) {
|
||||
if (!window.show_loadingscreen) {
|
||||
this.notifications.add("modified", "info", "New version of this page has just released.<br>Reload to see the modified content.");
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -12,7 +12,7 @@
|
|||
---------------
|
||||
|
||||
/* ====================================================================
|
||||
* Copyright (c) 1998-2011 The OpenSSL Project. All rights reserved.
|
||||
* Copyright (c) 1998-2016 The OpenSSL Project. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
=============================================================================
|
||||
OpenSSL v1.0.2a Precompiled Binaries for Win32
|
||||
OpenSSL v1.0.2j Precompiled Binaries for Win32
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
*** Release Information ***
|
||||
|
||||
Release Date: Mrz 20, 2015
|
||||
Release Date: Okt 01, 2016
|
||||
|
||||
Author: Frederik A. Winkelsdorf (opendec.wordpress.com)
|
||||
for the Indy Project (www.indyproject.org)
|
||||
|
@ -15,7 +15,7 @@ Dependencies: The libraries have no noteworthy dependencies
|
|||
|
||||
Installation: Copy both DLL files into your application directory
|
||||
|
||||
Supported OS: Windows 2000 up to Windows 8
|
||||
Supported OS: Windows 2000 up to Windows 10
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
|
@ -42,10 +42,10 @@ SOFTWARE AND/OR PATENTS.
|
|||
*** Build Information Win32 ***
|
||||
|
||||
Built with: Microsoft Visual C++ 2008 Express Edition
|
||||
The Netwide Assembler (NASM) v2.11.05 Win32
|
||||
Strawberry Perl v5.20.0.1 Win32 Portable
|
||||
The Netwide Assembler (NASM) v2.11.08 Win32
|
||||
Strawberry Perl v5.22.0.1 Win32 Portable
|
||||
Windows PowerShell
|
||||
FinalBuilder 7 Embarcadero Edition
|
||||
FinalBuilder 7
|
||||
|
||||
Commands: perl configure VC-WIN32
|
||||
ms\do_nasm
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -196,11 +196,13 @@ def openLibrary():
|
|||
global ssl
|
||||
try:
|
||||
if sys.platform.startswith("win"):
|
||||
dll_path = "src/lib/opensslVerify/libeay32.dll"
|
||||
dll_path = os.path.dirname(os.path.abspath(__file__)) + "/" + "libeay32.dll"
|
||||
elif sys.platform == "cygwin":
|
||||
dll_path = "/bin/cygcrypto-1.0.0.dll"
|
||||
elif os.path.isfile("../lib/libcrypto.so"): # ZeroBundle
|
||||
elif os.path.isfile("../lib/libcrypto.so"): # ZeroBundle OSX
|
||||
dll_path = "../lib/libcrypto.so"
|
||||
elif os.path.isfile("/opt/lib/libcrypto.so.1.0.0"): # For optware and entware
|
||||
dll_path = "/opt/lib/libcrypto.so.1.0.0"
|
||||
else:
|
||||
dll_path = "/usr/local/ssl/lib/libcrypto.so"
|
||||
ssl = _OpenSSL(dll_path)
|
||||
|
@ -456,7 +458,7 @@ if __name__ == "__main__":
|
|||
sign = btctools.ecdsa_sign("hello", priv) # HGbib2kv9gm9IJjDt1FXbXFczZi35u0rZR3iPUIt5GglDDCeIQ7v8eYXVNIaLoJRI4URGZrhwmsYQ9aVtRTnTfQ=
|
||||
|
||||
s = time.time()
|
||||
for i in range(100):
|
||||
for i in range(1000):
|
||||
pubkey = getMessagePubkey("hello", sign)
|
||||
verified = btctools.pubkey_to_address(pubkey) == address
|
||||
print "100x Verified", verified, time.time() - s
|
||||
|
|
Binary file not shown.
|
@ -434,10 +434,10 @@ def openLibrary():
|
|||
global OpenSSL
|
||||
try:
|
||||
if sys.platform.startswith("win"):
|
||||
dll_path = "src/lib/opensslVerify/libeay32.dll"
|
||||
dll_path = os.path.normpath(os.path.dirname(__file__) + "/../opensslVerify/" + "libeay32.dll")
|
||||
elif sys.platform == "cygwin":
|
||||
dll_path = "/bin/cygcrypto-1.0.0.dll"
|
||||
elif os.path.isfile("../lib/libcrypto.so"): # ZeroBundle
|
||||
elif os.path.isfile("../lib/libcrypto.so"): # ZeroBundle OSX
|
||||
dll_path = "../lib/libcrypto.so"
|
||||
else:
|
||||
dll_path = "/usr/local/ssl/lib/libcrypto.so"
|
||||
|
|
16
src/main.py
16
src/main.py
|
@ -30,14 +30,22 @@ if not config.arguments: # Config parse failed, show the help screen and exit
|
|||
# Create necessary files and dirs
|
||||
if not os.path.isdir(config.log_dir):
|
||||
os.mkdir(config.log_dir)
|
||||
try:
|
||||
os.chmod(config.log_dir, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
|
||||
except Exception, err:
|
||||
print "Can't change permission of %s: %s" % (config.log_dir, err)
|
||||
|
||||
if not os.path.isdir(config.data_dir):
|
||||
os.mkdir(config.data_dir)
|
||||
try:
|
||||
os.chmod(config.data_dir, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
|
||||
except Exception, err:
|
||||
print "Can't change permission of %s: %s" % (config.data_dir, err)
|
||||
|
||||
if not os.path.isfile("%s/sites.json" % config.data_dir):
|
||||
open("%s/sites.json" % config.data_dir, "w").write("{}")
|
||||
os.chmod("%s/sites.json" % config.data_dir, stat.S_IRUSR | stat.S_IWUSR)
|
||||
if not os.path.isfile("%s/users.json" % config.data_dir):
|
||||
open("%s/users.json" % config.data_dir, "w").write("{}")
|
||||
os.chmod("%s/users.json" % config.data_dir, stat.S_IRUSR | stat.S_IWUSR)
|
||||
|
||||
# Setup logging
|
||||
if config.action == "main":
|
||||
|
@ -180,7 +188,7 @@ class Actions(object):
|
|||
|
||||
logging.info("Site created!")
|
||||
|
||||
def siteSign(self, address, privatekey=None, inner_path="content.json", publish=False):
|
||||
def siteSign(self, address, privatekey=None, inner_path="content.json", publish=False, remove_missing_optional=False):
|
||||
from Site import Site
|
||||
from Site import SiteManager
|
||||
SiteManager.site_manager.load()
|
||||
|
@ -200,7 +208,7 @@ class Actions(object):
|
|||
import getpass
|
||||
privatekey = getpass.getpass("Private key (input hidden):")
|
||||
diffs = site.content_manager.getDiffs(inner_path)
|
||||
succ = site.content_manager.sign(inner_path=inner_path, privatekey=privatekey, update_changed_files=True)
|
||||
succ = site.content_manager.sign(inner_path=inner_path, privatekey=privatekey, update_changed_files=True, remove_missing_optional=remove_missing_optional)
|
||||
if succ and publish:
|
||||
self.sitePublish(address, inner_path=inner_path, diffs=diffs)
|
||||
|
||||
|
|
|
@ -15,8 +15,8 @@ def setMaxfilesopened(limit):
|
|||
import resource
|
||||
soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
|
||||
if soft < limit:
|
||||
logging.debug("Current RLIMIT_NOFILE: %s, changing to %s..." % (soft, limit))
|
||||
resource.setrlimit(resource.RLIMIT_NOFILE, (soft, hard))
|
||||
logging.debug("Current RLIMIT_NOFILE: %s (max: %s), changing to %s..." % (soft, hard, limit))
|
||||
resource.setrlimit(resource.RLIMIT_NOFILE, (limit, hard))
|
||||
return True
|
||||
|
||||
except Exception, err:
|
||||
|
|
|
@ -4,6 +4,7 @@ import httplib
|
|||
import logging
|
||||
from urlparse import urlparse
|
||||
from xml.dom.minidom import parseString
|
||||
from xml.parsers.expat import ExpatError
|
||||
|
||||
from gevent import socket
|
||||
|
||||
|
@ -82,7 +83,7 @@ def _retrieve_igd_profile(url):
|
|||
Retrieve the device's UPnP profile.
|
||||
"""
|
||||
try:
|
||||
return urllib2.urlopen(url.geturl(), timeout=5).read()
|
||||
return urllib2.urlopen(url.geturl(), timeout=5).read().decode('utf-8')
|
||||
except socket.error:
|
||||
raise IGDError('IGD profile query timed out')
|
||||
|
||||
|
@ -100,7 +101,11 @@ def _parse_igd_profile(profile_xml):
|
|||
WANIPConnection or WANPPPConnection and return
|
||||
the 'controlURL' and the service xml schema.
|
||||
"""
|
||||
dom = parseString(profile_xml)
|
||||
try:
|
||||
dom = parseString(profile_xml)
|
||||
except ExpatError as e:
|
||||
raise IGDError(
|
||||
'Unable to parse IGD reply: {0} \n\n\n {1}'.format(profile_xml, e))
|
||||
|
||||
service_types = dom.getElementsByTagName('serviceType')
|
||||
for service in service_types:
|
||||
|
@ -268,7 +273,7 @@ def _send_requests(messages, location, upnp_schema, control_path):
|
|||
raise UpnpError('Sending requests using UPnP failed.')
|
||||
|
||||
|
||||
def _orchestrate_soap_request(ip, port, msg_fn, desc=None, protos=['TCP', 'UDP']):
|
||||
def _orchestrate_soap_request(ip, port, msg_fn, desc=None, protos=("TCP", "UDP")):
|
||||
logging.debug("Trying using local ip: %s" % ip)
|
||||
idg_data = _collect_idg_data(ip)
|
||||
|
||||
|
@ -284,7 +289,7 @@ def _communicate_with_igd(port=15441,
|
|||
desc="UpnpPunch",
|
||||
retries=3,
|
||||
fn=_create_open_message,
|
||||
protos=["TCP", "UDP"]):
|
||||
protos=("TCP", "UDP")):
|
||||
"""
|
||||
Manage sending a message generated by 'fn'.
|
||||
"""
|
||||
|
@ -310,7 +315,7 @@ def _communicate_with_igd(port=15441,
|
|||
port, retries))
|
||||
|
||||
|
||||
def ask_to_open_port(port=15441, desc="UpnpPunch", retries=3, protos=["TCP", "UDP"]):
|
||||
def ask_to_open_port(port=15441, desc="UpnpPunch", retries=3, protos=("TCP", "UDP")):
|
||||
logging.debug("Trying to open port %d." % port)
|
||||
_communicate_with_igd(port=port,
|
||||
desc=desc,
|
||||
|
@ -319,7 +324,7 @@ def ask_to_open_port(port=15441, desc="UpnpPunch", retries=3, protos=["TCP", "UD
|
|||
protos=protos)
|
||||
|
||||
|
||||
def ask_to_close_port(port=15441, desc="UpnpPunch", retries=3, protos=["TCP", "UDP"]):
|
||||
def ask_to_close_port(port=15441, desc="UpnpPunch", retries=3, protos=("TCP", "UDP")):
|
||||
logging.debug("Trying to close port %d." % port)
|
||||
# retries=1 because multiple successes cause 500 response and failure
|
||||
_communicate_with_igd(port=port,
|
||||
|
|
|
@ -14,17 +14,15 @@ from Config import config
|
|||
|
||||
def atomicWrite(dest, content, mode="w"):
|
||||
try:
|
||||
permissions = stat.S_IMODE(os.lstat(dest).st_mode)
|
||||
with open(dest + "-new", mode) as f:
|
||||
with open(dest + "-tmpnew", mode) as f:
|
||||
f.write(content)
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
if os.path.isfile(dest + "-old"): # Previous incomplete write
|
||||
os.rename(dest + "-old", dest + "-old-%s" % time.time())
|
||||
os.rename(dest, dest + "-old")
|
||||
os.rename(dest + "-new", dest)
|
||||
os.chmod(dest, permissions)
|
||||
os.unlink(dest + "-old")
|
||||
if os.path.isfile(dest + "-tmpold"): # Previous incomplete write
|
||||
os.rename(dest + "-tmpold", dest + "-tmpold-%s" % time.time())
|
||||
os.rename(dest, dest + "-tmpold")
|
||||
os.rename(dest + "-tmpnew", dest)
|
||||
os.unlink(dest + "-tmpold")
|
||||
return True
|
||||
except Exception, err:
|
||||
from Debug import Debug
|
||||
|
@ -32,8 +30,8 @@ def atomicWrite(dest, content, mode="w"):
|
|||
"File %s write failed: %s, reverting..." %
|
||||
(dest, Debug.formatException(err))
|
||||
)
|
||||
if os.path.isfile(dest + "-old") and not os.path.isfile(dest):
|
||||
os.rename(dest + "-old", dest)
|
||||
if os.path.isfile(dest + "-tmpold") and not os.path.isfile(dest):
|
||||
os.rename(dest + "-tmpold", dest)
|
||||
return False
|
||||
|
||||
|
||||
|
@ -54,7 +52,7 @@ def openLocked(path, mode="w"):
|
|||
def getFreeSpace():
|
||||
free_space = -1
|
||||
if "statvfs" in dir(os): # Unix
|
||||
statvfs = os.statvfs(config.data_dir)
|
||||
statvfs = os.statvfs(config.data_dir.encode("utf8"))
|
||||
free_space = statvfs.f_frsize * statvfs.f_bavail
|
||||
else: # Windows
|
||||
try:
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import urllib
|
||||
import zipfile
|
||||
import os
|
||||
import sys
|
||||
import ssl
|
||||
import httplib
|
||||
import socket
|
||||
|
@ -52,6 +53,11 @@ def download():
|
|||
|
||||
def update():
|
||||
from Config import config
|
||||
if getattr(sys, 'source_update_dir', False):
|
||||
if not os.path.isdir(sys.source_update_dir):
|
||||
os.makedirs(sys.source_update_dir)
|
||||
os.chdir(sys.source_update_dir) # New source code will be stored in different directory
|
||||
|
||||
updatesite_path = config.data_dir + "/" + config.updatesite
|
||||
sites_json = json.load(open(config.data_dir + "/sites.json"))
|
||||
updatesite_bad_files = sites_json.get(config.updatesite, {}).get("cache", {}).get("bad_files", {})
|
||||
|
@ -79,7 +85,7 @@ def update():
|
|||
plugins_enabled.append(dir)
|
||||
print "Plugins enabled:", plugins_enabled, "disabled:", plugins_disabled
|
||||
|
||||
print "Extracting...",
|
||||
print "Extracting to %s..." % os.getcwd(),
|
||||
for inner_path in inner_paths:
|
||||
if ".." in inner_path:
|
||||
continue
|
||||
|
|
28
zeronet.py
28
zeronet.py
|
@ -45,11 +45,16 @@ def main():
|
|||
handler.close()
|
||||
logger.removeHandler(handler)
|
||||
|
||||
|
||||
except (Exception, ): # Prevent closing
|
||||
except Exception, err: # Prevent closing
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
traceback.print_exc(file=open("log/error.log", "a"))
|
||||
try:
|
||||
import logging
|
||||
logging.exception("Unhandled exception: %s" % err)
|
||||
except Exception, log_err:
|
||||
print "Failed to log error:", log_err
|
||||
traceback.print_exc()
|
||||
from Config import config
|
||||
traceback.print_exc(file=open(config.log_dir + "/error.log", "a"))
|
||||
|
||||
if main and main.update_after_shutdown: # Updater
|
||||
# Restart
|
||||
|
@ -58,11 +63,22 @@ def main():
|
|||
import time
|
||||
time.sleep(1) # Wait files to close
|
||||
args = sys.argv[:]
|
||||
args.insert(0, sys.executable)
|
||||
|
||||
sys.executable = sys.executable.replace(".pkg", "") # Frozen mac fix
|
||||
|
||||
if not getattr(sys, 'frozen', False):
|
||||
args.insert(0, sys.executable)
|
||||
|
||||
if sys.platform == 'win32':
|
||||
args = ['"%s"' % arg for arg in args]
|
||||
os.execv(sys.executable, args)
|
||||
|
||||
try:
|
||||
print "Executing %s %s" % (sys.executable, args)
|
||||
os.execv(sys.executable, args)
|
||||
except Exception, err:
|
||||
print "Execv error: %s" % err
|
||||
print "Bye."
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Reference in a new issue