diff --git a/lbrynet/__init__.py b/lbrynet/__init__.py index eb4d8454c..993518cab 100644 --- a/lbrynet/__init__.py +++ b/lbrynet/__init__.py @@ -4,5 +4,5 @@ import logging logging.getLogger(__name__).addHandler(logging.NullHandler()) -version = (0, 2, 0) +version = (0, 2, 1) __version__ = ".".join([str(x) for x in version]) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index cce937c21..895dbfa49 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -70,6 +70,8 @@ class LBRYWallet(object): self.max_expected_payment_time = datetime.timedelta(minutes=3) self.stopped = True + self.is_lagging = None + self.manage_running = False self._manage_count = 0 self._balance_refresh_time = 3 @@ -365,7 +367,8 @@ class LBRYWallet(object): value['content_license'] = content_license if author is not None: value['author'] = author - if sources is not None: + if isinstance(sources, dict): + sources['lbry_sd_hash'] = sd_hash value['sources'] = sources d = self._send_name_claim(name, json.dumps(value), amount) @@ -436,7 +439,8 @@ class LBRYWallet(object): d.addCallback(set_first_run) else: - d = defer.succeed(None) + d = defer.succeed(self._FIRST_RUN_YES if self._first_run else self._FIRST_RUN_NO) + d.addCallback(lambda _: self._first_run == self._FIRST_RUN_YES) return d @@ -900,6 +904,9 @@ class LBRYumWallet(LBRYWallet): self._start_check = None self._catch_up_check = None self._caught_up_counter = 0 + self.blocks_behind_alert = 0 + self.catchup_progress = 0 + self.max_behind = 0 def _start(self): @@ -991,10 +998,18 @@ class LBRYumWallet(LBRYWallet): self._catch_up_check = None blockchain_caught_d.callback(True) elif remote_height != 0: + self.blocks_behind_alert = remote_height - local_height + if self.blocks_behind_alert > self.max_behind: + self.max_behind = self.blocks_behind_alert + self.catchup_progress = int(100 * (self.blocks_behind_alert / (5 + self.max_behind))) if self._caught_up_counter == 0: alert.info('Catching up to the blockchain...showing blocks left...') if self._caught_up_counter % 30 == 0: alert.info('%d...', (remote_height - local_height)) + alert.info("Catching up: " + str(self.catchup_progress) + "%") + if self._caught_up_counter >= 600: + self.is_lagging = True + self._caught_up_counter += 1 @@ -1067,11 +1082,12 @@ class LBRYumWallet(LBRYWallet): decoded_tx['vout'] = [] for output in tx.outputs(): out = {} - out['value'] = output[2] + out['value'] = Decimal(output[2]) / Decimal(COIN) decoded_tx['vout'].append(out) return decoded_tx def _send_abandon(self, txid, address, amount): + log.info("Abandon " + str(txid) + " " + str(address) + " " + str(amount)) cmd = known_commands['abandonclaim'] func = getattr(self.cmd_runner, cmd.name) d = threads.deferToThread(func, txid, address, amount) @@ -1079,6 +1095,7 @@ class LBRYumWallet(LBRYWallet): return d def _broadcast_transaction(self, raw_tx): + log.info("Broadcast: " + str(raw_tx)) cmd = known_commands['broadcast'] func = getattr(self.cmd_runner, cmd.name) d = threads.deferToThread(func, raw_tx) diff --git a/lbrynet/lbryfilemanager/LBRYFileDownloader.py b/lbrynet/lbryfilemanager/LBRYFileDownloader.py index fed0824e5..2afc17588 100644 --- a/lbrynet/lbryfilemanager/LBRYFileDownloader.py +++ b/lbrynet/lbryfilemanager/LBRYFileDownloader.py @@ -119,7 +119,7 @@ class ManagedLBRYFileDownloaderFactory(object): def can_download(self, sd_validator): return True - def make_downloader(self, metadata, options, payment_rate_manager): + def make_downloader(self, metadata, options, payment_rate_manager, download_directory=None): data_rate = options[0] upload_allowed = options[1] @@ -137,7 +137,8 @@ class ManagedLBRYFileDownloaderFactory(object): d.addCallback(lambda stream_hash: self.lbry_file_manager.add_lbry_file(stream_hash, payment_rate_manager, data_rate, - upload_allowed)) + upload_allowed, + download_directory=download_directory)) return d @staticmethod diff --git a/lbrynet/lbryfilemanager/LBRYFileManager.py b/lbrynet/lbryfilemanager/LBRYFileManager.py index 59f972df3..d0dbca1ae 100644 --- a/lbrynet/lbryfilemanager/LBRYFileManager.py +++ b/lbrynet/lbryfilemanager/LBRYFileManager.py @@ -28,7 +28,7 @@ class LBRYFileManager(object): Keeps track of currently opened LBRY Files, their options, and their LBRY File specific metadata. """ - def __init__(self, session, stream_info_manager, sd_identifier, delete_data=False): + def __init__(self, session, stream_info_manager, sd_identifier, delete_data=False, download_directory=None): self.session = session self.stream_info_manager = stream_info_manager self.sd_identifier = sd_identifier @@ -36,8 +36,8 @@ class LBRYFileManager(object): self.sql_db = None # self.delete_data = delete_data # self.check_exists_loop = LoopingCall(self.check_files_exist) - if sys.platform.startswith("darwin"): - self.download_directory = os.path.join(os.path.expanduser("~"), 'Downloads') + if download_directory: + self.download_directory = download_directory else: self.download_directory = os.getcwd() log.debug("Download directory for LBRYFileManager: %s", str(self.download_directory)) @@ -122,7 +122,10 @@ class LBRYFileManager(object): d.addCallback(start_lbry_files) return d - def start_lbry_file(self, rowid, stream_hash, payment_rate_manager, blob_data_rate=None, upload_allowed=True): + def start_lbry_file(self, rowid, stream_hash, payment_rate_manager, blob_data_rate=None, upload_allowed=True, + download_directory=None): + if not download_directory: + download_directory = self.download_directory payment_rate_manager.min_blob_data_payment_rate = blob_data_rate lbry_file_downloader = ManagedLBRYFileDownloader(rowid, stream_hash, self.session.peer_finder, @@ -130,17 +133,17 @@ class LBRYFileManager(object): self.session.blob_manager, self.stream_info_manager, self, payment_rate_manager, self.session.wallet, - self.download_directory, + download_directory, upload_allowed) self.lbry_files.append(lbry_file_downloader) d = lbry_file_downloader.set_stream_info() d.addCallback(lambda _: lbry_file_downloader) return d - def add_lbry_file(self, stream_hash, payment_rate_manager, blob_data_rate=None, upload_allowed=True): + def add_lbry_file(self, stream_hash, payment_rate_manager, blob_data_rate=None, upload_allowed=True, download_directory=None): d = self._save_lbry_file(stream_hash, blob_data_rate) d.addCallback(lambda rowid: self.start_lbry_file(rowid, stream_hash, payment_rate_manager, - blob_data_rate, upload_allowed)) + blob_data_rate, upload_allowed, download_directory)) return d def delete_lbry_file(self, lbry_file): diff --git a/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py b/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py index f1dc04c00..c748a1015 100644 --- a/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py +++ b/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py @@ -28,7 +28,7 @@ class LBRYURIHandler(object): def check_status(self): status = None try: - status = json.loads(self.daemon.is_running())['result'] + status = self.daemon.is_running() if self.start_timeout < 30 and not status: sleep(1) self.start_timeout += 1 @@ -45,11 +45,11 @@ class LBRYURIHandler(object): else: raise Timeout("Timed out trying to start LBRY daemon") - def handle(self, lbry_name): + def handle_osx(self, lbry_name): lbry_process = [d for d in subprocess.Popen(['ps','aux'], stdout=subprocess.PIPE).stdout.readlines() if 'LBRY.app' in d and 'LBRYURIHandler' not in d] try: - status = json.loads(self.daemon.is_running())['result'] + status = self.daemon.is_running() except: status = None @@ -62,18 +62,22 @@ class LBRYURIHandler(object): started = True if lbry_name == "lbry" or lbry_name == "" and not started: - webbrowser.get('safari').open(UI_ADDRESS) + webbrowser.open(UI_ADDRESS) else: - r = json.loads(self.daemon.get({'name': lbry_name})) - if r['code'] == 200: - path = r['result']['path'].encode('utf-8') - extension = os.path.splitext(path)[1] - if extension in ['mp4', 'flv', 'mov', 'ogv']: - webbrowser.get('safari').open(UI_ADDRESS + "/view?name=" + lbry_name) - else: - webbrowser.get('safari').open('file://' + path) - else: - webbrowser.get('safari').open('http://lbry.io/get') + webbrowser.open(UI_ADDRESS + "/view?name=" + lbry_name) + + def handle_linux(self, lbry_name): + try: + is_running = self.daemon.is_running() + if not is_running: + sys.exit(0) + except: + sys.exit(0) + + if lbry_name == "lbry": + webbrowser.open(UI_ADDRESS) + else: + webbrowser.open(UI_ADDRESS + "/view?name=" + lbry_name) def main(args): @@ -81,8 +85,10 @@ def main(args): args = ['lbry://lbry'] name = args[0][7:] - LBRYURIHandler().handle(lbry_name=name) - + if sys.platform == "darwin": + LBRYURIHandler().handle_osx(lbry_name=name) + else: + LBRYURIHandler().handle_linux(lbry_name=name) if __name__ == "__main__": main(sys.argv[1:]) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index eab1a0bee..95340be0d 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -5,23 +5,26 @@ import simplejson as json import binascii import subprocess import logging +import logging.handlers import requests -# import rumps -# import httplib2 +import base64 +import base58 +import platform +import json from twisted.web import server, resource, static from twisted.internet import defer, threads, error, reactor from txjsonrpc import jsonrpclib from txjsonrpc.web import jsonrpc -from jsonrpc.proxy import JSONRPCProxy +from txjsonrpc.web.jsonrpc import Handler from datetime import datetime from decimal import Decimal -from StringIO import StringIO -from zipfile import ZipFile -from urllib import urlopen from appdirs import user_data_dir +from urllib2 import urlopen +from lbrynet import __version__ as lbrynet_version +from lbryum.version import ELECTRUM_VERSION as lbryum_version from lbrynet.core.PaymentRateManager import PaymentRateManager from lbrynet.core.server.BlobAvailabilityHandler import BlobAvailabilityHandlerFactory from lbrynet.core.server.BlobRequestHandler import BlobRequestHandlerFactory @@ -44,27 +47,167 @@ from lbrynet.core.LBRYcrdWallet import LBRYcrdWallet, LBRYumWallet from lbrynet.lbryfilemanager.LBRYFileManager import LBRYFileManager from lbrynet.lbryfile.LBRYFileMetadataManager import DBLBRYFileMetadataManager, TempLBRYFileMetadataManager + +if sys.platform != "darwin": + log_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") +else: + log_dir = user_data_dir("LBRY") + +if not os.path.isdir(log_dir): + os.mkdir(log_dir) + +LOG_FILENAME = os.path.join(log_dir, 'lbrynet-daemon.log') + log = logging.getLogger(__name__) +handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=262144, backupCount=5) +log.addHandler(handler) +log.setLevel(logging.INFO) + +STARTUP_STAGES = [ + ('initializing', 'Initializing...'), + ('loading_db', 'Loading databases...'), + ('loading_wallet', 'Catching up with the blockchain... %s'), + ('loading_file_manager', 'Setting up file manager'), + ('loading_server', 'Starting lbrynet'), + ('started', 'Started lbrynet') + ] + +ALLOWED_DURING_STARTUP = ['is_running', 'is_first_run', + 'get_time_behind_blockchain', 'stop', + 'daemon_status', 'get_start_notice', + 'version', 'check_for_new_version'] BAD_REQUEST = 400 NOT_FOUND = 404 OK_CODE = 200 - # TODO add login credentials in a conf file # TODO alert if your copy of a lbry file is out of date with the name record -class Bunch: - def __init__(self, params): - self.__dict__.update(params) - - class LBRYDaemon(jsonrpc.JSONRPC): """ LBRYnet daemon, a jsonrpc interface to lbry functions """ + isLeaf = True + def __init__(self, ui_version_info, wallet_type="lbryum"): + jsonrpc.JSONRPC.__init__(self) + reactor.addSystemEventTrigger('before', 'shutdown', self._shutdown) + + self.startup_status = STARTUP_STAGES[0] + self.startup_message = None + self.announced_startup = False + self.query_handlers = {} + self.ui_version = ui_version_info.replace('\n', '') + self.git_lbrynet_version = None + self.git_lbryum_version = None + self.wallet_type = wallet_type + self.session_settings = None + self.first_run = None + self.log_file = LOG_FILENAME + self.fetcher = None + self.current_db_revision = 1 + self.run_server = True + self.session = None + self.known_dht_nodes = KNOWN_DHT_NODES + self.platform_info = { + "processor": platform.processor(), + "python_version: ": platform.python_version(), + "platform": platform.platform(), + "os_release": platform.release(), + "os_system": platform.system(), + "lbrynet_version: ": lbrynet_version, + "lbryum_version: ": lbryum_version, + "ui_version": self.ui_version, + } + + if os.name == "nt": + from lbrynet.winhelpers.knownpaths import get_path, FOLDERID, UserHandle + self.download_directory = get_path(FOLDERID.Downloads, UserHandle.current) + self.db_dir = os.path.join(get_path(FOLDERID.RoamingAppData, UserHandle.current), "lbrynet") + self.lbrycrdd_path = "lbrycrdd.exe" + if wallet_type == "lbrycrd": + self.wallet_dir = os.path.join(get_path(FOLDERID.RoamingAppData, UserHandle.current), "lbrycrd") + else: + self.wallet_dir = os.path.join(get_path(FOLDERID.RoamingAppData, UserHandle.current), "lbryum") + elif sys.platform == "darwin": + self.download_directory = os.path.join(os.path.expanduser("~"), 'Downloads') + self.db_dir = user_data_dir("LBRY") + self.lbrycrdd_path = "./lbrycrdd" + if wallet_type == "lbrycrd": + self.wallet_dir = user_data_dir("lbrycrd") + else: + self.wallet_dir = user_data_dir("LBRY") + else: + self.download_directory = os.getcwd() + self.db_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") + self.lbrycrdd_path = "./lbrycrdd" + if wallet_type == "lbrycrd": + self.wallet_dir = os.path.join(os.path.expanduser("~"), ".lbrycrd") + else: + self.wallet_dir = os.path.join(os.path.expanduser("~"), ".lbryum") + + self.created_data_dir = False + if not os.path.exists(self.db_dir): + os.mkdir(self.db_dir) + self.created_data_dir = True + + self.blobfile_dir = os.path.join(self.db_dir, "blobfiles") + self.lbrycrd_conf = os.path.join(self.wallet_dir, "lbrycrd.conf") + self.autofetcher_conf = os.path.join(self.wallet_dir, "autofetcher.conf") + self.daemon_conf = os.path.join(self.db_dir, 'daemon_settings.json') + self.wallet_conf = os.path.join(self.wallet_dir, "lbrycrd.conf") + self.wallet_user = None + self.wallet_password = None + + self.sd_identifier = StreamDescriptorIdentifier() + self.stream_info_manager = TempLBRYFileMetadataManager() + self.settings = LBRYSettings(self.db_dir) + self.blob_request_payment_rate_manager = None + self.lbry_file_metadata_manager = None + self.lbry_file_manager = None + + #defaults for settings otherwise loaded from daemon_settings.json + self.default_settings = { + 'run_on_startup': False, + 'data_rate': MIN_BLOB_DATA_PAYMENT_RATE, + 'max_key_fee': DEFAULT_MAX_KEY_FEE, + 'default_download_directory': self.download_directory, + 'max_upload': 0.0, + 'max_download': 0.0, + 'upload_log': True, + 'search_timeout': 3.0, + 'max_search_results': DEFAULT_MAX_SEARCH_RESULTS, + 'wallet_type': wallet_type, + 'delete_blobs_on_remove': True, + 'peer_port': 3333, + 'dht_node_port': 4444, + 'use_upnp': True, + 'start_lbrycrdd': True, + } + if os.path.isfile(self.daemon_conf): + #load given settings, set missing settings to defaults + temp_settings = self.default_settings + f = open(self.daemon_conf, "r") + s = json.loads(f.read()) + f.close() + for k in temp_settings.keys(): + if k in s.keys(): + if type(temp_settings[k]) == type(s[k]): + temp_settings[k] = s[k] + self.__dict__[k] = s[k] + f = open(self.daemon_conf, "w") + f.write(json.dumps(temp_settings)) + f.close() + else: + log.info("Writing default settings: " + json.dumps(self.default_settings) + " --> " + str(self.daemon_conf)) + f = open(self.daemon_conf, "w") + f.write(json.dumps(self.default_settings)) + f.close() + for k in self.default_settings.keys(): + self.__dict__[k] = self.default_settings[k] + def render(self, request): request.content.seek(0, 0) # Unmarshal the JSON-RPC data. @@ -72,6 +215,9 @@ class LBRYDaemon(jsonrpc.JSONRPC): parsed = jsonrpclib.loads(content) functionPath = parsed.get("method") args = parsed.get('params') + + #TODO convert args to correct types if possible + id = parsed.get('id') version = parsed.get('jsonrpc') if version: @@ -82,6 +228,11 @@ class LBRYDaemon(jsonrpc.JSONRPC): version = jsonrpclib.VERSION_PRE1 # XXX this all needs to be re-worked to support logic for multiple # versions... + + if not self.announced_startup: + if functionPath not in ALLOWED_DURING_STARTUP: + return server.failure + try: function = self._getFunction(functionPath) except jsonrpclib.Fault, f: @@ -97,98 +248,76 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallback(self._cbRender, request, id, version) return server.NOT_DONE_YET - def setup(self, wallet_type, check_for_updates): - def _set_vars(wallet_type, check_for_updates): - reactor.addSystemEventTrigger('before', 'shutdown', self._shutdown) + def _cbRender(self, result, request, id, version): + if isinstance(result, Handler): + result = result.result - self.fetcher = None - self.current_db_revision = 1 - self.run_server = True - self.session = None - self.known_dht_nodes = KNOWN_DHT_NODES - if sys.platform != "darwin": - self.db_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") - else: - self.db_dir = user_data_dir("LBRY") - # self.db_dir = os.path.join(os.path.expanduser("~"), "Library/Application Support/lbrynet") - self.blobfile_dir = os.path.join(self.db_dir, "blobfiles") - self.peer_port = 3333 - self.dht_node_port = 4444 - self.first_run = False - if os.name == "nt": - from lbrynet.winhelpers.knownpaths import get_path, FOLDERID, UserHandle - self.download_directory = get_path(FOLDERID.Downloads, UserHandle.current) - self.wallet_dir = os.path.join(get_path(FOLDERID.RoamingAppData, UserHandle.current), "lbrycrd") - elif sys.platform == "darwin": - self.download_directory = os.path.join(os.path.expanduser("~"), 'Downloads') - if wallet_type == "lbrycrd": - self.wallet_dir = user_data_dir("lbrycrd") - else: - self.wallet_dir = user_data_dir("LBRY") - else: - if wallet_type == "lbrycrd": - self.wallet_dir = os.path.join(os.path.expanduser("~"), ".lbrycrd") - else: - self.wallet_dir = os.path.join(os.path.expanduser("~"), ".lbryum") - self.download_directory = os.getcwd() - self.daemon_conf = os.path.join(self.wallet_dir, 'daemon_settings.conf') - self.wallet_conf = os.path.join(self.wallet_dir, "lbrycrd.conf") - self.wallet_user = None - self.wallet_password = None - self.sd_identifier = StreamDescriptorIdentifier() - self.stream_info_manager = TempLBRYFileMetadataManager() - self.wallet_rpc_port = 8332 - self.downloads = [] - self.stream_frames = [] - self.default_blob_data_payment_rate = MIN_BLOB_DATA_PAYMENT_RATE - self.use_upnp = True - self.start_lbrycrdd = True - if os.name == "nt": - self.lbrycrdd_path = "lbrycrdd.exe" - else: - self.lbrycrdd_path = "./lbrycrdd" - self.delete_blobs_on_remove = True - self.blob_request_payment_rate_manager = None - self.lbry_file_metadata_manager = None - self.lbry_file_manager = None - self.settings = LBRYSettings(self.db_dir) - self.wallet_type = wallet_type - self.check_for_updates = check_for_updates - self.lbrycrd_conf = os.path.join(self.wallet_dir, "lbrycrd.conf") - self.autofetcher_conf = os.path.join(self.wallet_dir, "autofetcher.conf") - self.created_data_dir = False - if not os.path.exists(self.db_dir): - os.mkdir(self.db_dir) - self.created_data_dir = True - self.session_settings = None - self.data_rate = MIN_BLOB_DATA_PAYMENT_RATE - self.max_key_fee = DEFAULT_MAX_KEY_FEE - self.max_search_results = DEFAULT_MAX_SEARCH_RESULTS - self.startup_message = "" - self.announced_startup = False - self.search_timeout = 3.0 - self.query_handlers = {} - self.default_settings = { - 'run_on_startup': False, - 'data_rate': MIN_BLOB_DATA_PAYMENT_RATE, - 'max_key_fee': 10.0, - 'default_download_directory': self.download_directory, - 'max_upload': 0.0, - 'max_download': 0.0 - } + if isinstance(result, dict): + result = result['result'] + + if version == jsonrpclib.VERSION_PRE1: + if not isinstance(result, jsonrpclib.Fault): + result = (result,) + # Convert the result (python) to JSON-RPC + try: + s = jsonrpclib.dumps(result, version=version) + except: + f = jsonrpclib.Fault(self.FAILURE, "can't serialize output") + s = jsonrpclib.dumps(f, version=version) + request.setHeader("content-length", str(len(s))) + request.write(s) + request.finish() + + def _ebRender(self, failure, id): + if isinstance(failure.value, jsonrpclib.Fault): + return failure.value + log.error(failure) + return jsonrpclib.Fault(self.FAILURE, "error") + + def setup(self): + def _log_starting_vals(): + def _get_lbry_files_json(): + r = self._get_lbry_files() + return json.dumps(r) + + def _get_lbryum_version(): + r = urlopen("https://raw.githubusercontent.com/lbryio/lbryum/master/lib/version.py").read().split('\n') + version = next(line.split("=")[1].split("#")[0].replace(" ", "") + for line in r if "ELECTRUM_VERSION" in line) + version = version.replace("'", "") + log.info("remote lbryum " + str(version) + " > local lbryum " + str(lbryum_version) + " = " + str( + version > lbryum_version)) + return version + + def _get_lbrynet_version(): + r = urlopen("https://raw.githubusercontent.com/lbryio/lbry/master/lbrynet/__init__.py").read().split( + '\n') + vs = next(i for i in r if 'version =' in i).split("=")[1].replace(" ", "") + vt = tuple(int(x) for x in vs[1:-1].split(',')) + vr = ".".join([str(x) for x in vt]) + log.info("remote lbrynet " + str(vr) + " > local lbrynet " + str(lbrynet_version) + " = " + str( + vr > lbrynet_version)) + return vr + + self.git_lbrynet_version = _get_lbrynet_version() + self.git_lbryum_version = _get_lbryum_version() + + log.info("LBRY Files: " + _get_lbry_files_json()) + log.info("Starting balance: " + str(self.session.wallet.wallet_balance)) return defer.succeed(None) - def _disp_startup(): + def _announce_startup(): + self.announced_startup = True + self.startup_status = STARTUP_STAGES[5] log.info("[" + str(datetime.now()) + "] Started lbrynet-daemon") - return defer.succeed(None) log.info("[" + str(datetime.now()) + "] Starting lbrynet-daemon") d = defer.Deferred() - d.addCallback(lambda _:_set_vars(wallet_type, check_for_updates)) - d.addCallback(lambda _: self._setup_daemon_settings()) + d.addCallback(lambda _: self._initial_setup()) + d.addCallback(self._set_daemon_settings) d.addCallback(lambda _: threads.deferToThread(self._setup_data_directory)) d.addCallback(lambda _: self._check_db_migration()) d.addCallback(lambda _: self._get_settings()) @@ -201,16 +330,34 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallback(lambda _: self._setup_query_handlers()) d.addCallback(lambda _: self._setup_server()) d.addCallback(lambda _: self._setup_fetcher()) - d.addCallback(lambda _: _disp_startup()) + d.addCallback(lambda _: _log_starting_vals()) + d.addCallback(lambda _: _announce_startup()) d.callback(None) return defer.succeed(None) def _initial_setup(self): - return defer.fail(NotImplementedError()) + def _log_platform(): + log.info("Platform: " + json.dumps(self.platform_info)) + return defer.succeed(None) - def _setup_daemon_settings(self): - self.session_settings = self.default_settings + def _load_daemon_conf(): + if os.path.isfile(self.daemon_conf): + return json.loads(open(self.daemon_conf, "r").read()) + else: + log.info("Writing default settings : " + json.dumps(self.default_settings) + " --> " + str(self.daemon_conf)) + f = open(self.daemon_conf, "w") + f.write(json.dumps(self.default_settings)) + f.close() + return self.default_settings + + d = _log_platform() + d.addCallback(lambda _: _load_daemon_conf()) + + return d + + def _set_daemon_settings(self, settings): + self.session_settings = settings return defer.succeed(None) def _start_server(self): @@ -242,6 +389,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): return self._start_server() return defer.succeed(True) + self.startup_status = STARTUP_STAGES[4] + dl = self.settings.get_server_running_status() dl.addCallback(restore_running_status) return dl @@ -285,33 +434,80 @@ class LBRYDaemon(jsonrpc.JSONRPC): dl.addCallback(_set_query_handlers) return dl + def _upload_log(self): + if self.session_settings['upload_log']: + LOG_URL = "https://lbry.io/log-upload" + f = open(self.log_file, "r") + t = datetime.now() + log_name = base58.b58encode(self.lbryid)[:20] + "-" + str(t.month) + "-" + str(t.day) + "-" + str(t.year) + "-" + str(t.hour) + "-" + str(t.minute) + params = {'name': log_name, 'log': f.read()} + f.close() + requests.post(LOG_URL, params) + + return defer.succeed(None) + def _shutdown(self): log.info("Closing lbrynet session") - d = self._stop_server() + d = self._upload_log() + d.addCallback(lambda _: self._stop_server()) + d.addErrback(lambda err: log.info("Bad server shutdown: " + err.getTraceback())) if self.session is not None: d.addCallback(lambda _: self.session.shut_down()) + d.addErrback(lambda err: log.info("Bad session shutdown: " + err.getTraceback())) return d def _update_settings(self, settings): - if not isinstance(settings['run_on_startup'], bool): - return defer.fail() - elif not isinstance(settings['data_rate'], float): - return defer.fail() - elif not isinstance(settings['max_key_fee'], float): - return defer.fail() - elif not isinstance(settings['default_download_directory'], unicode): - return defer.fail() - elif not isinstance(settings['max_upload'], float): - return defer.fail() - elif not isinstance(settings['max_download'], float): - return defer.fail() + for k in settings.keys(): + if k == 'run_on_startup': + if type(settings['run_on_startup']) is bool: + self.session_settings['run_on_startup'] = settings['run_on_startup'] + else: + return defer.fail() + elif k == 'data_rate': + if type(settings['data_rate']) is float: + self.session_settings['data_rate'] = settings['data_rate'] + elif type(settings['data_rate']) is int: + self.session_settings['data_rate'] = float(settings['data_rate']) + else: + return defer.fail() + elif k == 'max_key_fee': + if type(settings['max_key_fee']) is float: + self.session_settings['max_key_fee'] = settings['max_key_fee'] + elif type(settings['max_key_fee']) is int: + self.session_settings['max_key_fee'] = float(settings['max_key_fee']) + else: + return defer.fail() + elif k == 'default_download_directory': + if type(settings['default_download_directory']) is unicode: + if os.path.isdir(settings['default_download_directory']): + self.session_settings['default_download_directory'] = settings['default_download_directory'] + else: + pass + else: + return defer.fail() + elif k == 'max_upload': + if type(settings['max_upload']) is float: + self.session_settings['max_upload'] = settings['max_upload'] + elif type(settings['max_upload']) is int: + self.session_settings['max_upload'] = float(settings['max_upload']) + else: + return defer.fail() + elif k == 'max_download': + if type(settings['max_download']) is float: + self.session_settings['max_download'] = settings['max_download'] + if type(settings['max_download']) is int: + self.session_settings['max_download'] = float(settings['max_download']) + else: + return defer.fail() + elif k == 'upload_log': + if type(settings['upload_log']) is bool: + self.session_settings['upload_log'] = settings['upload_log'] + else: + return defer.fail() - self.session_settings['run_on_startup'] = settings['run_on_startup'] - self.session_settings['data_rate'] = settings['data_rate'] - self.session_settings['max_key_fee'] = settings['max_key_fee'] - self.session_settings['default_download_directory'] = settings['default_download_directory'] - self.session_settings['max_upload'] = settings['max_upload'] - self.session_settings['max_download'] = settings['max_download'] + f = open(self.daemon_conf, "w") + f.write(json.dumps(self.session_settings)) + f.close() return defer.succeed(True) @@ -321,6 +517,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): return defer.succeed(None) def _setup_data_directory(self): + self.startup_status = STARTUP_STAGES[1] log.info("Loading databases...") if self.created_data_dir: db_revision = open(os.path.join(self.db_dir, "db_revision"), mode='w') @@ -366,14 +563,17 @@ class LBRYDaemon(jsonrpc.JSONRPC): if lbryid is None: return self._make_lbryid() else: + log.info("LBRY ID: " + base58.b58encode(lbryid)) self.lbryid = lbryid def _make_lbryid(self): self.lbryid = generate_id() + log.info("Generated new LBRY ID: " + base58.b58encode(self.lbryid)) d = self.settings.save_lbryid(self.lbryid) return d def _setup_lbry_file_manager(self): + self.startup_status = STARTUP_STAGES[3] self.lbry_file_metadata_manager = DBLBRYFileMetadataManager(self.db_dir) d = self.lbry_file_metadata_manager.setup() @@ -432,6 +632,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): blob_dir=self.blobfile_dir, dht_node_port=self.dht_node_port, known_dht_nodes=self.known_dht_nodes, peer_port=self.peer_port, use_upnp=self.use_upnp, wallet=results['wallet']) + self.startup_status = STARTUP_STAGES[2] dl = defer.DeferredList([d1, d2], fireOnOneErrback=True) dl.addCallback(combine_results) @@ -442,14 +643,22 @@ class LBRYDaemon(jsonrpc.JSONRPC): return dl def _check_first_run(self): + def _set_first_run_false(): + log.info("Not first run") + self.first_run = False + return 0.0 + d = self.session.wallet.is_first_run() - d.addCallback(lambda is_first_run: self._do_first_run() if is_first_run else 0.0) + + d.addCallback(lambda is_first_run: self._do_first_run() if is_first_run else _set_first_run_false()) return d def _do_first_run(self): + self.first_run = True d = self.session.wallet.get_new_address() def send_request(url, data): + log.info("Requesting first run credits") r = requests.post(url, json=data) if r.status_code == 200: return r.json()['credits_sent'] @@ -472,9 +681,9 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _show_first_run_result(self, credits_received): if credits_received != 0.0: points_string = locale.format_string("%.2f LBC", (round(credits_received, 2),), grouping=True) - self.startup_message = "Thank you for testing the alpha version of LBRY! You have been given %s for free because we love you. Please give them a few minutes to show up while you catch up with our blockchain." % points_string + self.startup_message = "Thank you for testing the alpha version of LBRY! You have been given %s for free because we love you. Please hang on for a few minutes for the next block to be mined. When you refresh this page and see your credits you're ready to go!." % points_string else: - self.startup_message = "Connected to LBRYnet" + self.startup_message = None def _get_lbrycrdd_path(self): def get_lbrycrdd_path_conf_file(): @@ -498,7 +707,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _setup_stream_identifier(self): file_saver_factory = LBRYFileSaverFactory(self.session.peer_finder, self.session.rate_limiter, self.session.blob_manager, self.stream_info_manager, - self.session.wallet, self.download_directory) + self.session.wallet, self.session_settings['default_download_directory']) self.sd_identifier.add_stream_downloader_factory(LBRYFileStreamType, file_saver_factory) file_opener_factory = LBRYFileOpenerFactory(self.session.peer_finder, self.session.rate_limiter, self.session.blob_manager, self.stream_info_manager, @@ -514,20 +723,30 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.sd_identifier.add_stream_downloader_factory(LBRYFileStreamType, downloader_factory) return defer.succeed(True) - def _download_name(self, name, timeout=DEFAULT_TIMEOUT): + def _download_name(self, name, timeout=DEFAULT_TIMEOUT, download_directory=None): + if not download_directory: + download_directory = self.session_settings['default_download_directory'] + elif not os.path.isdir(download_directory): + download_directory = self.session_settings['default_download_directory'] + def _disp_file(f): - file_path = os.path.join(self.download_directory, f.file_name) + file_path = os.path.join(self.session_settings['default_download_directory'], f.file_name) log.info("[" + str(datetime.now()) + "] Already downloaded: " + str(f.stream_hash) + " --> " + file_path) return defer.succeed(f) def _get_stream(name): def _disp(stream): - log.info("[" + str(datetime.now()) + "] Start stream: " + stream['stream_hash']) + stream_hash = stream['stream_hash'] + if isinstance(stream_hash, dict): + stream_hash = stream_hash['sd_hash'] + + log.info("[" + str(datetime.now()) + "] Start stream: " + stream_hash) return stream d = self.session.wallet.get_stream_info_for_name(name) stream = GetStream(self.sd_identifier, self.session, self.session.wallet, self.lbry_file_manager, - max_key_fee=self.max_key_fee, data_rate=self.data_rate, timeout=timeout) + max_key_fee=self.max_key_fee, data_rate=self.data_rate, timeout=timeout, + download_directory=download_directory) d.addCallback(_disp) d.addCallback(lambda stream_info: stream.start(stream_info)) d.addCallback(lambda _: self._path_from_name(name)) @@ -563,10 +782,11 @@ class LBRYDaemon(jsonrpc.JSONRPC): f.close() file_name = l['stream_name'].decode('hex') + for lbry_file in self.lbry_file_manager.lbry_files: if lbry_file.stream_name == file_name: if sys.platform == "darwin": - if os.path.isfile(os.path.join(self.download_directory, lbry_file.stream_name)): + if os.path.isfile(os.path.join(self.session_settings['default_download_directory'], lbry_file.stream_name)): return lbry_file else: return False @@ -577,6 +797,9 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _check(info): stream_hash = info['stream_hash'] + if isinstance(stream_hash, dict): + stream_hash = stream_hash['sd_hash'] + path = os.path.join(self.blobfile_dir, stream_hash) if os.path.isfile(path): log.info("[" + str(datetime.now()) + "] Search for lbry_file, returning: " + stream_hash) @@ -604,8 +827,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): d = self.lbry_file_manager.get_count_for_stream_hash(s_h) # TODO: could possibly be a timing issue here d.addCallback(lambda c: self.stream_info_manager.delete_stream(s_h) if c == 0 else True) - d.addCallback(lambda _: os.remove(os.path.join(self.download_directory, lbry_file.file_name)) if - os.path.isfile(os.path.join(self.download_directory, lbry_file.file_name)) else defer.succeed(None)) + d.addCallback(lambda _: os.remove(os.path.join(self.session_settings['default_download_directory'], lbry_file.file_name)) if + os.path.isfile(os.path.join(self.session_settings['default_download_directory'], lbry_file.file_name)) else defer.succeed(None)) return d d.addCallback(lambda _: finish_deletion(lbry_file)) @@ -614,14 +837,14 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _path_from_name(self, name): d = self._check_history(name) d.addCallback(lambda lbry_file: {'stream_hash': lbry_file.stream_hash, - 'path': os.path.join(self.download_directory, lbry_file.file_name)} + 'path': os.path.join(self.session_settings['default_download_directory'], lbry_file.file_name)} if lbry_file else defer.fail(UnknownNameError)) return d def _path_from_lbry_file(self, lbry_file): if lbry_file: r = {'stream_hash': lbry_file.stream_hash, - 'path': os.path.join(self.download_directory, lbry_file.file_name)} + 'path': os.path.join(self.session_settings['default_download_directory'], lbry_file.file_name)} return defer.succeed(r) else: return defer.fail(UnknownNameError) @@ -641,7 +864,9 @@ class LBRYDaemon(jsonrpc.JSONRPC): return d d = self.session.wallet.get_stream_info_for_name(name) - d.addCallback(lambda info: download_sd_blob(self.session, info['stream_hash'], + d.addCallback(lambda info: info['stream_hash'] if isinstance(info['stream_hash'], str) + else info['stream_hash']['sd_hash']) + d.addCallback(lambda sd_hash: download_sd_blob(self.session, sd_hash, self.blob_request_payment_rate_manager)) d.addCallback(self.sd_identifier.get_metadata_for_sd_blob) d.addCallback(lambda metadata: metadata.validator.info_to_show()) @@ -652,32 +877,159 @@ class LBRYDaemon(jsonrpc.JSONRPC): return d - def _render_response(self, result, code): - return json.dumps({'result': result, 'code': code}) + def _get_lbry_files(self): + r = [] + for f in self.lbry_file_manager.lbry_files: + if f.key: + t = {'completed': f.completed, 'file_name': f.file_name, 'key': binascii.b2a_hex(f.key), + 'points_paid': f.points_paid, 'stopped': f.stopped, 'stream_hash': f.stream_hash, + 'stream_name': f.stream_name, 'suggested_file_name': f.suggested_file_name, + 'upload_allowed': f.upload_allowed} - # def _log_to_slack(self, msg): - # URL = "https://hooks.slack.com/services/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" - # h = httplib2.Http() - # h.request(URL, 'POST', json.dumps({"text": msg}), headers={'Content-Type': 'application/json'}) + else: + t = {'completed': f.completed, 'file_name': f.file_name, 'key': None, + 'points_paid': f.points_paid, + 'stopped': f.stopped, 'stream_hash': f.stream_hash, 'stream_name': f.stream_name, + 'suggested_file_name': f.suggested_file_name, 'upload_allowed': f.upload_allowed} + + r.append(t) + return r + + def _render_response(self, result, code): + return defer.succeed({'result': result, 'code': code}) def jsonrpc_is_running(self): """ - Returns a startup message when the daemon starts, after which it will return True + Check if lbrynet daemon is running + + Args: + None + Returns: true if daemon completed startup, otherwise false """ - if self.startup_message != "" and self.announced_startup == False: - self.announced_startup = True - return self._render_response(self.startup_message, OK_CODE) - elif self.announced_startup: + log.info("[" + str(datetime.now()) + "] is_running: " + str(self.announced_startup)) + + if self.announced_startup: return self._render_response(True, OK_CODE) else: return self._render_response(False, OK_CODE) + def jsonrpc_daemon_status(self): + """ + Get lbrynet daemon status information + + Args: + None + Returns: + 'status_message': startup status message + 'status_code': status_code + if status_code is 'loading_wallet', also contains key 'progress': blockchain catchup progress + """ + + r = {'code': self.startup_status[0], 'message': self.startup_status[1], 'progress': None, 'is_lagging': None} + if self.startup_status[0] == 'loading_wallet': + r['is_lagging'] = self.session.wallet.is_lagging + if r['is_lagging'] == True: + r['message'] = "Synchronization with the blockchain is lagging... if this continues try restarting LBRY" + else: + r['message'] = r['message'] % (str(self.session.wallet.blocks_behind_alert) + " blocks behind") + r['progress'] = self.session.wallet.catchup_progress + + log.info("[" + str(datetime.now()) + "] daemon status: " + str(r)) + return self._render_response(r, OK_CODE) + + def jsonrpc_is_first_run(self): + """ + Check if this is the first time lbrynet daemon has been run + + Args: + None + Returns: + True if first run, otherwise False + """ + + log.info("[" + str(datetime.now()) + "] Check if is first run") + try: + d = self.session.wallet.is_first_run() + except: + d = defer.fail(None) + + d.addCallbacks(lambda r: self._render_response(r, OK_CODE), lambda _: self._render_response(None, OK_CODE)) + + return d + + def jsonrpc_get_start_notice(self): + """ + Get special message to be displayed at startup + + Args: + None + Returns: + Startup message, such as first run notification + """ + + log.info("[" + str(datetime.now()) + "] Get startup notice") + + if self.first_run and not self.session.wallet.wallet_balance: + return self._render_response(self.startup_message, OK_CODE) + elif self.first_run: + return self._render_response(None, OK_CODE) + else: + self._render_response(self.startup_message, OK_CODE) + + def jsonrpc_version(self): + """ + Get lbry version information + + Args: + None + Returns: + "platform": platform string + "os_release": os release string + "os_system": os name + "lbrynet_version: ": lbrynet_version, + "lbryum_version: ": lbryum_version, + "ui_version": commit hash of ui version being used + "remote_lbrynet": most recent lbrynet version available from github + "remote_lbryum": most recent lbryum version available from github + """ + + msg = { + 'platform': self.platform_info['platform'], + 'os_release': self.platform_info['os_release'], + 'os_system': self.platform_info['os_system'], + 'lbrynet_version': lbrynet_version, + 'lbryum_version': lbryum_version, + 'ui_version': self.ui_version, + 'remote_lbrynet': self.git_lbrynet_version, + 'remote_lbryum': self.git_lbryum_version + } + + log.info("[" + str(datetime.now()) + "] Get version info: " + json.dumps(msg)) + return self._render_response(msg, OK_CODE) + def jsonrpc_get_settings(self): """ - Get LBRY payment settings + Get lbrynet daemon settings - @return {'data_rate': float, 'max_key_fee': float} + Args: + None + Returns: + 'run_on_startup': bool, + 'data_rate': float, + 'max_key_fee': float, + 'default_download_directory': string, + 'max_upload': float, 0.0 for unlimited + 'max_download': float, 0.0 for unlimited + 'upload_log': bool, + 'search_timeout': float, + 'max_search_results': int, + 'wallet_type': string, + 'delete_blobs_on_remove': bool, + 'peer_port': int, + 'dht_node_port': int, + 'use_upnp': bool, + 'start_lbrycrdd': bool, """ log.info("[" + str(datetime.now()) + "] Get daemon settings") @@ -685,21 +1037,69 @@ class LBRYDaemon(jsonrpc.JSONRPC): def jsonrpc_set_settings(self, p): """ - Set LBRY payment settings + Set lbrynet daemon settings - @param settings: {'settings': {'data_rate': float, 'max_key_fee': float}} + Args: + 'run_on_startup': bool, + 'data_rate': float, + 'max_key_fee': float, + 'default_download_directory': string, + 'max_upload': float, 0.0 for unlimited + 'max_download': float, 0.0 for unlimited + 'upload_log': bool, + 'search_timeout': float, + 'max_search_results': int, + 'wallet_type': string, + 'delete_blobs_on_remove': bool, + 'peer_port': int, + 'dht_node_port': int, + 'use_upnp': bool, + 'start_lbrycrdd': bool, + Returns: + settings dict """ - d = self._update_settings(p) + def _log_settings_change(params): + log.info("[" + str(datetime.now()) + "] Set daemon settings to " + str(params)) - log.info("[" + str(datetime.now()) + "] Set daemon settings") - return self._render_response(True, OK_CODE) + d = self._update_settings(p) + d.addCallback(lambda _: _log_settings_change(p)) + d.addCallback(lambda _: self._render_response(self.session_settings, OK_CODE)) + + return d + + def jsonrpc_help(self, p=None): + """ + Function to retrieve docstring for API function + + Args: + optional 'function': function to retrieve documentation for + optional 'callable_during_startup': + Returns: + if given a function, returns given documentation + if given callable_during_startup flag, returns list of functions callable during the startup sequence + if no params are given, returns the list of callable functions + """ + + if not p: + return self._render_response(self._listFunctions(), OK_CODE) + elif 'callable_during_start' in p.keys(): + return self._render_response(ALLOWED_DURING_STARTUP, OK_CODE) + elif 'function' in p.keys(): + func_path = p['function'] + function = self._getFunction(func_path) + return self._render_response(function.__doc__, OK_CODE) + else: + return self._render_response(self.jsonrpc_help.__doc__, OK_CODE) def jsonrpc_start_fetcher(self): """ - Start automatically downloading new name claims as they happen + Start automatically downloading new name claims as they occur (off by default) - @return: confirmation message + Args: + None + Returns: + confirmation message """ self.fetcher.start() @@ -709,9 +1109,12 @@ class LBRYDaemon(jsonrpc.JSONRPC): def jsonrpc_stop_fetcher(self): """ - Stop automatically downloading new name claims as they happen + Stop automatically downloading new name claims as they occur - @return: confirmation message + Args: + None + Returns: + confirmation message """ self.fetcher.stop() @@ -722,7 +1125,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): """ Get fetcher status - @return: True/False + Args: + None + Returns: + True/False """ log.info("[" + str(datetime.now()) + "] Get fetcher status") @@ -730,19 +1136,25 @@ class LBRYDaemon(jsonrpc.JSONRPC): def jsonrpc_get_balance(self): """ - Get LBC balance + Get balance - @return: balance + Args: + None + Returns: + balance, float """ log.info("[" + str(datetime.now()) + "] Get balance") - return self._render_response(self.session.wallet.wallet_balance, OK_CODE) + return self._render_response(float(self.session.wallet.wallet_balance), OK_CODE) def jsonrpc_stop(self): """ Stop lbrynet-daemon - @return: shutdown message + Args: + None + Returns: + shutdown message """ def _disp_shutdown(): @@ -758,24 +1170,22 @@ class LBRYDaemon(jsonrpc.JSONRPC): """ Get LBRY files - @return: List of managed LBRY files + Args: + None + Returns: + List of lbry files: + 'completed': bool + 'file_name': string + 'key': hex string + 'points_paid': float + 'stopped': bool + 'stream_hash': base 58 string + 'stream_name': string + 'suggested_file_name': string + 'upload_allowed': bool """ - r = [] - for f in self.lbry_file_manager.lbry_files: - if f.key: - t = {'completed': f.completed, 'file_name': f.file_name, 'key': binascii.b2a_hex(f.key), - 'points_paid': f.points_paid, 'stopped': f.stopped, 'stream_hash': f.stream_hash, - 'stream_name': f.stream_name, 'suggested_file_name': f.suggested_file_name, - 'upload_allowed': f.upload_allowed} - - else: - t = {'completed': f.completed, 'file_name': f.file_name, 'key': None, 'points_paid': f.points_paid, - 'stopped': f.stopped, 'stream_hash': f.stream_hash, 'stream_name': f.stream_name, - 'suggested_file_name': f.suggested_file_name, 'upload_allowed': f.upload_allowed} - - r.append(json.dumps(t)) - + r = self._get_lbry_files() log.info("[" + str(datetime.now()) + "] Get LBRY files") return self._render_response(r, OK_CODE) @@ -783,17 +1193,28 @@ class LBRYDaemon(jsonrpc.JSONRPC): """ Resolve stream info from a LBRY uri - @param: {'name': name to look up} - @return: info for name claim + Args: + 'name': name to look up, string, do not include lbry:// prefix + Returns: + metadata from name claim """ - params = Bunch(p) + + if 'name' in p.keys(): + name = p['name'] + else: + return self._render_response(None, BAD_REQUEST) def _disp(info): - log.info("[" + str(datetime.now()) + "] Resolved info: " + info['stream_hash']) + stream_hash = info['stream_hash'] + if isinstance(stream_hash, dict): + stream_hash = stream_hash['sd_hash'] + + log.info("[" + str(datetime.now()) + "] Resolved info: " + stream_hash) + return self._render_response(info, OK_CODE) - d = self._resolve_name(params.name) - d.addCallbacks(_disp, lambda _: self._render_response('error', NOT_FOUND)) + d = self._resolve_name(name) + d.addCallbacks(_disp, lambda _: server.failure) d.callback(None) return d @@ -801,16 +1222,27 @@ class LBRYDaemon(jsonrpc.JSONRPC): """ Download stream from a LBRY uri - @param: name - @return: {'stream_hash': hex string, 'path': path of download} + Args: + 'name': name to download, string + optional 'download_directory': path to directory where file will be saved, string + Returns: + 'stream_hash': hex string + 'path': path of download """ - params = Bunch(p) if 'timeout' not in p.keys(): - params.timeout = DEFAULT_TIMEOUT + timeout = DEFAULT_TIMEOUT + else: + timeout = p['timeout'] - if params.name: - d = self._download_name(params.name, timeout=params.timeout) + if 'download_directory' not in p.keys(): + download_directory = self.session_settings['default_download_directory'] + else: + download_directory = p['download_directory'] + + if 'name' in p.keys(): + name = p['name'] + d = self._download_name(name=name, timeout=timeout, download_directory=download_directory) d.addCallbacks(lambda message: self._render_response(message, OK_CODE), lambda err: self._render_response('error', NOT_FOUND)) else: @@ -818,46 +1250,51 @@ class LBRYDaemon(jsonrpc.JSONRPC): return d - def jsonrpc_stop_lbry_file(self, p): - params = Bunch(p) - - try: - lbry_file = [f for f in self.lbry_file_manager.lbry_files if f.stream_hash == params.stream_hash][0] - except IndexError: - return defer.fail(UnknownNameError) - - if not lbry_file.stopped: - d = self.lbry_file_manager.toggle_lbry_file_running(lbry_file) - d.addCallback(lambda _: self._render_response("Stream has been stopped", OK_CODE)) - d.addErrback(lambda err: self._render_response(err.getTraceback(), )) - return d - else: - return json.dumps({'result': 'Stream was already stopped'}) - - def jsonrpc_start_lbry_file(self, p): - params = Bunch(p) - - try: - lbry_file = [f for f in self.lbry_file_manager.lbry_files if f.stream_hash == params.stream_hash][0] - except IndexError: - return defer.fail(UnknownNameError) - - if lbry_file.stopped: - d = self.lbry_file_manager.toggle_lbry_file_running(lbry_file) - d.callback(None) - return json.dumps({'result': 'Stream started'}) - else: - return json.dumps({'result': 'Stream was already running'}) + # def jsonrpc_stop_lbry_file(self, p): + # params = Bunch(p) + # + # try: + # lbry_file = [f for f in self.lbry_file_manager.lbry_files if f.stream_hash == params.stream_hash][0] + # except IndexError: + # return defer.fail(UnknownNameError) + # + # if not lbry_file.stopped: + # d = self.lbry_file_manager.toggle_lbry_file_running(lbry_file) + # d.addCallback(lambda _: self._render_response("Stream has been stopped", OK_CODE)) + # d.addErrback(lambda err: self._render_response(err.getTraceback(), )) + # return d + # else: + # return json.dumps({'result': 'Stream was already stopped'}) + # + # def jsonrpc_start_lbry_file(self, p): + # params = Bunch(p) + # + # try: + # lbry_file = [f for f in self.lbry_file_manager.lbry_files if f.stream_hash == params.stream_hash][0] + # except IndexError: + # return defer.fail(UnknownNameError) + # + # if lbry_file.stopped: + # d = self.lbry_file_manager.toggle_lbry_file_running(lbry_file) + # d.callback(None) + # return json.dumps({'result': 'Stream started'}) + # else: + # return json.dumps({'result': 'Stream was already running'}) def jsonrpc_search_nametrie(self, p): """ - Search the nametrie for claims beginning with search + Search the nametrie for claims beginning with search (yes, this is a dumb search, it'll be made better) - @param {'search': search string} - @return: List of search results + Args: + 'search': search query, string + Returns: + List of search results """ - params = Bunch(p) + if 'search' in p.keys(): + search = p['search'] + else: + return self._render_response(None, BAD_REQUEST) def _clean(n): t = [] @@ -891,16 +1328,17 @@ class LBRYDaemon(jsonrpc.JSONRPC): t['cost_est'] = r[2] consolidated_results.append(t) # log.info(str(t)) - return self._render_response(consolidated_results, OK_CODE) + return consolidated_results - log.info('[' + str(datetime.now()) + '] Search nametrie: ' + params.search) + log.info('[' + str(datetime.now()) + '] Search nametrie: ' + search) d = self.session.wallet.get_nametrie() - d.addCallback(lambda trie: [claim for claim in trie if claim['name'].startswith(params.search) and 'txid' in claim]) + d.addCallback(lambda trie: [claim for claim in trie if claim['name'].startswith(search) and 'txid' in claim]) d.addCallback(lambda claims: claims[:self.max_search_results]) d.addCallback(resolve_claims) d.addCallback(_clean) d.addCallback(_disp) + d.addCallback(lambda results: self._render_response(results, OK_CODE)) return d @@ -908,61 +1346,64 @@ class LBRYDaemon(jsonrpc.JSONRPC): """ Delete a lbry file - @param {'file_name': string} - @return: confirmation message + Args: + 'file_name': downloaded file name, string + Returns: + confirmation message """ - params = Bunch(p) - def _disp(file_name): log.info("[" + str(datetime.now()) + "] Deleted: " + file_name) return self._render_response("Deleted: " + file_name, OK_CODE) - lbry_files = [self._delete_lbry_file(f) for f in self.lbry_file_manager.lbry_files if params.file_name == f.file_name] - d = defer.DeferredList(lbry_files) - d.addCallback(lambda _: _disp(params.file_name)) - return d + if "file_name" in p.keys(): + lbry_files = [self._delete_lbry_file(f) for f in self.lbry_file_manager.lbry_files + if p['file_name'] == f.file_name] + d = defer.DeferredList(lbry_files) + d.addCallback(lambda _: _disp(p['file_name'])) + return d def jsonrpc_publish(self, p): """ Make a new name claim - @param: - @return: + Args: + 'name': name to be claimed, string + 'file_path': path to file to be associated with name, string + 'bid': amount of credits to commit in this claim, float + optional 'author': author, string + optional 'title': title, description + optional 'description': content description, string + optional 'thumbnail': thumbnail image url + optional 'key_fee': key fee to be paid to publisher, float (default 0.0) + optional 'key_fee_address': address for key fee to be sent to, string (defaults on new address) + optional 'content_license': content license string + optional 'sources': alternative sources dict, keys 'lbry_sd_hash', 'btih', 'url' + Returns: + Confirmation message """ - params = Bunch(p) + metadata_fields = ["name", "file_path", "bid", "author", "title", + "description", "thumbnail", "key_fee", "key_fee_address", + "content_license", "sources"] - metadata_fields = {"name": unicode, "file_path": unicode, "bid": float, "author": unicode, "title": unicode, - "description": unicode, "thumbnail": unicode, "key_fee": float, "key_fee_address": unicode, - "content_license": unicode, "sources": dict} + for k in metadata_fields: + if k not in p.keys(): + p[k] = None - for k in metadata_fields.keys(): - if k in params.__dict__.keys(): - if isinstance(params.__dict__[k], metadata_fields[k]): - if type(params.__dict__[k]) == unicode: - metadata_fields[k] = str(params.__dict__[k]) - else: - metadata_fields[k] = params.__dict__[k] - else: - metadata_fields[k] = None - else: - metadata_fields[k] = None + pub = Publisher(self.session, self.lbry_file_manager, self.session.wallet) - log.info("[" + str(datetime.now()) + "] Publish: ", metadata_fields) - - p = Publisher(self.session, self.lbry_file_manager, self.session.wallet) - d = p.start(name=metadata_fields['name'], - file_path=metadata_fields['file_path'], - bid=metadata_fields['bid'], - title=metadata_fields['title'], - description=metadata_fields['description'], - thumbnail=metadata_fields['thumbnail'], - key_fee=metadata_fields['key_fee'], - key_fee_address=metadata_fields['key_fee_address'], - content_license=metadata_fields['content_license'], - author=metadata_fields['author'], - sources=metadata_fields['sources']) + d = pub.start(p['name'], + p['file_path'], + p['bid'], + title=p['title'], + description=p['description'], + thumbnail=p['thumbnail'], + key_fee=p['key_fee'], + key_fee_address=p['key_fee_address'], + content_license=p['content_license'], + author=p['author'], + sources=p['sources']) d.addCallbacks(lambda msg: self._render_response(msg, OK_CODE), lambda err: self._render_response(err.getTraceback(), BAD_REQUEST)) @@ -971,53 +1412,73 @@ class LBRYDaemon(jsonrpc.JSONRPC): def jsonrpc_abandon_name(self, p): """ - Abandon and reclaim credits from a name claim + Abandon a name and reclaim credits from the claim - @param: {'txid': string} - @return: txid + Args: + 'txid': txid of claim, string + Return: + Confirmation message """ - params = Bunch(p) - def _disp(txid, tx): - log.info("[" + str(datetime.now()) + "] Abandoned name claim tx " + txid) - return self._render_response(txid, OK_CODE) + if 'txid' in p.keys(): + txid = p['txid'] + else: + return server.failure + + def _disp(x): + log.info("[" + str(datetime.now()) + "] Abandoned name claim tx " + str(x)) + return self._render_response(x, OK_CODE) d = defer.Deferred() - d.addCallback(lambda _: self.session.wallet.abandon_name(params.txid)) - d.addCallback(lambda tx: _disp(params.txid, tx)) - d.addErrback(lambda err: self._render_response(err.getTraceback(), BAD_REQUEST)) + d.addCallback(lambda _: self.session.wallet.abandon_name(txid)) + d.addCallback(_disp) d.callback(None) return d def jsonrpc_get_name_claims(self): """ - Get name claims + Get my name claims - @return: list of name claims + Args: + None + Returns + list of name claims """ + def _clean(claims): for c in claims: for k in c.keys(): if isinstance(c[k], Decimal): c[k] = float(c[k]) - return self._render_response(claims, OK_CODE) + return defer.succeed(claims) d = self.session.wallet.get_name_claims() d.addCallback(_clean) + d.addCallback(lambda claims: self._render_response(claims, OK_CODE)) return d def jsonrpc_get_time_behind_blockchain(self): """ - Get time behind blockchain + Get number of blocks behind the blockchain - @return: time behind blockchain + Args: + None + Returns: + number of blocks behind blockchain, int """ - d = self.session.wallet.get_most_recent_blocktime() - d.addCallback(get_time_behind_blockchain) - d.addCallbacks(lambda result: self._render_response(str(result), OK_CODE), - lambda result: self._render_response(result, BAD_REQUEST)) + + def _get_time_behind(): + try: + local_height = self.session.wallet.network.get_local_height() + remote_height = self.session.wallet.network.get_server_height() + return defer.succeed(remote_height - local_height) + except: + return defer.fail() + + d = _get_time_behind() + d.addCallback(lambda r: self._render_response(r, OK_CODE)) return d @@ -1025,14 +1486,19 @@ class LBRYDaemon(jsonrpc.JSONRPC): """ Generate a new wallet address - @return: new wallet address + Args: + None + Returns: + new wallet address, base 58 string """ + def _disp(address): log.info("[" + str(datetime.now()) + "] Got new wallet address: " + address) - return json.dumps(self._render_response(address, OK_CODE)) + return defer.succeed(address) d = self.session.wallet.get_new_address() d.addCallback(_disp) + d.addCallback(lambda address: self._render_response(address, OK_CODE)) return d # def jsonrpc_update_name(self, metadata): @@ -1056,6 +1522,15 @@ class LBRYDaemon(jsonrpc.JSONRPC): # return d def jsonrpc_toggle_fetcher_verbose(self): + """ + Toggle fetcher verbose mode + + Args: + None + Returns: + Fetcher verbose status, bool + """ + if self.fetcher.verbose: self.fetcher.verbose = False else: @@ -1064,51 +1539,26 @@ class LBRYDaemon(jsonrpc.JSONRPC): return self._render_response(self.fetcher.verbose, OK_CODE) def jsonrpc_check_for_new_version(self): - def _check_for_updates(package): - git_version = subprocess.check_output("git ls-remote " + package['git'] + " | grep HEAD | cut -f 1", shell=True) - up_to_date = False - if os.path.isfile(package['version_file']): - f = open(package['version_file'], 'r') - current_version = f.read() - f.close() + """ + Checks local version against versions in __init__.py and version.py in the lbrynet and lbryum repos - if git_version == current_version: - r = package['name'] + " is up to date" - up_to_date = True - else: - r = package['name'] + " version is out of date" + Args: + None + Returns: + true/false, true meaning that there is a new version available + """ + + + + def _check_version(): + if (lbrynet_version >= self.git_lbrynet_version) and (lbryum_version >= self.git_lbryum_version): + log.info("[" + str(datetime.now()) + "] Up to date") + return self._render_response(False, OK_CODE) else: - r = "Unknown version of " + package['name'] + log.info("[" + str(datetime.now()) + "] Updates available") + return self._render_response(True, OK_CODE) - return (up_to_date, r) - - package_infos = { - "lbrynet": {"name": "LBRYnet", - "git": "https://github.com/lbryio/lbry.git", - "version_file": os.path.join(self.db_dir, ".lbrynet_version"), - "clone": ".lbrygit", - }, - "lbryum": {"name": "lbryum", - "git": "https://github.com/lbryio/lbryum.git", - "version_file": os.path.join(self.db_dir, ".lbryum_version"), - "clone": ".lbryumgit", - }, - "lbry": {"name": "LBRY", - "git": "https://github.com/jackrobison/lbrynet-app.git", - "version_file": os.path.join(self.db_dir, ".lbry_app_version"), - "clone": None, - }, - } - - r = [_check_for_updates(package_infos[p]) for p in package_infos.keys()] - log.info("[" + str(datetime.now()) + "] Check for new version: " + json.dumps(r)) - return self._render_response(r, OK_CODE) - - def jsonrpc___dir__(self): - return ['is_running', 'get_settings', 'set_settings', 'start_fetcher', 'stop_fetcher', 'fetcher_status', - 'get_balance', 'stop', 'get_lbry_files', 'resolve_name', 'get', 'search_nametrie', - 'delete_lbry_file', 'check', 'publish', 'abandon_name', 'get_name_claims', - 'get_time_behind_blockchain', 'get_new_address', 'toggle_fetcher_verbose', 'check_for_new_version'] + return _check_version() class LBRYDaemonCommandHandler(object): @@ -1147,66 +1597,12 @@ class LBRYindex(resource.Resource): class LBRYFileRender(resource.Resource): isLeaf = False - def _render_path(self, path): - extension = os.path.splitext(path)[1] - if extension in ['mp4', 'flv', 'mov', 'ogv']: - return r'
' - - def _delayed_render(self, request, results): - request.write(str(results)) - request.finish() - def render_GET(self, request): if 'name' in request.args.keys(): api = jsonrpc.Proxy(API_CONNECTION_STRING) d = api.callRemote("get", {'name': request.args['name'][0]}) - d.addCallback(lambda response: self._delayed_render(request, self._render_path(json.loads(response)['result']['path'])) - if json.loads(response)['code'] == 200 - else self._delayed_render(request, "Error")) + d.addCallback(lambda results: static.File(results['path']).render_GET(request)) return server.NOT_DONE_YET else: - self._delayed_render(request, "Error") - return server.NOT_DONE_YET - - -# class LBRYFilePage(resource.Resource): -# isLeaf = False -# -# def _delayed_render(self, request, results): -# request.write(str(results)) -# request.finish() -# -# h = "%s" -# -# d = LBRYDaemonCommandHandler('get_lbry_files').run() -# d.addCallback(lambda r: json.loads(r)['result']) -# d.addCallback(lambda lbry_files: [h % (json.loads(lbry_file)['file_name'], json.loads(lbry_file)['file_name']) for lbry_file in lbry_files]) -# d.addCallback(lambda r: "" + ''.join(r) + "") -# d.addCallbacks(lambda results: self._delayed_render(request, results), -# lambda err: self._delayed_render(request, err.getTraceback())) -# -# return server.NOT_DONE_YET - - -class LBRYDaemonWeb(resource.Resource): - isLeaf = False - - def _delayed_render(self, request, results): - request.write(str(results)) - request.setResponseCode(json.loads(results)['code']) - request.finish() - - def render_GET(self, request): - func = request.args['function'][0] - del request.args['function'] - - p = {} - for k in request.args.keys(): - p[k] = request.args[k][0] - - d = LBRYDaemonCommandHandler(func).run(p) - d.addCallbacks(lambda results: self._delayed_render(request, results), - lambda err: self._delayed_render(request, json.dumps({'message': err.getTraceback(), 'code': BAD_REQUEST}))) - - return server.NOT_DONE_YET \ No newline at end of file + return server.failure diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py index 789c4ad84..5fc6f98ff 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -1,22 +1,49 @@ import argparse import logging -import tempfile +import logging.handlers +import subprocess import os import shutil +import webbrowser +import sys +import socket from StringIO import StringIO from zipfile import ZipFile from urllib import urlopen - +from datetime import datetime +from appdirs import user_data_dir from twisted.web import server, static from twisted.internet import reactor, defer from jsonrpc.proxy import JSONRPCProxy -from lbrynet.lbrynet_daemon.LBRYDaemon import LBRYDaemon, LBRYindex, LBRYDaemonWeb, LBRYFileRender -from lbrynet.conf import API_CONNECTION_STRING, API_INTERFACE, API_ADDRESS, API_PORT, DEFAULT_WALLET +from lbrynet.lbrynet_daemon.LBRYDaemon import LBRYDaemon, LBRYindex, LBRYFileRender +from lbrynet.conf import API_CONNECTION_STRING, API_INTERFACE, API_ADDRESS, API_PORT, DEFAULT_WALLET, UI_ADDRESS +if sys.platform != "darwin": + log_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") +else: + log_dir = user_data_dir("LBRY") + +if not os.path.isdir(log_dir): + os.mkdir(log_dir) + +LOG_FILENAME = os.path.join(log_dir, 'lbrynet-daemon.log') log = logging.getLogger(__name__) -logging.basicConfig(level=logging.INFO) +handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=262144, backupCount=5) +log.addHandler(handler) +log.setLevel(logging.INFO) + +REMOTE_SERVER = "www.google.com" + + +def test_internet_connection(): + try: + host = socket.gethostbyname(REMOTE_SERVER) + s = socket.create_connection((host, 80), 2) + return True + except: + return False def stop(): @@ -38,45 +65,114 @@ def start(): help="lbrycrd or lbryum, default lbryum", type=str, default=DEFAULT_WALLET) - parser.add_argument("--update", - help="True or false, default true", - type=str, - default="True") parser.add_argument("--ui", - help="temp or path, default temp, path is the path of the dist folder", - default="temp") + help="path to custom UI folder", + default="") + parser.add_argument("--branch", + help="Branch of lbry-web-ui repo to use, defaults on HEAD", + default="HEAD") + parser.add_argument('--no-launch', dest='launchui', action="store_false") + parser.set_defaults(launchui=True) + + try: + JSONRPCProxy.from_url(API_CONNECTION_STRING).is_running() + log.info("lbrynet-daemon is already running") + return + except: + pass log.info("Starting lbrynet-daemon from command line") + print "Starting lbrynet-daemon from command line" + print "To view activity, view the log file here: " + LOG_FILENAME + print "Web UI is available at http://%s:%i" %(API_INTERFACE, API_PORT) + print "JSONRPC API is available at " + API_CONNECTION_STRING + print "To quit press ctrl-c or call 'stop' via the API" args = parser.parse_args() - download_ui = True - if args.ui != "temp" and os.path.isdir(args.ui): - download_ui = False - ui_dir = args.ui - log.info("Using user specified UI directory: " + str(ui_dir)) + if args.branch == "HEAD": + GIT_CMD_STRING = "git ls-remote https://github.com/lbryio/lbry-web-ui.git | grep %s | cut -f 1" % args.branch + DIST_URL = "https://raw.githubusercontent.com/lbryio/lbry-web-ui/master/dist.zip" + else: + log.info("Using UI branch: " + args.branch) + GIT_CMD_STRING = "git ls-remote https://github.com/lbryio/lbry-web-ui.git | grep refs/heads/%s | cut -f 1" % args.branch + DIST_URL = "https://raw.githubusercontent.com/lbryio/lbry-web-ui/%s/dist.zip" % args.branch - if args.ui == "temp" or download_ui: - log.info("Downloading current web ui to temp directory") - ui_dir = tempfile.mkdtemp() - url = urlopen("https://rawgit.com/lbryio/lbry-web-ui/master/dist.zip") - z = ZipFile(StringIO(url.read())) - z.extractall(ui_dir) + def getui(ui_dir=None): + if ui_dir: + if os.path.isdir(ui_dir): + log.info("Using user specified UI directory: " + str(ui_dir)) + ui_version_info = "user-specified" + return defer.succeed([ui_dir, ui_version_info]) + else: + log.info("User specified UI directory doesn't exist: " + str(ui_dir)) - daemon = LBRYDaemon() - daemon.setup(args.wallet, args.update) + def download_ui(dest_dir, ui_version): + url = urlopen(DIST_URL) + z = ZipFile(StringIO(url.read())) + names = [i for i in z.namelist() if '.DS_Store' not in i and '__MACOSX' not in i] + z.extractall(dest_dir, members=names) + return defer.succeed([dest_dir, ui_version]) - root = LBRYindex(ui_dir) - root.putChild("css", static.File(os.path.join(ui_dir, "css"))) - root.putChild("font", static.File(os.path.join(ui_dir, "font"))) - root.putChild("img", static.File(os.path.join(ui_dir, "img"))) - root.putChild("js", static.File(os.path.join(ui_dir, "js"))) - root.putChild(API_ADDRESS, daemon) - root.putChild("webapi", LBRYDaemonWeb()) - root.putChild("view", LBRYFileRender()) + data_dir = user_data_dir("LBRY") + version_dir = os.path.join(data_dir, "ui_version_history") - reactor.listenTCP(API_PORT, server.Site(root), interface=API_INTERFACE) - reactor.run() + git_version = subprocess.check_output(GIT_CMD_STRING, shell=True) + if not git_version: + log.info("You should have been notified to install xcode command line tools, once it's installed you can start LBRY") + print "You should have been notified to install xcode command line tools, once it's installed you can start LBRY" + sys.exit(0) - if download_ui: - shutil.rmtree(ui_dir) + ui_version_info = git_version + + if not os.path.isdir(data_dir): + os.mkdir(data_dir) + + if not os.path.isdir(os.path.join(data_dir, "ui_version_history")): + os.mkdir(version_dir) + + if not os.path.isfile(os.path.join(version_dir, git_version)): + f = open(os.path.join(version_dir, git_version), "w") + version_message = "[" + str(datetime.now()) + "] Updating UI --> " + git_version + f.write(version_message) + f.close() + log.info(version_message) + + if os.path.isdir(os.path.join(data_dir, "lbry-web-ui")): + shutil.rmtree(os.path.join(data_dir, "lbry-web-ui")) + else: + version_message = "[" + str(datetime.now()) + "] UI version " + git_version + " up to date" + log.info(version_message) + + if os.path.isdir(os.path.join(data_dir, "lbry-web-ui")): + return defer.succeed([os.path.join(data_dir, "lbry-web-ui"), ui_version_info]) + else: + return download_ui(os.path.join(data_dir, "lbry-web-ui"), ui_version_info) + + def setupserver(ui_dir, ui_version): + root = LBRYindex(ui_dir) + root.putChild("css", static.File(os.path.join(ui_dir, "css"))) + root.putChild("font", static.File(os.path.join(ui_dir, "font"))) + root.putChild("img", static.File(os.path.join(ui_dir, "img"))) + root.putChild("js", static.File(os.path.join(ui_dir, "js"))) + root.putChild("view", LBRYFileRender()) + return defer.succeed([root, ui_version]) + + def setupapi(root, wallet, ui_version): + daemon = LBRYDaemon(ui_version, wallet_type=wallet) + root.putChild(API_ADDRESS, daemon) + reactor.listenTCP(API_PORT, server.Site(root), interface=API_INTERFACE) + return daemon.setup() + + if test_internet_connection(): + d = getui(args.ui) + d.addCallback(lambda r: setupserver(r[0], r[1])) + d.addCallback(lambda r: setupapi(r[0], args.wallet, r[1])) + if args.launchui: + d.addCallback(lambda _: webbrowser.open(UI_ADDRESS)) + reactor.run() + print "\nClosing lbrynet-daemon" + else: + log.info("Not connected to internet, unable to start") + print "Not connected to internet, unable to start" + return diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index 4552cd4af..5ebc4b59e 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -17,7 +17,7 @@ log = logging.getLogger(__name__) class GetStream(object): def __init__(self, sd_identifier, session, wallet, lbry_file_manager, max_key_fee, pay_key=True, data_rate=0.5, - timeout=DEFAULT_TIMEOUT): + timeout=DEFAULT_TIMEOUT, download_directory=None): self.wallet = wallet self.resolved_name = None self.description = None @@ -37,6 +37,7 @@ class GetStream(object): self.d = defer.Deferred(None) self.timeout = timeout self.timeout_counter = 0 + self.download_directory = download_directory self.download_path = None self.checker = LoopingCall(self.check_status) @@ -68,6 +69,8 @@ class GetStream(object): self.key_fee_address = None self.stream_hash = self.stream_info['stream_hash'] + if isinstance(self.stream_hash, dict): + self.stream_hash = self.stream_hash['sd_hash'] else: log.error("InvalidStreamInfoError in autofetcher: ", stream_info) @@ -86,7 +89,10 @@ class GetStream(object): self.d.addCallback(lambda _: download_sd_blob(self.session, self.stream_hash, self.payment_rate_manager)) self.d.addCallback(self.sd_identifier.get_metadata_for_sd_blob) self.d.addCallback(lambda metadata: (next(factory for factory in metadata.factories if isinstance(factory, ManagedLBRYFileDownloaderFactory)), metadata)) - self.d.addCallback(lambda (factory, metadata): factory.make_downloader(metadata, [self.data_rate, True], self.payment_rate_manager)) + self.d.addCallback(lambda (factory, metadata): factory.make_downloader(metadata, + [self.data_rate, True], + self.payment_rate_manager, + download_directory=self.download_directory)) self.d.addErrback(lambda err: err.trap(defer.CancelledError)) self.d.addErrback(lambda err: log.error("An exception occurred attempting to load the stream descriptor: %s", err.getTraceback())) self.d.addCallback(self._start_download) diff --git a/lbrynet/lbrynet_daemon/LBRYPublisher.py b/lbrynet/lbrynet_daemon/LBRYPublisher.py index ea7c015a3..eeb8bbed3 100644 --- a/lbrynet/lbrynet_daemon/LBRYPublisher.py +++ b/lbrynet/lbrynet_daemon/LBRYPublisher.py @@ -43,7 +43,7 @@ class Publisher(object): message = "[" + str(datetime.now()) + "] Published " + self.file_name + " --> lbry://" + \ str(self.publish_name) + " with txid: " + str(self.tx_hash) log.info(message) - return defer.succeed(message) + return defer.succeed(self.tx_hash) self.publish_name = name self.file_path = file_path @@ -108,7 +108,7 @@ class Publisher(object): return d def _claim_name(self): - d = self.wallet.claim_name(self.publish_name, {'sd_hash': self.sd_hash}, self.bid_amount, + d = self.wallet.claim_name(self.publish_name, self.sd_hash, self.bid_amount, description=self.description, key_fee=self.key_fee, key_fee_address=self.key_fee_address, thumbnail=self.thumbnail, content_license=self.content_license, author=self.author, @@ -121,6 +121,7 @@ class Publisher(object): return d def _show_publish_error(self, err): + log.info(err.getTraceback()) message = "An error occurred publishing %s to %s. Error: %s." if err.check(InsufficientFundsError): error_message = "Insufficient funds" diff --git a/lbrynet/lbrynet_gui/lbry.png b/lbrynet/lbrynet_gui/lbry.png new file mode 100644 index 000000000..16c7c4683 Binary files /dev/null and b/lbrynet/lbrynet_gui/lbry.png differ diff --git a/packaging/ubuntu/README.md b/packaging/ubuntu/README.md new file mode 100644 index 000000000..d1a3d81f8 --- /dev/null +++ b/packaging/ubuntu/README.md @@ -0,0 +1,5 @@ +# package scripts + +How to build LBRY packages. + +For best results, run on a fresh image. diff --git a/packaging/ubuntu/lbry b/packaging/ubuntu/lbry new file mode 100755 index 000000000..0c6edeeeb --- /dev/null +++ b/packaging/ubuntu/lbry @@ -0,0 +1,32 @@ +#!/bin/bash + +set -euo pipefail + +urlencode() { + local LANG=C + local length="${#1}" + for (( i = 0; i < length; i++ )); do + local c="${1:i:1}" + case $c in + [a-zA-Z0-9.~_-]) printf "$c" ;; + *) printf '%%%02X' "'$c" ;; + esac + done +} + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +if [ -z "$(pgrep lbrynet-daemon)" ]; then + echo "running lbrynet-daemon..." + $DIR/lbrynet-daemon --branch=settings-page & + sleep 3 # let the daemon load before connecting +fi + +ARG=${1:-} + +if [ -z "$ARG" ]; then + URL="" +else + URL="view?name=$(urlencode "$(echo "$ARG" | cut -c 8-)")" +fi + +/usr/bin/xdg-open "http://localhost:5279/$URL" diff --git a/packaging/ubuntu/lbry-init.conf b/packaging/ubuntu/lbry-init.conf new file mode 100644 index 000000000..c1e192deb --- /dev/null +++ b/packaging/ubuntu/lbry-init.conf @@ -0,0 +1,11 @@ +description "LBRY Daemon" + +#start on (local-filesystems and net-device-up IFACE=eth0) +stop on runlevel [016] + +#expect fork + +respawn +respawn limit 5 20 + +exec /usr/share/python/lbrynet/bin/lbrynet-daemon diff --git a/packaging/ubuntu/lbry.desktop b/packaging/ubuntu/lbry.desktop new file mode 100644 index 000000000..f034b97c6 --- /dev/null +++ b/packaging/ubuntu/lbry.desktop @@ -0,0 +1,15 @@ +[Desktop Entry] +Version=0.2.1 +Name=LBRY +# Only KDE 4 seems to use GenericName, so we reuse the KDE strings. +# From Ubuntu's language-pack-kde-XX-base packages, version 9.04-20090413. +GenericName=Filesharing +# Gnome and KDE 3 uses Comment. +Comment=Stream. Share. Earn. +#Exec=/usr/bin/xdg-open http://localhost:5279/view?name=%U +Exec=/usr/share/python/lbrynet/bin/lbry %U +Terminal=false +Icon=/usr/share/python/lbrynet/lbrynet/lbrynet_gui/lbry.png +Type=Application +Categories=Network;Internet;Filesharing +MimeType=x-scheme-handler/lbry; diff --git a/packaging/ubuntu/ubuntu_package_setup.sh b/packaging/ubuntu/ubuntu_package_setup.sh new file mode 100755 index 000000000..4be10c8a7 --- /dev/null +++ b/packaging/ubuntu/ubuntu_package_setup.sh @@ -0,0 +1,67 @@ +#!/bin/bash + +# Tested on fresh Ubuntu 14.04 install. + +# wget https://raw.githubusercontent.com/lbryio/lbry/master/packaging/ubuntu/ubuntu_package_setup.sh +# bash ubuntu_package_setup.sh master + +set -euo pipefail + +BRANCH=${1:-master} + +BUILD_DIR="lbry-build-$(date +%Y%m%d-%H%M%S)" +mkdir "$BUILD_DIR" +cd "$BUILD_DIR" + +# get the required OS packages +sudo add-apt-repository -y ppa:spotify-jyrki/dh-virtualenv +sudo apt-get update +sudo apt-get install -y build-essential git python-dev libffi-dev libssl-dev libgmp3-dev dh-virtualenv debhelper + +# need a modern version of pip (more modern than ubuntu default) +wget https://bootstrap.pypa.io/get-pip.py +sudo python get-pip.py +rm get-pip.py +sudo pip install make-deb + +# check out LBRY +git clone https://github.com/lbryio/lbry.git --branch "$BRANCH" + +# build packages +( + cd lbry + make-deb + dpkg-buildpackage -us -uc +) + + +### insert our extra files + +# extract .deb +PACKAGE="$(ls | grep '.deb')" +ar vx "$PACKAGE" +mkdir control data +tar -xvzf control.tar.gz --directory control +tar -xvJf data.tar.xz --directory data + +# add files +function addfile() { + FILE="$1" + TARGET="$2" + mkdir -p "$(dirname "data/$TARGET")" + cp "$FILE" "data/$TARGET" + echo "$(md5sum "data/$TARGET" | cut -d' ' -f1) $TARGET" >> control/md5sums +} +PACKAGING_DIR='lbry/packaging/ubuntu' +addfile "$PACKAGING_DIR/lbry" usr/share/python/lbrynet/bin/lbry +addfile "$PACKAGING_DIR/lbry.desktop" usr/share/applications/lbry.desktop +#addfile lbry/packaging/ubuntu/lbry-init.conf etc/init/lbry.conf + +# repackage .deb +sudo chown -R root:root control data +tar -cvzf control.tar.gz -C control . +tar -cvJf data.tar.xz -C data . +sudo chown root:root debian-binary control.tar.gz data.tar.xz +ar r "$PACKAGE" debian-binary control.tar.gz data.tar.xz + +# TODO: we can append to data.tar instead of extracting it all and recompressing diff --git a/requirements.txt b/requirements.txt index 103294fd4..d4423b5db 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,9 +19,10 @@ qrcode==5.2.2 requests==2.9.1 seccure==0.3.1.3 simplejson==3.8.2 -six==1.10.0 +six==1.9.0 slowaes==0.1a1 txJSON-RPC==0.3.1 unqlite==0.2.0 wsgiref==0.1.2 zope.interface==4.1.3 +base58==0.2.2 diff --git a/setup.py b/setup.py index 679781350..1863dad82 100644 --- a/setup.py +++ b/setup.py @@ -26,15 +26,10 @@ console_scripts = ['lbrynet-console = lbrynet.lbrynet_console.LBRYConsole:launch requires = ['pycrypto', 'twisted', 'miniupnpc', 'yapsy', 'seccure', 'python-bitcoinrpc==0.1', 'txJSON-RPC', 'requests>=2.4.2', 'unqlite==0.2.0', - 'leveldb', 'lbryum', 'jsonrpc', 'simplejson', 'appdirs'] - -if sys.platform == 'darwin': - requires.append('six==1.9.0') -else: - requires.append('six>=1.9.0') + 'leveldb', 'lbryum>=2.6.0.1', 'jsonrpc', 'simplejson', 'appdirs', 'six==1.9.0', 'base58'] gui_data_files = ['close2.gif', 'lbry-dark-242x80.gif', 'lbry-dark-icon.xbm', 'lbry-dark-icon.ico', - 'drop_down.gif', 'show_options.gif', 'hide_options.gif', 'lbry.conf'] + 'drop_down.gif', 'show_options.gif', 'hide_options.gif', 'lbry.conf', 'lbry.png'] gui_data_paths = [os.path.join(base_dir, 'lbrynet', 'lbrynet_gui', f) for f in gui_data_files] setup(name='lbrynet',