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