MergerSite plugin
This commit is contained in:
parent
b7b238b890
commit
e6456f491a
2 changed files with 306 additions and 0 deletions
305
plugins/MergerSite/MergerSitePlugin.py
Normal file
305
plugins/MergerSite/MergerSitePlugin.py
Normal file
|
@ -0,0 +1,305 @@
|
|||
import re
|
||||
|
||||
from Plugin import PluginManager
|
||||
from util import RateLimit
|
||||
from util import helper
|
||||
|
||||
if "merger_db" not in locals().keys(): # To keep merger_sites between module reloads
|
||||
merger_db = {} # Sites that allowed to list other sites {address: [type1, type2...]}
|
||||
merged_db = {} # Sites that allowed to be merged to other sites {address: type, ...}
|
||||
merged_to_merger = {} # {address: [site1, site2, ...]} cache
|
||||
site_manager = None # Site manager for merger sites
|
||||
|
||||
|
||||
# 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)
|
||||
if merged_match:
|
||||
merger_type = merged_match.group(1)
|
||||
# Check if merged site is allowed to include other sites
|
||||
if merger_type in merger_db.get(address, []):
|
||||
# Check if included site allows to include
|
||||
merged_address = merged_match.group(2)
|
||||
if merged_db.get(merged_address) == merger_type:
|
||||
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))
|
||||
else:
|
||||
raise Exception("No merger (%s) permission to load: <br>%s (%s not in %s)" % (
|
||||
address, inner_path, merger_type, merger_db.get(address, []))
|
||||
)
|
||||
else:
|
||||
raise Exception("Invalid merger path: %s" % inner_path)
|
||||
|
||||
|
||||
@PluginManager.registerTo("UiWebsocket")
|
||||
class UiWebsocketPlugin(object):
|
||||
# Download new site
|
||||
def actionMergerSiteAdd(self, to, addresses):
|
||||
if type(addresses) != list:
|
||||
# Single site add
|
||||
addresses = [addresses]
|
||||
# Check if the site has merger permission
|
||||
merger_types = merger_db.get(self.site.address)
|
||||
if not merger_types:
|
||||
return self.response(to, {"error": "Not a merger site"})
|
||||
|
||||
if RateLimit.isAllowed(self.site.address + "-MergerSiteAdd", 10) and len(addresses) == 1:
|
||||
# Without confirmation if only one site address and not called in last 10 sec
|
||||
self.cbMergerSiteAdd(to, addresses)
|
||||
else:
|
||||
self.cmd(
|
||||
"confirm",
|
||||
["Add <b>%s</b> new site?" % len(addresses), "Add"],
|
||||
lambda (res): self.cbMergerSiteAdd(to, addresses)
|
||||
)
|
||||
self.response(to, "ok")
|
||||
|
||||
# Callback of adding new site confirmation
|
||||
def cbMergerSiteAdd(self, to, addresses):
|
||||
added = 0
|
||||
for address in addresses:
|
||||
added += 1
|
||||
site_manager.need(address)
|
||||
if added:
|
||||
self.cmd("notification", ["done", "Added <b>%s</b> new site" % added, 5000])
|
||||
RateLimit.called(self.site.address + "-MergerSiteAdd")
|
||||
site_manager.updateMergerSites()
|
||||
|
||||
# Delete a merged site
|
||||
def actionMergerSiteDelete(self, to, address):
|
||||
site = self.server.sites.get(address)
|
||||
if not site:
|
||||
return self.response(to, {"error": "No site found: %s" % address})
|
||||
|
||||
merger_types = merger_db.get(self.site.address)
|
||||
if not merger_types:
|
||||
return self.response(to, {"error": "Not a merger site"})
|
||||
if merged_db.get(address) not in merger_types:
|
||||
return self.response(to, {"error": "Merged type (%s) not in %s" % (merged_db.get(address), merger_types)})
|
||||
|
||||
self.cmd("notification", ["done", "Site deleted: <b>%s</b>" % address, 5000])
|
||||
self.response(to, "ok")
|
||||
|
||||
# Lists merged sites
|
||||
def actionMergerSiteList(self, to, query_site_info=False):
|
||||
merger_types = merger_db.get(self.site.address)
|
||||
ret = {}
|
||||
if not merger_types:
|
||||
return self.response(to, {"error": "Not a merger site"})
|
||||
for address, merged_type in merged_db.iteritems():
|
||||
if merged_type not in merger_types:
|
||||
continue # Site not for us
|
||||
if query_site_info:
|
||||
site = self.server.sites.get(address)
|
||||
ret[address] = self.formatSiteInfo(site, create_user=False)
|
||||
else:
|
||||
ret[address] = merged_type
|
||||
self.response(to, ret)
|
||||
|
||||
# Add support merger sites for file commands
|
||||
def mergerFuncWrapper(self, func_name, to, inner_path, *args, **kwargs):
|
||||
func = getattr(super(UiWebsocketPlugin, self), func_name)
|
||||
if inner_path.startswith("merged-"):
|
||||
merged_address, merged_inner_path = checkMergerPath(self.site.address, inner_path)
|
||||
|
||||
# Set the same cert for merged site
|
||||
merger_cert = self.user.getSiteData(self.site.address).get("cert")
|
||||
if merger_cert:
|
||||
self.user.setCert(merged_address, merger_cert)
|
||||
|
||||
site_before = self.site # Save to be able to change it back after we ran the command
|
||||
self.site = self.server.sites.get(merged_address) # Change the site to the merged one
|
||||
try:
|
||||
back = func(to, merged_inner_path, *args, **kwargs)
|
||||
finally:
|
||||
self.site = site_before # Change back to original site
|
||||
return back
|
||||
else:
|
||||
return func(to, inner_path, *args, **kwargs)
|
||||
|
||||
def actionFileGet(self, to, inner_path, *args, **kwargs):
|
||||
return self.mergerFuncWrapper("actionFileGet", to, inner_path, *args, **kwargs)
|
||||
|
||||
def actionFileWrite(self, to, inner_path, *args, **kwargs):
|
||||
return self.mergerFuncWrapper("actionFileWrite", to, inner_path, *args, **kwargs)
|
||||
|
||||
def actionFileDelete(self, to, inner_path, *args, **kwargs):
|
||||
return self.mergerFuncWrapper("actionFileDelete", to, inner_path, *args, **kwargs)
|
||||
|
||||
def actionFileRules(self, to, inner_path, *args, **kwargs):
|
||||
return self.mergerFuncWrapper("actionFileRules", to, inner_path, *args, **kwargs)
|
||||
|
||||
# Add support merger sites for file commands with privatekey parameter
|
||||
def mergerFuncWrapperWithPrivatekey(self, func_name, to, privatekey, inner_path, *args, **kwargs):
|
||||
func = getattr(super(UiWebsocketPlugin, self), func_name)
|
||||
if inner_path.startswith("merged-"):
|
||||
merged_address, merged_inner_path = checkMergerPath(self.site.address, inner_path)
|
||||
merged_site = self.server.sites.get(merged_address)
|
||||
|
||||
# Set the same cert for merged site
|
||||
merger_cert = self.user.getSiteData(self.site.address).get("cert")
|
||||
if merger_cert:
|
||||
self.user.setCert(merged_address, merger_cert)
|
||||
|
||||
site_before = self.site # Save to be able to change it back after we ran the command
|
||||
self.site = merged_site # Change the site to the merged one
|
||||
try:
|
||||
back = func(to, privatekey, merged_inner_path, *args, **kwargs)
|
||||
finally:
|
||||
self.site = site_before # Change back to original site
|
||||
return back
|
||||
else:
|
||||
return func(to, privatekey, inner_path, *args, **kwargs)
|
||||
|
||||
def actionSiteSign(self, to, privatekey=None, inner_path="content.json", *args, **kwargs):
|
||||
return self.mergerFuncWrapperWithPrivatekey("actionSiteSign", to, privatekey, inner_path, *args, **kwargs)
|
||||
|
||||
def actionSitePublish(self, to, privatekey=None, inner_path="content.json", *args, **kwargs):
|
||||
return self.mergerFuncWrapperWithPrivatekey("actionSitePublish", to, privatekey, inner_path, *args, **kwargs)
|
||||
|
||||
def actionPermissionAdd(self, to, permission):
|
||||
super(UiWebsocketPlugin, self).actionPermissionAdd(to, permission)
|
||||
self.site.storage.rebuildDb()
|
||||
|
||||
|
||||
@PluginManager.registerTo("UiRequest")
|
||||
class UiRequestPlugin(object):
|
||||
# Allow to load merged site files using /merged-ZeroMe/address/file.jpg
|
||||
def parsePath(self, path):
|
||||
path_parts = super(UiRequestPlugin, self).parsePath(path)
|
||||
if "merged-" not in path: # Optimization
|
||||
return path_parts
|
||||
path_parts["address"], path_parts["inner_path"] = checkMergerPath(path_parts["address"], path_parts["inner_path"])
|
||||
return path_parts
|
||||
|
||||
|
||||
@PluginManager.registerTo("SiteStorage")
|
||||
class SiteStoragePlugin(object):
|
||||
# Also rebuild from merged sites
|
||||
def getDbFiles(self):
|
||||
merger_types = merger_db.get(self.site.address)
|
||||
|
||||
# First return the site's own db files
|
||||
for item in super(SiteStoragePlugin, self).getDbFiles():
|
||||
yield item
|
||||
|
||||
# Not a merger site, that's all
|
||||
if not merger_types:
|
||||
raise StopIteration
|
||||
|
||||
merged_sites = [
|
||||
site_manager.sites[address]
|
||||
for address, merged_type in merged_db.iteritems()
|
||||
if merged_type in merger_types
|
||||
]
|
||||
for merged_site in merged_sites:
|
||||
merged_type = merged_db[merged_site.address]
|
||||
for content_inner_path, content in merged_site.content_manager.contents.iteritems():
|
||||
# content.json file itself
|
||||
if merged_site.storage.isFile(content_inner_path): # Missing content.json file
|
||||
content_path = self.getPath("merged-%s/%s/%s" % (merged_type, merged_site.address, content_inner_path))
|
||||
yield content_path, merged_site.storage.open(content_inner_path)
|
||||
else:
|
||||
merged_site.log.error("[MISSING] %s" % content_inner_path)
|
||||
# Data files in content.json
|
||||
content_inner_path_dir = helper.getDirname(content_inner_path) # Content.json dir relative to site
|
||||
for file_relative_path in content["files"].keys():
|
||||
if not file_relative_path.endswith(".json"):
|
||||
continue # We only interesed in json files
|
||||
file_inner_path = content_inner_path_dir + file_relative_path # File Relative to site dir
|
||||
file_inner_path = file_inner_path.strip("/") # Strip leading /
|
||||
if merged_site.storage.isFile(file_inner_path):
|
||||
file_path = self.getPath("merged-%s/%s/%s" % (merged_type, merged_site.address, file_inner_path))
|
||||
yield file_path, merged_site.storage.open(file_inner_path)
|
||||
else:
|
||||
merged_site.log.error("[MISSING] %s" % file_inner_path)
|
||||
|
||||
# Also notice merger sites on a merged site file change
|
||||
def onUpdated(self, inner_path, file=None):
|
||||
super(SiteStoragePlugin, self).onUpdated(inner_path, file)
|
||||
|
||||
merged_type = merged_db.get(self.site.address)
|
||||
|
||||
for merger_site in merged_to_merger.get(self.site.address, []):
|
||||
if merger_site.address == self.site.address: # Avoid infinite loop
|
||||
continue
|
||||
virtual_path = "merged-%s/%s/%s" % (merged_type, self.site.address, inner_path)
|
||||
if inner_path.endswith(".json"):
|
||||
if file is not None:
|
||||
merger_site.storage.onUpdated(virtual_path, file=file)
|
||||
else:
|
||||
merger_site.storage.onUpdated(virtual_path, file=self.open(inner_path))
|
||||
else:
|
||||
merger_site.storage.onUpdated(virtual_path)
|
||||
|
||||
# Send the event to merger site's websocket
|
||||
for ws in merger_site.websockets:
|
||||
ws.event("siteChanged", self.site, {"event": ["file_done", virtual_path]})
|
||||
|
||||
|
||||
@PluginManager.registerTo("Site")
|
||||
class SitePlugin(object):
|
||||
def fileDone(self, inner_path):
|
||||
super(SitePlugin, self).fileDone(inner_path)
|
||||
|
||||
merged_type = merged_db.get(self.address)
|
||||
virtual_path = "merged-%s/%s/%s" % (merged_type, self.address, inner_path)
|
||||
|
||||
for merger_site in merged_to_merger.get(self.address, []):
|
||||
merger_site.fileDone(virtual_path)
|
||||
|
||||
def fileFailed(self, inner_path):
|
||||
super(SitePlugin, self).fileFailed(inner_path)
|
||||
|
||||
merged_type = merged_db.get(self.address)
|
||||
virtual_path = "merged-%s/%s/%s" % (merged_type, self.address, inner_path)
|
||||
|
||||
for merger_site in merged_to_merger.get(self.address, []):
|
||||
merger_site.fileFailed(virtual_path)
|
||||
|
||||
|
||||
@PluginManager.registerTo("SiteManager")
|
||||
class SiteManagerPlugin(object):
|
||||
# Update merger site for site types
|
||||
def updateMergerSites(self):
|
||||
global merger_db, merged_db, merged_to_merger, site_manager
|
||||
self.log.debug("Update merger sites")
|
||||
merger_db = {}
|
||||
merged_db = {}
|
||||
merged_to_merger = {}
|
||||
site_manager = self
|
||||
for site in self.sites.itervalues():
|
||||
# Update merger sites
|
||||
for permission in site.settings["permissions"]:
|
||||
if not permission.startswith("Merger:"):
|
||||
continue
|
||||
merger_type = permission.replace("Merger:", "")
|
||||
if site.address not in merger_db:
|
||||
merger_db[site.address] = []
|
||||
merger_db[site.address].append(merger_type)
|
||||
site_manager.sites[site.address] = site
|
||||
|
||||
# Update merged sites
|
||||
merged_type = site.content_manager.contents.get("content.json", {}).get("merged_type")
|
||||
if merged_type:
|
||||
merged_db[site.address] = merged_type
|
||||
|
||||
# Update merged to merger
|
||||
if merged_type:
|
||||
for merger_site in self.sites.itervalues():
|
||||
if "Merger:" + merged_type in merger_site.settings["permissions"]:
|
||||
if site.address not in merged_to_merger:
|
||||
merged_to_merger[site.address] = []
|
||||
merged_to_merger[site.address].append(merger_site)
|
||||
|
||||
|
||||
|
||||
def load(self, *args, **kwags):
|
||||
super(SiteManagerPlugin, self).load(*args, **kwags)
|
||||
self.updateMergerSites()
|
||||
|
||||
def save(self, *args, **kwags):
|
||||
super(SiteManagerPlugin, self).save(*args, **kwags)
|
||||
self.updateMergerSites()
|
1
plugins/MergerSite/__init__.py
Normal file
1
plugins/MergerSite/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
import MergerSitePlugin
|
Loading…
Reference in a new issue