lbry-sdk/lbrynet/lbrynet_console/ControlHandlers.py

2604 lines
106 KiB
Python
Raw Normal View History

import json
2015-08-20 17:27:15 +02:00
import logging
from time import sleep
from bitcoinrpc.authproxy import AuthServiceProxy
2015-11-15 03:53:16 +01:00
from twisted.internet.task import LoopingCall
2015-08-20 17:27:15 +02:00
from zope.interface import implements
#from lbrynet.core.StreamDescriptor import PlainStreamDescriptorWriter, BlobStreamDescriptorWriter
2015-08-20 17:27:15 +02:00
from lbrynet.core.PaymentRateManager import PaymentRateManager
from lbrynet.lbryfilemanager.LBRYFileCreator import create_lbry_file
from lbrynet.lbryfilemanager.LBRYFileDownloader import ManagedLBRYFileDownloader
# from lbrynet.lbryfile.StreamDescriptor import get_sd_info
from lbrynet.lbryfile.StreamDescriptor import publish_sd_blob, create_plain_sd
from lbrynet.lbrynet_console.interfaces import ICommandHandler, ICommandHandlerFactory
from lbrynet.core.StreamDescriptor import download_sd_blob#, BlobStreamDescriptorReader
from lbrynet.core.Error import UnknownNameError, InvalidBlobHashError, InsufficientFundsError
2015-09-22 18:08:17 +02:00
from lbrynet.core.Error import InvalidStreamInfoError
from lbrynet.core.utils import is_valid_blobhash
from twisted.internet import defer, threads
2015-11-15 03:53:16 +01:00
import datetime
import os
2015-08-20 17:27:15 +02:00
log = logging.getLogger(__name__)
2015-08-20 17:27:15 +02:00
class InvalidChoiceError(Exception):
pass
class InvalidValueError(Exception):
pass
#class ControlHandlerFactory(object):
# implements(IControlHandlerFactory)
2015-08-20 17:27:15 +02:00
# control_handler_class = None
2015-08-20 17:27:15 +02:00
# def get_prompt_description(self):
# return self.control_handler_class.prompt_description
# def __init__(self, *args):
# self.args = args
# def get_handler(self):
# args = self.args
# return self.control_handler_class(*args)
#class ControlHandler(object):
# implements(IControlHandler)
# prompt_description = None
def get_log_file():
log_file = "console.log"
logging_handlers = logging.getLogger().handlers
if len(logging_handlers):
log_file = logging_handlers[0].baseFilename
return log_file
class RoundedTime(object):
SECOND = 0
MINUTE = 1
HOUR = 2
DAY = 3
WEEK = 4
units = ['second', 'minute', 'hour', 'day', 'week']
def __init__(self, unit, val):
assert unit < len(self.units)
self.unit = unit
self.val = val
def __str__(self):
assert self.unit < len(self.units)
unit_str = self.units[self.unit]
if self.val != 1:
unit_str += "s"
return "%d %s" % (self.val, unit_str)
def get_time_behind_blockchain(best_block_time):
best_time = datetime.datetime.utcfromtimestamp(best_block_time)
diff = datetime.datetime.utcnow() - best_time
if diff.days > 0:
if diff.days >= 7:
val = diff.days // 7
unit = RoundedTime.WEEK
else:
val = diff.days
unit = RoundedTime.DAY
elif diff.seconds >= 60 * 60:
val = diff.seconds // (60 * 60)
unit = RoundedTime.HOUR
elif diff.seconds >= 60:
val = diff.seconds // 60
unit = RoundedTime.MINUTE
else:
val = diff.seconds
unit = RoundedTime.SECOND
return RoundedTime(unit, val)
class CommandHandlerFactory(object):
implements(ICommandHandlerFactory)
priority = 0
short_help = "This should be overridden"
full_help = "This should really be overridden"
command = "this-must-be-overridden"
control_handler_class = None
2015-08-20 17:27:15 +02:00
def __init__(self, *args):
self.args = args
def get_prompt_description(self):
return self.control_handler_class.prompt_description
def get_handler(self, console):
return self.control_handler_class(console, *self.args)
2015-08-20 17:27:15 +02:00
class CommandHandler(object):
implements(ICommandHandler)
2015-08-20 17:27:15 +02:00
prompt_description = None
def __init__(self, console):
self.console = console
self.finished_deferred = defer.Deferred()
2015-08-20 17:27:15 +02:00
def start(self):
pass
def handle_line(self, line):
pass
def get_shortcuts_for_options(option_names):
shortcut_keys = []
names_with_shortcuts = []
for option_name in option_names:
name_with_shortcut = ''
found_shortcut = False
for c in option_name:
if not found_shortcut and not c.lower() in shortcut_keys:
name_with_shortcut += '[' + c.lower() + ']'
shortcut_keys.append(c.lower())
found_shortcut = True
else:
name_with_shortcut += c
if found_shortcut is False:
shortcut_keys.append("")
names_with_shortcuts.append(name_with_shortcut)
return shortcut_keys, names_with_shortcuts
2015-08-20 17:27:15 +02:00
class RecursiveCommandHandler(CommandHandler):
def __init__(self, console, exit_after_one_done=False, reset_after_each_done=False):
CommandHandler.__init__(self, console)
2015-08-20 17:27:15 +02:00
self.current_handler = None
self.exit_after_one_done = exit_after_one_done
self.reset_after_each_done = reset_after_each_done
self._set_control_handlers()
def _get_control_handler_factories(self):
raise NotImplementedError()
2015-08-20 17:27:15 +02:00
def _set_control_handlers(self):
self.control_handlers = {i + 1: handler for i, handler in enumerate(self._get_control_handler_factories())}
def start(self):
self._show_prompt()
def handler_finished(self):
self.current_handler = None
if self.exit_after_one_done is True:
self.finished_deferred.callback(None)
else:
if self.reset_after_each_done:
self._set_control_handlers()
self._show_prompt()
def handler_failed(self, err):
log.error("An error occurred in some handler: %s", err.getTraceback())
self.finished_deferred.callback(None)
2015-08-20 17:27:15 +02:00
def handle_line(self, line):
if self.current_handler is None:
if line is None:
num = None
else:
try:
num = int(line)
except ValueError:
num = None
if num == 0:
self.finished_deferred.callback(None)
return
2015-08-20 17:27:15 +02:00
if num in self.control_handlers:
self.current_handler = self.control_handlers[num].get_handler(self.console)
self.current_handler.finished_deferred.addCallbacks(lambda _: self.handler_finished(),
self.handler_failed)
self.current_handler.start()
return
2015-08-20 17:27:15 +02:00
if self.current_handler is not None:
self.current_handler.handle_line(line)
return
2015-08-20 17:27:15 +02:00
if self.current_handler is None:
self._show_prompt()
2015-08-20 17:27:15 +02:00
def _show_prompt(self):
2015-08-20 17:27:15 +02:00
prompt_string = "Options:\n"
prompt_string += "[0] Exit this menu\n"
for num, handler in self.control_handlers.iteritems():
prompt_string += "[" + str(num) + "] " + handler.get_prompt_description() + "\n"
self.console.sendLine(prompt_string)
2015-08-20 17:27:15 +02:00
class ModifyPaymentRate(CommandHandler):
2015-08-20 17:27:15 +02:00
def __init__(self, console):
CommandHandler.__init__(self, console)
2015-08-20 17:27:15 +02:00
self._prompt_choices = {'cancel': (self._cancel, "Don't change anything")}
self.got_input = False
def start(self):
self._show_prompt_string()
2015-08-20 17:27:15 +02:00
def handle_line(self, line):
if self.got_input is False:
self.got_input = True
if line.lower() in self._prompt_choices:
d = self._prompt_choices[line.lower()][0]()
d.addCallback(self._choice_made)
else:
try:
rate = float(line)
except ValueError:
self.console.sendLine("Rate must be a number")
self.finished_deferred.callback(None)
return
d = self._set_rate(rate)
d.addCallback(lambda _: self._choice_made("Successfully set the rate"))
2015-08-20 17:27:15 +02:00
@staticmethod
def _cancel():
return defer.succeed("No change was made")
2015-08-20 17:27:15 +02:00
def _set_rate(self, rate):
pass
def _get_current_status(self):
pass
def _choice_made(self, result=None):
if result is not None:
self.console.sendLine(result)
self.finished_deferred.callback(None)
def _show_prompt_string(self):
2015-08-20 17:27:15 +02:00
prompt_string = self._get_current_status() + "\n"
for prompt_choice, (func, help_string) in self._prompt_choices.iteritems():
prompt_string += prompt_choice + ": " + help_string + "\n"
prompt_string += "To change the current rate, enter the desired rate\n"
prompt_string += "Then hit enter\n"
self.console.sendLine(prompt_string)
2015-08-20 17:27:15 +02:00
class ApplicationStatus(CommandHandler):
#prompt_description = "Application Status"
2015-08-20 17:27:15 +02:00
def __init__(self, console, rate_limiter, dht_node):
CommandHandler.__init__(self, console)
2015-08-20 17:27:15 +02:00
self.rate_limiter = rate_limiter
self.dht_node = dht_node
def start(self):
d = self._show_status()
d.chainDeferred(self.finished_deferred)
return d
def _show_status(self):
2015-08-20 17:27:15 +02:00
status = "Total bytes uploaded: " + str(self.rate_limiter.total_ul_bytes) + "\n"
status += "Total bytes downloaded: " + str(self.rate_limiter.total_dl_bytes) + "\n"
if self.dht_node is not None:
status += "Approximate number of nodes in DHT: " + str(self.dht_node.getApproximateTotalDHTNodes()) + "\n"
status += "Approximate number of blobs in DHT: " + str(self.dht_node.getApproximateTotalHashes()) + "\n"
self.console.sendLine(status)
return defer.succeed(None)
2015-08-20 17:27:15 +02:00
class ApplicationStatusFactory(CommandHandlerFactory):
2015-08-20 17:27:15 +02:00
control_handler_class = ApplicationStatus
command = "application-status"
short_help = "Show application status"
full_help = "Show total bytes uploaded to other peers, total bytes downloaded from peers," \
" approximate number of nodes in the DHT, and approximate number of hashes" \
" in the DHT"
2015-08-20 17:27:15 +02:00
class GetWalletBalances(CommandHandler):
#prompt_description = "Show wallet point balances"
2015-08-20 17:27:15 +02:00
def __init__(self, console, wallet):
CommandHandler.__init__(self, console)
2015-08-20 17:27:15 +02:00
self.wallet = wallet
def start(self):
d = self._get_wallet_balances()
d.chainDeferred(self.finished_deferred)
return d
#def handle_line(self, line):
# assert line is None, "Show wallet balances should not be passed any arguments"
# return True, self._get_wallet_balances()
2015-08-20 17:27:15 +02:00
def _show_time_behind_blockchain(self, rounded_time):
if rounded_time.unit >= RoundedTime.HOUR:
self.console.sendLine("\n\nYour balance may be out of date. This application\n"
"is %s behind the LBC blockchain. It may take a few minutes to\n"
"catch up the first time you run this early version of LBRY.\n"
"Please be patient =).\n\n" % str(rounded_time))
else:
self.console.sendLine("")
def _log_recent_blocktime_error(self, err):
log.error("An error occurred looking up the most recent blocktime: %s", err.getTraceback())
self.console.sendLine("")
2015-08-20 17:27:15 +02:00
def _get_wallet_balances(self):
d = self.wallet.get_balance()
def format_balance(balance):
if balance == 0:
balance = 0
balance_string = "balance: " + str(balance) + " LBC"
self.console.sendLine(balance_string)
d = self.wallet.get_most_recent_blocktime()
d.addCallback(get_time_behind_blockchain)
d.addCallback(self._show_time_behind_blockchain)
d.addErrback(self._log_recent_blocktime_error)
return d
2015-08-20 17:27:15 +02:00
d.addCallback(format_balance)
return d
class GetWalletBalancesFactory(CommandHandlerFactory):
2015-08-20 17:27:15 +02:00
control_handler_class = GetWalletBalances
priority = 10
command = "balance"
short_help = "Show LBRYcrd balance"
full_help = "Show the LBRYcrd balance of the wallet to which this application is connected"
2015-08-20 17:27:15 +02:00
class GetNewWalletAddress(CommandHandler):
#prompt_description = "Get a new LBRYcrd address"
def __init__(self, console, wallet):
CommandHandler.__init__(self, console)
self.wallet = wallet
def start(self):
d = self._get_new_address()
d.chainDeferred(self.finished_deferred)
return d
def _get_new_address(self):
#assert line is None, "Get new LBRYcrd address should not be passed any arguments"
d = self.wallet.get_new_address()
def show_address(address):
self.console.sendLine(str(address))
d.addCallback(show_address)
return d
class GetNewWalletAddressFactory(CommandHandlerFactory):
control_handler_class = GetNewWalletAddress
command = "get-new-address"
short_help = "Get a new LBRYcrd address"
full_help = "Get a new LBRYcrd address from the wallet to which this application is connected"
class ShutDown(CommandHandler):
#prompt_description = "Shut down"
2015-08-20 17:27:15 +02:00
def __init__(self, console, lbry_service):
CommandHandler.__init__(self, console)
2015-08-20 17:27:15 +02:00
self.lbry_service = lbry_service
def start(self):
d = self._shut_down()
return d
#def handle_line(self, line):
# assert line is None, "Shut down should not be passed any arguments"
# return True, self._shut_down()
2015-08-20 17:27:15 +02:00
def _shut_down(self):
#d = self.lbry_service.shut_down()
2015-08-20 17:27:15 +02:00
#def stop_reactor():
from twisted.internet import reactor
self.console.sendLine("Shutting down.")
reactor.stop()
2015-08-20 17:27:15 +02:00
#d.addBoth(lambda _: stop_reactor())
return defer.succeed(True)
2015-08-20 17:27:15 +02:00
class ShutDownFactory(CommandHandlerFactory):
2015-08-20 17:27:15 +02:00
control_handler_class = ShutDown
priority = 5
command = "exit"
short_help = "Shut down"
full_help = "Shut down"
2015-08-20 17:27:15 +02:00
class LBRYFileStatus(CommandHandler):
#prompt_description = "Print status information for all LBRY Files"
2015-08-20 17:27:15 +02:00
def __init__(self, console, lbry_file_manager):
CommandHandler.__init__(self, console)
2015-08-20 17:27:15 +02:00
self.lbry_file_manager = lbry_file_manager
def start(self):
2015-08-20 17:27:15 +02:00
d = self.lbry_file_manager.get_lbry_file_status_reports()
d.addCallback(self._show_statuses)
d.chainDeferred(self.finished_deferred)
return d
2015-08-20 17:27:15 +02:00
#def handle_line(self, line):
# assert line is None, "print status should not be passed any arguments"
# d = self.lbry_file_manager.get_lbry_file_status_reports()
# d.addCallback(self.format_statuses)
# return True, d
def _show_statuses(self, status_reports):
2015-08-20 17:27:15 +02:00
status_strings = []
for status_report in status_reports:
s = status_report.name + " status: " + status_report.running_status + "\n"
s += str(status_report.num_completed) + " completed out of " + str(status_report.num_known) + "\n"
status_strings.append(s)
self.console.sendLine(''.join(status_strings))
2015-08-20 17:27:15 +02:00
class LBRYFileStatusFactory(CommandHandlerFactory):
2015-08-20 17:27:15 +02:00
control_handler_class = LBRYFileStatus
command = "lbryfile-status"
short_help = "Print status information for LBRY files"
full_help = "Print the status information for all streams that are being saved to disk." \
"This includes whether the stream is currently downloading and the progress" \
"of the download."
2015-08-20 17:27:15 +02:00
class AddStream(CommandHandler):
#prompt_description = None
#line_prompt = None
cancel_prompt = "Trying to locate the stream's metadata. Type \"cancel\" to cancel..."
canceled_message = "Canceled downloading."
2015-08-20 17:27:15 +02:00
def __init__(self, console, sd_identifier, base_payment_rate_manager, wallet):
CommandHandler.__init__(self, console)
2015-08-20 17:27:15 +02:00
self.sd_identifier = sd_identifier
self.wallet = wallet
self.loading_metadata_deferred = None
self.metadata = None
2015-08-20 17:27:15 +02:00
self.factory = None
self.factory_choice_strings = None # (command, command_string, shortcut)
self.factory_choices = None # {command: factory}
self.download_options = []
2015-08-20 17:27:15 +02:00
self.options_left = []
self.options_chosen = []
self.current_option = None
self.current_choice = None
2015-08-20 17:27:15 +02:00
self.got_options_response = False
self.loading_failed = False
self.payment_rate_manager = PaymentRateManager(base_payment_rate_manager)
def start(self):
self.console.sendLine(self.cancel_prompt)
self.loading_metadata_deferred.addCallback(self._handle_metadata)
self.loading_metadata_deferred.addErrback(self._handle_load_canceled)
self.loading_metadata_deferred.addErrback(self._handle_load_failed)
2015-08-20 17:27:15 +02:00
def handle_line(self, line):
# first, print that metadata is being looked up. give the option to cancel, and
# listen for the word cancel
# when that's done, present the metadata, how to change options, how to cancel,
# and list the ways to download
#
#
#if line is None:
# return False, defer.succeed(self.line_prompt)
#if self.loading_failed is True:
# return True, None
if self.loading_metadata_deferred is not None:
2015-08-20 17:27:15 +02:00
if line.lower() == "cancel":
self.loading_metadata_deferred.cancel()
self.loading_metadata_deferred = None
2015-08-20 17:27:15 +02:00
else:
self.console.sendLine(self.cancel_prompt)
return
#if self.metadata is None:
# self.loading_metadata_deferred = self._load_metadata(line)
# cancel_prompt_d = defer.succeed(self.cancel_prompt)
# self.loading_metadata_deferred.addCallback(self._choose_factory)
# self.loading_metadata_deferred.addErrback(self._handle_load_canceled)
# self.loading_metadata_deferred.addErrback(self._handle_load_failed)
# return False, cancel_prompt_d, self.loading_metadata_deferred
2015-08-20 17:27:15 +02:00
if self.current_option is not None:
if self.current_choice is None:
try:
self.current_choice = self._get_choice_from_input(line)
except InvalidChoiceError:
self.console.sendLine(self._get_next_option_prompt(invalid_choice=True))
return
choice = self.current_option.option_types[self.current_choice]
if choice.value == float or choice.value == bool:
self.console.sendLine(self._get_choice_value_prompt())
return
else:
value = choice.value
else:
try:
value = self._get_value_for_choice(line)
except InvalidValueError:
self.console.sendLine(self._get_choice_value_prompt(invalid_value=True))
return
self.options_chosen.append(value)
self.current_choice = None
self.current_option = None
2015-08-20 17:27:15 +02:00
self.options_left = self.options_left[1:]
if self.options_left:
self.console.sendLine(self._get_next_option_prompt())
return
2015-08-20 17:27:15 +02:00
else:
self.current_option = None
self._show_factory_choices()
return
if self.factory_choice_strings is not None:
command = self._get_factory_choice_command(line)
if command == "cancel":
self.console.sendLine(self.canceled_message)
self.finished_deferred.callback(None)
elif command == "options":
self.options_left = self.download_options[:]
self.options_chosen = []
self.console.sendLine(self._get_next_option_prompt())
else:
if command in self.factory_choices:
self.factory = self.factory_choices[command]
self._start_download()
self.console.sendLine("Downloading in the background. Use the command 'status'\n"
"to check the status of the download.")
self.finished_deferred.callback(None)
else:
self._show_factory_choices()
return
#if self.factory is None:
# try:
# choice = int(line)
# except ValueError:
# return False, defer.succeed(self._show_factory_choices())
# if choice in xrange(len(self.metadata.factories)):
# self.factory = self.metadata.factories[choice]
# return False, defer.succeed(self._show_info_and_options())
# else:
# return False, defer.succeed(self._show_factory_choices())
#if self.got_options_response is False:
# self.got_options_response = True
# if line == 'y' or line == 'Y' and self.options_left:
# return False, defer.succeed(self._get_next_option_prompt())
# else:
# self.options_chosen = [option.default_value for option in self.options_left]
# self.options_left = []
# return False, defer.succeed(self.line_prompt3)
#if line == 'y' or line == 'Y':
# d = self._start_download()
#else:
# d = defer.succeed("Download cancelled")
#return True, d
2015-08-20 17:27:15 +02:00
def _get_choice_from_input(self, line):
try:
choice_num = int(line)
except ValueError:
raise InvalidChoiceError()
if 0 <= choice_num < len(self.current_option.option_types):
return choice_num
raise InvalidChoiceError()
2015-08-20 17:27:15 +02:00
def _get_factory_choice_command(self, line):
for command, printed_command, shortcut in self.factory_choice_strings:
if line == command or line == shortcut:
return command
def _load_metadata(self, sd_file):
2015-08-20 17:27:15 +02:00
return defer.fail(NotImplementedError())
def _handle_load_canceled(self, err):
err.trap(defer.CancelledError)
self.console.sendLine(self.canceled_message)
self.finished_deferred.callback(None)
2015-08-20 17:27:15 +02:00
def _handle_load_failed(self, err):
self.loading_failed = True
log.error("An exception occurred attempting to load the stream descriptor: %s", err.getTraceback())
log_file = get_log_file()
self.console.sendLine("An unexpected error occurred attempting to load the stream's metadata.\n"
"See %s for further details.\n\n" % log_file)
self.finished_deferred.callback(None)
2015-08-20 17:27:15 +02:00
def _handle_metadata(self, metadata):
self.loading_metadata_deferred = None
self.metadata = metadata
self.factory_choices = {}
for factory in self.metadata.factories:
self.factory_choices[factory.get_description()] = factory
self.download_options = self.metadata.options.get_downloader_options(self.metadata.validator,
self.payment_rate_manager)
self.options_chosen = [option.default_value for option in self.download_options]
self.factory_choice_strings = []
factory_choice_names = ['cancel']
if self.download_options:
factory_choice_names.append('options')
factory_choice_names += self.factory_choices.keys()
shortcuts, names_with_shortcuts = get_shortcuts_for_options(factory_choice_names)
self.factory_choice_strings = zip(factory_choice_names, names_with_shortcuts, shortcuts)
#if len(self.metadata.factories) == 1:
# self.factory = self.metadata.factories[0]
# return self._show_info_and_options()
self._show_info_and_options()
2015-08-20 17:27:15 +02:00
return self._show_factory_choices()
def _get_estimated_cost_string(self):
estimated_cost_string = "unknown LBC"
for option, option_value in zip(self.download_options, self.options_chosen):
if option.short_description == "data payment rate":
if option_value == None:
rate = self.payment_rate_manager.get_effective_min_blob_data_payment_rate()
else:
rate = option_value
stream_size = None
for field, val in self.metadata.validator.info_to_show():
if field == "stream_size":
stream_size = int(val)
if stream_size is not None and rate is not None:
estimated_cost_string = str(stream_size * 1.0 / 2**20 * rate) + " LBC"
return estimated_cost_string
2015-08-20 17:27:15 +02:00
def _show_factory_choices(self):
prompt = "\n"
prompt += "Estimated cost: " + self._get_estimated_cost_string()
prompt += "\n\n"
for factory_choice_string in self.factory_choice_strings:
prompt += factory_choice_string[1] + '\n'
self.console.sendLine(str(prompt))
2015-08-20 17:27:15 +02:00
def _show_info_and_options(self):
#self.download_options = self.metadata.options.get_downloader_options(self.metadata.validator,
# self.payment_rate_manager)
2015-08-20 17:27:15 +02:00
prompt = "Stream info:\n"
for field_name, value in self._get_info_to_show():
if field_name == "stream_size":
value = str(self._get_formatted_stream_size(int(value)))
prompt += field_name + ": " + value + "\n"
2015-08-20 17:27:15 +02:00
prompt += "\nOptions:\n"
for option in self.download_options:
prompt += option.long_description + ": " + str(option.default_value_description) + "\n"
self.console.sendLine(str(prompt))
2015-08-20 17:27:15 +02:00
@staticmethod
def _get_formatted_stream_size(stream_size):
if isinstance(stream_size, (int, long)):
if stream_size >= 2**40:
units = "TB"
factor = 2**40
elif stream_size >= 2**30:
units = "GB"
factor = 2**30
elif stream_size >= 2**20:
units = "MB"
factor = 2**20
elif stream_size >= 2**10:
units = "KB"
factor = 2**10
else:
return str(stream_size) + " B"
return "%.1f %s" % (round((stream_size * 1.0 / factor), 1), units)
return stream_size
def _get_info_to_show(self):
return self.metadata.validator.info_to_show()
def _get_list_of_option_types(self):
options_string = ""
for i, option_type in enumerate(self.current_option.option_types):
options_string += "[%s] %s\n" % (str(i), option_type.long_description)
options_string += "Enter choice:"
return options_string
2015-08-20 17:27:15 +02:00
def _get_choice_value_prompt(self, invalid_value=False):
choice = self.current_option.option_types[self.current_choice]
choice_string = ""
if invalid_value is True:
"Invalid value entered. Try again.\n"
if choice.short_description is not None:
choice_string += choice.short_description + "\n"
if choice.value == float:
choice_string += "Enter floating point number (e.g. 1.0):"
elif choice.value == bool:
true_string = "Yes"
false_string = "No"
if choice.bool_options_description is not None:
true_string, false_string = choice.bool_options_description
choice_string += "[0] %s\n[1] %s\nEnter choice:" % (true_string, false_string)
else:
NotImplementedError()
return choice_string
def _get_value_for_choice(self, choice_input):
choice = self.current_option.option_types[self.current_choice]
if choice.value == float:
try:
return float(choice_input)
except ValueError:
raise InvalidValueError()
elif choice.value == bool:
if choice_input == "0":
return True
elif choice_input == "1":
return False
raise InvalidValueError()
raise NotImplementedError()
def _get_next_option_prompt(self, invalid_choice=False):
2015-08-20 17:27:15 +02:00
assert len(self.options_left), "Something went wrong. There were no options left"
self.current_option = self.options_left[0]
2015-08-20 17:27:15 +02:00
choice_string = ""
if invalid_choice is True:
2015-08-20 17:27:15 +02:00
choice_string += "Invalid response entered. Try again.\n"
choice_string += self.current_option.long_description + "\n"
if len(self.current_option.option_types) > 1:
choice_string += self._get_list_of_option_types()
elif len(self.current_option.option_types) == 1:
self.current_choice = 0
choice_string += self._get_choice_value_prompt()
2015-08-20 17:27:15 +02:00
return choice_string
def _start_download(self):
d = self._make_downloader()
def do_download(stream_downloader):
d = stream_downloader.start()
d.addCallback(lambda result: self._download_succeeded(stream_downloader, result))
return d
d.addCallback(do_download)
d.addErrback(self._handle_download_error)
2015-08-20 17:27:15 +02:00
return d
def _download_succeeded(self, stream_downloader, result):
self.console.sendLine("%s: %s." % (str(stream_downloader), str(result)))
def _handle_download_error(self, err):
if err.check(InsufficientFundsError):
self.console.sendLine("Download stopped due to insufficient funds.")
d = self.wallet.get_most_recent_blocktime()
d.addCallback(get_time_behind_blockchain)
d.addCallback(self._show_time_behind_blockchain_download)
d.addErrback(self._log_recent_blockchain_time_error_download)
else:
log.error("An unexpected error has caused the download to stop: %s" % err.getTraceback())
log_file = get_log_file()
self.console.sendLine("An unexpected error has caused the download to stop:\n%s\n\nSee %s for further details." % (err.getErrorMessage(), log_file))
2015-08-20 17:27:15 +02:00
def _make_downloader(self):
return self.factory.make_downloader(self.metadata, self.options_chosen,
2015-08-20 17:27:15 +02:00
self.payment_rate_manager)
def _show_time_behind_blockchain_download(self, rounded_time):
if rounded_time.unit >= RoundedTime.HOUR:
self.console.sendLine("\nThis application is %s behind the LBC blockchain, so some of your\n"
"funds may not be available. Use 'get-blockchain-status' to check if\n"
"your application is up to date with the blockchain.\n\n"
"It may take a few minutes to catch up the first time you run this\n"
"early version of LBRY. Please be patient =).\n\n" % str(rounded_time))
def _log_recent_blockchain_time_error_download(self, err):
log.error("An error occurred trying to look up the most recent blocktime: %s", err.getTraceback())
2015-08-20 17:27:15 +02:00
class AddStreamFromSD(AddStream):
#prompt_description = "Add a stream from a stream descriptor file"
#line_prompt = "Stream descriptor file name:"
2015-08-20 17:27:15 +02:00
def start(self, sd_file):
self.loading_metadata_deferred = self.sd_identifier.get_metadata_for_sd_file(sd_file)
return AddStream.start(self)
2015-08-20 17:27:15 +02:00
class AddStreamFromSDFactory(CommandHandlerFactory):
2015-08-20 17:27:15 +02:00
control_handler_class = AddStreamFromSD
command = "get-sd"
short_help = "Download a stream from a plaintext stream descriptor file"
full_help = "Download a stream from a plaintext stream descriptor file.\n" \
"Takes one argument, the filename of the stream descriptor.\n\n" \
"get-sd <stream descriptor filename>"
2015-08-20 17:27:15 +02:00
class AddStreamFromHash(AddStream):
#prompt_description = "Add a stream from a hash"
#line_prompt = "Stream descriptor hash:"
2015-08-20 17:27:15 +02:00
def __init__(self, console, sd_identifier, session, wallet):
AddStream.__init__(self, console, sd_identifier, session.base_payment_rate_manager, wallet)
2015-08-20 17:27:15 +02:00
self.session = session
def start(self, sd_hash):
self.loading_metadata_deferred = download_sd_blob(self.session, sd_hash,
self.payment_rate_manager)
self.loading_metadata_deferred.addCallback(self.sd_identifier.get_metadata_for_sd_blob)
AddStream.start(self)
2015-08-20 17:27:15 +02:00
def _handle_load_failed(self, err):
self.loading_failed = True
if err.check(InvalidBlobHashError):
self.console.sendLine("The hash you entered is invalid. It must be 96 characters long"
" and contain only hex characters.\n\n")
self.finished_deferred.callback(None)
return
if err.check(InsufficientFundsError):
self.console.sendLine("Insufficient funds to download the metadata blob.")
d = self.wallet.get_most_recent_blocktime()
d.addCallback(get_time_behind_blockchain)
d.addCallback(self._show_time_behind_blockchain_download)
d.addErrback(self._log_recent_blockchain_time_error_download)
d.addCallback(lambda _: self.console.sendLine("\n"))
d.chainDeferred(self.finished_deferred)
return
return AddStream._handle_load_failed(self, err)
2015-08-20 17:27:15 +02:00
class AddStreamFromHashFactory(CommandHandlerFactory):
2015-08-20 17:27:15 +02:00
control_handler_class = AddStreamFromHash
command = "get-hash"
short_help = "Download a stream from a hash"
full_help = "Download a stream from the hash of the stream descriptor. The stream " \
"descriptor file will be downloaded from LBRYnet and then read.\n" \
"Takes one argument, the sha384 hashsum of the stream descriptor.\n\n" \
"get-hash <stream descriptor sha384 hashsum>"
2015-08-20 17:27:15 +02:00
class AddStreamFromLBRYcrdName(AddStreamFromHash):
#prompt_description = "Add a stream from a short name"
#line_prompt = "Short name:"
2015-08-20 17:27:15 +02:00
def __init__(self, console, sd_identifier, session, wallet):
AddStreamFromHash.__init__(self, console, sd_identifier, session, wallet)
2015-09-22 18:08:17 +02:00
self.wallet = wallet
self.resolved_name = None
2015-09-22 18:08:17 +02:00
self.description = None
self.key_fee = None
self.key_fee_address = None
self.name = None
2015-08-20 17:27:15 +02:00
def start(self, name):
self.name = name
self.loading_metadata_deferred = self._resolve_name(name)
self.loading_metadata_deferred.addCallback(lambda stream_hash: download_sd_blob(self.session,
stream_hash,
self.payment_rate_manager))
self.loading_metadata_deferred.addCallback(self.sd_identifier.get_metadata_for_sd_blob)
AddStream.start(self)
2015-08-20 17:27:15 +02:00
def _resolve_name(self, name):
def get_name_from_info(stream_info):
2015-09-22 18:08:17 +02:00
if 'stream_hash' not in stream_info:
raise InvalidStreamInfoError(name)
self.resolved_name = stream_info.get('name', None)
self.description = stream_info.get('description', None)
try:
if 'key_fee' in stream_info:
self.key_fee = float(stream_info['key_fee'])
except ValueError:
self.key_fee = None
self.key_fee_address = stream_info.get('key_fee_address', None)
2015-08-20 17:27:15 +02:00
return stream_info['stream_hash']
d = self.wallet.get_most_recent_blocktime()
d.addCallback(get_time_behind_blockchain)
d.addCallback(self._show_time_behind_blockchain_resolve)
d.addErrback(self._log_recent_blockchain_time_error_resolve)
d.addCallback(lambda _: self.wallet.get_stream_info_for_name(name))
2015-08-20 17:27:15 +02:00
d.addCallback(get_name_from_info)
return d
def _show_time_behind_blockchain_resolve(self, rounded_time):
if rounded_time.unit >= RoundedTime.HOUR:
self.console.sendLine("\nThis application is %s behind the LBC blockchain, which may be\n"
"preventing this name from being resolved correctly. Use 'get-blockchain-status'\n"
"to check if your application is up to date with the blockchain.\n\n"
"It may take a few minutes to catch up the first time you run\n"
"this early version of LBRY. Please be patient =).\n\n" % str(rounded_time))
else:
self.console.sendLine("\n")
def _log_recent_blockchain_time_error_resolve(self, err):
log.error("An error occurred trying to look up the most recent blocktime: %s", err.getTraceback())
def _handle_load_failed(self, err):
self.loading_failed = True
if err.check(UnknownNameError):
if is_valid_blobhash(self.name):
self.loading_failed = False
self.loading_metadata_deferred = None
AddStreamFromHash.start(self, self.name)
return
else:
self.console.sendLine("The name %s could not be found." % err.getErrorMessage())
self.finished_deferred.callback(True)
return
elif err.check(InvalidBlobHashError):
self.console.sendLine("The metadata for this name is invalid. The stream cannot be downloaded.\n\n")
self.finished_deferred.callback(None)
return
return AddStreamFromHash._handle_load_failed(self, err)
2015-09-22 18:08:17 +02:00
def _start_download(self):
d = self._pay_key_fee()
d.addCallback(lambda _: AddStream._start_download(self))
return d
def _pay_key_fee(self):
if self.key_fee is not None and self.key_fee_address is not None:
reserved_points = self.wallet.reserve_points(self.key_fee_address, self.key_fee)
if reserved_points is None:
return defer.fail(InsufficientFundsError())
return self.wallet.send_points_to_address(reserved_points, self.key_fee)
return defer.succeed(True)
def _get_info_to_show(self):
i = AddStream._get_info_to_show(self)
if self.description is not None:
i.append(("description", self.description))
if self.key_fee is None or self.key_fee_address is None:
i.append(("decryption key fee", "Free"))
else:
i.append(("decryption key fee", str(self.key_fee)))
i.append(("address to pay key fee", str(self.key_fee_address)))
return i
2015-08-20 17:27:15 +02:00
class AddStreamFromLBRYcrdNameFactory(CommandHandlerFactory):
2015-08-20 17:27:15 +02:00
control_handler_class = AddStreamFromLBRYcrdName
priority = 100
command = "get"
short_help = "Download a stream from a name"
full_help = "Download a stream associated with a name on the LBRYcrd blockchain. The name will be" \
" looked up on the blockchain, and if it is associated with a stream descriptor hash," \
" that stream descriptor will be downloaded and read. If the given name is itself a valid " \
"hash, and the name doesn't exist on the blockchain, then the name will be used as the " \
"stream descriptor hash as in get-hash. Use get-hash if you want no ambiguity.\n" \
"Takes one argument, the name.\n\n" \
"Usage: get <name>"
2015-08-20 17:27:15 +02:00
class LBRYFileChooser(RecursiveCommandHandler):
2015-08-20 17:27:15 +02:00
def __init__(self, console, lbry_file_manager, factory_class, *args, **kwargs):
2015-08-20 17:27:15 +02:00
"""
@param lbry_file_manager:
@param factory_class:
@param args: all arguments that will be passed to the factory
@param kwargs: all arguments that will be passed to the superclass' __init__
@return:
"""
self.lbry_file_manager = lbry_file_manager
self.factory_class = factory_class
self.args = args
RecursiveCommandHandler.__init__(self, console, **kwargs)
2015-08-20 17:27:15 +02:00
def _get_control_handler_factories(self):
control_handler_factories = []
for lbry_file in self.lbry_file_manager.lbry_files:
control_handler_factories.append(self.factory_class(self.console, lbry_file, *self.args))
2015-08-20 17:27:15 +02:00
return control_handler_factories
class LBRYFileChooserFactory(CommandHandlerFactory):
2015-08-20 17:27:15 +02:00
def get_prompt_description(self):
lbry_file = self.args[0]
return lbry_file.file_name
class DeleteLBRYFileChooser(LBRYFileChooser):
#prompt_description = "Delete LBRY File"
2015-08-20 17:27:15 +02:00
def __init__(self, console, stream_info_manager, blob_manager, lbry_file_manager):
LBRYFileChooser.__init__(self, console, lbry_file_manager, DeleteLBRYFileFactory,
stream_info_manager, blob_manager, lbry_file_manager,
exit_after_one_done=True)
2015-08-20 17:27:15 +02:00
class DeleteLBRYFileChooserFactory(CommandHandlerFactory):
2015-08-20 17:27:15 +02:00
control_handler_class = DeleteLBRYFileChooser
command = "delete-lbryfile"
short_help = "Delete an LBRY file"
full_help = "Delete an LBRY file which has been downloaded or created by this application.\n" \
"\nGives the option to also delete the encrypted chunks of data associated with " \
"the file. If they are deleted, they will all have to be downloaded again if " \
"lbrynet-console is asked to download that file again, and lbrynet-console will " \
"not be able to upload those chunks of data to other peers on LBRYnet."
2015-08-20 17:27:15 +02:00
class DeleteLBRYFile(CommandHandler):
#prompt_description = "Delete LBRY File"
delete_data_prompt = "Also delete data? (y/n): "
confirm_prompt = "Are you sure? (y/n): "
2015-08-20 17:27:15 +02:00
def __init__(self, console, lbry_file, stream_info_manager, blob_manager, lbry_file_manager):
CommandHandler.__init__(self, console)
2015-08-20 17:27:15 +02:00
self.lbry_file = lbry_file
self.stream_info_manager = stream_info_manager
self.blob_manager = blob_manager
self.lbry_file_manager = lbry_file_manager
self.got_delete_data = False
self.delete_data = False
self.got_confirmation = False
def start(self):
self.console.send(self.delete_data_prompt)
2015-08-20 17:27:15 +02:00
def handle_line(self, line):
#if line is None:
# return False, defer.succeed(self.line_prompt)
if self.got_delete_data is False:
self.got_delete_data = True
if line.lower() in ['y', 'yes']:
self.delete_data = True
self.console.send(self.confirm_prompt)
return
if self.got_confirmation is False:
self.got_confirmation = True
if line.lower() in ['y', 'yes']:
d = self._delete_lbry_file()
def show_done():
self.console.sendLine("Successfully deleted " + str(self.lbry_file.stream_name))
def delete_failed(err):
self.console.sendLine("Deletion unsuccessful. Reason: %s" % err.getErrorMessage())
d.addCallbacks(lambda _: show_done(), delete_failed)
d.chainDeferred(self.finished_deferred)
else:
self.console.sendLine("Canceled deletion.")
self.finished_deferred.callback(None)
2015-08-20 17:27:15 +02:00
def _delete_lbry_file(self):
d = self.lbry_file_manager.delete_lbry_file(self.lbry_file)
2015-08-20 17:27:15 +02:00
def finish_deletion():
if self.delete_data is True:
d = self.lbry_file.delete_data()
2015-08-20 17:27:15 +02:00
else:
d = defer.succeed(True)
d.addCallback(lambda _: self._delete_stream_data())
return d
d.addCallback(lambda _: finish_deletion())
return d
def _delete_stream_data(self):
s_h = self.lbry_file.stream_hash
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)
return d
2015-08-20 17:27:15 +02:00
class DeleteLBRYFileFactory(LBRYFileChooserFactory):
control_handler_class = DeleteLBRYFile
class ToggleLBRYFileRunningChooser(LBRYFileChooser):
#prompt_description = "Toggle whether an LBRY File is running"
2015-08-20 17:27:15 +02:00
def __init__(self, console, lbry_file_manager):
LBRYFileChooser.__init__(self, console, lbry_file_manager, ToggleLBRYFileRunningFactory,
lbry_file_manager, exit_after_one_done=True)
2015-08-20 17:27:15 +02:00
class ToggleLBRYFileRunningChooserFactory(CommandHandlerFactory):
2015-08-20 17:27:15 +02:00
control_handler_class = ToggleLBRYFileRunningChooser
command = "toggle-running"
short_help = "Toggle whether an LBRY file is running"
full_help = "Toggle whether an LBRY file, which is being saved by this application," \
"is currently being downloaded."
2015-08-20 17:27:15 +02:00
class ToggleLBRYFileRunning(CommandHandler):
#prompt_description = "Toggle whether an LBRY File is running"
2015-08-20 17:27:15 +02:00
def __init__(self, console, lbry_file, lbry_file_manager):
CommandHandler.__init__(self, console)
2015-08-20 17:27:15 +02:00
self.lbry_file = lbry_file
self.lbry_file_manager = lbry_file_manager
def start(self):
d = self.lbry_file_manager.toggle_lbry_file_running(self.lbry_file)
d.addErrback(self._handle_download_error)
self.finished_deferred.callback(None)
2015-08-20 17:27:15 +02:00
@staticmethod
def _handle_download_error(err):
if err.check(InsufficientFundsError):
return "Download stopped due to insufficient funds."
else:
log.error("An unexpected error occurred due to toggling an LBRY file running. %s", err.getTraceback())
log_file = "console.log"
if len(log.handlers):
log_file = log.handlers[0].baseFilename
return "An unexpected error occurred. See %s for details." % log_file
2015-08-20 17:27:15 +02:00
class ToggleLBRYFileRunningFactory(LBRYFileChooserFactory):
control_handler_class = ToggleLBRYFileRunning
class CreateLBRYFile(CommandHandler):
#prompt_description = "Create an LBRY File from file"
2015-08-20 17:27:15 +02:00
line_prompt = "File name: "
def __init__(self, console, session, lbry_file_manager):
CommandHandler.__init__(self, console)
2015-08-20 17:27:15 +02:00
self.session = session
self.lbry_file_manager = lbry_file_manager
def start(self, file_name):
d = create_lbry_file(self.session, self.lbry_file_manager, file_name, open(file_name))
d.addCallback(self.add_to_lbry_files)
d.addCallback(lambda _: self.console.sendLine("Successfully created " + str(file_name)))
self.console.sendLine("Creating an LBRY file from " + str(file_name) + " in the background.")
self.finished_deferred.callback(None)
2015-08-20 17:27:15 +02:00
def add_to_lbry_files(self, stream_hash):
prm = PaymentRateManager(self.session.base_payment_rate_manager)
d = self.lbry_file_manager.add_lbry_file(stream_hash, prm)
d.addCallback(self.set_status)
return d
def set_status(self, lbry_file_downloader):
d = self.lbry_file_manager.change_lbry_file_status(lbry_file_downloader,
ManagedLBRYFileDownloader.STATUS_FINISHED)
d.addCallback(lambda _: lbry_file_downloader.restore())
2015-08-20 17:27:15 +02:00
return d
class CreateLBRYFileFactory(CommandHandlerFactory):
2015-08-20 17:27:15 +02:00
control_handler_class = CreateLBRYFile
command = "create-lbryfile"
short_help = "LBRYize a file"
full_help = "Encrypt a file, split it into chunks, and make those chunks available on LBRYnet. Also " \
"create a 'stream descriptor file' which contains all of the metadata needed to download " \
"the encrypted chunks from LBRYnet and put them back together. This plain stream descriptor " \
"can be passed around via other file sharing methods like email. Additionally, this " \
"application can publish the stream descriptor to LBRYnet so that the LBRY file can be " \
"downloaded via the hash of the stream descriptor."
2015-08-20 17:27:15 +02:00
class PublishStreamDescriptorChooser(LBRYFileChooser):
#prompt_description = "Publish a stream descriptor file to the DHT for an LBRY File"
2015-08-20 17:27:15 +02:00
def __init__(self, console, stream_info_manager, blob_manager, lbry_file_manager):
LBRYFileChooser.__init__(self, console, lbry_file_manager, PublishStreamDescriptorFactory,
stream_info_manager, blob_manager, lbry_file_manager,
exit_after_one_done=True)
2015-08-20 17:27:15 +02:00
class PublishStreamDescriptorChooserFactory(CommandHandlerFactory):
2015-08-20 17:27:15 +02:00
control_handler_class = PublishStreamDescriptorChooser
command = "release-lbryfile"
short_help = "Put a stream descriptor onto LBRYnet"
full_help = "Make a stream descriptor available on LBRYnet at its sha384 hashsum. If the stream " \
"descriptor is made available on LBRYnet, anyone will be able to download it via its " \
"hash via LBRYnet, and the LBRY file can then be downloaded if it is available."
2015-08-20 17:27:15 +02:00
class PublishStreamDescriptor(CommandHandler):
#prompt_description = "Publish a stream descriptor file to the DHT for an LBRY File"
2015-08-20 17:27:15 +02:00
def __init__(self, console, lbry_file, stream_info_manager, blob_manager):
CommandHandler.__init__(self, console)
2015-08-20 17:27:15 +02:00
self.lbry_file = lbry_file
self.stream_info_manager = stream_info_manager
self.blob_manager = blob_manager
def start(self):
d = publish_sd_blob(self.stream_info_manager, self.blob_manager, self.lbry_file.stream_hash)
d.addCallback(lambda sd_hash: self.console.sendLine(sd_hash))
d.chainDeferred(self.finished_deferred)
2015-08-20 17:27:15 +02:00
#def _publish_sd_blob(self):
# descriptor_writer = BlobStreamDescriptorWriter(self.blob_manager)
2015-08-20 17:27:15 +02:00
# d = get_sd_info(self.stream_info_manager, self.lbry_file.stream_hash, True)
# d.addCallback(descriptor_writer.create_descriptor)
2015-08-20 17:27:15 +02:00
# def add_sd_blob_to_stream(sd_blob_hash):
# d = self.stream_info_manager.save_sd_blob_hash_to_stream(self.lbry_file.stream_hash, sd_blob_hash)
# d.addCallback(lambda _: sd_blob_hash)
# return d
2015-08-20 17:27:15 +02:00
# d.addCallback(add_sd_blob_to_stream)
# return d
2015-08-20 17:27:15 +02:00
class PublishStreamDescriptorFactory(LBRYFileChooserFactory):
control_handler_class = PublishStreamDescriptor
class ShowPublishedSDHashesChooser(LBRYFileChooser):
#prompt_description = "Show published stream descriptors for an LBRY File"
2015-08-20 17:27:15 +02:00
def __init__(self, console, stream_info_manager, lbry_file_manager):
LBRYFileChooser.__init__(self, console, lbry_file_manager, ShowPublishedSDHashesFactory,
stream_info_manager, lbry_file_manager)
2015-08-20 17:27:15 +02:00
class ShowPublishedSDHashesChooserFactory(CommandHandlerFactory):
2015-08-20 17:27:15 +02:00
control_handler_class = ShowPublishedSDHashesChooser
command = "show-lbryfile-sd-hashes"
short_help = "Show the published stream descriptor files associated with an LBRY file"
full_help = "Show the published stream descriptor files associated with an LBRY file. " \
"These files contain the metadata for LBRY files. These files can be accessed " \
"on lbrynet via their hash. From that, lbrynet-console can download the LBRY file " \
"if it is available on lbrynet."
2015-08-20 17:27:15 +02:00
class ShowPublishedSDHashes(CommandHandler):
#prompt_description = "Show published stream descriptors for an LBRY File"
2015-08-20 17:27:15 +02:00
def __init__(self, console, lbry_file, stream_info_manager, lbry_file_manager):
CommandHandler.__init__(self, console)
2015-08-20 17:27:15 +02:00
self.lbry_file = lbry_file
self.stream_info_manager = stream_info_manager
self.lbry_file_manager = lbry_file_manager
def start(self):
d = self._show_sd_hashes()
d.chainDeferred(self.finished_deferred)
2015-08-20 17:27:15 +02:00
def _show_sd_hashes(self):
d = self.stream_info_manager.get_sd_blob_hashes_for_stream(self.lbry_file.stream_hash)
def format_blob_hashes(sd_blob_hashes):
self.console.sendLine("\n".join([str(b) for b in sd_blob_hashes]))
2015-08-20 17:27:15 +02:00
d.addCallback(format_blob_hashes)
return d
class ShowPublishedSDHashesFactory(LBRYFileChooserFactory):
control_handler_class = ShowPublishedSDHashes
class CreatePlainStreamDescriptorChooser(LBRYFileChooser):
#prompt_description = "Create a plain stream descriptor file for an LBRY File"
2015-08-20 17:27:15 +02:00
def __init__(self, console, lbry_file_manager):
LBRYFileChooser.__init__(self, console, lbry_file_manager,
CreatePlainStreamDescriptorFactory, lbry_file_manager,
2015-08-20 17:27:15 +02:00
exit_after_one_done=True)
class CreatePlainStreamDescriptorChooserFactory(CommandHandlerFactory):
2015-08-20 17:27:15 +02:00
control_handler_class = CreatePlainStreamDescriptorChooser
command = "create-stream-descriptor"
short_help = "Create a plaintext stream descriptor file for an LBRY file"
full_help = "Create a plaintext stream descriptor file for an LBRY file. This file, " \
"which traditionally has the file extension .cryptsd, can be shared " \
"through a variety of means, including email and file transfer. Anyone " \
"possessing this file will be able to download the LBRY file if it is " \
"available on lbrynet."
2015-08-20 17:27:15 +02:00
class CreatePlainStreamDescriptor(CommandHandler):
2015-08-20 17:27:15 +02:00
prompt_description = "Create a plain stream descriptor file for an LBRY File"
def __init__(self, console, lbry_file, lbry_file_manager):
CommandHandler.__init__(self, console)
2015-08-20 17:27:15 +02:00
self.lbry_file = lbry_file
self.lbry_file_manager = lbry_file_manager
self.sd_file_name = None
self.overwrite_old = False
2015-08-20 17:27:15 +02:00
def start(self):
self.console.sendLine(self._get_file_name_prompt())
2015-08-20 17:27:15 +02:00
def handle_line(self, line):
if self.sd_file_name is None:
self.sd_file_name = line
d = self._get_file_name()
d.addCallback(lambda file_name: create_plain_sd(self.lbry_file_manager.stream_info_manager,
self.lbry_file.stream_hash, file_name,
self.overwrite_old))
d.addCallback(lambda sd_file_name: self.console.sendLine("Wrote stream metadata to " + sd_file_name))
d.chainDeferred(self.finished_deferred)
2015-08-20 17:27:15 +02:00
def _get_file_name_prompt(self):
file_name = self.lbry_file.file_name
if not file_name:
file_name = "_"
file_name += ".cryptsd"
return "Stream Descriptor file name (blank for default, %s):" % file_name
def _get_file_name(self):
if self.sd_file_name:
file_name = self.sd_file_name
self.overwrite_old = True
else:
file_name = self.lbry_file.file_name
file_name = file_name + ".cryptsd"
return defer.succeed(file_name)
2015-08-20 17:27:15 +02:00
class CreatePlainStreamDescriptorFactory(LBRYFileChooserFactory):
control_handler_class = CreatePlainStreamDescriptor
class ShowLBRYFileStreamHashChooser(LBRYFileChooser):
#prompt_description = "Show an LBRY File's stream hash (not usually what you want)"
2015-08-20 17:27:15 +02:00
def __init__(self, console, lbry_file_manager):
LBRYFileChooser.__init__(self, console, lbry_file_manager, ShowLBRYFileStreamHashFactory)
2015-08-20 17:27:15 +02:00
class ShowLBRYFileStreamHashChooserFactory(CommandHandlerFactory):
2015-08-20 17:27:15 +02:00
control_handler_class = ShowLBRYFileStreamHashChooser
command = "lbryfile-streamhash"
short_help = "Show an LBRY file's stream hash"
full_help = "Show the stream hash of an LBRY file, which is how the LBRY file is referenced internally" \
" by this application and therefore not usually what you want to see."
2015-08-20 17:27:15 +02:00
class ShowLBRYFileStreamHash(CommandHandler):
#prompt_description = "Show an LBRY File's stream hash (not usually what you want)"
2015-08-20 17:27:15 +02:00
def __init__(self, console, lbry_file):
CommandHandler.__init__(self, console)
2015-08-20 17:27:15 +02:00
self.lbry_file = lbry_file
def start(self):
self.console.sendLine(str(self.lbry_file.stream_hash))
self.finished_deferred.callback(None)
2015-08-20 17:27:15 +02:00
class ShowLBRYFileStreamHashFactory(LBRYFileChooserFactory):
control_handler_class = ShowLBRYFileStreamHash
class ModifyLBRYFileDataPaymentRate(ModifyPaymentRate):
prompt_description = "Modify LBRY File data payment rate"
def __init__(self, console, lbry_file, lbry_file_manager):
ModifyPaymentRate.__init__(self, console)
2015-08-20 17:27:15 +02:00
self._prompt_choices['unset'] = (self._unset, "Use the default LBRY file data rate")
self.lbry_file = lbry_file
self.lbry_file_manager = lbry_file_manager
self.payment_rate_manager = lbry_file.payment_rate_manager
def _unset(self):
d = self._set_rate(None)
d.addCallback(lambda _: "Using the default LBRY file data rate")
return d
2015-08-20 17:27:15 +02:00
def _set_rate(self, rate):
self.payment_rate_manager.min_blob_data_payment_rate = rate
return self.lbry_file_manager.set_lbry_file_data_payment_rate(self.lbry_file, rate)
2015-08-20 17:27:15 +02:00
def _get_current_status(self):
status = "The LBRY file's current data payment rate is "
effective_rate = self.payment_rate_manager.get_effective_min_blob_data_payment_rate()
if self.payment_rate_manager.min_blob_data_payment_rate is None:
status += "set to use the default LBRY file data payment rate, "
status += str(effective_rate)
return status
class ModifyLBRYFileDataPaymentRateFactory(CommandHandlerFactory):
2015-08-20 17:27:15 +02:00
control_handler_class = ModifyLBRYFileDataPaymentRate
class ModifyLBRYFileOptionsChooser(LBRYFileChooser):
#prompt_description = "Modify an LBRY File's options"
2015-08-20 17:27:15 +02:00
def __init__(self, console, lbry_file_manager):
LBRYFileChooser.__init__(self, console, lbry_file_manager, ModifyLBRYFileOptionsFactory, lbry_file_manager)
2015-08-20 17:27:15 +02:00
class ModifyLBRYFileOptionsChooserFactory(CommandHandlerFactory):
2015-08-20 17:27:15 +02:00
control_handler_class = ModifyLBRYFileOptionsChooser
command = "modify-lbryfile-options"
short_help = "Modify an LBRY file's options"
full_help = "Modify an LBRY file's options. Options include, and are limited to, " \
"changing the rate that the application will pay for data related to " \
"this LBRY file."
2015-08-20 17:27:15 +02:00
class ModifyLBRYFileOptions(RecursiveCommandHandler):
#prompt_description = "Modify an LBRY File's options"
2015-08-20 17:27:15 +02:00
def __init__(self, console, lbry_file, lbry_file_manager):
2015-08-20 17:27:15 +02:00
self.lbry_file = lbry_file
self.lbry_file_manager = lbry_file_manager
RecursiveCommandHandler.__init__(self, console)
2015-08-20 17:27:15 +02:00
def _get_control_handler_factories(self):
factories = []
factories.append(ModifyLBRYFileDataPaymentRateFactory(self.lbry_file, self.lbry_file_manager))
return factories
class ModifyLBRYFileOptionsFactory(LBRYFileChooserFactory):
control_handler_class = ModifyLBRYFileOptions
class ClaimName(CommandHandler):
#prompt_description = "Publish to an lbry:// address"
other_hash_prompt = "Enter the hash you would like to publish:"
short_desc_prompt = "Enter a short description:"
#sd_failure_message = "Unable to find a stream descriptor for that file."
requested_price_prompt = "Enter the fee others should pay for the decryption key for this stream. Leave blank for no fee:"
lbrycrd_address_prompt = "Enter the LBRYcrd address to which the key fee should be sent. If left blank a new address will be used from the wallet:"
bid_amount_prompt = "Enter the number of credits you wish to use to support your bid for the name:"
choose_name_prompt = "Enter the name to which you would like to publish:"
def __init__(self, console, wallet, lbry_file_manager, blob_manager):
CommandHandler.__init__(self, console)
self.wallet = wallet
self.lbry_file_manager = lbry_file_manager
self.blob_manager = blob_manager
self.file_type_options = []
self.file_type_chosen = None
self.lbry_file_list = []
2015-08-20 17:27:15 +02:00
self.sd_hash = None
self.key_fee = None
self.key_fee_chosen = False
self.need_address = True
self.chosen_address = None
self.bid_amount = None
self.chosen_name = None
self.short_description = None
self.verified = False
2015-08-20 17:27:15 +02:00
def start(self):
self.console.sendLine(self._get_file_type_options())
2015-08-20 17:27:15 +02:00
def handle_line(self, line):
#if line is None:
# return False, defer.succeed(self._get_file_type_options())
#if self.failed is True:
# return True, defer.succeed(None)
if self.file_type_chosen is None:
try:
choice = int(line)
except ValueError:
choice = -1
if choice < 0 or choice >= len(self.file_type_options):
self.console.sendLine("You must enter a valid number.\n\n%s" % self._get_file_type_options())
return
if self.file_type_options[choice][0] is None:
self.console.sendLine("Publishing canceled.")
self.finished_deferred.callback(None)
return
self.file_type_chosen = self.file_type_options[choice][0]
if self.file_type_chosen == "hash":
self.console.sendLine(self.other_hash_prompt)
return
else:
self._set_sd_hash_and_get_desc_prompt()
return
2015-08-20 17:27:15 +02:00
if self.sd_hash is None:
self.sd_hash = line
self.console.sendLine(self.short_desc_prompt)
return
if self.short_description is None:
self.short_description = line
self.console.sendLine(self.requested_price_prompt)
return
if self.key_fee_chosen is False:
if line:
try:
self.key_fee = float(line)
except ValueError:
self.console.sendLine("Leave blank or enter a floating point number.\n\n%s" % self.requested_price_prompt)
return
self.key_fee_chosen = True
if self.key_fee is None or self.key_fee <= 0:
self.need_address = False
self.console.sendLine(self.bid_amount_prompt)
return
self.console.sendLine(self.lbrycrd_address_prompt)
return
if self.need_address is True:
if line:
self.chosen_address = line
d = defer.succeed(None)
else:
d = self._get_new_address()
self.need_address = False
d.addCallback(lambda _: self.console.sendLine(self.bid_amount_prompt))
return
if self.bid_amount is None:
try:
self.bid_amount = float(line)
except ValueError:
self.console.sendLine("Must be a floating point number.\n\n%s" % self.bid_amount_prompt)
return
self.console.sendLine(self.choose_name_prompt)
return
if self.chosen_name is None:
self.chosen_name = line
self.console.sendLine(self._get_verification_prompt())
return
if self.verified is False:
if line.lower() == "yes":
d = self._claim_name()
else:
d = defer.succeed("Claim canceled")
d.chainDeferred(self.finished_deferred)
def _get_file_type_options(self):
options = []
pattern = "[%d] %s\n"
prompt_string = "What would you like to publish?\n"
prompt_string += "LBRY Files:\n"
i = 0
for lbry_file in self.lbry_file_manager.lbry_files:
options.append((lbry_file, lbry_file.file_name))
prompt_string += pattern % (i, lbry_file.file_name)
i += 1
prompt_string += "Other:\n"
options.append(("hash", "Enter a hash"))
prompt_string += pattern % (i, "Enter a hash")
i += 1
options.append((None, "Cancel"))
prompt_string += pattern % (i, "Cancel")
self.file_type_options = options
return prompt_string
def _choose_sd(self, sd_blob_hashes):
if not sd_blob_hashes:
return publish_sd_blob(self.lbry_file_manager.stream_info_manager, self.blob_manager,
self.file_type_chosen.stream_hash)
else:
return defer.succeed(sd_blob_hashes[0])
def _set_sd_hash_and_get_desc_prompt(self):
d = self.lbry_file_manager.stream_info_manager.get_sd_blob_hashes_for_stream(self.file_type_chosen.stream_hash)
d.addCallback(self._choose_sd)
def set_sd_hash(sd_hash):
self.sd_hash = sd_hash
self.console.sendLine(self.short_desc_prompt)
def sd_hash_failed(err):
self.console.sendLine("An error occurred getting the stream descriptor hash: %s" % err.getErrorMessage())
self.finished_deferred.callback(None)
d.addCallback(set_sd_hash)
d.addErrback(sd_hash_failed)
return d
def _get_new_address(self):
d = self.wallet.get_new_address()
def set_address(address):
self.chosen_address = address
d.addCallback(set_address)
return d
def _get_verification_prompt(self):
v_string = "Ensure the following details are correct:\n"
if self.file_type_chosen != "hash":
v_string += "File name: %s\n" % str(self.file_type_chosen.file_name)
v_string += "Hash: %s\n" % str(self.sd_hash)
v_string += "Description: %s\n" % str(self.short_description)
v_string += "Key fee: %s\n" % str(self.key_fee)
if self.chosen_address is not None:
v_string += "Key fee address: %s\n" % str(self.chosen_address)
v_string += "Bid amount: %s\n" % str(self.bid_amount)
v_string += "Name: %s\n" % str(self.chosen_name)
v_string += "\nIf this is correct, type 'yes'. Otherwise, type 'no' and the bid will be aborted:"
return v_string
2015-08-20 17:27:15 +02:00
def _claim_name(self):
d = self.wallet.claim_name(self.chosen_name, self.sd_hash, float(self.bid_amount),
description=self.short_description, key_fee=self.key_fee,
key_fee_address=self.chosen_address)
d.addCallback(lambda response: self.console.sendLine(response))
return d
2015-08-20 17:27:15 +02:00
class ClaimNameFactory(CommandHandlerFactory):
2015-08-20 17:27:15 +02:00
control_handler_class = ClaimName
command = "claim"
short_help = "Dedicate some LBC toward an lbry:// address"
full_help = "Dedicate some LBY toward associate an LBRY file, or any hash, with " \
"an lbry:// address. On lbry, whoever dedicates the most credits to an " \
"lbry:// address controls that address. This command will let you choose " \
"to associate either on LBRY file or any given value with the address.\n" \
"This command will ask for a few additional fields, explained here:\n\n" \
"The title will be presented to users before they download the file.\n" \
"The bid amount is the number of LBC that will be dedicated toward " \
"the lbry://address being registered. On lbry, whoever dedicates the most " \
"credits to the address controls that address.\n" \
"The decryption key fee is the amount of LBC that users will be charged " \
"when consuming this file. The fees will be sent to the provided key fee address.\n" \
"The description will be presented to users before they download the file.\n"
2015-08-20 17:27:15 +02:00
class Publish(CommandHandler):
couldnt_read_file_error = "Unable to read %s. The file must exist and you must have permission to read it."
bid_amount_not_number = "Bid amount must be a number (e.g. 5 or 10.0)"
key_fee_not_number = "Decryption key fee must be a number (e.g. 5 or 10.0)"
def __init__(self, console, session, lbry_file_manager, wallet):
CommandHandler.__init__(self, console)
self.session = session
self.lbry_file_manager = lbry_file_manager
self.wallet = wallet
self.received_file_name = False
self.file_path = None
self.file_name = None
self.title = None
self.publish_name = None
self.bid_amount = None
self.key_fee = None
self.key_fee_address = None
self.key_fee_address_chosen = False
self.description = None
self.verified = False
self.lbry_file = None
self.sd_hash = None
self.tx_hash = None
def start(self, file_name=None):#, title=None, publish_name=None, bid_amount=None,
# key_fee=None, key_fee_address=None):
#def set_other_fields():
# self.title = title
# self.publish_name = publish_name
# if bid_amount is not None:
# try:
# self.bid_amount = float(bid_amount)
# except ValueError:
# self.console.sendLine(self.bid_amount_not_number)
# self.finished_deferred.callback(None)
# return
# if key_fee is not None:
# try:
# self.key_fee = float(key_fee)
# except ValueError:
# self.console.sendLine(self.key_fee_not_number)
# self.finished_deferred.callback(None)
# return
# if key_fee_address is not None:
# self.key_fee_address = key_fee_address
# self.key_fee_address_chosen = True
# self._send_next_prompt()
def handle_error(err):
if err.check(IOError):
self.console.sendLine(self.couldnt_read_file_error % str(file_name))
else:
self.console.sendLine("An unexpected error occurred: %s" % str(err.getErrorMessage()))
self.finished_deferred.callback(None)
if file_name is not None:
self.received_file_name = True
d = self._check_file_path(file_name)
#d.addCallback(lambda _: set_other_fields())
d.addCallbacks(lambda _: self._send_next_prompt(), handle_error)
else:
self._send_next_prompt()
def handle_line(self, line):
d = defer.succeed(True)
if self.file_name is None:
if self.received_file_name is False:
self.received_file_name = True
d = self._check_file_path(line)
def file_name_failed(err):
err.trap(IOError)
self.console.sendLine(self.couldnt_read_file_error % line)
self.finished_deferred.callback(None)
return False
d.addErrback(file_name_failed)
elif self.title is None:
self.title = line
elif self.publish_name is None:
self.publish_name = line
elif self.bid_amount is None:
try:
self.bid_amount = float(line)
except ValueError:
self.console.sendLine(self.bid_amount_not_number)
elif self.key_fee is None:
try:
self.key_fee = float(line)
except ValueError:
self.console.sendLine(self.key_fee_not_number)
elif self.key_fee_address_chosen is False and self.key_fee > 0:
if line:
self.key_fee_address = line
else:
d = self._get_new_address()
self.key_fee_address_chosen = True
elif self.description is None:
self.description = line
elif self.verified is False:
if line.lower() in ['yes', 'y']:
self._do_publish()
self.console.sendLine("Publishing in the background.")
else:
self.console.sendLine("Canceled.")
self.finished_deferred.callback(None)
return
else:
return
d.addCallbacks(lambda s: self._send_next_prompt() if s is True else None,
self.finished_deferred.errback)
def _check_file_path(self, file_path):
def check_file_threaded():
f = open(file_path)
f.close()
self.file_path = file_path
self.file_name = os.path.basename(self.file_path)
return True
return threads.deferToThread(check_file_threaded)
def _get_new_address(self):
d = self.wallet.get_new_address()
def set_address(address):
self.key_fee_address = address
return True
d.addCallback(set_address)
return d
def _send_next_prompt(self):
prompt = None
if self.file_name is None:
prompt = "Path to file: "
elif self.title is None:
prompt = "Title: "
elif self.publish_name is None:
prompt = "Publish to: lbry://"
elif self.bid_amount is None:
prompt = "Bid amount for published name in LBC: "
elif self.key_fee is None:
prompt = "Decryption key fee in LBC: "
elif self.key_fee_address_chosen is False and self.key_fee > 0:
prompt = "Decryption key fee sent to (leave blank for a new address): "
elif self.description is None:
prompt = "Description: "
elif self.verified is False:
prompt = self._get_verification_prompt()
if prompt is not None:
self.console.send(prompt)
def _get_verification_prompt(self):
v_string = "\nPlease review the following details.\n\n"
v_string += "Path to file: %s\n" % str(self.file_path)
v_string += "File name: %s\n" % str(self.file_name)
v_string += "Title: %s\n" % str(self.title)
v_string += "Published to: lbry://%s\n" % str(self.publish_name)
v_string += "Bid amount: %s LBC\n" % str(self.bid_amount)
v_string += "Fee for decryption key: %s LBC\n" % str(self.key_fee)
if self.key_fee > 0:
v_string += "Decryption key address: %s\n" % str(self.key_fee_address)
v_string += "Description: %s\n" % str(self.description)
v_string += "Is this correct? (y/n): "
return v_string
def set_status(self, lbry_file_downloader):
self.lbry_file = lbry_file_downloader
d = self.lbry_file_manager.change_lbry_file_status(self.lbry_file,
ManagedLBRYFileDownloader.STATUS_FINISHED)
d.addCallback(lambda _: lbry_file_downloader.restore())
return d
def add_to_lbry_files(self, stream_hash):
prm = PaymentRateManager(self.session.base_payment_rate_manager)
d = self.lbry_file_manager.add_lbry_file(stream_hash, prm)
d.addCallback(self.set_status)
return d
def _create_sd_blob(self):
d = publish_sd_blob(self.lbry_file_manager.stream_info_manager, self.session.blob_manager,
self.lbry_file.stream_hash)
def set_sd_hash(sd_hash):
self.sd_hash = sd_hash
d.addCallback(set_sd_hash)
return d
def _claim_name(self):
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)
def set_tx_hash(tx_hash):
self.tx_hash = tx_hash
d.addCallback(set_tx_hash)
return d
def _show_result(self):
message = "Finished publishing %s to %s. The txid of the LBRYcrd claim is %s."
self.console.sendLine(message % (str(self.file_name), str(self.publish_name), str(self.tx_hash)))
def _show_time_behind_blockchain(self, rounded_time):
if rounded_time.unit >= RoundedTime.HOUR:
self.console.sendLine("This application is %s behind the LBC blockchain\n"
"and therefore may not have all of the funds you expect\n"
"available at this time. It may take a few minutes to\n"
"catch up the first time you run this early version of LBRY.\n"
"Please be patient =).\n" % str(rounded_time))
def _log_best_blocktime_error(self, err):
log.error("An error occurred checking the best time of the blockchain: %s", err.getTraceback())
def _show_publish_error(self, err):
message = "An error occurred publishing %s to %s. Error: %s."
if err.check(InsufficientFundsError):
d = self.wallet.get_most_recent_blocktime()
d.addCallback(get_time_behind_blockchain)
d.addCallback(self._show_time_behind_blockchain)
d.addErrback(self._log_best_blocktime_error)
error_message = "Insufficient funds"
else:
d = defer.succeed(True)
error_message = err.getErrorMessage()
self.console.sendLine(message % (str(self.file_name), str(self.publish_name), error_message))
log.error(message, str(self.file_name), str(self.publish_name), err.getTraceback())
return d
def _do_publish(self):
2015-11-07 06:15:42 +01:00
d = create_lbry_file(self.session, self.lbry_file_manager, self.file_name, open(self.file_path))
d.addCallback(self.add_to_lbry_files)
d.addCallback(lambda _: self._create_sd_blob())
d.addCallback(lambda _: self._claim_name())
d.addCallbacks(lambda _: self._show_result(), self._show_publish_error)
return d
class PublishFactory(CommandHandlerFactory):
control_handler_class = Publish
priority = 90
command = "publish"
short_help = "Publish a file to lbrynet"
full_help = "Publish a file to lbrynet.\n\n" \
"Usage: publish [file_name]\n\n" \
"This command takes (or prompts) for a file, prompts for additional " \
"information about that file, and then makes that file available on " \
"lbrynet via an lbry:// address.\n" \
"The file given must exist or publish will fail.\n" \
"The title will be presented to users before they download the file.\n" \
"The bid amount is the number of LBC that will be dedicated toward " \
"the lbry://address being registered. On lbry, whoever dedicates the most " \
"credits to the address controls that address.\n" \
"The decryption key fee is the amount of LBC that users will be charged " \
"when consuming this file. The fees will be sent to the provided key fee address.\n" \
"The description will be presented to users before they download the file.\n"
2015-08-20 17:27:15 +02:00
class ModifyDefaultDataPaymentRate(ModifyPaymentRate):
prompt_description = "Modify default data payment rate"
def __init__(self, console, payment_rate_manager, settings):
ModifyPaymentRate.__init__(self, console)
2015-08-20 17:27:15 +02:00
self.settings = settings
self.payment_rate_manager = payment_rate_manager
def _set_rate(self, rate):
self.payment_rate_manager.min_blob_data_payment_rate = rate
return self.settings.save_default_data_payment_rate(rate)
def _get_current_status(self):
status = "The current default data payment rate is "
status += str(self.payment_rate_manager.min_blob_data_payment_rate)
return status
class ModifyDefaultDataPaymentRateFactory(CommandHandlerFactory):
2015-08-20 17:27:15 +02:00
control_handler_class = ModifyDefaultDataPaymentRate
class ForceCheckBlobFileConsistency(CommandHandler):
prompt_description = "Verify consistency of stored chunks"
2015-08-20 17:27:15 +02:00
def __init__(self, console, blob_manager):
CommandHandler.__init__(self, console)
2015-08-20 17:27:15 +02:00
self.blob_manager = blob_manager
def start(self):
self._check_consistency()
self.console.sendLine("Checking consistency in the background.")
self.finished_deferred.callback(None)
2015-08-20 17:27:15 +02:00
def _check_consistency(self):
d = self.blob_manager.check_consistency()
d.addCallback(lambda _: self.console.sendLine("Finished checking stored blobs"))
2015-08-20 17:27:15 +02:00
return d
class ForceCheckBlobFileConsistencyFactory(CommandHandlerFactory):
2015-08-20 17:27:15 +02:00
control_handler_class = ForceCheckBlobFileConsistency
class ModifyApplicationDefaults(RecursiveCommandHandler):
#prompt_description = "Modify application settings"
2015-08-20 17:27:15 +02:00
def __init__(self, console, lbry_service):
2015-08-20 17:27:15 +02:00
self.lbry_service = lbry_service
RecursiveCommandHandler.__init__(self, console)
2015-08-20 17:27:15 +02:00
def _get_control_handler_factories(self):
return [ModifyDefaultDataPaymentRateFactory(self.lbry_service.session.base_payment_rate_manager,
self.lbry_service.settings),
ForceCheckBlobFileConsistencyFactory(self.lbry_service.session.blob_manager)]
class ModifyApplicationDefaultsFactory(CommandHandlerFactory):
2015-08-20 17:27:15 +02:00
control_handler_class = ModifyApplicationDefaults
command = "modify-application-defaults"
short_help = "Modify application settings"
full_help = "Either change the default rate to pay for data downloads or check " \
"that the chunks of data on disk match up with the chunks of data " \
"the application thinks are on disk."
2015-08-20 17:27:15 +02:00
class ShowServerStatus(CommandHandler):
#prompt_description = "Show the status of the server"
2015-08-20 17:27:15 +02:00
def __init__(self, console, lbry_service):
CommandHandler.__init__(self, console)
2015-08-20 17:27:15 +02:00
self.lbry_service = lbry_service
def start(self):
#assert line is None, "Show server status should not be passed any arguments"
d = self._get_status()
d.chainDeferred(self.finished_deferred)
2015-08-20 17:27:15 +02:00
def _get_status(self):
status_string = "Server status:\n"
status_string += "Port: " + str(self.lbry_service.peer_port) + "\n"
status_string += "Running: " + str(self.lbry_service.lbry_server_port is not None) + "\n"
if self.lbry_service.blob_request_payment_rate_manager is not None:
rate = self.lbry_service.blob_request_payment_rate_manager.get_effective_min_blob_data_payment_rate()
status_string += "Min blob data payment rate: "
if self.lbry_service.blob_request_payment_rate_manager.min_blob_data_payment_rate is None:
status_string += "Using application default (" + str(rate) + ")\n"
else:
status_string += str(rate)
status_string += "\n"
#status_string += "Min crypt info payment rate: "
#status_string += str(self.lbry_service._server_payment_rate_manager.get_min_live_blob_info_payment_rate())
#status_string += "\n"
self.console.sendLine(status_string)
return defer.succeed(None)
2015-08-20 17:27:15 +02:00
class ShowServerStatusFactory(CommandHandlerFactory):
2015-08-20 17:27:15 +02:00
control_handler_class = ShowServerStatus
command = "server-status"
short_help = "Show the server's status"
full_help = "Show the port on which the server is running, whether the server is running, and the" \
" payment rate which the server accepts for data uploads"
2015-08-20 17:27:15 +02:00
class StartServer(CommandHandler):
2015-08-20 17:27:15 +02:00
prompt_description = "Start the server"
def __init__(self, console, lbry_service):
CommandHandler.__init__(self, console)
2015-08-20 17:27:15 +02:00
self.lbry_service = lbry_service
def start(self):
#assert line is None, "Start server should not be passed any arguments"
2015-08-20 17:27:15 +02:00
d = self.lbry_service.start_server()
d.addCallback(lambda _: self.lbry_service.settings.save_server_running_status(running=True))
d.addCallback(lambda _: self.console.sendLine("Successfully started the server"))
d.chainDeferred(self.finished_deferred)
#return True, d
2015-08-20 17:27:15 +02:00
class StartServerFactory(CommandHandlerFactory):
2015-08-20 17:27:15 +02:00
control_handler_class = StartServer
class StopServer(CommandHandler):
2015-08-20 17:27:15 +02:00
prompt_description = "Stop the server"
def __init__(self, console, lbry_service):
CommandHandler.__init__(self, console)
2015-08-20 17:27:15 +02:00
self.lbry_service = lbry_service
def start(self):
#assert line is None, "Stop server should not be passed any arguments"
2015-08-20 17:27:15 +02:00
d = self.lbry_service.stop_server()
d.addCallback(lambda _: self.lbry_service.settings.save_server_running_status(running=False))
d.addCallback(lambda _: self.console.sendLine("Successfully stopped the server"))
d.chainDeferred(self.finished_deferred)
#return True, d
2015-08-20 17:27:15 +02:00
class StopServerFactory(CommandHandlerFactory):
2015-08-20 17:27:15 +02:00
control_handler_class = StopServer
class ModifyServerDataPaymentRate(ModifyPaymentRate):
prompt_description = "Modify server data payment rate"
def __init__(self, console, payment_rate_manager, settings):
ModifyPaymentRate.__init__(self, console)
2015-08-20 17:27:15 +02:00
self._prompt_choices['unset'] = (self._unset, "Use the application default data rate")
self.settings = settings
self.payment_rate_manager = payment_rate_manager
def _unset(self):
d = self._set_rate(None)
d.addCallback(lambda _: "Using the application default data rate")
return d
2015-08-20 17:27:15 +02:00
def _set_rate(self, rate):
self.payment_rate_manager.min_blob_data_payment_rate = rate
return self.settings.save_server_data_payment_rate(rate)
def _get_current_status(self):
effective_rate = self.payment_rate_manager.get_effective_min_blob_data_payment_rate()
status = "The current server data payment rate is "
if self.payment_rate_manager.min_blob_data_payment_rate is None:
status += "set to use the application default, "
status += str(effective_rate)
return status
class ModifyServerDataPaymentRateFactory(CommandHandlerFactory):
2015-08-20 17:27:15 +02:00
control_handler_class = ModifyServerDataPaymentRate
# class ModifyServerCryptInfoPaymentRate(ModifyPaymentRate):
# prompt_description = "Modify server live stream metadata payment rate"
#
# def __init__(self, payment_rate_manager, settings):
# ModifyPaymentRate.__init__(self)
# self._prompt_choices['unset'] = (self._unset,
# "Use the application default live stream metadata rate")
# self.settings = settings
# self.payment_rate_manager = payment_rate_manager
#
# def _unset(self):
# d = self._set_rate(None)
# d.addCallback(lambda _: "Using the application default live stream metadata rate")
# return True, d
#
# def _set_rate(self, rate):
# self.payment_rate_manager.min_live_blob_info_payment_rate = rate
# return self.settings.save_server_crypt_info_payment_rate(rate)
#
# def _get_current_status(self):
# effective_rate = self.payment_rate_manager.get_effective_min_live_blob_info_payment_rate()
# status = "The current server live stream metadata payment rate is "
# if self.payment_rate_manager.get_min_blob_data_payment_rate() is None:
# status += "set to use the application default, "
# status += str(effective_rate)
# else:
# status += str(effective_rate)
# return status
#
#
# class ModifyServerCryptInfoPaymentRateFactory(ControlHandlerFactory):
# control_handler_class = ModifyServerCryptInfoPaymentRate
class DisableQueryHandler(CommandHandler):
def __init__(self, console, query_handlers, query_handler, settings):
CommandHandler.__init__(self, console)
2015-08-20 17:27:15 +02:00
self.query_handlers = query_handlers
self.query_handler = query_handler
self.settings = settings
def start(self):
#assert line is None, "DisableQueryHandler should not be passed any arguments"
2015-08-20 17:27:15 +02:00
self.query_handlers[self.query_handler] = False
d = self.settings.disable_query_handler(self.query_handler.get_primary_query_identifier())
d.addCallback(lambda _: self.console.sendLine("Disabled the query handler"))
d.chainDeferred(self.finished_deferred)
2015-08-20 17:27:15 +02:00
class DisableQueryHandlerFactory(CommandHandlerFactory):
2015-08-20 17:27:15 +02:00
control_handler_class = DisableQueryHandler
def get_prompt_description(self):
query_handler = self.args[1]
return "Disable " + str(query_handler.get_description())
class EnableQueryHandler(CommandHandler):
def __init__(self, console, query_handlers, query_handler, settings):
CommandHandler.__init__(self, console)
2015-08-20 17:27:15 +02:00
self.query_handlers = query_handlers
self.query_handler = query_handler
self.settings = settings
def start(self):
#assert line is None, "EnableQueryHandler should not be passed any arguments"
2015-08-20 17:27:15 +02:00
self.query_handlers[self.query_handler] = True
d = self.settings.enable_query_handler(self.query_handler.get_primary_query_identifier())
d.addCallback(lambda _: self.console.sendLine("Enabled the query handler"))
d.chainDeferred(self.finished_deferred)
2015-08-20 17:27:15 +02:00
class EnableQueryHandlerFactory(CommandHandlerFactory):
2015-08-20 17:27:15 +02:00
control_handler_class = EnableQueryHandler
def get_prompt_description(self):
query_handler = self.args[1]
return "Enable " + str(query_handler.get_description())
class ModifyServerEnabledQueries(RecursiveCommandHandler):
2015-08-20 17:27:15 +02:00
prompt_description = "Modify which queries the server will respond to"
def __init__(self, console, query_handlers, settings):
2015-08-20 17:27:15 +02:00
self.query_handlers = query_handlers
self.settings = settings
RecursiveCommandHandler.__init__(self, console, reset_after_each_done=True)
2015-08-20 17:27:15 +02:00
def _get_control_handler_factories(self):
factories = []
for query_handler, enabled in self.query_handlers.iteritems():
if enabled:
factories.append(DisableQueryHandlerFactory(self.query_handlers, query_handler, self.settings))
else:
factories.append(EnableQueryHandlerFactory(self.query_handlers, query_handler, self.settings))
return factories
class ModifyServerEnabledQueriesFactory(CommandHandlerFactory):
2015-08-20 17:27:15 +02:00
control_handler_class = ModifyServerEnabledQueries
class ImmediateAnnounceAllBlobs(CommandHandler):
prompt_description = "Immediately announce all hashes to the DHT"
2015-08-20 17:27:15 +02:00
def __init__(self, console, blob_manager):
CommandHandler.__init__(self, console)
2015-08-20 17:27:15 +02:00
self.blob_manager = blob_manager
def start(self):
#assert line is None, "Immediate Announce should not be passed any arguments"
2015-08-20 17:27:15 +02:00
d = self.blob_manager.immediate_announce_all_blobs()
d.addCallback(lambda _: self.console.sendLine("Done announcing"))
d.chainDeferred(self.finished_deferred)
2015-08-20 17:27:15 +02:00
class ImmediateAnnounceAllBlobsFactory(CommandHandlerFactory):
2015-08-20 17:27:15 +02:00
control_handler_class = ImmediateAnnounceAllBlobs
command = "announce-blobs"
short_help = "Announce all blobs to the dht"
full_help = "Immediately re-broadcast all hashes associated with the server to " \
"the distributed hash table."
2015-08-20 17:27:15 +02:00
class ModifyServerSettings(RecursiveCommandHandler):
#prompt_description = "Modify server settings"
2015-08-20 17:27:15 +02:00
def __init__(self, console, lbry_service):
2015-08-20 17:27:15 +02:00
self.lbry_service = lbry_service
RecursiveCommandHandler.__init__(self, console, reset_after_each_done=True)
2015-08-20 17:27:15 +02:00
def _get_control_handler_factories(self):
factories = []
if self.lbry_service.lbry_server_port is not None:
factories.append(StopServerFactory(self.lbry_service))
else:
factories.append(StartServerFactory(self.lbry_service))
factories.append(
ModifyServerDataPaymentRateFactory(
self.lbry_service.blob_request_payment_rate_manager,
self.lbry_service.settings
)
)
#factories.append(ModifyServerCryptInfoPaymentRateFactory(self.lbry_service._server_payment_rate_manager,
# self.lbry_service.settings))
factories.append(ModifyServerEnabledQueriesFactory(self.lbry_service.query_handlers,
self.lbry_service.settings))
factories.append(ImmediateAnnounceAllBlobsFactory(self.lbry_service.session.blob_manager))
return factories
class ModifyServerSettingsFactory(CommandHandlerFactory):
2015-08-20 17:27:15 +02:00
control_handler_class = ModifyServerSettings
command = "modify-server-settings"
short_help = "Modify server settings"
full_help = "Modify server settings. Settings that can be modified:\n\n" \
"1) Queries that the server will answer from other peers. For example, " \
"stop the server from responding to requests for metadata, or stop " \
"the server from uploading data.\n" \
"2) Change whether the server is running at all.\n" \
"3) Change the minimum rate the server will accept for data uploads.\n" \
"4) Immediately re-broadcast all hashes associated with the server to " \
"the distributed hash table."
2015-08-20 17:27:15 +02:00
class PeerChooser(RecursiveCommandHandler):
2015-08-20 17:27:15 +02:00
def __init__(self, console, peer_manager, factory_class, *args, **kwargs):
2015-08-20 17:27:15 +02:00
"""
@param peer_manager:
@param factory_class:
@param args: all arguments that will be passed to the factory
@param kwargs: all arguments that will be passed to the superclass' __init__
@return:
"""
self.peer_manager = peer_manager
self.factory_class = factory_class
self.args = args
RecursiveCommandHandler.__init__(self, console, **kwargs)
2015-08-20 17:27:15 +02:00
def _get_control_handler_factories(self):
control_handler_factories = []
for peer in self.peer_manager.peers:
control_handler_factories.append(self.factory_class(peer, *self.args))
return control_handler_factories
class PeerChooserFactory(CommandHandlerFactory):
2015-08-20 17:27:15 +02:00
def get_prompt_description(self):
peer = self.args[0]
return str(peer)
class ShowPeerStats(CommandHandler):
2015-08-20 17:27:15 +02:00
prompt_description = "Show the peer's stats"
def __init__(self, console, peer):
CommandHandler.__init__(self, console)
2015-08-20 17:27:15 +02:00
self.peer = peer
def start(self):
self.console.sendLine(self._get_peer_stats_string())
self.finished_deferred.callback(None)
2015-08-20 17:27:15 +02:00
def _get_peer_stats_string(self):
stats = "Statistics for " + str(self.peer) + '\n'
stats += " current_score: " + str(self.peer.score) + '\n'
stats += " is_available: " + str(self.peer.is_available()) + '\n'
for stat_type, amount in self.peer.stats.iteritems():
stats += " " + stat_type + ": " + str(amount) + "\n"
return stats
class ShowPeerStatsFactory(CommandHandlerFactory):
2015-08-20 17:27:15 +02:00
control_handler_class = ShowPeerStats
class PeerStatsAndSettings(RecursiveCommandHandler):
def __init__(self, console, peer):
2015-08-20 17:27:15 +02:00
self.peer = peer
RecursiveCommandHandler.__init__(self, console, reset_after_each_done=True)
2015-08-20 17:27:15 +02:00
def _get_control_handler_factories(self):
factories = []
factories.append(ShowPeerStatsFactory(self.peer))
return factories
class PeerStatsAndSettingsFactory(PeerChooserFactory):
control_handler_class = PeerStatsAndSettings
class PeerStatsAndSettingsChooser(PeerChooser):
#prompt_description = "View peer stats and modify peer settings"
2015-08-20 17:27:15 +02:00
def __init__(self, console, peer_manager):
PeerChooser.__init__(self, console, peer_manager, PeerStatsAndSettingsFactory)
2015-08-20 17:27:15 +02:00
class PeerStatsAndSettingsChooserFactory(CommandHandlerFactory):
control_handler_class = PeerStatsAndSettingsChooser
command = "peer-stats"
short_help = "Show some peer statistics"
full_help = "Show the list of peers that this application has been " \
"in contact with. Give the option to show further details " \
"for each peer including the number of bytes transferred " \
"and the 'score' of the peer which is used in deciding " \
"which peers to connect to."
class LBRYFileStatusModifier(CommandHandler):
def __init__(self, console, lbry_file, stream_info_manager, blob_manager, lbry_file_manager):
CommandHandler.__init__(self, console)
self.lbry_file = lbry_file
self.stream_info_manager = stream_info_manager
self.blob_manager = blob_manager
self.lbry_file_manager = lbry_file_manager
self.current_handler = None
def start(self):
d = self.lbry_file.status()
d.addCallback(self._show_prompt)
def handle_line(self, line):
if self.current_handler is None:
if line:
if line.lower() == 'd':
self.current_handler = DeleteLBRYFile(self.console, self.lbry_file,
self.stream_info_manager, self.blob_manager,
self.lbry_file_manager)
elif line.lower() == 't':
self.current_handler = ToggleLBRYFileRunning(self.console, self.lbry_file,
self.lbry_file_manager)
else:
self.console.sendLine("Invalid selection\n")
self.finished_deferred.callback(None)
return
else:
self.console.sendLine("")
self.finished_deferred.callback(None)
return
try:
self.current_handler.start()
except Exception as e:
self.console.sendLine("Operation failed. Error: %s\n" % str(e))
import traceback
log.error(traceback.format_exc())
self.finished_deferred.callback(None)
return
self.current_handler.finished_deferred.chainDeferred(self.finished_deferred)
else:
try:
self.current_handler.handle_line(line)
except Exception as e:
self.console.sendLine("Operation failed. Error: %s\n" % str(e))
import traceback
log.error(traceback.format_exc())
self.finished_deferred.callback(None)
def _show_prompt(self, status_report):
self.console.sendLine("\n%s - %d chunks downloaded out of %d - %s" % (str(status_report.name),
status_report.num_completed,
status_report.num_known,
str(status_report.running_status)))
self.console.sendLine("\nTo delete this file, type 'd'. To toggle its running status, type 't'. "
"Then hit enter. To do nothing, just hit enter.")
self.console.send("Choice: ")
class Status(CommandHandler):
lbry_file_status_format = "[%d] %s - %s bytes - %s - %s - %s%% - %s"
def __init__(self, console, lbry_service, rate_limiter, lbry_file_manager, blob_manager, wallet=None):
CommandHandler.__init__(self, console)
self.lbry_service = lbry_service
self.rate_limiter = rate_limiter
self.lbry_file_manager = lbry_file_manager
self.blob_manager = blob_manager
self.wallet = wallet
self.chosen_lbry_file = None
self.current_handler = None
def start(self):
d = self._show_general_status()
d.addCallback(lambda _: self._show_lbry_file_status())
d.addCallback(lambda _: self._show_prompt())
def handle_line(self, line):
if self.current_handler is None:
if line:
try:
index = int(line)
except ValueError:
self.console.sendLine("Choice must be a number.\n")
self.finished_deferred.callback(None)
return
if index < 0 or index >= len(self.lbry_files):
self.console.sendLine("Invalid choice.\n")
self.finished_deferred.callback(None)
return
self.current_handler = LBRYFileStatusModifier(self.console, self.lbry_files[index],
self.lbry_file_manager.stream_info_manager,
self.blob_manager, self.lbry_file_manager)
try:
self.current_handler.start()
except Exception as e:
self.console.sendLine("Selection failed. Error: %s\n" % str(e))
import traceback
log.error(traceback.format_exc())
self.finished_deferred.callback(None)
return
self.current_handler.finished_deferred.chainDeferred(self.finished_deferred)
else:
self.console.sendLine("")
self.finished_deferred.callback(None)
else:
try:
self.current_handler.handle_line(line)
except Exception as e:
self.console.sendLine("Operation failed. Error: %s\n" % str(e))
import traceback
log.error(traceback.format_exc())
self.finished_deferred.callback(None)
def _show_general_status(self):
#self.console.sendLine("Total bytes uploaded: %s" % str(self.rate_limiter.total_ul_bytes))
#self.console.sendLine("Total bytes downloaded: %s" % str(self.rate_limiter.total_dl_bytes))
#self.console.sendLine("Server running: %s" % str(self.lbry_service.lbry_server_port is not None))
#self.console.sendLine("Server port: %s" % str(self.lbry_service.peer_port))
return defer.succeed(True)
def _get_name_and_validity_for_lbry_file(self, lbry_file):
if self.wallet is None:
return defer.succeed(None)
d = self.lbry_file_manager.stream_info_manager.get_sd_blob_hashes_for_stream(lbry_file.stream_hash)
d.addCallback(lambda sd_blob_hashes: self.wallet.get_name_and_validity_for_sd_hash(sd_blob_hashes[0]) if len(sd_blob_hashes) else None)
return d
def _show_lbry_file_status(self):
self.lbry_files = self.lbry_file_manager.lbry_files
status_ds = map(lambda lbry_file: lbry_file.status(), self.lbry_files)
status_dl = defer.DeferredList(status_ds)
size_ds = map(lambda lbry_file: lbry_file.get_total_bytes(), self.lbry_files)
size_dl = defer.DeferredList(size_ds)
name_validity_ds = map(self._get_name_and_validity_for_lbry_file, self.lbry_files)
name_validity_dl = defer.DeferredList(name_validity_ds)
dl = defer.DeferredList([status_dl, size_dl, name_validity_dl])
def show_statuses(statuses):
status_reports = statuses[0][1]
sizes = statuses[1][1]
name_validities = statuses[2][1]
for i, (lbry_file, (succ1, status), (succ2, size), (succ3, name_validity)) in enumerate(zip(self.lbry_files, status_reports, sizes, name_validities)):
percent_done = "unknown"
name = lbry_file.file_name
claimed_name = ""
claimed_name_valid = ""
if succ3 and name_validity:
validity = name_validity[1]
if validity == "valid":
claimed_name_valid = ""
else:
claimed_name_valid = "(" + validity + ")"
claimed_name = name_validity[0]
total_bytes = "unknown"
running_status = "unknown"
if succ1:
percent_done = "0"
if status.num_known > 0:
percent = 100.0 * status.num_completed / status.num_known
percent_done = "%.2f" % percent
running_status = status.running_status
if succ2:
total_bytes = "%d" % size
self.console.sendLine(self.lbry_file_status_format % (i, str(name), total_bytes,
str(claimed_name_valid),
str(claimed_name),
percent_done, str(running_status)))
dl.addCallback(show_statuses)
return dl
def _show_prompt(self):
self.console.sendLine("\n\nTo alter the status of any file shown above, type the number next to it "
"and then hit 'enter'. Otherwise, just hit 'enter'.")
self.console.send("Choice: ")
class StatusFactory(CommandHandlerFactory):
control_handler_class = Status
priority = 20
command = "status"
short_help = "Show or alter status of files being downloaded"
full_help = "Show or alter status of files being downloaded\n\n" \
"Show the list of files that are currently downloading " \
"or have been downloaded, and give the option to " \
"toggle whether the file is actively downloading or " \
"to remove the file."
2015-12-14 19:14:04 +01:00
# class AutoFetcherStart(CommandHandler):
# def __init__(self, console, autofetcher):
# CommandHandler.__init__(self, console)
# self.autofetcher = autofetcher
#
# def start(self):
# self.autofetcher.start(self.console)
# self.finished_deferred.callback(None)
#
#
# class AutoFetcherStop(CommandHandler):
# def __init__(self, console, autofetcher):
# CommandHandler.__init__(self, console)
# self.autofetcher = autofetcher
#
# def start(self):
# self.autofetcher.stop(self.console)
# self.finished_deferred.callback(None)
#
#
# class AutoFetcherStatus(CommandHandler):
# def __init__(self, console, autofetcher):
# CommandHandler.__init__(self, console)
# self.autofetcher = autofetcher
#
# def start(self):
# self.autofetcher.check_if_running(self.console)
# self.finished_deferred.callback(None)
2015-12-14 19:14:04 +01:00
# class AutoFetcherStartFactory(CommandHandlerFactory):
# control_handler_class = AutoFetcherStart
# command = "start-autofetcher"
# short_help = "Start downloading all lbry files as they are published"
#
#
# class AutoFetcherStopFactory(CommandHandlerFactory):
# control_handler_class = AutoFetcherStop
# command = "stop-autofetcher"
# short_help = "Stop downloading all lbry files as they are published"
#
#
# class AutoFetcherStatusFactory(CommandHandlerFactory):
# control_handler_class = AutoFetcherStatus
# command = "autofetcher-status"
# short_help = "Check autofetcher status"
2015-11-15 03:53:16 +01:00
class BlockchainStatus(CommandHandler):
def __init__(self, console, wallet=None):
CommandHandler.__init__(self, console)
self.wallet = wallet
def start(self):
d = self.wallet.get_most_recent_blocktime()
d.addCallback(get_time_behind_blockchain)
2015-11-15 03:53:16 +01:00
d.addCallbacks(self._show_time_behind_blockchain, self._show_error)
d.chainDeferred(self.finished_deferred)
return d
def _show_time_behind_blockchain(self, rounded_time):
if rounded_time.unit >= RoundedTime.HOUR:
self.console.sendLine("This application is %s behind the LBC blockchain. It\n"
"should only take a few minutes to catch up." % str(rounded_time))
2015-11-15 03:53:16 +01:00
else:
self.console.sendLine("This application is up to date with the LBC blockchain.")
def _show_error(self, err):
log.error(err.getTraceback())
2015-11-15 03:53:16 +01:00
self.console.sendLine("Unable to determine the status of the blockchain.")
class BlockchainStatusFactory(CommandHandlerFactory):
control_handler_class = BlockchainStatus
command = "get-blockchain-status"
short_help = "Show whether this application has caught up with the LBC blockchain"
full_help = "Show whether this applications has caught up with the LBC blockchain"