diff --git a/.gitignore b/.gitignore index b36b47bae..4cce3d364 100644 --- a/.gitignore +++ b/.gitignore @@ -9,12 +9,11 @@ /build /dist +/lbrynet.egg-info .idea/.name .coverage .DS_Store -lbrynet.egg-info/PKG-INFO - # temporary files from the twisted.trial test runner -_trial_temp/ \ No newline at end of file +_trial_temp/ diff --git a/FAQ.md b/FAQ.md index 091acd711..d3c631d26 100644 --- a/FAQ.md +++ b/FAQ.md @@ -77,9 +77,10 @@ Note: the lbry api can only be used while either the app or lbrynet-daemon comma sys.exit(0) api = JSONRPCProxy.from_url(API_CONNECTION_STRING) - if not api.is_running(): - print api.daemon_status() + status = api.status() + if not status['is_running']: + print status else: - for func in api.help(): - print "%s:\n%s" % (func, api.help({'function': func})) + for cmd in api.commands(): + print "%s:\n%s" % (cmd, api.help({'command': cmd})) diff --git a/README.md b/README.md index 221a5e6cf..fa1b1cb7e 100644 --- a/README.md +++ b/README.md @@ -49,11 +49,12 @@ except: API_CONNECTION_STRING = "http://localhost:5279/lbryapi" api = JSONRPCProxy.from_url(API_CONNECTION_STRING) -if not api.is_running(): - print api.daemon_status() +status = api.status() +if not status['is_running']: + print status else: - for func in api.help(): - print "%s:\n%s" % (func, api.help({'function': func})) + for cmd in api.commands(): + print "%s:\n%s" % (cmd, api.help({'command': cmd})) ``` If you've installed lbrynet, it comes with a file sharing application, called `lbrynet-daemon`, which breaks diff --git a/lbrynet/core/Error.py b/lbrynet/core/Error.py index 91bf48dcc..36e20151a 100644 --- a/lbrynet/core/Error.py +++ b/lbrynet/core/Error.py @@ -96,9 +96,5 @@ class InvalidAuthenticationToken(Exception): pass -class SubhandlerError(Exception): - pass - - class NegotiationError(Exception): pass diff --git a/lbrynet/core/Wallet.py b/lbrynet/core/Wallet.py index ebaf82497..334a9390c 100644 --- a/lbrynet/core/Wallet.py +++ b/lbrynet/core/Wallet.py @@ -182,10 +182,6 @@ class Wallet(object): """This class implements the Wallet interface for the LBRYcrd payment system""" implements(IWallet) - _FIRST_RUN_UNKNOWN = 0 - _FIRST_RUN_YES = 1 - _FIRST_RUN_NO = 2 - def __init__(self, storage): if not isinstance(storage, MetaDataStorage): raise ValueError('storage must be an instance of MetaDataStorage') @@ -209,7 +205,6 @@ class Wallet(object): self._manage_count = 0 self._balance_refresh_time = 3 self._batch_count = 20 - self._first_run = self._FIRST_RUN_UNKNOWN def start(self): def start_manage(): @@ -703,20 +698,6 @@ class Wallet(object): def get_available_balance(self): return float(self.wallet_balance - self.total_reserved_points) - def is_first_run(self): - if self._first_run == self._FIRST_RUN_UNKNOWN: - d = self._check_first_run() - - def set_first_run(is_first): - self._first_run = self._FIRST_RUN_YES if is_first else self._FIRST_RUN_NO - - d.addCallback(set_first_run) - else: - 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 - def _get_status_of_claim(self, claim_outpoint, name, sd_hash): d = self.get_claims_from_tx(claim_outpoint['txid']) @@ -819,9 +800,6 @@ class Wallet(object): def _get_claims_for_name(self, name): return defer.fail(NotImplementedError()) - def _check_first_run(self): - return defer.fail(NotImplementedError()) - def _send_name_claim(self, name, val, amount): return defer.fail(NotImplementedError()) @@ -865,13 +843,13 @@ class LBRYumWallet(Wallet): self._config = config self.network = None self.wallet = None - self.first_run = False + self.is_first_run = False self.printed_retrieving_headers = False self._start_check = None self._catch_up_check = None self._caught_up_counter = 0 self._lag_counter = 0 - self.blocks_behind_alert = 0 + self.blocks_behind = 0 self.catchup_progress = 0 self.max_behind = 0 @@ -881,16 +859,15 @@ class LBRYumWallet(Wallet): def setup_network(): self.config = make_config(self._config) self.network = Network(self.config) - alert.info("Loading the wallet...") + alert.info("Loading the wallet") return defer.succeed(self.network.start()) - d = setup_network() def check_started(): if self.network.is_connecting(): if not self.printed_retrieving_headers and self.network.blockchain.retrieving_headers: - alert.info("Running the wallet for the first time...this may take a moment.") + alert.info("Running the wallet for the first time. This may take a moment.") self.printed_retrieving_headers = True return False self._start_check.stop() @@ -941,7 +918,7 @@ class LBRYumWallet(Wallet): storage = lbryum.wallet.WalletStorage(path) wallet = lbryum.wallet.Wallet(storage) if not storage.file_exists: - self.first_run = True + self.is_first_run = True seed = wallet.make_seed() wallet.add_seed(seed, None) wallet.create_master_keys(None) @@ -957,7 +934,13 @@ class LBRYumWallet(Wallet): local_height = self.network.get_catchup_progress() remote_height = self.network.get_server_height() - if remote_height != 0 and remote_height - local_height <= 5: + if remote_height == 0: + return + + height_diff = remote_height - local_height + + if height_diff <= 5: + self.blocks_behind = 0 msg = "" if self._caught_up_counter != 0: msg += "All caught up. " @@ -966,27 +949,29 @@ class LBRYumWallet(Wallet): self._catch_up_check.stop() self._catch_up_check = None blockchain_caught_d.callback(True) + return - elif remote_height != 0: - past_blocks_behind = self.blocks_behind_alert - self.blocks_behind_alert = remote_height - local_height - if self.blocks_behind_alert < past_blocks_behind: - self._lag_counter = 0 - self.is_lagging = False - else: - self._lag_counter += 1 - if self._lag_counter >= 900: - self.is_lagging = True + if height_diff < self.blocks_behind: + # We're making progress in catching up + self._lag_counter = 0 + self.is_lagging = False + else: + # No progress. Might be lagging + self._lag_counter += 1 + if self._lag_counter >= 900: + self.is_lagging = True - 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 with the blockchain...showing blocks left...') - if self._caught_up_counter % 30 == 0: - alert.info('%d...', (remote_height - local_height)) + self.blocks_behind = height_diff - self._caught_up_counter += 1 + if self.blocks_behind > self.max_behind: + self.max_behind = self.blocks_behind + self.catchup_progress = int(100 * (self.blocks_behind / (5 + self.max_behind))) + if self._caught_up_counter == 0: + alert.info('Catching up with the blockchain') + if self._caught_up_counter % 30 == 0: + alert.info('Blocks left: %d', (remote_height - local_height)) + + self._caught_up_counter += 1 def log_error(err): log.warning(err.getErrorMessage()) @@ -1052,9 +1037,6 @@ class LBRYumWallet(Wallet): def get_name_claims(self): return self._run_cmd_as_defer_succeed('getnameclaims') - def _check_first_run(self): - return defer.succeed(self.first_run) - def _get_claims_for_name(self, name): return self._run_cmd_as_defer_to_thread('getclaimsforname', name) diff --git a/lbrynet/create_network.py b/lbrynet/create_network.py index 2c321d4c3..ea814ea11 100644 --- a/lbrynet/create_network.py +++ b/lbrynet/create_network.py @@ -14,7 +14,7 @@ amount = 0 def destroyNetwork(nodes): - print 'Destroying Kademlia network...' + print 'Destroying Kademlia network' i = 0 for node in nodes: i += 1 @@ -50,12 +50,12 @@ def main(): else: import socket ipAddress = socket.gethostbyname(socket.gethostname()) - print 'Network interface IP address omitted; using %s...' % ipAddress + print 'Network interface IP address omitted; using %s' % ipAddress startPort = 4000 port = startPort+1 nodes = [] - print 'Creating Kademlia network...' + print 'Creating Kademlia network' try: node = os.spawnlp( os.P_NOWAIT, 'lbrynet-launch-node', 'lbrynet-launch-node', str(startPort)) diff --git a/lbrynet/dht_scripts.py b/lbrynet/dht_scripts.py index dc8709015..6c3e503bb 100644 --- a/lbrynet/dht_scripts.py +++ b/lbrynet/dht_scripts.py @@ -16,10 +16,10 @@ def print_usage(): def join_network(udp_port, known_nodes): lbryid = generate_id() - log.info('Creating Node...') + log.info('Creating Node') node = Node(udpPort=udp_port, lbryid=lbryid) - log.info('Joining network...') + log.info('Joining network') d = node.joinNetwork(known_nodes) def log_network_size(): diff --git a/lbrynet/dhttest.py b/lbrynet/dhttest.py index c31773381..8470353b7 100644 --- a/lbrynet/dhttest.py +++ b/lbrynet/dhttest.py @@ -59,7 +59,7 @@ def storeValueCallback(*args, **kwargs): """ Callback function that is invoked when the storeValue() operation succeeds """ print 'Value has been stored in the DHT' # Now that the value has been stored, schedule that the value is read again after 2.5 seconds - print 'Scheduling retrieval in 2.5 seconds...' + print 'Scheduling retrieval in 2.5 seconds' twisted.internet.reactor.callLater(2.5, getValue) @@ -72,7 +72,7 @@ def getValue(): """ Retrieves the value of the specified key (KEY) from the DHT """ global node, KEY # Get the value for the specified key (immediately returns a Twisted deferred result) - print ('\nRetrieving value from DHT for key "%s"...' % + print ('\nRetrieving value from DHT for key "%s"' % binascii.unhexlify("f7d9dc4de674eaa2c5a022eb95bc0d33ec2e75c6")) deferredResult = node.iterativeFindValue( binascii.unhexlify("f7d9dc4de674eaa2c5a022eb95bc0d33ec2e75c6")) @@ -91,13 +91,13 @@ def getValueCallback(result): print result # Either way, schedule a "delete" operation for the key - print 'Scheduling shutdown in 2.5 seconds...' + print 'Scheduling shutdown in 2.5 seconds' twisted.internet.reactor.callLater(2.5, stop) def stop(): """ Stops the Twisted reactor, and thus the script """ - print '\nStopping Kademlia node and terminating script...' + print '\nStopping Kademlia node and terminating script' twisted.internet.reactor.stop() if __name__ == '__main__': @@ -145,7 +145,7 @@ if __name__ == '__main__': # # If you wish to have a pure Kademlia network, use the # entangled.kademlia.node.Node class instead - print 'Creating Node...' + print 'Creating Node' node = Node(udpPort=int(sys.argv[1]), lbryid=lbryid) # Schedule the node to join the Kademlia/Entangled DHT diff --git a/lbrynet/lbrynet_daemon/Daemon.py b/lbrynet/lbrynet_daemon/Daemon.py index d3004150b..0aabf5046 100644 --- a/lbrynet/lbrynet_daemon/Daemon.py +++ b/lbrynet/lbrynet_daemon/Daemon.py @@ -10,6 +10,8 @@ import base58 import requests import urllib import simplejson as json +import textwrap +from urllib2 import urlopen from decimal import Decimal from twisted.web import server @@ -48,26 +50,24 @@ from lbrynet.core.server.BlobRequestHandler import BlobRequestHandlerFactory from lbrynet.core.server.ServerProtocol import ServerProtocolFactory from lbrynet.core.Error import InsufficientFundsError - log = logging.getLogger(__name__) - INITIALIZING_CODE = 'initializing' LOADING_DB_CODE = 'loading_db' -LOADING_wallet_CODE = 'loading_wallet' +LOADING_WALLET_CODE = 'loading_wallet' LOADING_FILE_MANAGER_CODE = 'loading_file_manager' LOADING_SERVER_CODE = 'loading_server' STARTED_CODE = 'started' WAITING_FOR_FIRST_RUN_CREDITS = 'waiting_for_credits' STARTUP_STAGES = [ - (INITIALIZING_CODE, 'Initializing...'), - (LOADING_DB_CODE, 'Loading databases...'), - (LOADING_wallet_CODE, 'Catching up with the blockchain... %s'), - (LOADING_FILE_MANAGER_CODE, 'Setting up file manager'), - (LOADING_SERVER_CODE, 'Starting lbrynet'), - (STARTED_CODE, 'Started lbrynet'), - (WAITING_FOR_FIRST_RUN_CREDITS, 'Waiting for first run credits...') - ] + (INITIALIZING_CODE, 'Initializing'), + (LOADING_DB_CODE, 'Loading databases'), + (LOADING_WALLET_CODE, 'Catching up with the blockchain'), + (LOADING_FILE_MANAGER_CODE, 'Setting up file manager'), + (LOADING_SERVER_CODE, 'Starting lbrynet'), + (STARTED_CODE, 'Started lbrynet'), + (WAITING_FOR_FIRST_RUN_CREDITS, 'Waiting for first run credits'), +] # TODO: make this consistent with the stages in Downloader.py DOWNLOAD_METADATA_CODE = 'downloading_metadata' @@ -75,26 +75,24 @@ DOWNLOAD_TIMEOUT_CODE = 'timeout' DOWNLOAD_RUNNING_CODE = 'running' DOWNLOAD_STOPPED_CODE = 'stopped' STREAM_STAGES = [ - (INITIALIZING_CODE, 'Initializing...'), - (DOWNLOAD_METADATA_CODE, 'Downloading metadata'), - (DOWNLOAD_RUNNING_CODE, 'Started %s, got %s/%s blobs, stream status: %s'), - (DOWNLOAD_STOPPED_CODE, 'Paused stream'), - (DOWNLOAD_TIMEOUT_CODE, 'Stream timed out') - ] + (INITIALIZING_CODE, 'Initializing'), + (DOWNLOAD_METADATA_CODE, 'Downloading metadata'), + (DOWNLOAD_RUNNING_CODE, 'Started %s, got %s/%s blobs, stream status: %s'), + (DOWNLOAD_STOPPED_CODE, 'Paused stream'), + (DOWNLOAD_TIMEOUT_CODE, 'Stream timed out') +] -CONNECT_CODE_VERSION_CHECK = 'version_check' -CONNECT_CODE_NETWORK = 'network_connection' -CONNECT_CODE_wallet = 'wallet_catchup_lag' -CONNECTION_PROBLEM_CODES = [ - (CONNECT_CODE_VERSION_CHECK, "There was a problem checking for updates on github"), - (CONNECT_CODE_NETWORK, "Your internet connection appears to have been interrupted"), - (CONNECT_CODE_wallet, - "Synchronization with the blockchain is lagging... if this continues try restarting LBRY") - ] - -BAD_REQUEST = 400 -NOT_FOUND = 404 -OK_CODE = 200 +CONNECTION_STATUS_CONNECTED = 'connected' +CONNECTION_STATUS_VERSION_CHECK = 'version_check' +CONNECTION_STATUS_NETWORK = 'network_connection' +CONNECTION_STATUS_WALLET = 'wallet_catchup_lag' +CONNECTION_MESSAGES = { + CONNECTION_STATUS_CONNECTED: 'No connection problems detected', + CONNECTION_STATUS_VERSION_CHECK: "There was a problem checking for updates on github", + CONNECTION_STATUS_NETWORK: "Your internet connection appears to have been interrupted", + CONNECTION_STATUS_WALLET: "Catching up with the blockchain is slow. " + + "If this continues try restarting LBRY", +} PENDING_ID = "not set" SHORT_ID_LEN = 20 @@ -104,7 +102,7 @@ class Checker: """The looping calls the daemon runs""" INTERNET_CONNECTION = 'internet_connection_checker' VERSION = 'version_checker' - CONNECTION_PROBLEM = 'connection_problem_checker' + CONNECTION_STATUS = 'connection_status_checker' PENDING_CLAIM = 'pending_claim_checker' @@ -118,8 +116,6 @@ class FileID: # 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 -REMOTE_SERVER = "www.lbry.io" - class NoValidSearch(Exception): pass @@ -207,10 +203,10 @@ class Daemon(AuthJSONRPCServer): reactor.addSystemEventTrigger('before', 'shutdown', self._shutdown) self.allowed_during_startup = [ - 'is_running', 'is_first_run', - 'get_time_behind_blockchain', 'stop', - 'daemon_status', 'get_start_notice', - 'version' + 'stop', 'status', 'version', + # delete these once they are fully removed: + 'is_running', 'is_first_run', 'get_time_behind_blockchain', 'daemon_status', + 'get_start_notice', ] last_version = {'last_version': {'lbrynet': lbrynet_version, 'lbryum': lbryum_version}} conf.settings.update(last_version) @@ -246,9 +242,8 @@ class Daemon(AuthJSONRPCServer): self.startup_scripts = conf.settings.startup_scripts self.startup_status = STARTUP_STAGES[0] - self.startup_message = None self.connected_to_internet = True - self.connection_problem = None + self.connection_status_code = None self.git_lbrynet_version = None self.git_lbryum_version = None self.platform = None @@ -266,7 +261,6 @@ class Daemon(AuthJSONRPCServer): self.analytics_manager = analytics_manager self.lbryid = conf.settings.lbryid - self.daemon_conf = conf.settings.get_conf_filename() self.wallet_user = None self.wallet_password = None @@ -279,7 +273,7 @@ class Daemon(AuthJSONRPCServer): calls = { Checker.INTERNET_CONNECTION: LoopingCall(CheckInternetConnection(self)), Checker.VERSION: LoopingCall(CheckRemoteVersions(self)), - Checker.CONNECTION_PROBLEM: LoopingCall(self._check_connection_problems), + Checker.CONNECTION_STATUS: LoopingCall(self._update_connection_status), Checker.PENDING_CLAIM: LoopingCall(self._check_pending_claims), } self.looping_call_manager = LoopingCallManager(calls) @@ -323,7 +317,7 @@ class Daemon(AuthJSONRPCServer): self.looping_call_manager.start(Checker.INTERNET_CONNECTION, 3600) self.looping_call_manager.start(Checker.VERSION, 3600 * 12) - self.looping_call_manager.start(Checker.CONNECTION_PROBLEM, 1) + self.looping_call_manager.start(Checker.CONNECTION_STATUS, 1) self.exchange_rate_manager.start() if conf.settings.host_ui: @@ -382,6 +376,7 @@ class Daemon(AuthJSONRPCServer): def _check_lbrynet_connection(self): def _log_success(): log.info("lbrynet connectivity test passed") + def _log_failure(): log.info("lbrynet connectivity test failed") @@ -390,18 +385,17 @@ class Daemon(AuthJSONRPCServer): d = download_sd_blob(self.session, wonderfullife_sh, self.session.base_payment_rate_manager) d.addCallbacks(lambda _: _log_success, lambda _: _log_failure) - def _check_connection_problems(self): - if not self.git_lbrynet_version or not self.git_lbryum_version: - self.connection_problem = CONNECTION_PROBLEM_CODES[0] + def _update_connection_status(self): + self.connection_status_code = CONNECTION_STATUS_CONNECTED - elif self.startup_status[0] == 'loading_wallet': - if self.session.wallet.is_lagging: - self.connection_problem = CONNECTION_PROBLEM_CODES[2] - else: - self.connection_problem = None + if not self.git_lbrynet_version or not self.git_lbryum_version: + self.connection_status_code = CONNECTION_STATUS_VERSION_CHECK + + elif self.startup_status[0] == 'loading_wallet' and self.session.wallet.is_lagging: + self.connection_status_code = CONNECTION_STATUS_WALLET if not self.connected_to_internet: - self.connection_problem = CONNECTION_PROBLEM_CODES[1] + self.connection_status_code = CONNECTION_STATUS_NETWORK # claim_out is dictionary containing 'txid' and 'nout' def _add_to_pending_claims(self, name, claim_out): @@ -423,6 +417,7 @@ class Daemon(AuthJSONRPCServer): return _start_file(l) else: return "LBRY file was already running" + d = defer.succeed(self.pending_claims.pop(name)) d.addCallback(lambda _: self._get_lbry_file(FileID.NAME, name, return_json=False)) d.addCallback(start_stopped_file) @@ -430,7 +425,7 @@ class Daemon(AuthJSONRPCServer): def re_add_to_pending_claims(name): log.warning("Re-add %s to pending claims", name) txid, nout = self.pending_claims.pop(name) - claim_out = {'txid':txid, 'nout':nout} + claim_out = {'txid': txid, 'nout': nout} self._add_to_pending_claims(name, claim_out) def _process_lbry_file(name, lbry_file): @@ -596,6 +591,7 @@ class Daemon(AuthJSONRPCServer): 'search_timeout': float, 'cache_time': int } + def can_update_key(settings, key, setting_type): return ( isinstance(settings[key], setting_type) or @@ -638,7 +634,7 @@ class Daemon(AuthJSONRPCServer): def _setup_data_directory(self): old_revision = 1 self.startup_status = STARTUP_STAGES[1] - log.info("Loading databases...") + log.info("Loading databases") if self.created_data_dir: self._write_db_revision_file(self.current_db_revision) log.debug("Created the db revision file: %s", self.db_revision_file) @@ -663,7 +659,7 @@ class Daemon(AuthJSONRPCServer): if old_revision < self.current_db_revision: from lbrynet.db_migrator import dbmigrator - log.info("Upgrading your databases...") + log.info("Upgrading your databases") d = threads.deferToThread( dbmigrator.migrate_db, self.db_dir, old_revision, self.current_db_revision) d.addCallback(lambda _: update_version_file_and_print_success()) @@ -686,6 +682,7 @@ class Daemon(AuthJSONRPCServer): self.session, self.lbry_file_metadata_manager, self.sd_identifier, download_directory=self.download_directory) return self.lbry_file_manager.setup() + d.addCallback(lambda _: set_lbry_file_manager()) return d @@ -762,6 +759,7 @@ class Daemon(AuthJSONRPCServer): def _download_sd_blob(self, sd_hash, timeout=None): timeout = timeout if timeout is not None else conf.settings.sd_download_timeout + def cb(result): if not r.called: r.callback(result) @@ -858,6 +856,7 @@ class Daemon(AuthJSONRPCServer): os.remove(filename) else: return defer.succeed(None) + d.addCallback(lambda _: remove_if_file) return d @@ -905,7 +904,7 @@ class Daemon(AuthJSONRPCServer): if self.session.payment_rate_manager.generous: return 0.0 - return size / (10**6) * conf.settings.data_rate + return size / (10 ** 6) * conf.settings.data_rate def get_est_cost_using_known_size(self, name, size): """ @@ -974,6 +973,7 @@ class Daemon(AuthJSONRPCServer): if l.sd_hash == sd: return defer.succeed(l) return defer.succeed(None) + d = self._resolve_name(name) d.addCallback(_get_file) @@ -1001,9 +1001,9 @@ class Daemon(AuthJSONRPCServer): return d d = defer.DeferredList([ - safe_get(l.sd_hash) - for l in self.lbry_file_manager.lbry_files - ]) + safe_get(l.sd_hash) + for l in self.lbry_file_manager.lbry_files + ]) return d def _reflect(self, lbry_file): @@ -1051,95 +1051,156 @@ class Daemon(AuthJSONRPCServer): return defer.succeed(None) - def jsonrpc_is_running(self): + ############################################################################ + # # + # JSON-RPC API methods start here # + # # + ############################################################################ + + def jsonrpc_status(self, p={}): """ - Check if lbrynet daemon is running + Return daemon status Args: - None - Returns: true if daemon completed startup, otherwise false + session_status: bool + blockchain_status: bool + Returns: + daemon status """ - log.info("is_running: " + str(self.announced_startup)) + response = { + 'lbry_id': base58.b58encode(self.lbryid)[:SHORT_ID_LEN], + 'is_running': self.announced_startup, + 'is_first_run': self.session.wallet.is_first_run if self.session else None, + 'startup_status': { + 'code': self.startup_status[0], + 'message': self.startup_status[1], + }, + 'connection_status': { + 'code': self.connection_status_code, + 'message': ( + CONNECTION_MESSAGES[self.connection_status_code] + if self.connection_status_code is not None + else '' + ), + }, + 'blocks_behind': ( + self.session.wallet.blocks_behind + if self.session and self.wallet_type == LBRYUM_WALLET + else 'unknown' + ), + } - if self.announced_startup: - return self._render_response(True, OK_CODE) - else: - return self._render_response(False, OK_CODE) + d = defer.succeed(None) + + if p.get('session_status', False): + d.addCallback(lambda _: self.session.blob_manager.get_all_verified_blobs()) + + def _include_session_status(blobs): + response['session_status'] = { + 'managed_blobs': len(blobs), + 'managed_streams': len(self.lbry_file_manager.lbry_files), + } + + d.addCallback(_include_session_status) + + if p.get('blockchain_status', False) and self.session and self.session.wallet: + # calculate blocks_behind more accurately + local_height = self.session.wallet.network.get_local_height() + remote_height = self.session.wallet.network.get_server_height() + response['blocks_behind'] = remote_height - local_height + + d.addCallback(lambda _: self.session.wallet.get_best_blockhash()) + + def _include_blockchain_status(best_hash): + response['blockchain_status'] = { + 'best_blockhash': best_hash, + } + + d.addCallback(_include_blockchain_status) + + d.addCallback(lambda x: self._render_response(response)) + return d + # return self._render_response(response) + + def jsonrpc_get_best_blockhash(self): + """ + DEPRECATED. Use `status blockchain_status=True` instead + """ + d = self.jsonrpc_status({'blockchain_status': True}) + d.addCallback(lambda x: self._render_response( + x['result']['blockchain_status']['best_blockhash'])) + return d + + def jsonrpc_is_running(self): + """ + DEPRECATED. Use `status` instead + """ + d = self.jsonrpc_status({'blockchain_status': True}) + d.addCallback(lambda x: self._render_response(x['result']['is_running'])) + return d def jsonrpc_daemon_status(self): """ - Get lbrynet daemon status information - - Args: - None - Returns: - 'message': startup status message - 'code': status_code - 'progress': progress, only used in loading_wallet - 'is_lagging': flag set to indicate lag, if set message will contain relevant message + DEPRECATED. Use `status` instead """ - r = {'code': self.startup_status[0], 'message': self.startup_status[1], - 'progress': None, 'is_lagging': None, 'problem_code': None} + def _simulate_old_daemon_status(status): + message = status['result']['startup_status']['message'] + problem_code = None + progress = None - if self.connection_problem: - r['problem_code'] = self.connection_problem[0] - r['message'] = self.connection_problem[1] - r['is_lagging'] = True - elif self.startup_status[0] == LOADING_wallet_CODE: - if self.wallet_type == LBRYUM_WALLET: - if self.session.wallet.blocks_behind_alert != 0: - r['message'] %= str(self.session.wallet.blocks_behind_alert) + " blocks behind" - r['progress'] = self.session.wallet.catchup_progress - else: - r['message'] = "Catching up with the blockchain" - r['progress'] = 0 - else: - r['message'] = "Catching up with the blockchain" - r['progress'] = 0 - return self._render_response(r, OK_CODE) + if self.connection_status_code != CONNECTION_STATUS_CONNECTED: + problem_code = self.connection_status_code + message = CONNECTION_MESSAGES[self.connection_status_code] + elif status['result']['startup_status']['code'] == LOADING_WALLET_CODE: + message = "Catching up with the blockchain." + progress = 0 + if status['result']['blocks_behind'] > 0: + message += ' ' + str(status['result']['blocks_behind']) + " blocks behind." + progress = self.session.wallet.catchup_progress + + return { + 'message': message, + 'code': status['result']['startup_status']['code'], + 'progress': progress, + 'is_lagging': self.connection_status_code != CONNECTION_STATUS_CONNECTED, + 'problem_code': problem_code, + } + + d = self.jsonrpc_status() + d.addCallback(_simulate_old_daemon_status) + d.addCallback(lambda x: self._render_response(x)) # is this necessary? + return d 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 + DEPRECATED. Use `status` instead """ - - log.info("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)) - + d = self.jsonrpc_status({'blockchain_status': True}) + d.addCallback(lambda x: self._render_response(x['result']['is_first_run'])) return d - def jsonrpc_get_start_notice(self): + def jsonrpc_get_lbry_session_info(self): """ - Get special message to be displayed at startup - - Args: - None - Returns: - Startup message, such as first run notification + DEPRECATED. Use `status` instead """ - log.info("Get startup notice") + d = self.jsonrpc_status({'session_status': True}) + d.addCallback(lambda x: self._render_response({ + 'lbry_id': x['result']['lbry_id'], + 'managed_blobs': x['result']['session_status']['managed_blobs'], + 'managed_streams': x['result']['session_status']['managed_streams'], + })) + return d - 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_get_time_behind_blockchain(self): + """ + DEPRECATED. Use `status` instead + """ + d = self.jsonrpc_status({'blockchain_status': True}) # blockchain_status=True is needed + d.addCallback(lambda x: self._render_response(x['result']['blocks_behind'])) + return d def jsonrpc_version(self): """ @@ -1183,7 +1244,7 @@ class Daemon(AuthJSONRPCServer): } log.info("Get version info: " + json.dumps(msg)) - return self._render_response(msg, OK_CODE) + return self._render_response(msg) def jsonrpc_report_bug(self, p): """ @@ -1198,35 +1259,15 @@ class Daemon(AuthJSONRPCServer): bug_message = p['message'] platform_name = self._get_platform()['platform'] report_bug_to_slack(bug_message, self.lbryid, platform_name, lbrynet_version) - return self._render_response(True, OK_CODE) - - def jsonrpc_get_lbry_session_info(self): - """ - Get information about the current lbrynet session - - Args: - None - Returns: - 'lbry_id': string, - 'managed_blobs': int, number of completed blobs in the blob manager, - 'managed_streams': int, number of lbry files in the file manager - """ - - d = self.session.blob_manager.get_all_verified_blobs() - - def _prepare_message(blobs): - msg = { - 'lbry_id': base58.b58encode(self.lbryid)[:SHORT_ID_LEN], - 'managed_blobs': len(blobs), - 'managed_streams': len(self.lbry_file_manager.lbry_files), - } - return msg - - d.addCallback(_prepare_message) - d.addCallback(lambda r: self._render_response(r, OK_CODE)) - return d + return self._render_response(True) def jsonrpc_get_settings(self): + """ + DEPRECATED. Use `settings_get` instead. + """ + return self.jsonrpc_settings_get() + + def jsonrpc_settings_get(self): """ Get lbrynet daemon settings @@ -1253,10 +1294,17 @@ class Daemon(AuthJSONRPCServer): log.info("Get daemon settings") settings_dict = conf.settings.get_dict() settings_dict['lbryid'] = binascii.hexlify(settings_dict['lbryid']) - return self._render_response(settings_dict, OK_CODE) + return self._render_response(settings_dict) @AuthJSONRPCServer.auth_required def jsonrpc_set_settings(self, p): + """ + DEPRECATED. Use `settings_set` instead. + """ + return self.jsonrpc_settings_set(p) + + @AuthJSONRPCServer.auth_required + def jsonrpc_settings_set(self, p): """ Set lbrynet daemon settings @@ -1282,74 +1330,91 @@ class Daemon(AuthJSONRPCServer): d.addErrback(lambda err: log.info(err.getTraceback())) d.addCallback(lambda _: _log_settings_change()) d.addCallback( - lambda _: self._render_response(conf.settings.get_adjustable_settings_dict(), OK_CODE)) + lambda _: self._render_response(conf.settings.get_adjustable_settings_dict())) return d def jsonrpc_help(self, p=None): - """Function to retrieve docstring for API function + """ + Return a useful message for an API command Args: - optional 'function': function to retrieve documentation for - optional 'callable_during_startup': + 'command': optional, command to retrieve documentation for 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 given a command, returns documentation about that command + otherwise returns general help message """ - if not p: - return self._render_response(sorted(self.callable_methods.keys()), OK_CODE) - elif 'callable_during_start' in p.keys(): - return self._render_response(self.allowed_during_startup, OK_CODE) - elif 'function' in p.keys(): - func_path = p['function'] - function = self.callable_methods.get(func_path) - return self._render_response(function.__doc__, OK_CODE) + if p and 'command' in p: + fn = self.callable_methods.get(p['command']) + if fn is None: + return self._render_response( + "No help available for '" + p['command'] + "'. It is not a valid command." + ) + return self._render_response(textwrap.dedent(fn.__doc__)) else: - return self._render_response(self.jsonrpc_help.__doc__, OK_CODE) + return self._render_response(textwrap.dedent(self.jsonrpc_help.__doc__)) + + def jsonrpc_commands(self): + """ + Return a list of available commands + + Returns: + list + """ + return self._render_response(sorted(self.callable_methods.keys())) def jsonrpc_get_balance(self): """ - Get balance + DEPRECATED. Use `wallet_balance` instead. + """ + return self.jsonrpc_wallet_balance() + + def jsonrpc_wallet_balance(self): + """ + Return the balance of the wallet - Args: - None Returns: balance, float """ - - log.info("Get balance") - return self._render_response(float(self.session.wallet.wallet_balance), OK_CODE) + return self._render_response(float(self.session.wallet.wallet_balance)) def jsonrpc_stop(self): + """ + DEPRECATED. Use `daemon_stop` instead. + """ + return self.jsonrpc_daemon_stop() + + def jsonrpc_daemon_stop(self): """ Stop lbrynet-daemon - Args: - None Returns: shutdown message """ - def _disp_shutdown(): + def _display_shutdown_message(): log.info("Shutting down lbrynet daemon") d = self._shutdown() - d.addCallback(lambda _: _disp_shutdown()) + d.addCallback(lambda _: _display_shutdown_message()) d.addCallback(lambda _: reactor.callLater(0.0, reactor.stop)) - - return self._render_response("Shutting down", OK_CODE) + return self._render_response("Shutting down") def jsonrpc_get_lbry_files(self): """ - Get LBRY files + DEPRECATED. Use `file_list` instead. + """ + return self.jsonrpc_file_list() + + def jsonrpc_file_list(self): + """ + List files Args: None Returns: - List of lbry files: + List of files, with the following keys: 'completed': bool 'file_name': string 'key': hex string @@ -1363,12 +1428,19 @@ class Daemon(AuthJSONRPCServer): """ d = self._get_lbry_files() - d.addCallback(lambda r: self._render_response([d[1] for d in r if d[0]], OK_CODE)) + d.addCallback(lambda r: self._render_response([d[1] for d in r if d[0]])) return d def jsonrpc_get_lbry_file(self, p): - """Get lbry file + """ + DEPRECATED. Use `file_get` instead. + """ + return self.jsonrpc_file_get(p) + + def jsonrpc_file_get(self, p): + """ + Get a file Args: 'name': get file by lbry uri, @@ -1387,7 +1459,7 @@ class Daemon(AuthJSONRPCServer): 'sd_hash': string """ d = self._get_deferred_for_lbry_file(p) - d.addCallback(lambda r: self._render_response(r, OK_CODE)) + d.addCallback(lambda r: self._render_response(r)) return d def _get_deferred_for_lbry_file(self, p): @@ -1412,11 +1484,11 @@ class Daemon(AuthJSONRPCServer): name = p.get(FileID.NAME) if not name: - return self._render_response(None, BAD_REQUEST) + return self._render_response(None) d = self._resolve_name(name, force_refresh=force) d.addCallbacks( - lambda info: self._render_response(info, OK_CODE), + lambda info: self._render_response(info), # TODO: Is server.failure a module? It looks like it: # # In [1]: import twisted.web.server @@ -1427,27 +1499,18 @@ class Daemon(AuthJSONRPCServer): # If so, maybe we should return something else. errback=log.fail(lambda err: server.failure), errbackArgs=('Failed to resolve name',), - errbackKeywords={'level':'INFO'}, + errbackKeywords={'level': 'INFO'}, ) return d - @AuthJSONRPCServer.auth_required - def jsonrpc_get_my_claim(self, p): - """ - Return existing claim for a given name - - Args: - 'name': name to look up - Returns: - claim info, False if no such claim exists - """ - - name = p[FileID.NAME] - d = self.session.wallet.get_my_claim(name) - d.addCallback(lambda r: self._render_response(r, OK_CODE)) - return d - def jsonrpc_get_claim_info(self, p): + """ + DEPRECATED. Use `claim_show` instead. + """ + return self.jsonrpc_claim_show(p) + + def jsonrpc_claim_show(self, p): + """ Resolve claim info from a LBRY uri @@ -1464,7 +1527,7 @@ class Daemon(AuthJSONRPCServer): if not r: return False else: - r['amount'] = float(r['amount']) / 10**8 + r['amount'] = float(r['amount']) / 10 ** 8 return r name = p[FileID.NAME] @@ -1472,7 +1535,7 @@ class Daemon(AuthJSONRPCServer): nout = p.get('nout', None) d = self.session.wallet.get_claim_info(name, txid, nout) d.addCallback(_convert_amount_to_float) - d.addCallback(lambda r: self._render_response(r, OK_CODE)) + d.addCallback(lambda r: self._render_response(r)) return d def _process_get_parameters(self, p): @@ -1499,7 +1562,8 @@ class Daemon(AuthJSONRPCServer): @AuthJSONRPCServer.auth_required @defer.inlineCallbacks def jsonrpc_get(self, p): - """Download stream from a LBRY uri. + """ + Download stream from a LBRY uri. Args: 'name': name to download, string @@ -1532,7 +1596,7 @@ class Daemon(AuthJSONRPCServer): 'stream_hash': params.sd_hash if params.stream_info else lbry_file.sd_hash, 'path': os.path.join(lbry_file.download_directory, lbry_file.file_name) } - response = yield self._render_response(message, OK_CODE) + response = yield self._render_response(message) defer.returnValue(response) download_id = utils.random_string() @@ -1549,7 +1613,7 @@ class Daemon(AuthJSONRPCServer): except Exception as e: self.analytics_manager.send_download_errored(download_id, name, stream_info) log.exception('Failed to get %s', params.name) - response = yield self._render_response(str(e), OK_CODE) + response = yield self._render_response(str(e)) else: # TODO: should stream_hash key be changed to sd_hash? message = { @@ -1562,52 +1626,88 @@ class Daemon(AuthJSONRPCServer): lambda _: self.analytics_manager.send_download_finished( download_id, name, stream_info) ) - response = yield self._render_response(message, OK_CODE) + response = yield self._render_response(message) defer.returnValue(response) @AuthJSONRPCServer.auth_required - @defer.inlineCallbacks def jsonrpc_stop_lbry_file(self, p): """ - Stop lbry file - - Args: - 'name': stop file by lbry uri, - 'sd_hash': stop file by the hash in the name claim, - 'file_name': stop file by its name in the downloads folder, - Returns: - confirmation message + DEPRECATED. Use `file_seed status=stop` instead. """ - searchtype, value = get_lbry_file_search_value(p) - lbry_file = yield self._get_lbry_file(searchtype, value, return_json=False) - if not lbry_file: - raise Exception('Unable to find a file for {}:{}'.format(searchtype, value)) - if lbry_file.stopped: - msg = "LBRY file wasn't running" - else: - yield self.lbry_file_manager.toggle_lbry_file_running(lbry_file) - msg = "Stopped LBRY file" - defer.returnValue(self._render_response(msg, OK_CODE)) + p['status'] = 'stop' + return self.jsonrpc_file_seed(p) @AuthJSONRPCServer.auth_required def jsonrpc_start_lbry_file(self, p): """ - Stop lbry file + DEPRECATED. Use `file_seed status=start` instead. + """ + p['status'] = 'start' + return self.jsonrpc_file_seed(p) + + @AuthJSONRPCServer.auth_required + @defer.inlineCallbacks + def jsonrpc_file_seed(self, p): + """ + Start or stop seeding a file Args: - 'name': stop file by lbry uri, - 'sd_hash': stop file by the hash in the name claim, - 'file_name': stop file by its name in the downloads folder, + 'status': "start" or "stop" + 'name': start file by lbry uri, + 'sd_hash': start file by the hash in the name claim, + 'file_name': start file by its name in the downloads folder, Returns: confirmation message """ - def _start_file(f): - if f.stopped: - d = self.lbry_file_manager.toggle_lbry_file_running(f) - return defer.succeed("Started LBRY file") - else: - return "LBRY file was already running" + status = p.get('status', None) + if status is None: + raise Exception('"status" option required') + if status not in ['start', 'stop']: + raise Exception('Status must be "start" or "stop".') + + search_type, value = get_lbry_file_search_value(p) + lbry_file = yield self._get_lbry_file(search_type, value, return_json=False) + if not lbry_file: + raise Exception('Unable to find a file for {}:{}'.format(search_type, value)) + + if status == 'start' and lbry_file.stopped or status == 'stop' and not lbry_file.stopped: + yield self.lbry_file_manager.toggle_lbry_file_running(lbry_file) + msg = "Started seeding file" if status == 'start' else "Stopped seeding file" + else: + msg = ( + "File was already being seeded" if status == 'start' else "File was already stopped" + ) + defer.returnValue(self._render_response(msg)) + + @AuthJSONRPCServer.auth_required + def jsonrpc_delete_lbry_file(self, p): + """ + DEPRECATED. Use `file_delete` instead + """ + return self.jsonrpc_file_delete(p) + + @AuthJSONRPCServer.auth_required + def jsonrpc_file_delete(self, p): + """ + Delete a lbry file + + Args: + 'file_name': downloaded file name, string + Returns: + confirmation message + """ + + # TODO: is this option used? if yes, document it. if no, remove it + delete_file = p.get('delete_target_file', True) + + def _delete_file(f): + if not f: + return False + file_name = f.file_name + d = self._delete_lbry_file(f, delete_file=delete_file) + d.addCallback(lambda _: "Deleted file: " + file_name) + return d try: searchtype, value = get_lbry_file_search_value(p) @@ -1615,12 +1715,18 @@ class Daemon(AuthJSONRPCServer): d = defer.fail() else: d = self._get_lbry_file(searchtype, value, return_json=False) - d.addCallback(_start_file) + d.addCallback(_delete_file) - d.addCallback(lambda r: self._render_response(r, OK_CODE)) + d.addCallback(lambda r: self._render_response(r)) return d def jsonrpc_get_est_cost(self, p): + """ + DEPRECATED. Use `stream_cost_estimate` instead + """ + return self.jsonrpc_stream_cost_estimate(p) + + def jsonrpc_stream_cost_estimate(self, p): """ Get estimated cost for a lbry stream @@ -1635,39 +1741,7 @@ class Daemon(AuthJSONRPCServer): name = p.get(FileID.NAME, None) d = self.get_est_cost(name, size) - d.addCallback(lambda r: self._render_response(r, OK_CODE)) - return d - - @AuthJSONRPCServer.auth_required - def jsonrpc_delete_lbry_file(self, p): - """ - Delete a lbry file - - Args: - 'file_name': downloaded file name, string - Returns: - confirmation message - """ - - delete_file = p.get('delete_target_file', True) - - def _delete_file(f): - if not f: - return False - file_name = f.file_name - d = self._delete_lbry_file(f, delete_file=delete_file) - d.addCallback(lambda _: "Deleted LBRY file" + file_name) - return d - - try: - searchtype, value = get_lbry_file_search_value(p) - except NoValidSearch: - d = defer.fail() - else: - d = self._get_lbry_file(searchtype, value, return_json=False) - d.addCallback(_delete_file) - - d.addCallback(lambda r: self._render_response(r, OK_CODE)) + d.addCallback(lambda r: self._render_response(r)) return d @AuthJSONRPCServer.auth_required @@ -1682,6 +1756,8 @@ class Daemon(AuthJSONRPCServer): 'metadata': metadata dictionary optional 'fee' Returns: + 'success' : True if claim was succesful , False otherwise + 'reason' : if not succesful, give reason 'txid' : txid of resulting transaction if succesful 'nout' : nout of the resulting support claim if succesful 'fee' : fee paid for the claim transaction if succesful @@ -1744,12 +1820,19 @@ class Daemon(AuthJSONRPCServer): d.addCallback(lambda claim_out: _reflect_if_possible(sd_hash, claim_out)) d.addCallback(lambda claim_out: self._add_to_pending_claims(name, claim_out)) - d.addCallback(lambda r: self._render_response(r, OK_CODE)) + d.addCallback(lambda r: self._render_response(r)) return d @AuthJSONRPCServer.auth_required def jsonrpc_abandon_claim(self, p): + """ + DEPRECATED. Use `claim_abandon` instead + """ + return self.jsonrpc_claim_abandon(p) + + @AuthJSONRPCServer.auth_required + def jsonrpc_claim_abandon(self, p): """ Abandon a name and reclaim credits from the claim Args: @@ -1759,22 +1842,17 @@ class Daemon(AuthJSONRPCServer): txid : txid of resulting transaction if succesful fee : fee paid for the transaction if succesful """ - if 'txid' in p.keys() and 'nout' in p.keys(): - txid = p['txid'] - nout = p['nout'] - else: - # TODO: return a useful error message + if 'txid' not in p or 'nout' not in p: return server.failure def _disp(x): log.info("Abandoned name claim tx " + str(x)) - return self._render_response(x, OK_CODE) + return self._render_response(x) d = defer.Deferred() - d.addCallback(lambda _: self.session.wallet.abandon_claim(txid, nout)) + d.addCallback(lambda _: self.session.wallet.abandon_claim(p['txid'], p['nout'])) d.addCallback(_disp) - d.callback(None) - + d.callback(None) # TODO: is this line necessary??? return d @AuthJSONRPCServer.auth_required @@ -1792,6 +1870,13 @@ class Daemon(AuthJSONRPCServer): @AuthJSONRPCServer.auth_required def jsonrpc_support_claim(self, p): + """ + DEPRECATED. Use `claim_abandon` instead + """ + return self.jsonrpc_claim_new_support(p) + + @AuthJSONRPCServer.auth_required + def jsonrpc_claim_new_support(self, p): """ Support a name claim @@ -1809,13 +1894,39 @@ class Daemon(AuthJSONRPCServer): claim_id = p['claim_id'] amount = p['amount'] d = self.session.wallet.support_claim(name, claim_id, amount) - d.addCallback(lambda r: self._render_response(r, OK_CODE)) + d.addCallback(lambda r: self._render_response(r)) + return d + + # TODO: merge this into claim_list + @AuthJSONRPCServer.auth_required + def jsonrpc_get_my_claim(self, p): + """ + DEPRECATED. This method will be removed in a future release. + + Return existing claim for a given name + + Args: + 'name': name to look up + Returns: + claim info, False if no such claim exists + """ + + d = self.session.wallet.get_my_claim(p[FileID.NAME]) + d.addCallback(lambda r: self._render_response(r)) return d @AuthJSONRPCServer.auth_required def jsonrpc_get_name_claims(self): """ - Get my name claims + DEPRECATED. Use `claim_list_mine` instead + """ + return self.jsonrpc_claim_list_mine() + + # TODO: claim_list_mine should be merged into claim_list, but idk how to authenticate it -Grin + @AuthJSONRPCServer.auth_required + def jsonrpc_claim_list_mine(self): + """ + List my name claims Args: None @@ -1832,29 +1943,53 @@ class Daemon(AuthJSONRPCServer): d = self.session.wallet.get_name_claims() d.addCallback(_clean) - d.addCallback(lambda claims: self._render_response(claims, OK_CODE)) - + d.addCallback(lambda claims: self._render_response(claims)) return d def jsonrpc_get_claims_for_name(self, p): + """ + DEPRECATED. Use `claim_list` instead. + """ + return self.jsonrpc_claim_list(p) + + def jsonrpc_get_claims_for_tx(self, p): + """ + DEPRECATED. Use `claim_list` instead. + """ + return self.jsonrpc_claim_list(p) + + def jsonrpc_claim_list(self, p): """ Get claims for a name Args: - 'name': name + name: file name + txid: transaction id of a name claim transaction Returns list of name claims """ - name = p[FileID.NAME] - d = self.session.wallet.get_claims_for_name(name) - d.addCallback(lambda r: self._render_response(r, OK_CODE)) + if FileID.NAME in p: + d = self.session.wallet.get_claims_for_name(p[FileID.NAME]) + elif 'txid' in p: + d = self.session.wallet.get_claims_from_tx(p['txid']) + else: + return server.failure + + d.addCallback(lambda r: self._render_response(r)) return d @AuthJSONRPCServer.auth_required def jsonrpc_get_transaction_history(self): """ - Get transaction history + DEPRECATED. Use `transaction_list` instead + """ + return self.jsonrpc_transaction_list() + + @AuthJSONRPCServer.auth_required + def jsonrpc_transaction_list(self): + """ + List transactions Args: None @@ -1863,11 +1998,38 @@ class Daemon(AuthJSONRPCServer): """ d = self.session.wallet.get_history() - d.addCallback(lambda r: self._render_response(r, OK_CODE)) + d.addCallback(lambda r: self._render_response(r)) + return d + + def jsonrpc_get_transaction(self, p): + """ + DEPRECATED. Use `transaction_show` instead + """ + return self.jsonrpc_transaction_show(p) + + def jsonrpc_transaction_show(self, p): + """ + Get a decoded transaction from a txid + + Args: + txid: txid hex string + Returns: + JSON formatted transaction + """ + + d = self.session.wallet.get_transaction(p['txid']) + d.addCallback(lambda r: self._render_response(r)) return d @AuthJSONRPCServer.auth_required def jsonrpc_address_is_mine(self, p): + """ + DEPRECATED. Use `wallet_is_address_mine` instead + """ + return self.jsonrpc_wallet_is_address_mine(p) + + @AuthJSONRPCServer.auth_required + def jsonrpc_wallet_is_address_mine(self, p): """ Checks if an address is associated with the current wallet. @@ -1877,15 +2039,19 @@ class Daemon(AuthJSONRPCServer): is_mine: bool """ - address = p['address'] - - d = self.session.wallet.address_is_mine(address) - d.addCallback(lambda is_mine: self._render_response(is_mine, OK_CODE)) - + d = self.session.wallet.address_is_mine(p['address']) + d.addCallback(lambda is_mine: self._render_response(is_mine)) return d @AuthJSONRPCServer.auth_required def jsonrpc_get_public_key_from_wallet(self, p): + """ + DEPRECATED. Use `wallet_is_address_mine` instead + """ + return self.jsonrpc_wallet_public_key(p) + + @AuthJSONRPCServer.auth_required + def jsonrpc_wallet_public_key(self, p): """ Get public key from wallet address @@ -1895,35 +2061,18 @@ class Daemon(AuthJSONRPCServer): public key """ - wallet = p['wallet'] - d = self.session.wallet.get_pub_keys(wallet) - d.addCallback(lambda r: self._render_response(r, OK_CODE)) - - def jsonrpc_get_time_behind_blockchain(self): - """ - Get number of blocks behind the blockchain - - Args: - None - Returns: - number of blocks behind blockchain, int - """ - - 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 + d = self.session.wallet.get_pub_keys(p['wallet']) + d.addCallback(lambda r: self._render_response(r)) @AuthJSONRPCServer.auth_required def jsonrpc_get_new_address(self): + """ + DEPRECATED. Use `wallet_new_address` instead + """ + return self.jsonrpc_wallet_new_address() + + @AuthJSONRPCServer.auth_required + def jsonrpc_wallet_new_address(self): """ Generate a new wallet address @@ -1939,7 +2088,7 @@ class Daemon(AuthJSONRPCServer): d = self.session.wallet.get_new_address() d.addCallback(_disp) - d.addCallback(lambda address: self._render_response(address, OK_CODE)) + d.addCallback(lambda address: self._render_response(address)) return d @AuthJSONRPCServer.auth_required @@ -1954,7 +2103,7 @@ class Daemon(AuthJSONRPCServer): True if payment successfully scheduled """ - if 'amount' in p.keys() and 'address' in p.keys(): + if 'amount' in p and 'address' in p: amount = p['amount'] address = p['address'] else: @@ -1965,24 +2114,16 @@ class Daemon(AuthJSONRPCServer): if reserved_points is None: return defer.fail(InsufficientFundsError()) d = self.session.wallet.send_points_to_address(reserved_points, amount) - d.addCallback(lambda _: self._render_response(True, OK_CODE)) - return d - - def jsonrpc_get_best_blockhash(self): - """ - Get hash of most recent block - - Args: - None - Returns: - Hash of most recent block - """ - - d = self.session.wallet.get_best_blockhash() - d.addCallback(lambda r: self._render_response(r, OK_CODE)) + d.addCallback(lambda _: self._render_response(True)) return d def jsonrpc_get_block(self, p): + """ + DEPRECATED. Use `block_show` instead + """ + return self.jsonrpc_block_show(p) + + def jsonrpc_block_show(self, p): """ Get contents of a block @@ -1992,41 +2133,26 @@ class Daemon(AuthJSONRPCServer): requested block """ - if 'blockhash' in p.keys(): - blockhash = p['blockhash'] - d = self.session.wallet.get_block(blockhash) - elif 'height' in p.keys(): - height = p['height'] - d = self.session.wallet.get_block_info(height) + if 'blockhash' in p: + d = self.session.wallet.get_block(p['blockhash']) + elif 'height' in p: + d = self.session.wallet.get_block_info(p['height']) d.addCallback(lambda blockhash: self.session.wallet.get_block(blockhash)) else: # TODO: return a useful error message return server.failure - d.addCallback(lambda r: self._render_response(r, OK_CODE)) - return d - - def jsonrpc_get_claims_for_tx(self, p): - """ - Get claims for tx - - Args: - txid: txid of a name claim transaction - Returns: - any claims contained in the requested tx - """ - - if 'txid' in p.keys(): - txid = p['txid'] - else: - # TODO: return a useful error message - return server.failure - - d = self.session.wallet.get_claims_from_tx(txid) - d.addCallback(lambda r: self._render_response(r, OK_CODE)) + d.addCallback(lambda r: self._render_response(r)) return d @AuthJSONRPCServer.auth_required def jsonrpc_download_descriptor(self, p): + """ + DEPRECATED. Use `blob_get` instead + """ + return self.jsonrpc_blob_get(p) + + @AuthJSONRPCServer.auth_required + def jsonrpc_blob_get(self, p): """ Download and return a sd blob @@ -2039,8 +2165,8 @@ class Daemon(AuthJSONRPCServer): timeout = p.get('timeout', conf.settings.sd_download_timeout) d = self._download_sd_blob(sd_hash, timeout) d.addCallbacks( - lambda r: self._render_response(r, OK_CODE), - lambda _: self._render_response(False, OK_CODE)) + lambda r: self._render_response(r), + lambda _: self._render_response(False)) return d def jsonrpc_get_nametrie(self): @@ -2055,45 +2181,13 @@ class Daemon(AuthJSONRPCServer): d = self.session.wallet.get_nametrie() d.addCallback(lambda r: [i for i in r if 'txid' in i.keys()]) - d.addCallback(lambda r: self._render_response(r, OK_CODE)) - return d - - @AuthJSONRPCServer.auth_required - def jsonrpc_set_miner(self, p): - """ - Start of stop the miner, function only available when lbrycrd is set as the wallet - - Args: - run: True/False - Returns: - miner status, True/False - """ - - stat = p['run'] - if stat: - d = self.session.wallet.start_miner() - else: - d = self.session.wallet.stop_miner() - d.addCallback(lambda _: self.session.wallet.get_miner_status()) - d.addCallback(lambda r: self._render_response(r, OK_CODE)) - return d - - def jsonrpc_get_miner_status(self): - """ - Get status of miner - - Args: - None - Returns: - True/False - """ - - d = self.session.wallet.get_miner_status() - d.addCallback(lambda r: self._render_response(r, OK_CODE)) + d.addCallback(lambda r: self._render_response(r)) return d def jsonrpc_log(self, p): """ + DEPRECATED. This method will be removed in a future release. + Log message Args: @@ -2102,12 +2196,14 @@ class Daemon(AuthJSONRPCServer): True """ - message = p['message'] - log.info("API client log request: %s" % message) - return self._render_response(True, OK_CODE) + log.info("API client log request: %s" % p['message']) + return self._render_response(True) def jsonrpc_upload_log(self, p=None): - """Upload log + """ + DEPRECATED. This method will be removed in a future release. + + Upload log Args, optional: 'name_prefix': prefix to indicate what is requesting the log upload @@ -2119,32 +2215,29 @@ class Daemon(AuthJSONRPCServer): """ + exclude_previous = True + force = False + log_type = None + if p: - if 'name_prefix' in p.keys(): + if 'name_prefix' in p: log_type = p['name_prefix'] + '_api' - elif 'log_type' in p.keys(): + elif 'log_type' in p: log_type = p['log_type'] + '_api' - else: - log_type = None - if 'exclude_previous' in p.keys(): + if 'exclude_previous' in p: exclude_previous = p['exclude_previous'] - else: - exclude_previous = True - if 'message' in p.keys(): + if 'message' in p: log.info("Upload log message: " + str(p['message'])) - if 'force' in p.keys(): + if 'force' in p: force = p['force'] - else: - force = False else: log_type = "api" - exclude_previous = True d = self._upload_log(log_type=log_type, exclude_previous=exclude_previous, force=force) - d.addCallback(lambda _: self._render_response(True, OK_CODE)) + d.addCallback(lambda _: self._render_response(True)) return d @AuthJSONRPCServer.auth_required @@ -2169,7 +2262,7 @@ class Daemon(AuthJSONRPCServer): d = self.lbry_ui_manager.setup(branch=p['branch'], check_requirements=check_require) else: d = self.lbry_ui_manager.setup(check_requirements=check_require) - d.addCallback(lambda r: self._render_response(r, OK_CODE)) + d.addCallback(lambda r: self._render_response(r)) return d @@ -2190,10 +2283,16 @@ class Daemon(AuthJSONRPCServer): # No easy way to reveal specific files on Linux, so just open the containing directory d = threads.deferToThread(subprocess.Popen, ['xdg-open', os.path.dirname(path)]) - d.addCallback(lambda _: self._render_response(True, OK_CODE)) + d.addCallback(lambda _: self._render_response(True)) return d def jsonrpc_get_peers_for_hash(self, p): + """ + DEPRECATED. Use `peer_list` instead + """ + return self.jsonrpc_peer_list(p) + + def jsonrpc_peer_list(self, p): """ Get peers for blob hash @@ -2207,12 +2306,18 @@ class Daemon(AuthJSONRPCServer): d = self.session.peer_finder.find_peers_for_blob(blob_hash) d.addCallback(lambda r: [[c.host, c.port, c.is_available()] for c in r]) - d.addCallback(lambda r: self._render_response(r, OK_CODE)) + d.addCallback(lambda r: self._render_response(r)) return d def jsonrpc_announce_all_blobs_to_dht(self): """ - Announce all blobs to the dht + DEPRECATED. Use `blob_announce_all` instead. + """ + return self.jsonrpc_blob_announce_all() + + def jsonrpc_blob_announce_all(self): + """ + Announce all blobs to the DHT Args: None @@ -2221,7 +2326,7 @@ class Daemon(AuthJSONRPCServer): """ d = self.session.blob_manager.immediate_announce_all_blobs() - d.addCallback(lambda _: self._render_response("Announced", OK_CODE)) + d.addCallback(lambda _: self._render_response("Announced")) return d def jsonrpc_reflect(self, p): @@ -2238,11 +2343,17 @@ class Daemon(AuthJSONRPCServer): d = self._get_lbry_file(FileID.SD_HASH, sd_hash, return_json=False) d.addCallback(self._reflect) d.addCallbacks( - lambda _: self._render_response(True, OK_CODE), - lambda err: self._render_response(err.getTraceback(), OK_CODE)) + lambda _: self._render_response(True), + lambda err: self._render_response(err.getTraceback())) return d def jsonrpc_get_blob_hashes(self): + """ + DEPRECATED. Use `blob_list` instead + """ + return self.jsonrpc_blob_list() + + def jsonrpc_blob_list(self): """ Returns all blob hashes @@ -2253,10 +2364,16 @@ class Daemon(AuthJSONRPCServer): """ d = self.session.blob_manager.get_all_verified_blobs() - d.addCallback(lambda r: self._render_response(r, OK_CODE)) + d.addCallback(lambda r: self._render_response(r)) return d def jsonrpc_reflect_all_blobs(self): + """ + DEPRECATED. Use `blob_reflect_all` instead + """ + return self.jsonrpc_blob_reflect_all() + + def jsonrpc_blob_reflect_all(self): """ Reflects all saved blobs @@ -2268,7 +2385,7 @@ class Daemon(AuthJSONRPCServer): d = self.session.blob_manager.get_all_verified_blobs() d.addCallback(self._reflect_blobs) - d.addCallback(lambda r: self._render_response(r, OK_CODE)) + d.addCallback(lambda r: self._render_response(r)) return d def jsonrpc_get_mean_availability(self): @@ -2281,7 +2398,7 @@ class Daemon(AuthJSONRPCServer): Mean peers for a blob """ - d = self._render_response(self.session.blob_tracker.last_mean_availability, OK_CODE) + d = self._render_response(self.session.blob_tracker.last_mean_availability) return d def jsonrpc_get_availability(self, p): @@ -2305,9 +2422,7 @@ class Daemon(AuthJSONRPCServer): else: return 0.0 - name = p[FileID.NAME] - - d = self._resolve_name(name, force_refresh=True) + d = self._resolve_name(p[FileID.NAME], force_refresh=True) d.addCallback(get_sd_hash) d.addCallback(self._download_sd_blob) d.addCallbacks( @@ -2315,15 +2430,38 @@ class Daemon(AuthJSONRPCServer): lambda _: []) d.addCallback(self.session.blob_tracker.get_availability_for_blobs) d.addCallback(_get_mean) - d.addCallback(lambda result: self._render_response(result, OK_CODE)) + d.addCallback(lambda result: self._render_response(result)) return d @AuthJSONRPCServer.auth_required def jsonrpc_test_api_authentication(self): if self._use_authentication: - return self._render_response(True, OK_CODE) - return self._render_response("Not using authentication", OK_CODE) + return self._render_response(True) + return self._render_response("Not using authentication") + + def jsonrpc_get_start_notice(self): + """ + DEPRECATED. + + Get special message to be displayed at startup + Args: + None + Returns: + Startup message, such as first run notification + """ + + def _get_startup_message(resp): + status = resp['result'] + if status['is_first_run'] and self.session.wallet.wallet_balance: + return self._render_response(None) + else: + return self._render_response(status['startup_status']['message']) + + d = self.jsonrpc_status() + d.addCallback(_get_startup_message) + return d + def get_lbryum_version_from_github(): @@ -2636,7 +2774,7 @@ class _GetFileHelper(object): def loggly_time_string(dt): formatted_dt = dt.strftime("%Y-%m-%dT%H:%M:%S") - milliseconds = str(round(dt.microsecond * (10.0**-5), 3)) + milliseconds = str(round(dt.microsecond * (10.0 ** -5), 3)) return urllib.quote_plus(formatted_dt + milliseconds + "Z") diff --git a/lbrynet/lbrynet_daemon/DaemonCLI.py b/lbrynet/lbrynet_daemon/DaemonCLI.py index 2211f13de..e3339ca32 100644 --- a/lbrynet/lbrynet_daemon/DaemonCLI.py +++ b/lbrynet/lbrynet_daemon/DaemonCLI.py @@ -1,18 +1,93 @@ -import sys import argparse import json +import os +import sys + from lbrynet import conf -from lbrynet.lbrynet_daemon.auth.client import LBRYAPIClient +from lbrynet.lbrynet_daemon.auth.client import JSONRPCException, LBRYAPIClient +from lbrynet.lbrynet_daemon.Daemon import LOADING_WALLET_CODE from jsonrpc.common import RPCError +from urllib2 import URLError, HTTPError +from httplib import UNAUTHORIZED +def main(): + parser = argparse.ArgumentParser(add_help=False) + _, arguments = parser.parse_known_args() -help_msg = "Usage: lbrynet-cli method kwargs\n" \ - + "Examples: " \ - + "lbrynet-cli resolve_name name=what\n" \ - + "lbrynet-cli get_balance\n" \ - + "lbrynet-cli help function=resolve_name\n" \ - + "\n******lbrynet-cli functions******\n" + conf.initialize_settings() + conf.update_settings_from_file() + api = LBRYAPIClient.get_client() + + try: + status = api.status() + except URLError as err: + if isinstance(err, HTTPError) and err.code == UNAUTHORIZED: + print_error("Daemon requires authentication, but none was provided.", + suggest_help=False) + else: + print_error("Could not connect to daemon. Are you sure it's running?", + suggest_help=False) + return 1 + + if status['startup_status']['code'] != "started": + print "Daemon is in the process of starting. Please try again in a bit." + message = status['startup_status']['message'] + if message: + if ( + status['startup_status']['code'] == LOADING_WALLET_CODE + and status['blocks_behind'] > 0 + ): + message += '. Blocks left: ' + str(status['blocks_behind']) + print " Status: " + message + return 1 + + if len(arguments) < 1: + print_help(api) + return 1 + + method = arguments[0] + try: + params = parse_params(arguments[1:]) + except InvalidParameters as e: + print_error(e.message) + return 1 + + # TODO: check if port is bound. Error if its not + + if method in ['--help', '-h', 'help']: + if len(params) == 0: + print_help(api) + elif 'command' not in params: + print_error( + 'To get help on a specific command, use `{} help command=COMMAND_NAME`'.format( + os.path.basename(sys.argv[0])) + ) + else: + print api.call('help', params).strip() + + elif method not in api.commands(): + print_error("'" + method + "' is not a valid command.") + + else: + try: + result = api.call(method, params) + if isinstance(result, basestring): + # printing the undumped string is prettier + print result + else: + print json.dumps(result, sort_keys=True, indent=2, separators=(',', ': ')) + except (RPCError, KeyError, JSONRPCException) as err: + # TODO: The api should return proper error codes + # and messages so that they can be passed along to the user + # instead of this generic message. + # https://app.asana.com/0/158602294500137/200173944358192 + print "Something went wrong, here's the usage for %s:" % method + print api.call('help', {'command': method}) + if hasattr(err, 'msg'): + print "Here's the traceback for the error you encountered:" + print err.msg + return 1 def guess_type(x): @@ -28,77 +103,89 @@ def guess_type(x): return x +def parse_params(params): + if len(params) > 1: + return get_params_from_kwargs(params) + elif len(params) == 1: + try: + return json.loads(params[0]) + except ValueError: + return get_params_from_kwargs(params) + else: + return {} + + +class InvalidParameters(Exception): + pass + + def get_params_from_kwargs(params): params_for_return = {} for i in params: - eq_pos = i.index('=') - k, v = i[:eq_pos], i[eq_pos+1:] + try: + eq_pos = i.index('=') + except ValueError: + raise InvalidParameters('{} is not in = format'.format(i)) + k, v = i[:eq_pos], i[eq_pos + 1:] params_for_return[k] = guess_type(v) return params_for_return -def main(): - conf.initialize_settings() - api = LBRYAPIClient.config() +def print_help_suggestion(): + print "See `{} help` for more information.".format(os.path.basename(sys.argv[0])) - try: - status = api.daemon_status() - assert status.get('code', False) == "started" - except Exception: + +def print_error(message, suggest_help=True): + print "ERROR: " + message + if suggest_help: + print_help_suggestion() + + +def print_help(api): + print "\n".join([ + "NAME", + " lbrynet-cli - LBRY command line client.", + "", + "USAGE", + " lbrynet-cli []", + "", + "EXAMPLES", + " lbrynet-cli commands # list available commands", + " lbrynet-cli status # get daemon status", + " lbrynet-cli resolve_name name=what # resolve a name", + " lbrynet-cli help command=resolve_name # get help for a command", + "", + "COMMANDS", + wrap_list_to_term_width(api.commands(), prefix=' ') + ]) + + +def wrap_list_to_term_width(l, width=None, separator=', ', prefix=''): + if width is None: try: - conf.settings.update({'use_auth_http': not conf.settings.use_auth_http}) - api = LBRYAPIClient.config() - status = api.daemon_status() - assert status.get('code', False) == "started" - except Exception: - print "lbrynet-daemon isn't running" - sys.exit(1) + _, width = os.popen('stty size', 'r').read().split() + width = int(width) + except: + pass + if not width: + width = 80 - parser = argparse.ArgumentParser() - parser.add_argument('method', nargs=1) - parser.add_argument('params', nargs=argparse.REMAINDER, default=None) - args = parser.parse_args() + lines = [] + curr_line = '' + for item in l: + new_line = curr_line + item + separator + if len(new_line) + len(prefix) > width: + lines.append(curr_line) + curr_line = item + separator + else: + curr_line = new_line + lines.append(curr_line) - meth = args.method[0] - params = {} - - if len(args.params) > 1: - params = get_params_from_kwargs(args.params) - elif len(args.params) == 1: - try: - params = json.loads(args.params[0]) - except ValueError: - params = get_params_from_kwargs(args.params) - - msg = help_msg - for f in api.help(): - msg += f + "\n" - - if meth in ['--help', '-h', 'help']: - print msg - sys.exit(1) - - if meth in api.help(): - try: - if params: - result = LBRYAPIClient.config(service=meth, params=params) - else: - result = LBRYAPIClient.config(service=meth, params=params) - print json.dumps(result, sort_keys=True) - except RPCError as err: - # TODO: The api should return proper error codes - # and messages so that they can be passed along to the user - # instead of this generic message. - # https://app.asana.com/0/158602294500137/200173944358192 - print "Something went wrong, here's the usage for %s:" % meth - print api.help({'function': meth}) - print "Here's the traceback for the error you encountered:" - print err.msg - - else: - print "Unknown function" - print msg + ret = prefix + ("\n" + prefix).join(lines) + if ret.endswith(separator): + ret = ret[:-len(separator)] + return ret if __name__ == '__main__': - main() + sys.exit(main()) diff --git a/lbrynet/lbrynet_daemon/DaemonControl.py b/lbrynet/lbrynet_daemon/DaemonControl.py index 541880491..8b40d5848 100644 --- a/lbrynet/lbrynet_daemon/DaemonControl.py +++ b/lbrynet/lbrynet_daemon/DaemonControl.py @@ -9,7 +9,7 @@ from jsonrpc.proxy import JSONRPCProxy from lbrynet import analytics from lbrynet import conf from lbrynet.core import utils -from lbrynet.lbrynet_daemon.auth import client +from lbrynet.lbrynet_daemon.auth.client import LBRYAPIClient from lbrynet.lbrynet_daemon.DaemonServer import DaemonServer @@ -24,7 +24,7 @@ def stop(): conf.initialize_settings() log_support.configure_console() try: - client.LBRYAPIClient.config().stop() + LBRYAPIClient.get_client().call('stop') except Exception: log.exception('Failed to stop deamon') else: diff --git a/lbrynet/lbrynet_daemon/Downloader.py b/lbrynet/lbrynet_daemon/Downloader.py index 88eb10163..f17fb34e3 100644 --- a/lbrynet/lbrynet_daemon/Downloader.py +++ b/lbrynet/lbrynet_daemon/Downloader.py @@ -18,7 +18,7 @@ DOWNLOAD_RUNNING_CODE = 'running' # TODO: is this ever used? DOWNLOAD_STOPPED_CODE = 'stopped' STREAM_STAGES = [ - (INITIALIZING_CODE, 'Initializing...'), + (INITIALIZING_CODE, 'Initializing'), (DOWNLOAD_METADATA_CODE, 'Downloading metadata'), (DOWNLOAD_RUNNING_CODE, 'Started stream'), (DOWNLOAD_STOPPED_CODE, 'Paused stream'), diff --git a/lbrynet/lbrynet_daemon/auth/client.py b/lbrynet/lbrynet_daemon/auth/client.py index 35cf33844..2a37fb729 100644 --- a/lbrynet/lbrynet_daemon/auth/client.py +++ b/lbrynet/lbrynet_daemon/auth/client.py @@ -23,8 +23,7 @@ class JSONRPCException(Exception): class AuthAPIClient(object): - def __init__(self, key, timeout, connection, count, service, cookies, auth, url, login_url): - self.__service_name = service + def __init__(self, key, timeout, connection, count, cookies, auth, url, login_url): self.__api_key = key self.__service_url = login_url self.__id_count = count @@ -35,30 +34,25 @@ class AuthAPIClient(object): def __getattr__(self, name): if name.startswith('__') and name.endswith('__'): - # Python internal stuff - raise AttributeError - if self.__service_name is not None: - name = "%s.%s" % (self.__service_name, name) - return AuthAPIClient(key=self.__api_key, - timeout=HTTP_TIMEOUT, - connection=self.__conn, - count=self.__id_count, - service=name, - cookies=self.__cookies, - auth=self.__auth_header, - url=self.__url, - login_url=self.__service_url) + raise AttributeError # Python internal stuff - def __call__(self, *args): + def f(*args): + return self.call(name, args[0] if args else {}) + + return f + + def call(self, method, params={}): self.__id_count += 1 - pre_auth_postdata = {'version': '1.1', - 'method': self.__service_name, - 'params': args, - 'id': self.__id_count} - to_auth = get_auth_message(pre_auth_postdata) + pre_auth_post_data = { + 'version': '1.1', + 'method': method, + 'params': [params], + 'id': self.__id_count + } + to_auth = get_auth_message(pre_auth_post_data) token = self.__api_key.get_hmac(to_auth) - pre_auth_postdata.update({'hmac': token}) - postdata = json.dumps(pre_auth_postdata) + pre_auth_post_data.update({'hmac': token}) + post_data = json.dumps(pre_auth_post_data) service_url = self.__service_url auth_header = self.__auth_header cookies = self.__cookies @@ -66,11 +60,13 @@ class AuthAPIClient(object): req = requests.Request(method='POST', url=service_url, - data=postdata, - headers={'Host': host, - 'User-Agent': USER_AGENT, - 'Authorization': auth_header, - 'Content-type': 'application/json'}, + data=post_data, + headers={ + 'Host': host, + 'User-Agent': USER_AGENT, + 'Authorization': auth_header, + 'Content-type': 'application/json' + }, cookies=cookies) r = req.prepare() http_response = self.__conn.send(r) @@ -101,7 +97,6 @@ class AuthAPIClient(object): def config(cls, key_name=None, key=None, pw_path=None, timeout=HTTP_TIMEOUT, connection=None, count=0, - service=None, cookies=None, auth=None, url=None, login_url=None): @@ -157,21 +152,11 @@ class AuthAPIClient(object): assert cookies.get(LBRY_SECRET, False), "Missing cookie" secret = cookies.get(LBRY_SECRET) api_key = APIKey(secret, api_key_name) - return cls(api_key, timeout, conn, id_count, service, cookies, - auth_header, url, service_url) + return cls(api_key, timeout, conn, id_count, cookies, auth_header, url, service_url) class LBRYAPIClient(object): @staticmethod - def config(service=None, params=None): - if conf.settings.use_auth_http: - if service is None: - return AuthAPIClient.config() - log.error("Try auth") - if params is not None: - return AuthAPIClient.config(service=service)(params) - return AuthAPIClient.config(service=service)() - url = conf.settings.API_CONNECTION_STRING - if service is None: - return JSONRPCProxy.from_url(url) - return JSONRPCProxy.from_url(url).call(service, params) + def get_client(): + return AuthAPIClient.config() if conf.settings.use_auth_http else \ + JSONRPCProxy.from_url(conf.settings.API_CONNECTION_STRING) diff --git a/lbrynet/lbrynet_daemon/auth/server.py b/lbrynet/lbrynet_daemon/auth/server.py index 5e76a6413..d96a25a45 100644 --- a/lbrynet/lbrynet_daemon/auth/server.py +++ b/lbrynet/lbrynet_daemon/auth/server.py @@ -8,13 +8,15 @@ from twisted.internet import defer from twisted.python.failure import Failure from txjsonrpc import jsonrpclib -from lbrynet.core.Error import InvalidAuthenticationToken, InvalidHeaderError, SubhandlerError +from lbrynet.core.Error import InvalidAuthenticationToken, InvalidHeaderError from lbrynet import conf from lbrynet.lbrynet_daemon.auth.util import APIKey, get_auth_message from lbrynet.lbrynet_daemon.auth.client import LBRY_SECRET log = logging.getLogger(__name__) +EMPTY_PARAMS = [{}] + def default_decimal(obj): if isinstance(obj, Decimal): @@ -34,7 +36,6 @@ class JSONRPCException(Exception): class AuthorizedBase(object): def __init__(self): self.authorized_functions = [] - self.subhandlers = [] self.callable_methods = {} for methodname in dir(self): @@ -43,21 +44,12 @@ class AuthorizedBase(object): self.callable_methods.update({methodname.split("jsonrpc_")[1]: method}) if hasattr(method, '_auth_required'): self.authorized_functions.append(methodname.split("jsonrpc_")[1]) - elif not methodname.startswith("__"): - method = getattr(self, methodname) - if hasattr(method, '_subhandler'): - self.subhandlers.append(method) @staticmethod def auth_required(f): f._auth_required = True return f - @staticmethod - def subhandler(f): - f._subhandler = True - return f - class AuthJSONRPCServer(AuthorizedBase): """Authorized JSONRPC server used as the base class for the LBRY API @@ -69,12 +61,6 @@ class AuthJSONRPCServer(AuthorizedBase): @AuthJSONRPCServer.auth_required: this requires the client include a valid hmac authentication token in their request - @AuthJSONRPCServer.subhandler: include the tagged method in - the processing of requests, to allow inheriting classes to - modify request handling. Tagged methods will be passed the - request object, and return True when finished to indicate - success - Attributes: allowed_during_startup (list): list of api methods that are callable before the server has finished startup @@ -84,8 +70,6 @@ class AuthJSONRPCServer(AuthorizedBase): authorized_functions (list): list of api methods that require authentication - subhandlers (list): list of subhandlers - callable_methods (dict): dictionary of api_callable_name: method values """ @@ -131,6 +115,7 @@ class AuthJSONRPCServer(AuthorizedBase): if self._initialize_session(session_id): def expire_session(): self._unregister_user_session(session_id) + session.startCheckingExpiration() session.notifyOnExpire(expire_session) message = "OK" @@ -138,7 +123,8 @@ class AuthJSONRPCServer(AuthorizedBase): self._set_headers(request, message, True) self._render_message(request, message) return server.NOT_DONE_YET - session.touch() + else: + session.touch() request.content.seek(0, 0) content = request.content.read() @@ -155,12 +141,6 @@ class AuthJSONRPCServer(AuthorizedBase): token = parsed.pop('hmac', None) version = self._get_jsonrpc_version(parsed.get('jsonrpc'), id_) - try: - self._run_subhandlers(request) - except SubhandlerError as err: - self._render_error(err, request, id_, version) - return server.NOT_DONE_YET - reply_with_next_secret = False if self._use_authentication: if function_name in self.authorized_functions: @@ -182,7 +162,7 @@ class AuthJSONRPCServer(AuthorizedBase): self._render_error(err, request, id_, version) return server.NOT_DONE_YET - if args == [{}]: + if args == EMPTY_PARAMS: d = defer.maybeDeferred(function) else: d = defer.maybeDeferred(function, *args) @@ -281,6 +261,7 @@ class AuthJSONRPCServer(AuthorizedBase): return False def _verify_token(self, session_id, message, token): + assert token is not None, InvalidAuthenticationToken to_auth = get_auth_message(message) api_key = self.sessions.get(session_id) assert api_key.compare_hmac(to_auth, token), InvalidAuthenticationToken @@ -297,18 +278,13 @@ class AuthJSONRPCServer(AuthorizedBase): version_for_return = jsonrpclib.VERSION_PRE1 return version_for_return - def _run_subhandlers(self, request): - for handler in self.subhandlers: - if not handler(request): - raise SubhandlerError("Subhandler error processing request: %s", request) - def _callback_render(self, result, request, id_, version, auth_required=False): - result_for_return = result if not isinstance(result, dict) else result['result'] + result_for_return = result if version == jsonrpclib.VERSION_PRE1: if not isinstance(result, jsonrpclib.Fault): result_for_return = (result_for_return,) - # Convert the result (python) to JSON-RPC + try: encoded_message = jsonrpclib.dumps( result_for_return, id=id_, version=version, default=default_decimal) @@ -318,5 +294,5 @@ class AuthJSONRPCServer(AuthorizedBase): log.exception("Failed to render API response: %s", result) self._render_error(err, request, id_, version) - def _render_response(self, result, code): - return defer.succeed({'result': result, 'code': code}) + def _render_response(self, result): + return defer.succeed(result) diff --git a/packaging/osx/lbry-osx-app/lbry_uri_handler/LBRYURIHandler.py b/packaging/osx/lbry-osx-app/lbry_uri_handler/LBRYURIHandler.py index f6990cfea..f90bce9b3 100644 --- a/packaging/osx/lbry-osx-app/lbry_uri_handler/LBRYURIHandler.py +++ b/packaging/osx/lbry-osx-app/lbry_uri_handler/LBRYURIHandler.py @@ -34,7 +34,7 @@ class LBRYURIHandler(object): except: cmd = r'DIR = "$( cd "$(dirname "${BASH_SOURCE[0]}" )" && pwd )"' \ r'if [-z "$(pgrep lbrynet-daemon)"]; then' \ - r'echo "running lbrynet-daemon..."' \ + r'echo "running lbrynet-daemon"' \ r'$DIR / lbrynet - daemon &' \ r'sleep 3 # let the daemon load before connecting' \ r'fi' diff --git a/packaging/ubuntu/lbry b/packaging/ubuntu/lbry index f84c33fb6..4bd051dcd 100755 --- a/packaging/ubuntu/lbry +++ b/packaging/ubuntu/lbry @@ -27,7 +27,7 @@ DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" if [ -z "$(pgrep lbrynet-daemon)" ]; then - echo "running lbrynet-daemon..." + echo "running lbrynet-daemon" $DIR/lbrynet-daemon --no-launch & sleep 3 # let the daemon load before connecting fi diff --git a/packaging/uri_handler/LBRYURIHandler.py b/packaging/uri_handler/LBRYURIHandler.py index 9e2bea6a5..d22aeea19 100644 --- a/packaging/uri_handler/LBRYURIHandler.py +++ b/packaging/uri_handler/LBRYURIHandler.py @@ -11,7 +11,7 @@ from lbrynet import conf class LBRYURIHandler(object): def __init__(self): self.started_daemon = False - self.daemon = LBRYAPIClient.config() + self.daemon = LBRYAPIClient.get_client() def handle_osx(self, lbry_name): self.check_daemon() @@ -27,7 +27,7 @@ class LBRYURIHandler(object): if not self.started_daemon: cmd = r'DIR = "$( cd "$(dirname "${BASH_SOURCE[0]}" )" && pwd )"' \ r'if [-z "$(pgrep lbrynet-daemon)"]; then' \ - r'echo "running lbrynet-daemon..."' \ + r'echo "running lbrynet-daemon"' \ r'$DIR / lbrynet - daemon &' \ r'sleep 3 # let the daemon load before connecting' \ r'fi' @@ -49,7 +49,8 @@ class LBRYURIHandler(object): def check_daemon(self): try: - self.started_daemon = self.daemon.is_running() + status = self.daemon.call('status') + self.started_daemon = status['is_running'] except: self.started_daemon = False diff --git a/tests/functional/test_misc.py b/tests/functional/test_misc.py index 1e0efc37e..61af9e337 100644 --- a/tests/functional/test_misc.py +++ b/tests/functional/test_misc.py @@ -944,7 +944,7 @@ class TestTransfer(TestCase): self.assertEqual(hashsum.hexdigest(), "4ca2aafb4101c1e42235aad24fbb83be") def delete_lbry_file(): - logging.debug("deleting the file...") + logging.debug("deleting the file") d = self.lbry_file_manager.delete_lbry_file(downloaders[0]) d.addCallback(lambda _: self.lbry_file_manager.get_count_for_stream_hash(downloaders[0].stream_hash)) d.addCallback(