forked from LBRYCommunity/lbry-sdk
225 lines
9.3 KiB
Python
225 lines
9.3 KiB
Python
import os
|
|
import logging
|
|
import shutil
|
|
import json
|
|
|
|
from urllib2 import urlopen
|
|
from StringIO import StringIO
|
|
from twisted.internet import defer
|
|
from twisted.internet.task import LoopingCall
|
|
from lbrynet.conf import settings
|
|
from lbrynet.lbrynet_daemon.Resources import NoCacheStaticFile
|
|
from lbrynet import __version__ as lbrynet_version
|
|
from lbryum.version import LBRYUM_VERSION as lbryum_version
|
|
from zipfile import ZipFile
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
class UIManager(object):
|
|
def __init__(self, root):
|
|
self.ui_root = os.path.join(settings.data_dir, "lbry-ui")
|
|
self.active_dir = os.path.join(self.ui_root, "active")
|
|
self.update_dir = os.path.join(self.ui_root, "update")
|
|
|
|
if not os.path.isdir(self.ui_root):
|
|
os.mkdir(self.ui_root)
|
|
if not os.path.isdir(self.active_dir):
|
|
os.mkdir(self.active_dir)
|
|
if not os.path.isdir(self.update_dir):
|
|
os.mkdir(self.update_dir)
|
|
|
|
self.config = os.path.join(self.ui_root, "active.json")
|
|
self.update_requires = os.path.join(self.update_dir, "requirements.txt")
|
|
self.requirements = {}
|
|
self.check_requirements = True
|
|
self.ui_dir = self.active_dir
|
|
self.git_version = None
|
|
self.root = root
|
|
self.branch = None
|
|
self.update_checker = LoopingCall(self.setup)
|
|
|
|
if not os.path.isfile(os.path.join(self.config)):
|
|
self.loaded_git_version = None
|
|
self.loaded_branch = None
|
|
self.loaded_requirements = None
|
|
else:
|
|
try:
|
|
f = open(self.config, "r")
|
|
loaded_ui = json.loads(f.read())
|
|
f.close()
|
|
self.loaded_git_version = loaded_ui['commit']
|
|
self.loaded_branch = loaded_ui['branch']
|
|
self.loaded_requirements = loaded_ui['requirements']
|
|
except:
|
|
self.loaded_git_version = None
|
|
self.loaded_branch = None
|
|
self.loaded_requirements = None
|
|
|
|
def setup(self, branch=None, check_requirements=None, user_specified=None):
|
|
local_ui_path = settings.local_ui_path or user_specified
|
|
self.branch = settings.ui_branch or branch
|
|
self.check_requirements = settings.check_ui_requirements or check_requirements
|
|
|
|
if local_ui_path:
|
|
if os.path.isdir(local_ui_path):
|
|
log.info("Checking user specified UI directory: " + str(local_ui_path))
|
|
self.branch = "user-specified"
|
|
self.loaded_git_version = "user-specified"
|
|
d = self.migrate_ui(source=local_ui_path)
|
|
d.addCallback(lambda _: self._load_ui())
|
|
return d
|
|
else:
|
|
log.info("User specified UI directory doesn't exist, using " + self.branch)
|
|
elif self.loaded_branch == "user-specified":
|
|
log.info("Loading user provided UI")
|
|
d = self._load_ui()
|
|
return d
|
|
else:
|
|
log.info("Checking for updates for UI branch: " + self.branch)
|
|
self._git_url = "https://s3.amazonaws.com/lbry-ui/{}/data.json".format(self.branch)
|
|
self._dist_url = "https://s3.amazonaws.com/lbry-ui/{}/dist.zip".format(self.branch)
|
|
|
|
d = self._up_to_date()
|
|
d.addCallback(lambda r: self._download_ui() if not r else self._load_ui())
|
|
return d
|
|
|
|
def _up_to_date(self):
|
|
def _get_git_info():
|
|
try:
|
|
response = urlopen(self._git_url)
|
|
data = json.loads(response.read())
|
|
return defer.succeed(data['sha'])
|
|
except Exception:
|
|
return defer.fail()
|
|
|
|
def _set_git(version):
|
|
self.git_version = version.replace('\n', '')
|
|
if self.git_version == self.loaded_git_version:
|
|
log.info("UI is up to date")
|
|
return defer.succeed(True)
|
|
else:
|
|
log.info("UI updates available, checking if installation meets requirements")
|
|
return defer.succeed(False)
|
|
|
|
def _use_existing():
|
|
log.info("Failed to check for new ui version, trying to use cached ui")
|
|
return defer.succeed(True)
|
|
|
|
d = _get_git_info()
|
|
d.addCallbacks(_set_git, lambda _: _use_existing)
|
|
return d
|
|
|
|
def migrate_ui(self, source=None):
|
|
if not source:
|
|
requires_file = self.update_requires
|
|
source_dir = self.update_dir
|
|
delete_source = True
|
|
else:
|
|
requires_file = os.path.join(source, "requirements.txt")
|
|
source_dir = source
|
|
delete_source = False
|
|
|
|
def _skip_requirements():
|
|
log.info("Skipping ui requirement check")
|
|
return defer.succeed(True)
|
|
|
|
def _check_requirements():
|
|
if not os.path.isfile(requires_file):
|
|
log.info("No requirements.txt file, rejecting request to migrate this UI")
|
|
return defer.succeed(False)
|
|
|
|
f = open(requires_file, "r")
|
|
for requirement in [line for line in f.read().split('\n') if line]:
|
|
t = requirement.split('=')
|
|
if len(t) == 3:
|
|
self.requirements[t[0]] = {'version': t[1], 'operator': '=='}
|
|
elif t[0][-1] == ">":
|
|
self.requirements[t[0][:-1]] = {'version': t[1], 'operator': '>='}
|
|
elif t[0][-1] == "<":
|
|
self.requirements[t[0][:-1]] = {'version': t[1], 'operator': '<='}
|
|
f.close()
|
|
passed_requirements = True
|
|
for r in self.requirements:
|
|
if r == 'lbrynet':
|
|
c = lbrynet_version
|
|
elif r == 'lbryum':
|
|
c = lbryum_version
|
|
else:
|
|
c = None
|
|
if c:
|
|
if self.requirements[r]['operator'] == '==':
|
|
if not self.requirements[r]['version'] == c:
|
|
passed_requirements = False
|
|
log.info("Local version %s of %s does not meet UI requirement for version %s" % (
|
|
c, r, self.requirements[r]['version']))
|
|
else:
|
|
log.info("Local version of %s meets ui requirement" % r)
|
|
if self.requirements[r]['operator'] == '>=':
|
|
if not self.requirements[r]['version'] <= c:
|
|
passed_requirements = False
|
|
log.info("Local version %s of %s does not meet UI requirement for version %s" % (
|
|
c, r, self.requirements[r]['version']))
|
|
else:
|
|
log.info("Local version of %s meets ui requirement" % r)
|
|
if self.requirements[r]['operator'] == '<=':
|
|
if not self.requirements[r]['version'] >= c:
|
|
passed_requirements = False
|
|
log.info("Local version %s of %s does not meet UI requirement for version %s" % (
|
|
c, r, self.requirements[r]['version']))
|
|
else:
|
|
log.info("Local version of %s meets ui requirement" % r)
|
|
return defer.succeed(passed_requirements)
|
|
|
|
def _disp_failure():
|
|
log.info("Failed to satisfy requirements for branch '%s', update was not loaded" % self.branch)
|
|
return defer.succeed(False)
|
|
|
|
def _do_migrate():
|
|
if os.path.isdir(self.active_dir):
|
|
shutil.rmtree(self.active_dir)
|
|
shutil.copytree(source_dir, self.active_dir)
|
|
if delete_source:
|
|
shutil.rmtree(source_dir)
|
|
|
|
log.info("Loaded UI update")
|
|
|
|
f = open(self.config, "w")
|
|
loaded_ui = {'commit': self.git_version, 'branch': self.branch, 'requirements': self.requirements}
|
|
f.write(json.dumps(loaded_ui))
|
|
f.close()
|
|
|
|
self.loaded_git_version = loaded_ui['commit']
|
|
self.loaded_branch = loaded_ui['branch']
|
|
self.loaded_requirements = loaded_ui['requirements']
|
|
return defer.succeed(True)
|
|
|
|
d = _check_requirements() if self.check_requirements else _skip_requirements()
|
|
d.addCallback(lambda r: _do_migrate() if r else _disp_failure())
|
|
return d
|
|
|
|
def _download_ui(self):
|
|
def _delete_update_dir():
|
|
if os.path.isdir(self.update_dir):
|
|
shutil.rmtree(self.update_dir)
|
|
return defer.succeed(None)
|
|
|
|
def _dl_ui():
|
|
url = urlopen(self._dist_url)
|
|
z = ZipFile(StringIO(url.read()))
|
|
names = [i for i in z.namelist() if '.DS_exStore' not in i and '__MACOSX' not in i]
|
|
z.extractall(self.update_dir, members=names)
|
|
log.info("Downloaded files for UI commit " + str(self.git_version).replace("\n", ""))
|
|
return self.branch
|
|
|
|
d = _delete_update_dir()
|
|
d.addCallback(lambda _: _dl_ui())
|
|
d.addCallback(lambda _: self.migrate_ui())
|
|
d.addCallback(lambda _: self._load_ui())
|
|
return d
|
|
|
|
def _load_ui(self):
|
|
for d in [i[0] for i in os.walk(self.active_dir) if os.path.dirname(i[0]) == self.active_dir]:
|
|
self.root.putChild(os.path.basename(d), NoCacheStaticFile(d))
|
|
return defer.succeed(True)
|