diff --git a/lbrynet/lbrynet_daemon/Daemon.py b/lbrynet/lbrynet_daemon/Daemon.py index 2589d3887..7f0e4c612 100644 --- a/lbrynet/lbrynet_daemon/Daemon.py +++ b/lbrynet/lbrynet_daemon/Daemon.py @@ -33,7 +33,6 @@ from lbrynet.lbryfile.StreamDescriptor import save_sd_info from lbrynet.lbryfile.EncryptedFileMetadataManager import DBEncryptedFileMetadataManager from lbrynet.lbryfile.StreamDescriptor import EncryptedFileStreamType from lbrynet.lbryfilemanager.EncryptedFileManager import EncryptedFileManager -from lbrynet.lbrynet_daemon.UIManager import UIManager from lbrynet.lbrynet_daemon.Downloader import GetStream from lbrynet.lbrynet_daemon.Publisher import Publisher from lbrynet.lbrynet_daemon.ExchangeRateManager import ExchangeRateManager @@ -269,7 +268,6 @@ class Daemon(AuthJSONRPCServer): self.looping_call_manager = LoopingCallManager(calls) self.sd_identifier = StreamDescriptorIdentifier() self.stream_info_manager = DBEncryptedFileMetadataManager(self.db_dir) - self.lbry_ui_manager = UIManager(root) self.lbry_file_manager = None @defer.inlineCallbacks @@ -307,11 +305,6 @@ class Daemon(AuthJSONRPCServer): self.looping_call_manager.start(Checker.CONNECTION_STATUS, 30) self.exchange_rate_manager.start() - if conf.settings['host_ui']: - self.lbry_ui_manager.update_checker.start(1800, now=False) - yield self.lbry_ui_manager.setup() - if launch_ui: - self.lbry_ui_manager.launch() yield self._initial_setup() yield threads.deferToThread(self._setup_data_directory) yield self._check_db_migration() @@ -329,7 +322,6 @@ class Daemon(AuthJSONRPCServer): def _get_platform(self): if self.platform is None: self.platform = system_info.get_platform() - self.platform["ui_version"] = self.lbry_ui_manager.loaded_git_version return self.platform def _initial_setup(self): @@ -531,8 +523,6 @@ class Daemon(AuthJSONRPCServer): self.looping_call_manager.shutdown() if self.analytics_manager: self.analytics_manager.shutdown() - if self.lbry_ui_manager.update_checker.running: - self.lbry_ui_manager.update_checker.stop() self._clean_up_temp_files() @@ -2225,28 +2215,6 @@ class Daemon(AuthJSONRPCServer): d.addCallback(lambda _: self._render_response(True)) return d - @AuthJSONRPCServer.auth_required - def jsonrpc_configure_ui(self, branch=None, path=None, check_requirements=True): - """ - Configure the UI being hosted - - Args, optional: - 'branch': a branch name on lbryio/lbry-web-ui - 'path': path to a ui folder - """ - - if path is not None: - d = self.lbry_ui_manager.setup( - user_specified=path, check_requirements=check_requirements) - elif branch is not None: - d = self.lbry_ui_manager.setup(branch=branch, check_requirements=check_requirements) - else: - d = self.lbry_ui_manager.setup(check_requirements=check_requirements) - - d.addCallback(lambda r: self._render_response(r)) - - return d - @AuthJSONRPCServer.auth_required @defer.inlineCallbacks def jsonrpc_open(self, sd_hash): diff --git a/lbrynet/lbrynet_daemon/UIManager.py b/lbrynet/lbrynet_daemon/UIManager.py deleted file mode 100644 index 1fb69b5a2..000000000 --- a/lbrynet/lbrynet_daemon/UIManager.py +++ /dev/null @@ -1,348 +0,0 @@ -import os -import logging -import shutil -import json -import webbrowser -from urllib2 import urlopen -from StringIO import StringIO -from zipfile import ZipFile - -import pkg_resources -from twisted.internet import defer -from twisted.internet.task import LoopingCall - -from lbrynet import conf -from lbrynet.lbrynet_daemon.Resources import NoCacheStaticFile -from lbrynet import __version__ as lbrynet_version -from lbryum.version import LBRYUM_VERSION as lbryum_version - -log = logging.getLogger(__name__) - - -class UIManager(object): - def __init__(self, root): - self.ui_root = os.path.join(conf.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 = user_specified or conf.settings['local_ui_path'] - - self.branch = branch or conf.settings['ui_branch'] - self.check_requirements = (check_requirements if check_requirements is not None - else conf.settings['check_ui_requirements']) - - # Note that this currently overrides any manual setting of UI. - # It might be worth considering changing that behavior but the expectation - # is generally that any manual setting of the UI will happen during development - # and not for folks checking out the QA / RC builds that bundle the UI. - if self._check_for_bundled_ui(): - return defer.succeed(True) - - 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 = defer.maybeDeferred(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 _check_for_bundled_ui(self): - """Try to load a bundled UI and return True if successful, False otherwise""" - try: - bundled_path = get_bundled_ui_path() - except Exception: - log.warning('Failed to get path for bundled UI', exc_info=True) - return False - else: - bundle_manager = BundledUIManager(self.root, self.active_dir, bundled_path) - loaded = bundle_manager.setup() - if loaded: - self.loaded_git_version = bundle_manager.version() - return loaded - - def _up_to_date(self): - def _get_git_info(): - try: - # TODO: should this be switched to the non-blocking getPage? - response = urlopen(self._git_url) - return defer.succeed(read_sha(response)) - 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) - requirements = Requirements(requires_file) - passed_requirements = requirements.check(lbrynet_version, lbryum_version) - 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(): - replace_dir(self.active_dir, source_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): - return load_ui(self.root, self.active_dir) - - def launch(self): - webbrowser.open(conf.settings.get_ui_address()) - - -class BundledUIManager(object): - """Copies the UI bundled with lbrynet, if available. - - For the QA and nightly builds, we include a copy of the most - recent checkout of the development UI. For production builds - nothing is bundled. - - n.b: For QA and nightly builds the update check is skipped. - """ - def __init__(self, root, active_dir, bundled_ui_path): - self.root = root - self.active_dir = active_dir - self.bundled_ui_path = bundled_ui_path - self.data_path = os.path.join(bundled_ui_path, 'data.json') - self._version = None - - def version(self): - if not self._version: - self._version = open_and_read_sha(self.data_path) - return self._version - - def bundle_is_available(self): - return os.path.exists(self.data_path) - - def setup(self): - """Load the bundled UI if possible and necessary - - Returns True if there is a bundled UI, False otherwise - """ - if not self.bundle_is_available(): - log.debug('No bundled UI is available') - return False - if not self.is_active_already_bundled_ui(): - replace_dir(self.active_dir, self.bundled_ui_path) - log.info('Loading the bundled UI') - load_ui(self.root, self.active_dir) - return True - - def is_active_already_bundled_ui(self): - target_data_path = os.path.join(self.active_dir, 'data.json') - if os.path.exists(target_data_path): - target_version = open_and_read_sha(target_data_path) - if self.version() == target_version: - return True - return False - - -def get_bundled_ui_path(): - return pkg_resources.resource_filename('lbrynet', 'resources/ui') - - -def are_same_version(data_a, data_b): - """Compare two data files and return True if they are the same version""" - with open(data_a) as a: - with open(data_b) as b: - return read_sha(a) == read_sha(b) - - -def open_and_read_sha(filename): - with open(filename) as f: - return read_sha(f) - - -def read_sha(filelike): - data = json.load(filelike) - return data['sha'] - - -def replace_dir(active_dir, source_dir): - if os.path.isdir(active_dir): - shutil.rmtree(active_dir) - shutil.copytree(source_dir, active_dir) - - -def load_ui(root, active_dir): - for name in os.listdir(active_dir): - entry = os.path.join(active_dir, name) - if os.path.isdir(entry): - root.putChild(os.path.basename(entry), NoCacheStaticFile(entry)) - - -class Requirements(object): - def __init__(self, requires_file): - self.requires_file = requires_file - - def check(self, lbrynet_version, lbryum_version): - requirements = self._read() - expected = {'lbrynet': lbrynet_version, 'lbryum': lbryum_version} - return check_requirements(requirements, expected) - - def _read(self): - requirements = {} - with open(self.requires_file, "r") as f: - for requirement in [line for line in f.read().split('\n') if line]: - t = requirement.split('=') - if len(t) == 3: - requirements[t[0]] = {'version': t[1], 'operator': '=='} - elif t[0][-1] == ">": - requirements[t[0][:-1]] = {'version': t[1], 'operator': '>='} - elif t[0][-1] == "<": - requirements[t[0][:-1]] = {'version': t[1], 'operator': '<='} - return requirements - - -def check_requirements(expected, actual): - passed_requirements = True - for name in expected: - if name in actual: - version = actual[name] - else: - continue - log_msg = "Local version %s of %s does not meet UI requirement for version %s" - if expected[name]['operator'] == '==': - if not expected[name]['version'] == version: - passed_requirements = False - log.info(log_msg, version, name, expected[name]['version']) - else: - log.info("Local version of %s meets ui requirement" % name) - if expected[name]['operator'] == '>=': - if not expected[name]['version'] <= version: - passed_requirements = False - log.info(log_msg, version, name, expected[name]['version']) - else: - log.info("Local version of %s meets ui requirement" % name) - if expected[name]['operator'] == '<=': - if not expected[name]['version'] >= version: - passed_requirements = False - log.info(log_msg, version, name, expected[name]['version']) - else: - log.info("Local version of %s meets ui requirement" % name) - return passed_requirements