Forking what is left of ZeroNet and hopefully adding an AT Proto Frontend/Proxy
at main 805 lines 32 kB view raw
1import re 2import os 3import html 4import sys 5import math 6import time 7import json 8import io 9import urllib 10import urllib.parse 11 12import gevent 13 14import util 15from Config import config 16from Plugin import PluginManager 17from Debug import Debug 18from Translate import Translate 19from util import helper 20from util.Flag import flag 21from .ZipStream import ZipStream 22 23plugin_dir = os.path.dirname(__file__) 24media_dir = plugin_dir + "/media" 25 26loc_cache = {} 27if "_" not in locals(): 28 _ = Translate(plugin_dir + "/languages/") 29 30 31@PluginManager.registerTo("UiRequest") 32class UiRequestPlugin(object): 33 # Inject our resources to end of original file streams 34 def actionUiMedia(self, path): 35 if path == "/uimedia/all.js" or path == "/uimedia/all.css": 36 # First yield the original file and header 37 body_generator = super(UiRequestPlugin, self).actionUiMedia(path) 38 for part in body_generator: 39 yield part 40 41 # Append our media file to the end 42 ext = re.match(".*(js|css)$", path).group(1) 43 plugin_media_file = "%s/all.%s" % (media_dir, ext) 44 if config.debug: 45 # If debugging merge *.css to all.css and *.js to all.js 46 from Debug import DebugMedia 47 DebugMedia.merge(plugin_media_file) 48 if ext == "js": 49 yield _.translateData(open(plugin_media_file).read()).encode("utf8") 50 else: 51 for part in self.actionFile(plugin_media_file, send_header=False): 52 yield part 53 elif path.startswith("/uimedia/globe/"): # Serve WebGL globe files 54 file_name = re.match(".*/(.*)", path).group(1) 55 plugin_media_file = "%s_globe/%s" % (media_dir, file_name) 56 if config.debug and path.endswith("all.js"): 57 # If debugging merge *.css to all.css and *.js to all.js 58 from Debug import DebugMedia 59 DebugMedia.merge(plugin_media_file) 60 for part in self.actionFile(plugin_media_file): 61 yield part 62 else: 63 for part in super(UiRequestPlugin, self).actionUiMedia(path): 64 yield part 65 66 def actionZip(self): 67 address = self.get["address"] 68 site = self.server.site_manager.get(address) 69 if not site: 70 return self.error404("Site not found") 71 72 title = site.content_manager.contents.get("content.json", {}).get("title", "") 73 filename = "%s-backup-%s.zip" % (title, time.strftime("%Y-%m-%d_%H_%M")) 74 filename_quoted = urllib.parse.quote(filename) 75 self.sendHeader(content_type="application/zip", extra_headers={'Content-Disposition': 'attachment; filename="%s"' % filename_quoted}) 76 77 return self.streamZip(site.storage.getPath(".")) 78 79 def streamZip(self, dir_path): 80 zs = ZipStream(dir_path) 81 while 1: 82 data = zs.read() 83 if not data: 84 break 85 yield data 86 87 88@PluginManager.registerTo("UiWebsocket") 89class UiWebsocketPlugin(object): 90 def sidebarRenderPeerStats(self, body, site): 91 connected = len([peer for peer in list(site.peers.values()) if peer.connection and peer.connection.connected]) 92 connectable = len([peer_id for peer_id in list(site.peers.keys()) if not peer_id.endswith(":0")]) 93 onion = len([peer_id for peer_id in list(site.peers.keys()) if ".onion" in peer_id]) 94 local = len([peer for peer in list(site.peers.values()) if helper.isPrivateIp(peer.ip)]) 95 peers_total = len(site.peers) 96 97 # Add myself 98 if site.isServing(): 99 peers_total += 1 100 if any(site.connection_server.port_opened.values()): 101 connectable += 1 102 if site.connection_server.tor_manager.start_onions: 103 onion += 1 104 105 if peers_total: 106 percent_connected = float(connected) / peers_total 107 percent_connectable = float(connectable) / peers_total 108 percent_onion = float(onion) / peers_total 109 else: 110 percent_connectable = percent_connected = percent_onion = 0 111 112 if local: 113 local_html = _("<li class='color-yellow'><span>{_[Local]}:</span><b>{local}</b></li>") 114 else: 115 local_html = "" 116 117 peer_ips = [peer.key for peer in site.getConnectablePeers(20, allow_private=False)] 118 peer_ips.sort(key=lambda peer_ip: ".onion:" in peer_ip) 119 copy_link = "http://127.0.0.1:43110/%s/?zeronet_peers=%s" % ( 120 site.content_manager.contents.get("content.json", {}).get("domain", site.address), 121 ",".join(peer_ips) 122 ) 123 124 body.append(_(""" 125 <li> 126 <label> 127 {_[Peers]} 128 <small class="label-right"><a href='{copy_link}' id='link-copypeers' class='link-right'>{_[Copy to clipboard]}</a></small> 129 </label> 130 <ul class='graph'> 131 <li style='width: 100%' class='total back-black' title="{_[Total peers]}"></li> 132 <li style='width: {percent_connectable:.0%}' class='connectable back-blue' title='{_[Connectable peers]}'></li> 133 <li style='width: {percent_onion:.0%}' class='connected back-purple' title='{_[Onion]}'></li> 134 <li style='width: {percent_connected:.0%}' class='connected back-green' title='{_[Connected peers]}'></li> 135 </ul> 136 <ul class='graph-legend'> 137 <li class='color-green'><span>{_[Connected]}:</span><b>{connected}</b></li> 138 <li class='color-blue'><span>{_[Connectable]}:</span><b>{connectable}</b></li> 139 <li class='color-purple'><span>{_[Onion]}:</span><b>{onion}</b></li> 140 {local_html} 141 <li class='color-black'><span>{_[Total]}:</span><b>{peers_total}</b></li> 142 </ul> 143 </li> 144 """.replace("{local_html}", local_html))) 145 146 def sidebarRenderTransferStats(self, body, site): 147 recv = float(site.settings.get("bytes_recv", 0)) / 1024 / 1024 148 sent = float(site.settings.get("bytes_sent", 0)) / 1024 / 1024 149 transfer_total = recv + sent 150 if transfer_total: 151 percent_recv = recv / transfer_total 152 percent_sent = sent / transfer_total 153 else: 154 percent_recv = 0.5 155 percent_sent = 0.5 156 157 body.append(_(""" 158 <li> 159 <label>{_[Data transfer]}</label> 160 <ul class='graph graph-stacked'> 161 <li style='width: {percent_recv:.0%}' class='received back-yellow' title="{_[Received bytes]}"></li> 162 <li style='width: {percent_sent:.0%}' class='sent back-green' title="{_[Sent bytes]}"></li> 163 </ul> 164 <ul class='graph-legend'> 165 <li class='color-yellow'><span>{_[Received]}:</span><b>{recv:.2f}MB</b></li> 166 <li class='color-green'<span>{_[Sent]}:</span><b>{sent:.2f}MB</b></li> 167 </ul> 168 </li> 169 """)) 170 171 def sidebarRenderFileStats(self, body, site): 172 body.append(_(""" 173 <li> 174 <label> 175 {_[Files]} 176 <a href='/list/{site.address}' class='link-right link-outline' id="browse-files">{_[Browse files]}</a> 177 <small class="label-right"> 178 <a href='/ZeroNet-Internal/Zip?address={site.address}' id='link-zip' class='link-right' download='site.zip'>{_[Save as .zip]}</a> 179 </small> 180 </label> 181 <ul class='graph graph-stacked'> 182 """)) 183 184 extensions = ( 185 ("html", "yellow"), 186 ("css", "orange"), 187 ("js", "purple"), 188 ("Image", "green"), 189 ("json", "darkblue"), 190 ("User data", "blue"), 191 ("Other", "white"), 192 ("Total", "black") 193 ) 194 # Collect stats 195 size_filetypes = {} 196 size_total = 0 197 contents = site.content_manager.listContents() # Without user files 198 for inner_path in contents: 199 content = site.content_manager.contents[inner_path] 200 if "files" not in content or content["files"] is None: 201 continue 202 for file_name, file_details in list(content["files"].items()): 203 size_total += file_details["size"] 204 ext = file_name.split(".")[-1] 205 size_filetypes[ext] = size_filetypes.get(ext, 0) + file_details["size"] 206 207 # Get user file sizes 208 size_user_content = site.content_manager.contents.execute( 209 "SELECT SUM(size) + SUM(size_files) AS size FROM content WHERE ?", 210 {"not__inner_path": contents} 211 ).fetchone()["size"] 212 if not size_user_content: 213 size_user_content = 0 214 size_filetypes["User data"] = size_user_content 215 size_total += size_user_content 216 217 # The missing difference is content.json sizes 218 if "json" in size_filetypes: 219 size_filetypes["json"] += max(0, site.settings["size"] - size_total) 220 size_total = size_other = site.settings["size"] 221 222 # Bar 223 for extension, color in extensions: 224 if extension == "Total": 225 continue 226 if extension == "Other": 227 size = max(0, size_other) 228 elif extension == "Image": 229 size = size_filetypes.get("jpg", 0) + size_filetypes.get("png", 0) + size_filetypes.get("gif", 0) 230 size_other -= size 231 else: 232 size = size_filetypes.get(extension, 0) 233 size_other -= size 234 if size_total == 0: 235 percent = 0 236 else: 237 percent = 100 * (float(size) / size_total) 238 percent = math.floor(percent * 100) / 100 # Floor to 2 digits 239 body.append( 240 """<li style='width: %.2f%%' class='%s back-%s' title="%s"></li>""" % 241 (percent, _[extension], color, _[extension]) 242 ) 243 244 # Legend 245 body.append("</ul><ul class='graph-legend'>") 246 for extension, color in extensions: 247 if extension == "Other": 248 size = max(0, size_other) 249 elif extension == "Image": 250 size = size_filetypes.get("jpg", 0) + size_filetypes.get("png", 0) + size_filetypes.get("gif", 0) 251 elif extension == "Total": 252 size = size_total 253 else: 254 size = size_filetypes.get(extension, 0) 255 256 if extension == "js": 257 title = "javascript" 258 else: 259 title = extension 260 261 if size > 1024 * 1024 * 10: # Format as mB is more than 10mB 262 size_formatted = "%.0fMB" % (size / 1024 / 1024) 263 else: 264 size_formatted = "%.0fkB" % (size / 1024) 265 266 body.append("<li class='color-%s'><span>%s:</span><b>%s</b></li>" % (color, _[title], size_formatted)) 267 268 body.append("</ul></li>") 269 270 def sidebarRenderSizeLimit(self, body, site): 271 free_space = helper.getFreeSpace() / 1024 / 1024 272 size = float(site.settings["size"]) / 1024 / 1024 273 size_limit = site.getSizeLimit() 274 percent_used = size / size_limit 275 276 body.append(_(""" 277 <li> 278 <label>{_[Size limit]} <small>({_[limit used]}: {percent_used:.0%}, {_[free space]}: {free_space:,.0f}MB)</small></label> 279 <input type='text' class='text text-num' value="{size_limit}" id='input-sitelimit'/><span class='text-post'>MB</span> 280 <a href='#Set' class='button' id='button-sitelimit'>{_[Set]}</a> 281 </li> 282 """)) 283 284 def sidebarRenderOptionalFileStats(self, body, site): 285 size_total = float(site.settings["size_optional"]) 286 size_downloaded = float(site.settings["optional_downloaded"]) 287 288 if not size_total: 289 return False 290 291 percent_downloaded = size_downloaded / size_total 292 293 size_formatted_total = size_total / 1024 / 1024 294 size_formatted_downloaded = size_downloaded / 1024 / 1024 295 296 body.append(_(""" 297 <li> 298 <label>{_[Optional files]}</label> 299 <ul class='graph'> 300 <li style='width: 100%' class='total back-black' title="{_[Total size]}"></li> 301 <li style='width: {percent_downloaded:.0%}' class='connected back-green' title='{_[Downloaded files]}'></li> 302 </ul> 303 <ul class='graph-legend'> 304 <li class='color-green'><span>{_[Downloaded]}:</span><b>{size_formatted_downloaded:.2f}MB</b></li> 305 <li class='color-black'><span>{_[Total]}:</span><b>{size_formatted_total:.2f}MB</b></li> 306 </ul> 307 </li> 308 """)) 309 310 return True 311 312 def sidebarRenderOptionalFileSettings(self, body, site): 313 if self.site.settings.get("autodownloadoptional"): 314 checked = "checked='checked'" 315 else: 316 checked = "" 317 318 body.append(_(""" 319 <li> 320 <label>{_[Help distribute added optional files]}</label> 321 <input type="checkbox" class="checkbox" id="checkbox-autodownloadoptional" {checked}/><div class="checkbox-skin"></div> 322 """)) 323 324 if hasattr(config, "autodownload_bigfile_size_limit"): 325 autodownload_bigfile_size_limit = int(site.settings.get("autodownload_bigfile_size_limit", config.autodownload_bigfile_size_limit)) 326 body.append(_(""" 327 <div class='settings-autodownloadoptional'> 328 <label>{_[Auto download big file size limit]}</label> 329 <input type='text' class='text text-num' value="{autodownload_bigfile_size_limit}" id='input-autodownload_bigfile_size_limit'/><span class='text-post'>MB</span> 330 <a href='#Set' class='button' id='button-autodownload_bigfile_size_limit'>{_[Set]}</a> 331 <a href='#Download+previous' class='button' id='button-autodownload_previous'>{_[Download previous files]}</a> 332 </div> 333 """)) 334 body.append("</li>") 335 336 def sidebarRenderBadFiles(self, body, site): 337 body.append(_(""" 338 <li> 339 <label>{_[Needs to be updated]}:</label> 340 <ul class='filelist'> 341 """)) 342 343 i = 0 344 for bad_file, tries in site.bad_files.items(): 345 i += 1 346 body.append(_("""<li class='color-red' title="{bad_file_path} ({tries})">{bad_filename}</li>""", { 347 "bad_file_path": bad_file, 348 "bad_filename": helper.getFilename(bad_file), 349 "tries": _.pluralize(tries, "{} try", "{} tries") 350 })) 351 if i > 30: 352 break 353 354 if len(site.bad_files) > 30: 355 num_bad_files = len(site.bad_files) - 30 356 body.append(_("""<li class='color-red'>{_[+ {num_bad_files} more]}</li>""", nested=True)) 357 358 body.append(""" 359 </ul> 360 </li> 361 """) 362 363 def sidebarRenderDbOptions(self, body, site): 364 if site.storage.db: 365 inner_path = site.storage.getInnerPath(site.storage.db.db_path) 366 size = float(site.storage.getSize(inner_path)) / 1024 367 feeds = len(site.storage.db.schema.get("feeds", {})) 368 else: 369 inner_path = _["No database found"] 370 size = 0.0 371 feeds = 0 372 373 body.append(_(""" 374 <li> 375 <label>{_[Database]} <small>({size:.2f}kB, {_[search feeds]}: {_[{feeds} query]})</small></label> 376 <div class='flex'> 377 <input type='text' class='text disabled' value="{inner_path}" disabled='disabled'/> 378 <a href='#Reload' id="button-dbreload" class='button'>{_[Reload]}</a> 379 <a href='#Rebuild' id="button-dbrebuild" class='button'>{_[Rebuild]}</a> 380 </div> 381 </li> 382 """, nested=True)) 383 384 def sidebarRenderIdentity(self, body, site): 385 auth_address = self.user.getAuthAddress(self.site.address, create=False) 386 rules = self.site.content_manager.getRules("data/users/%s/content.json" % auth_address) 387 if rules and rules.get("max_size"): 388 quota = rules["max_size"] / 1024 389 try: 390 content = site.content_manager.contents["data/users/%s/content.json" % auth_address] 391 used = len(json.dumps(content)) + sum([file["size"] for file in list(content["files"].values())]) 392 except: 393 used = 0 394 used = used / 1024 395 else: 396 quota = used = 0 397 398 body.append(_(""" 399 <li> 400 <label>{_[Identity address]} <small>({_[limit used]}: {used:.2f}kB / {quota:.2f}kB)</small></label> 401 <div class='flex'> 402 <span class='input text disabled'>{auth_address}</span> 403 <a href='#Change' class='button' id='button-identity'>{_[Change]}</a> 404 </div> 405 </li> 406 """)) 407 408 def sidebarRenderControls(self, body, site): 409 auth_address = self.user.getAuthAddress(self.site.address, create=False) 410 if self.site.settings["serving"]: 411 class_pause = "" 412 class_resume = "hidden" 413 else: 414 class_pause = "hidden" 415 class_resume = "" 416 417 body.append(_(""" 418 <li> 419 <label>{_[Site control]}</label> 420 <a href='#Update' class='button noupdate' id='button-update'>{_[Update]}</a> 421 <a href='#Pause' class='button {class_pause}' id='button-pause'>{_[Pause]}</a> 422 <a href='#Resume' class='button {class_resume}' id='button-resume'>{_[Resume]}</a> 423 <a href='#Delete' class='button noupdate' id='button-delete'>{_[Delete]}</a> 424 </li> 425 """)) 426 427 donate_key = site.content_manager.contents.get("content.json", {}).get("donate", True) 428 site_address = self.site.address 429 body.append(_(""" 430 <li> 431 <label>{_[Site address]}</label><br> 432 <div class='flex'> 433 <span class='input text disabled'>{site_address}</span> 434 """)) 435 if donate_key == False or donate_key == "": 436 pass 437 elif (type(donate_key) == str or type(donate_key) == str) and len(donate_key) > 0: 438 body.append(_(""" 439 </div> 440 </li> 441 <li> 442 <label>{_[Donate]}</label><br> 443 <div class='flex'> 444 {donate_key} 445 """)) 446 else: 447 body.append(_(""" 448 <a href='bitcoin:{site_address}' class='button' id='button-donate'>{_[Donate]}</a> 449 """)) 450 body.append(_(""" 451 </div> 452 </li> 453 """)) 454 455 def sidebarRenderOwnedCheckbox(self, body, site): 456 if self.site.settings["own"]: 457 checked = "checked='checked'" 458 else: 459 checked = "" 460 461 body.append(_(""" 462 <h2 class='owned-title'>{_[This is my site]}</h2> 463 <input type="checkbox" class="checkbox" id="checkbox-owned" {checked}/><div class="checkbox-skin"></div> 464 """)) 465 466 def sidebarRenderOwnSettings(self, body, site): 467 title = site.content_manager.contents.get("content.json", {}).get("title", "") 468 description = site.content_manager.contents.get("content.json", {}).get("description", "") 469 470 body.append(_(""" 471 <li> 472 <label for='settings-title'>{_[Site title]}</label> 473 <input type='text' class='text' value="{title}" id='settings-title'/> 474 </li> 475 476 <li> 477 <label for='settings-description'>{_[Site description]}</label> 478 <input type='text' class='text' value="{description}" id='settings-description'/> 479 </li> 480 481 <li> 482 <a href='#Save' class='button' id='button-settings'>{_[Save site settings]}</a> 483 </li> 484 """)) 485 486 def sidebarRenderContents(self, body, site): 487 has_privatekey = bool(self.user.getSiteData(site.address, create=False).get("privatekey")) 488 if has_privatekey: 489 tag_privatekey = _("{_[Private key saved.]} <a href='#Forget+private+key' id='privatekey-forget' class='link-right'>{_[Forget]}</a>") 490 else: 491 tag_privatekey = _("<a href='#Add+private+key' id='privatekey-add' class='link-right'>{_[Add saved private key]}</a>") 492 493 body.append(_(""" 494 <li> 495 <label>{_[Content publishing]} <small class='label-right'>{tag_privatekey}</small></label> 496 """.replace("{tag_privatekey}", tag_privatekey))) 497 498 # Choose content you want to sign 499 body.append(_(""" 500 <div class='flex'> 501 <input type='text' class='text' value="content.json" id='input-contents'/> 502 <a href='#Sign-and-Publish' id='button-sign-publish' class='button'>{_[Sign and publish]}</a> 503 <a href='#Sign-or-Publish' id='menu-sign-publish'>\u22EE</a> 504 </div> 505 """)) 506 507 contents = ["content.json"] 508 contents += list(site.content_manager.contents.get("content.json", {}).get("includes", {}).keys()) 509 body.append(_("<div class='contents'>{_[Choose]}: ")) 510 for content in contents: 511 body.append(_("<a href='{content}' class='contents-content'>{content}</a> ")) 512 body.append("</div>") 513 body.append("</li>") 514 515 @flag.admin 516 def actionSidebarGetHtmlTag(self, to): 517 site = self.site 518 519 body = [] 520 521 body.append("<div>") 522 body.append("<a href='#Close' class='close'>&times;</a>") 523 body.append("<h1>%s</h1>" % html.escape(site.content_manager.contents.get("content.json", {}).get("title", ""), True)) 524 525 body.append("<div class='globe loading'></div>") 526 527 body.append("<ul class='fields'>") 528 529 self.sidebarRenderPeerStats(body, site) 530 self.sidebarRenderTransferStats(body, site) 531 self.sidebarRenderFileStats(body, site) 532 self.sidebarRenderSizeLimit(body, site) 533 has_optional = self.sidebarRenderOptionalFileStats(body, site) 534 if has_optional: 535 self.sidebarRenderOptionalFileSettings(body, site) 536 self.sidebarRenderDbOptions(body, site) 537 self.sidebarRenderIdentity(body, site) 538 self.sidebarRenderControls(body, site) 539 if site.bad_files: 540 self.sidebarRenderBadFiles(body, site) 541 542 self.sidebarRenderOwnedCheckbox(body, site) 543 body.append("<div class='settings-owned'>") 544 self.sidebarRenderOwnSettings(body, site) 545 self.sidebarRenderContents(body, site) 546 body.append("</div>") 547 body.append("</ul>") 548 body.append("</div>") 549 550 body.append("<div class='menu template'>") 551 body.append("<a href='#'' class='menu-item template'>Template</a>") 552 body.append("</div>") 553 554 self.response(to, "".join(body)) 555 556 def downloadGeoLiteDb(self, db_path): 557 import gzip 558 import shutil 559 from util import helper 560 561 if config.offline: 562 return False 563 564 self.log.info("Downloading GeoLite2 City database...") 565 self.cmd("progress", ["geolite-info", _["Downloading GeoLite2 City database (one time only, ~20MB)..."], 0]) 566 db_urls = [ 567 "https://raw.githubusercontent.com/aemr3/GeoLite2-Database/master/GeoLite2-City.mmdb.gz", 568 "https://raw.githubusercontent.com/texnikru/GeoLite2-Database/master/GeoLite2-City.mmdb.gz" 569 ] 570 for db_url in db_urls: 571 downloadl_err = None 572 try: 573 # Download 574 response = helper.httpRequest(db_url) 575 data_size = response.getheader('content-length') 576 data_recv = 0 577 data = io.BytesIO() 578 while True: 579 buff = response.read(1024 * 512) 580 if not buff: 581 break 582 data.write(buff) 583 data_recv += 1024 * 512 584 if data_size: 585 progress = int(float(data_recv) / int(data_size) * 100) 586 self.cmd("progress", ["geolite-info", _["Downloading GeoLite2 City database (one time only, ~20MB)..."], progress]) 587 self.log.info("GeoLite2 City database downloaded (%s bytes), unpacking..." % data.tell()) 588 data.seek(0) 589 590 # Unpack 591 with gzip.GzipFile(fileobj=data) as gzip_file: 592 shutil.copyfileobj(gzip_file, open(db_path, "wb")) 593 594 self.cmd("progress", ["geolite-info", _["GeoLite2 City database downloaded!"], 100]) 595 time.sleep(2) # Wait for notify animation 596 self.log.info("GeoLite2 City database is ready at: %s" % db_path) 597 return True 598 except Exception as err: 599 download_err = err 600 self.log.error("Error downloading %s: %s" % (db_url, err)) 601 pass 602 self.cmd("progress", [ 603 "geolite-info", 604 _["GeoLite2 City database download error: {}!<br>Please download manually and unpack to data dir:<br>{}"].format(download_err, db_urls[0]), 605 -100 606 ]) 607 608 def getLoc(self, geodb, ip): 609 global loc_cache 610 611 if ip in loc_cache: 612 return loc_cache[ip] 613 else: 614 try: 615 loc_data = geodb.get(ip) 616 except: 617 loc_data = None 618 619 if not loc_data or "location" not in loc_data: 620 loc_cache[ip] = None 621 return None 622 623 loc = { 624 "lat": loc_data["location"]["latitude"], 625 "lon": loc_data["location"]["longitude"], 626 } 627 if "city" in loc_data: 628 loc["city"] = loc_data["city"]["names"]["en"] 629 630 if "country" in loc_data: 631 loc["country"] = loc_data["country"]["names"]["en"] 632 633 loc_cache[ip] = loc 634 return loc 635 636 @util.Noparallel() 637 def getGeoipDb(self): 638 db_name = 'GeoLite2-City.mmdb' 639 640 sys_db_paths = [] 641 if sys.platform == "linux": 642 sys_db_paths += ['/usr/share/GeoIP/' + db_name] 643 644 data_dir_db_path = os.path.join(config.data_dir, db_name) 645 646 db_paths = sys_db_paths + [data_dir_db_path] 647 648 for path in db_paths: 649 if os.path.isfile(path) and os.path.getsize(path) > 0: 650 return path 651 652 self.log.info("GeoIP database not found at [%s]. Downloading to: %s", 653 " ".join(db_paths), data_dir_db_path) 654 if self.downloadGeoLiteDb(data_dir_db_path): 655 return data_dir_db_path 656 return None 657 658 def getPeerLocations(self, peers): 659 import maxminddb 660 661 db_path = self.getGeoipDb() 662 if not db_path: 663 self.log.debug("Not showing peer locations: no GeoIP database") 664 return False 665 666 geodb = maxminddb.open_database(db_path) 667 668 peers = list(peers.values()) 669 # Place bars 670 peer_locations = [] 671 placed = {} # Already placed bars here 672 for peer in peers: 673 # Height of bar 674 if peer.connection and peer.connection.last_ping_delay: 675 ping = round(peer.connection.last_ping_delay * 1000) 676 else: 677 ping = None 678 loc = self.getLoc(geodb, peer.ip) 679 680 if not loc: 681 continue 682 # Create position array 683 lat, lon = loc["lat"], loc["lon"] 684 latlon = "%s,%s" % (lat, lon) 685 if latlon in placed and helper.getIpType(peer.ip) == "ipv4": # Dont place more than 1 bar to same place, fake repos using ip address last two part 686 lat += float(128 - int(peer.ip.split(".")[-2])) / 50 687 lon += float(128 - int(peer.ip.split(".")[-1])) / 50 688 latlon = "%s,%s" % (lat, lon) 689 placed[latlon] = True 690 peer_location = {} 691 peer_location.update(loc) 692 peer_location["lat"] = lat 693 peer_location["lon"] = lon 694 peer_location["ping"] = ping 695 696 peer_locations.append(peer_location) 697 698 # Append myself 699 for ip in self.site.connection_server.ip_external_list: 700 my_loc = self.getLoc(geodb, ip) 701 if my_loc: 702 my_loc["ping"] = 0 703 peer_locations.append(my_loc) 704 705 return peer_locations 706 707 @flag.admin 708 @flag.async_run 709 def actionSidebarGetPeers(self, to): 710 try: 711 peer_locations = self.getPeerLocations(self.site.peers) 712 globe_data = [] 713 ping_times = [ 714 peer_location["ping"] 715 for peer_location in peer_locations 716 if peer_location["ping"] 717 ] 718 if ping_times: 719 ping_avg = sum(ping_times) / float(len(ping_times)) 720 else: 721 ping_avg = 0 722 723 for peer_location in peer_locations: 724 if peer_location["ping"] == 0: # Me 725 height = -0.135 726 elif peer_location["ping"]: 727 height = min(0.20, math.log(1 + peer_location["ping"] / ping_avg, 300)) 728 else: 729 height = -0.03 730 731 globe_data += [peer_location["lat"], peer_location["lon"], height] 732 733 self.response(to, globe_data) 734 except Exception as err: 735 self.log.debug("sidebarGetPeers error: %s" % Debug.formatException(err)) 736 self.response(to, {"error": str(err)}) 737 738 @flag.admin 739 @flag.no_multiuser 740 def actionSiteSetOwned(self, to, owned): 741 if self.site.address == config.updatesite: 742 return {"error": "You can't change the ownership of the updater site"} 743 744 self.site.settings["own"] = bool(owned) 745 self.site.updateWebsocket(owned=owned) 746 return "ok" 747 748 @flag.admin 749 @flag.no_multiuser 750 def actionSiteRecoverPrivatekey(self, to): 751 from Crypt import CryptBitcoin 752 753 site_data = self.user.sites[self.site.address] 754 if site_data.get("privatekey"): 755 return {"error": "This site already has saved privated key"} 756 757 address_index = self.site.content_manager.contents.get("content.json", {}).get("address_index") 758 if not address_index: 759 return {"error": "No address_index in content.json"} 760 761 privatekey = CryptBitcoin.hdPrivatekey(self.user.master_seed, address_index) 762 privatekey_address = CryptBitcoin.privatekeyToAddress(privatekey) 763 764 if privatekey_address == self.site.address: 765 site_data["privatekey"] = privatekey 766 self.user.save() 767 self.site.updateWebsocket(recover_privatekey=True) 768 return "ok" 769 else: 770 return {"error": "Unable to deliver private key for this site from current user's master_seed"} 771 772 @flag.admin 773 @flag.no_multiuser 774 def actionUserSetSitePrivatekey(self, to, privatekey): 775 site_data = self.user.sites[self.site.address] 776 site_data["privatekey"] = privatekey 777 self.site.updateWebsocket(set_privatekey=bool(privatekey)) 778 self.user.save() 779 780 return "ok" 781 782 @flag.admin 783 @flag.no_multiuser 784 def actionSiteSetAutodownloadoptional(self, to, owned): 785 self.site.settings["autodownloadoptional"] = bool(owned) 786 self.site.worker_manager.removeSolvedFileTasks() 787 788 @flag.no_multiuser 789 @flag.admin 790 def actionDbReload(self, to): 791 self.site.storage.closeDb() 792 self.site.storage.getDb() 793 794 return self.response(to, "ok") 795 796 @flag.no_multiuser 797 @flag.admin 798 def actionDbRebuild(self, to): 799 try: 800 self.site.storage.rebuildDb() 801 except Exception as err: 802 return self.response(to, {"error": str(err)}) 803 804 805 return self.response(to, "ok")