lbry-sdk/lbrynet/lbrynet_console/ControlHandlers.py

1525 lines
58 KiB
Python

import logging
from zope.interface import implements
from lbrynet.core.StreamDescriptor import PlainStreamDescriptorWriter, BlobStreamDescriptorWriter
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.lbrynet_console.interfaces import IControlHandler, IControlHandlerFactory
from lbrynet.core.StreamDescriptor import download_sd_blob, BlobStreamDescriptorReader
from lbrynet.core.Error import UnknownNameError, InvalidBlobHashError, InsufficientFundsError
from twisted.internet import defer, threads
import os
log = logging.getLogger(__name__)
class InvalidChoiceError(Exception):
pass
class InvalidValueError(Exception):
pass
class ControlHandlerFactory(object):
implements(IControlHandlerFactory)
control_handler_class = None
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
class RecursiveControlHandler(ControlHandler):
def __init__(self, exit_after_one_done=False, reset_after_each_done=False):
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()
def _set_control_handlers(self):
self.control_handlers = {i + 1: handler for i, handler in enumerate(self._get_control_handler_factories())}
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:
return True, None
if num in self.control_handlers:
self.current_handler = self.control_handlers[num].get_handler()
line = None
ds = []
if self.current_handler is not None:
r = self.current_handler.handle_line(line)
done, ds = r[0], list(r[1:])
if done is True:
self.current_handler = None
if self.exit_after_one_done is True:
return r
if self.reset_after_each_done:
self._set_control_handlers()
if self.current_handler is None:
ds += [self.get_prompt()]
return (False,) + tuple(ds)
def get_prompt(self):
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"
return defer.succeed(prompt_string)
class ModifyPaymentRate(ControlHandler):
def __init__(self):
self._prompt_choices = {'cancel': (self._cancel, "Don't change anything")}
def handle_line(self, line):
if line is None:
return False, defer.succeed(self._get_prompt_string())
elif line.lower() in self._prompt_choices:
return self._prompt_choices[line.lower()][0]()
else:
try:
rate = float(line)
except ValueError:
return True, defer.succeed("Rate must be a number")
d = self._set_rate(rate)
d.addCallback(lambda _: "Successfully set the rate")
return True, d
def _cancel(self):
return True, defer.succeed("No change was made")
def _set_rate(self, rate):
pass
def _get_current_status(self):
pass
def _get_prompt_string(self):
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"
return prompt_string
class ApplicationStatus(ControlHandler):
prompt_description = "Application Status"
def __init__(self, rate_limiter, dht_node):
self.rate_limiter = rate_limiter
self.dht_node = dht_node
def handle_line(self, line):
assert line is None, "Application status should not be passed any arguments"
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"
return True, defer.succeed(status)
class ApplicationStatusFactory(ControlHandlerFactory):
control_handler_class = ApplicationStatus
class GetWalletBalances(ControlHandler):
prompt_description = "Show wallet point balances"
def __init__(self, wallet):
self.wallet = wallet
def handle_line(self, line):
assert line is None, "Show wallet balances should not be passed any arguments"
return True, self._get_wallet_balances()
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\n"
return balance_string
d.addCallback(format_balance)
return d
class GetWalletBalancesFactory(ControlHandlerFactory):
control_handler_class = GetWalletBalances
class GetNewWalletAddress(ControlHandler):
prompt_description = "Get a new LBRYcrd address"
def __init__(self, wallet):
self.wallet = wallet
def handle_line(self, line):
assert line is None, "Get new LBRYcrd address should not be passed any arguments"
d = self.wallet.get_new_address()
d.addCallback(lambda address: str(address))
return True, d
class GetNewWalletAddressFactory(ControlHandlerFactory):
control_handler_class = GetNewWalletAddress
class ShutDown(ControlHandler):
prompt_description = "Shut down"
def __init__(self, lbry_service):
self.lbry_service = lbry_service
def handle_line(self, line):
assert line is None, "Shut down should not be passed any arguments"
return True, self._shut_down()
def _shut_down(self):
d = self.lbry_service.shut_down()
def stop_reactor():
from twisted.internet import reactor
reactor.stop()
d.addBoth(lambda _: stop_reactor())
d.addCallback(lambda _: "Shut down successfully")
return d
class ShutDownFactory(ControlHandlerFactory):
control_handler_class = ShutDown
class LBRYFileStatus(ControlHandler):
prompt_description = "Print status information for all LBRY Files"
def __init__(self, lbry_file_manager):
self.lbry_file_manager = lbry_file_manager
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 format_statuses(self, status_reports):
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)
return ''.join(status_strings)
class LBRYFileStatusFactory(ControlHandlerFactory):
control_handler_class = LBRYFileStatus
class AddStream(ControlHandler):
prompt_description = None
line_prompt = None
cancel_prompt = "Trying to locate the stream's metadata. Type \"cancel\" to cancel..."
canceled_message = "Canceled locating the stream's metadata."
line_prompt2 = "Modify options? (y/n)"
line_prompt3 = "Start download? (y/n)"
def __init__(self, sd_identifier, base_payment_rate_manager):
self.sd_identifier = sd_identifier
self.loading_info_and_factories_deferred = None
self.factories = None
self.factory = None
self.info_validator = None
self.options = None
self.options_left = []
self.options_chosen = []
self.current_option = None
self.current_choice = None
self.downloader = None
self.got_options_response = False
self.loading_failed = False
self.payment_rate_manager = PaymentRateManager(base_payment_rate_manager)
def handle_line(self, line):
if line is None:
return False, defer.succeed(self.line_prompt)
if self.loading_failed is True:
return True, None
if self.loading_info_and_factories_deferred is not None:
if line.lower() == "cancel":
self.loading_info_and_factories_deferred.cancel()
self.loading_info_and_factories_deferred = None
return True, None
else:
return False, defer.succeed(self.cancel_prompt)
if self.factories is None:
self.loading_info_and_factories_deferred = self._load_info_and_factories(line)
cancel_prompt_d = defer.succeed(self.cancel_prompt)
self.loading_info_and_factories_deferred.addCallback(self._choose_factory)
self.loading_info_and_factories_deferred.addErrback(self._handle_load_canceled)
self.loading_info_and_factories_deferred.addErrback(self._handle_load_failed)
return False, cancel_prompt_d, self.loading_info_and_factories_deferred
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.factories)):
self.factory = self.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 self.current_option is not None:
if self.current_choice is None:
try:
self.current_choice = self._get_choice_from_input(line)
except InvalidChoiceError:
return False, defer.succeed(self._get_next_option_prompt(invalid_choice=True))
choice = self.current_option.option_types[self.current_choice]
if choice.value == float or choice.value == bool:
return False, defer.succeed(self._get_choice_value_prompt())
else:
value = choice.value
else:
try:
value = self._get_value_for_choice(line)
except InvalidValueError:
return False, defer.succeed(self._get_choice_value_prompt(invalid_value=True))
self.options_chosen.append(value)
self.current_choice = None
self.current_option = None
self.options_left = self.options_left[1:]
if self.options_left:
return False, defer.succeed(self._get_next_option_prompt())
else:
self.current_option = None
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
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()
def _load_info_and_factories(self, sd_file):
return defer.fail(NotImplementedError())
def _handle_load_canceled(self, err):
err.trap(defer.CancelledError)
return defer.succeed(self.canceled_message)
def _handle_load_failed(self, err):
self.loading_failed = True
log.error("An exception occurred attempting to load the stream descriptor: %s", err.getTraceback())
return defer.succeed("An unexpected error occurred attempting to load the stream's metadata.\n"
"See console.log for further details.\n\n"
"Press enter to continue")
def _choose_factory(self, info_and_factories):
self.loading_info_and_factories_deferred = None
self.info_validator, self.options, self.factories = info_and_factories
if len(self.factories) == 1:
self.factory = self.factories[0]
return self._show_info_and_options()
return self._show_factory_choices()
def _show_factory_choices(self):
prompt = "Choose what to do with the file:\n"
for i, factory in enumerate(self.factories):
prompt += "[" + str(i) + "] " + factory.get_description() + '\n'
return str(prompt)
def _show_info_and_options(self):
self.options_left = self.options.get_downloader_options(self.info_validator,
self.payment_rate_manager)
prompt = "Stream info:\n"
for info_line in self.info_validator.info_to_show():
prompt += info_line[0] + ": " + info_line[1] + "\n"
prompt += "\nOptions:\n"
for option in self.options_left:
prompt += option.long_description + ": " + str(option.default_value_description) + "\n"
prompt += "\nModify options? (y/n)"
return str(prompt)
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
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, input):
choice = self.current_option.option_types[self.current_choice]
if choice.value == float:
try:
return float(input)
except ValueError:
raise InvalidValueError()
elif choice.value == bool:
if input == "0":
return True
elif input == "1":
return False
raise InvalidValueError()
raise NotImplementedError()
def _get_next_option_prompt(self, invalid_choice=False):
assert len(self.options_left), "Something went wrong. There were no options left"
self.current_option = self.options_left[0]
choice_string = ""
if invalid_choice is True:
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()
return choice_string
def _start_download(self):
d = self._make_downloader()
d.addCallback(lambda stream_downloader: stream_downloader.start())
d.addErrback(self._handle_download_error)
return d
def _handle_download_error(self, err):
if err.check(InsufficientFundsError):
return "Download stopped due to insufficient funds."
else:
log.error("An unexpected error has caused the download to stop: %s" % err.getTraceback())
return "An unexpected error has caused the download to stop. See console.log for details."
def _make_downloader(self):
return self.factory.make_downloader(self.info_validator, self.options_chosen,
self.payment_rate_manager)
class AddStreamFromSD(AddStream):
prompt_description = "Add a stream from a stream descriptor file"
line_prompt = "Stream descriptor file name:"
def _load_info_and_factories(self, sd_file):
return self.sd_identifier.get_info_and_factories_for_sd_file(sd_file)
class AddStreamFromSDFactory(ControlHandlerFactory):
control_handler_class = AddStreamFromSD
class AddStreamFromHash(AddStream):
prompt_description = "Add a stream from a hash"
line_prompt = "Stream descriptor hash:"
def __init__(self, sd_identifier, session):
AddStream.__init__(self, sd_identifier, session.base_payment_rate_manager)
self.session = session
def _load_info_and_factories(self, sd_hash):
d = download_sd_blob(self.session, sd_hash, self.payment_rate_manager)
d.addCallback(self.sd_identifier.get_info_and_factories_for_sd_blob)
return d
def _handle_load_failed(self, err):
self.loading_failed = True
if err.check(InvalidBlobHashError):
return defer.succeed("The hash you entered is invalid. It must be 96 characters long and "
"contain only hex characters.\n\nPress enter to continue")
if err.check(InsufficientFundsError):
return defer.succeed("Insufficient funds to download the metadata blob.\n\nPress enter to continue")
return AddStream._handle_load_failed(self, err)
class AddStreamFromHashFactory(ControlHandlerFactory):
control_handler_class = AddStreamFromHash
class AddStreamFromLBRYcrdName(AddStreamFromHash):
prompt_description = "Add a stream from a short name"
line_prompt = "Short name:"
def __init__(self, sd_identifier, session, name_resolver):
AddStreamFromHash.__init__(self, sd_identifier, session)
self.name_resolver = name_resolver
def _load_info_and_factories(self, name):
d = self._resolve_name(name)
d.addCallback(lambda stream_hash: AddStreamFromHash._load_info_and_factories(self, stream_hash))
return d
def _resolve_name(self, name):
def get_name_from_info(stream_info):
return stream_info['stream_hash']
d = self.name_resolver.get_stream_info_for_name(name)
d.addCallback(get_name_from_info)
return d
def _handle_load_failed(self, err):
self.loading_failed = True
if err.check(UnknownNameError):
return defer.succeed("The name %s could not be found.\n\n"
"Press enter to continue" % err.getErrorMessage())
elif err.check(InvalidBlobHashError):
return defer.succeed("The metadata for this name is invalid. The stream cannot be downloaded.\n\n" +
"Press enter to continue")
return AddStreamFromHash._handle_load_failed(self, err)
class AddStreamFromLBRYcrdNameFactory(ControlHandlerFactory):
control_handler_class = AddStreamFromLBRYcrdName
class LBRYFileChooser(RecursiveControlHandler):
def __init__(self, lbry_file_manager, factory_class, *args, **kwargs):
"""
@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
RecursiveControlHandler.__init__(self, **kwargs)
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(lbry_file, *self.args))
return control_handler_factories
class LBRYFileChooserFactory(ControlHandlerFactory):
def get_prompt_description(self):
lbry_file = self.args[0]
return lbry_file.file_name
class DeleteLBRYFileChooser(LBRYFileChooser):
prompt_description = "Delete LBRY File"
def __init__(self, stream_info_manager, blob_manager, lbry_file_manager):
LBRYFileChooser.__init__(self, lbry_file_manager, DeleteLBRYFileFactory, stream_info_manager,
blob_manager, lbry_file_manager, exit_after_one_done=True)
class DeleteLBRYFileChooserFactory(ControlHandlerFactory):
control_handler_class = DeleteLBRYFileChooser
class DeleteLBRYFile(ControlHandler):
prompt_description = "Delete LBRY File"
line_prompt = "Also delete data? (y/n):"
def __init__(self, lbry_file, stream_info_manager, blob_manager, lbry_file_manager):
self.lbry_file = lbry_file
self.stream_info_manager = stream_info_manager
self.blob_manager = blob_manager
self.lbry_file_manager = lbry_file_manager
def handle_line(self, line):
if line is None:
return False, defer.succeed(self.line_prompt)
delete_data = False
if line == 'y' or line == 'Y':
delete_data = True
d = self._delete_lbry_file(delete_data)
d.addCallback(lambda _: "Successfully deleted " + str(self.lbry_file.stream_name))
return True, d
def _delete_lbry_file(self, delete_data):
d = self.lbry_file_manager.delete_lbry_file(self.lbry_file.stream_hash)
def finish_deletion():
if delete_data is True:
d = self._delete_data()
else:
d = defer.succeed(True)
d.addCallback(lambda _: self._delete_stream_data())
return d
d.addCallback(lambda _: finish_deletion())
return d
def _delete_data(self):
d1 = self.stream_info_manager.get_blobs_for_stream(self.lbry_file.stream_hash)
def get_blob_hashes(blob_infos):
return [b[0] for b in blob_infos if b[0] is not None]
d1.addCallback(get_blob_hashes)
d2 = self.stream_info_manager.get_sd_blob_hashes_for_stream(self.lbry_file.stream_hash)
def combine_blob_hashes(results):
blob_hashes = []
for success, result in results:
if success is True:
blob_hashes.extend(result)
return blob_hashes
def delete_blobs(blob_hashes):
self.blob_manager.delete_blobs(blob_hashes)
return True
dl = defer.DeferredList([d1, d2], fireOnOneErrback=True)
dl.addCallback(combine_blob_hashes)
dl.addCallback(delete_blobs)
return dl
def _delete_stream_data(self):
return self.stream_info_manager.delete_stream(self.lbry_file.stream_hash)
class DeleteLBRYFileFactory(LBRYFileChooserFactory):
control_handler_class = DeleteLBRYFile
class ToggleLBRYFileRunningChooser(LBRYFileChooser):
prompt_description = "Toggle whether an LBRY File is running"
def __init__(self, lbry_file_manager):
LBRYFileChooser.__init__(self, lbry_file_manager, ToggleLBRYFileRunningFactory, lbry_file_manager,
exit_after_one_done=True)
class ToggleLBRYFileRunningChooserFactory(ControlHandlerFactory):
control_handler_class = ToggleLBRYFileRunningChooser
class ToggleLBRYFileRunning(ControlHandler):
prompt_description = "Toggle whether an LBRY File is running"
def __init__(self, lbry_file, lbry_file_manager):
self.lbry_file = lbry_file
self.lbry_file_manager = lbry_file_manager
def handle_line(self, line):
d = self.lbry_file_manager.toggle_lbry_file_running(self.lbry_file.stream_hash)
d.addErrback(self._handle_download_error)
return True, d
def _handle_download_error(self, 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())
return "An unexpected error occurred. See console.log for details."
class ToggleLBRYFileRunningFactory(LBRYFileChooserFactory):
control_handler_class = ToggleLBRYFileRunning
class CreateLBRYFile(ControlHandler):
prompt_description = "Create an LBRY File from file"
line_prompt = "File name: "
def __init__(self, session, lbry_file_manager):
self.session = session
self.lbry_file_manager = lbry_file_manager
def handle_line(self, line):
if line is None:
return False, defer.succeed(self.line_prompt)
else:
d = create_lbry_file(self.session, self.lbry_file_manager, line, open(line))
d.addCallback(self.add_to_lbry_files)
d.addCallback(lambda _: "Successfully created " + str(line))
return True, 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, stream_hash)
return d
def set_status(self, lbry_file_downloader, stream_hash):
d = self.lbry_file_manager.change_lbry_file_status(stream_hash,
ManagedLBRYFileDownloader.STATUS_FINISHED)
d.addCallback(lambda _: lbry_file_downloader.restore())
return d
class CreateLBRYFileFactory(ControlHandlerFactory):
control_handler_class = CreateLBRYFile
class PublishStreamDescriptorChooser(LBRYFileChooser):
prompt_description = "Publish a stream descriptor file to the DHT for an LBRY File"
def __init__(self, stream_info_manager, blob_manager, lbry_file_manager):
LBRYFileChooser.__init__(self, lbry_file_manager, PublishStreamDescriptorFactory, stream_info_manager,
blob_manager, lbry_file_manager, exit_after_one_done=True)
class PublishStreamDescriptorChooserFactory(ControlHandlerFactory):
control_handler_class = PublishStreamDescriptorChooser
class PublishStreamDescriptor(ControlHandler):
prompt_description = "Publish a stream descriptor file to the DHT for an LBRY File"
def __init__(self, lbry_file, stream_info_manager, blob_manager, lbry_file_manager):
self.lbry_file = lbry_file
self.stream_info_manager = stream_info_manager
self.blob_manager = blob_manager
self.lbry_file_manager = lbry_file_manager
def handle_line(self, line):
return True, self._publish_sd_blob()
def _publish_sd_blob(self):
descriptor_writer = BlobStreamDescriptorWriter(self.blob_manager)
d = get_sd_info(self.lbry_file_manager.stream_info_manager, self.lbry_file.stream_hash, True)
d.addCallback(descriptor_writer.create_descriptor)
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
d.addCallback(add_sd_blob_to_stream)
return d
class PublishStreamDescriptorFactory(LBRYFileChooserFactory):
control_handler_class = PublishStreamDescriptor
class ShowPublishedSDHashesChooser(LBRYFileChooser):
prompt_description = "Show published stream descriptors for an LBRY File"
def __init__(self, stream_info_manager, lbry_file_manager):
LBRYFileChooser.__init__(self, lbry_file_manager, ShowPublishedSDHashesFactory, stream_info_manager,
lbry_file_manager)
class ShowPublishedSDHashesChooserFactory(ControlHandlerFactory):
control_handler_class = ShowPublishedSDHashesChooser
class ShowPublishedSDHashes(ControlHandler):
prompt_description = "Show published stream descriptors for an LBRY File"
def __init__(self, lbry_file, stream_info_manager, lbry_file_manager):
self.lbry_file = lbry_file
self.stream_info_manager = stream_info_manager
self.lbry_file_manager = lbry_file_manager
def handle_line(self, line):
return True, self._show_sd_hashes()
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):
return "\n".join([str(b) for b in sd_blob_hashes])
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"
def __init__(self, lbry_file_manager):
LBRYFileChooser.__init__(self, lbry_file_manager, CreatePlainStreamDescriptorFactory, lbry_file_manager,
exit_after_one_done=True)
class CreatePlainStreamDescriptorChooserFactory(ControlHandlerFactory):
control_handler_class = CreatePlainStreamDescriptorChooser
class CreatePlainStreamDescriptor(ControlHandler):
prompt_description = "Create a plain stream descriptor file for an LBRY File"
def __init__(self, lbry_file, lbry_file_manager):
self.lbry_file = lbry_file
self.lbry_file_manager = lbry_file_manager
self.sd_file_name = None
def handle_line(self, line):
if line is None:
return False, self._get_file_name_prompt()
self.sd_file_name = line
d = threads.deferToThread(self._get_file_name)
d.addCallback(self._create_sd)
return True, d
def _get_file_name_prompt(self):
file_name = self.lbry_file.file_name
if not file_name:
file_name = "_"
file_name += ".cryptsd"
return defer.succeed("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
else:
file_name = self.lbry_file.file_name
if not file_name:
file_name = "_"
file_name += ".cryptsd"
if os.path.exists(file_name):
ext_num = 1
while os.path.exists(file_name + "_" + str(ext_num)):
ext_num += 1
file_name = file_name + "_" + str(ext_num)
return file_name
def _create_sd(self, file_name):
descriptor_writer = PlainStreamDescriptorWriter(file_name)
d = get_sd_info(self.lbry_file_manager.stream_info_manager, self.lbry_file.stream_hash, True)
d.addCallback(descriptor_writer.create_descriptor)
d.addCallback(lambda sd_file_name: "Wrote stream metadata to " + sd_file_name)
return d
class CreatePlainStreamDescriptorFactory(LBRYFileChooserFactory):
control_handler_class = CreatePlainStreamDescriptor
class ShowLBRYFileStreamHashChooser(LBRYFileChooser):
prompt_description = "Show an LBRY File's stream hash (not usually what you want)"
def __init__(self, lbry_file_manager):
LBRYFileChooser.__init__(self, lbry_file_manager, ShowLBRYFileStreamHashFactory)
class ShowLBRYFileStreamHashChooserFactory(ControlHandlerFactory):
control_handler_class = ShowLBRYFileStreamHashChooser
class ShowLBRYFileStreamHash(ControlHandler):
prompt_description = "Show an LBRY File's stream hash (not usually what you want)"
def __init__(self, lbry_file):
self.lbry_file = lbry_file
def handle_line(self, line):
return True, defer.succeed(str(self.lbry_file.stream_hash))
class ShowLBRYFileStreamHashFactory(LBRYFileChooserFactory):
control_handler_class = ShowLBRYFileStreamHash
class ModifyLBRYFileDataPaymentRate(ModifyPaymentRate):
prompt_description = "Modify LBRY File data payment rate"
def __init__(self, lbry_file, lbry_file_manager):
ModifyPaymentRate.__init__(self)
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 True, d
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.stream_hash, rate)
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(ControlHandlerFactory):
control_handler_class = ModifyLBRYFileDataPaymentRate
class ModifyLBRYFileOptionsChooser(LBRYFileChooser):
prompt_description = "Modify an LBRY File's options"
def __init__(self, lbry_file_manager):
LBRYFileChooser.__init__(self, lbry_file_manager, ModifyLBRYFileOptionsFactory, lbry_file_manager)
class ModifyLBRYFileOptionsChooserFactory(ControlHandlerFactory):
control_handler_class = ModifyLBRYFileOptionsChooser
class ModifyLBRYFileOptions(RecursiveControlHandler):
prompt_description = "Modify an LBRY File's options"
def __init__(self, lbry_file, lbry_file_manager):
self.lbry_file = lbry_file
self.lbry_file_manager = lbry_file_manager
RecursiveControlHandler.__init__(self)
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(ControlHandler):
prompt_description = "Publish to an lbry:// address"
other_hash_prompt = "Enter the hash you would like to publish:"
stream_length_prompt = "Enter the total length of the stream, or leave blank if not applicable:"
short_desc_prompt = "Enter a short description:"
sd_failure_message = "Unable to find a stream descriptor for that file.\n\nPress enter to continue"
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, wallet, lbry_file_manager, blob_manager, sd_identifier):
self.wallet = wallet
self.lbry_file_manager = lbry_file_manager
self.blob_manager = blob_manager
self.sd_identifier = sd_identifier
self.file_type_options = []
self.file_type_chosen = None
self.lbry_file_list = []
self.sd_hash = None
self.stream_length = None
self.stream_length_chosen = False
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.failed = False
self.short_description = None
self.verified = False
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):
return False, defer.succeed("You must enter a valid number.\n\n%s" % self._get_file_type_options())
if self.file_type_options[choice] is None:
return True, defer.succeed("Publishing canceled.")
self.file_type_chosen = self.file_type_options[choice][0]
if self.file_type_chosen == "hash":
return False, defer.succeed(self.other_hash_prompt)
else:
return False, self._set_length_and_get_desc_prompt()
if self.sd_hash is None:
self.sd_hash = line
return False, defer.succeed(self.stream_length_prompt)
if self.stream_length_chosen is False:
if line:
try:
self.stream_length = int(line)
except ValueError:
return False, defer.succeed("You must enter an integer or leave blank.\n\n%s" % self.stream_length_prompt)
else:
self.stream_length = None
self.stream_length_chosen = True
return False, defer.succeed(self.short_desc_prompt)
if self.short_description is None:
self.short_description = line
return False, defer.succeed(self.requested_price_prompt)
if self.key_fee_chosen is False:
if line:
try:
self.key_fee = float(line)
except ValueError:
return False, defer.succeed("Leave blank or enter a floating point number.\n\n%s" % self.requested_price_prompt)
self.key_fee_chosen = True
if self.key_fee is None or self.key_fee <= 0:
self.need_address = False
return False, defer.succeed(self.bid_amount_prompt)
return False, defer.succeed(self.lbrycrd_address_prompt)
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.bid_amount_prompt)
return False, d
if self.bid_amount is None:
try:
self.bid_amount = float(line)
except ValueError:
return False, defer.succeed("Must be a floating point number.\n\n%s" % self.bid_amount_prompt)
return False, defer.succeed(self.choose_name_prompt)
if self.chosen_name is None:
self.chosen_name = line
return False, defer.succeed(self._get_verification_prompt())
if self.verified is False:
if line.lower() == "yes":
return True, self._claim_name()
else:
return True, defer.succeed("Claim canceled")
def _get_file_type_options(self):
options = []
for lbry_file in self.lbry_file_manager.lbry_files:
options.append((lbry_file, lbry_file.file_name))
options.append(("hash", "Enter a hash"))
options.append((None, "Cancel"))
self.file_type_options = options
prompt_string = "What would you like to publish?\n"
for i, option in enumerate(options):
prompt_string += "[%d] %s\n" % (i, option[1])
return prompt_string
def _try_to_get_length_from_sd_hash(self):
d = self.blob_manager.get_blob(self.sd_hash, upload_allowed=True)
def log_error(err):
self.failed = True
log.error("An error occurred getting the length from an sd blob: %s", err.getTraceback())
return False
def get_validator_for_blob(blob):
if not blob.verified:
return None
d = self.sd_identifier.get_info_and_factories_for_sd_blob(blob)
d.addCallback(lambda v_o_f: v_o_f[0])
return d
d.addCallback(get_validator_for_blob)
def get_length_from_validator(validator):
if validator is not None:
self.stream_length = validator.get_length_of_stream()
return True
d.addCallback(get_length_from_validator)
d.addErrback(log_error)
return d
def _choose_sd(self, sd_blob_hashes):
if not sd_blob_hashes:
self.failed = True
return defer.succeed(False)
self.sd_hash = sd_blob_hashes[0]
self.stream_length_chosen = True
return self._try_to_get_length_from_sd_hash()
def _set_length_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)
d.addCallback(lambda success: self.short_desc_prompt if success else self.sd_failure_message)
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 += "Length: %s\n" % str(self.stream_length)
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
def _claim_name(self):
d = self.wallet.claim_name(self.chosen_name, self.sd_hash, float(self.bid_amount),
stream_length=self.stream_length,
description=self.short_description, key_fee=self.key_fee,
key_fee_address=self.chosen_address)
d.addCallback(lambda response: str(response))
return d
class ClaimNameFactory(ControlHandlerFactory):
control_handler_class = ClaimName
class ModifyDefaultDataPaymentRate(ModifyPaymentRate):
prompt_description = "Modify default data payment rate"
def __init__(self, payment_rate_manager, settings):
ModifyPaymentRate.__init__(self)
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(ControlHandlerFactory):
control_handler_class = ModifyDefaultDataPaymentRate
class ForceCheckBlobFileConsistency(ControlHandler):
prompt_description = "Verify consistency of stored blobs"
def __init__(self, blob_manager):
self.blob_manager = blob_manager
def handle_line(self, line):
assert line is None, "Check consistency should not be passed any arguments"
return True, self._check_consistency()
def _check_consistency(self):
d = self.blob_manager.check_consistency()
d.addCallback(lambda _: "Finished checking stored blobs")
return d
class ForceCheckBlobFileConsistencyFactory(ControlHandlerFactory):
control_handler_class = ForceCheckBlobFileConsistency
class ModifyApplicationDefaults(RecursiveControlHandler):
prompt_description = "Modify application settings"
def __init__(self, lbry_service):
self.lbry_service = lbry_service
RecursiveControlHandler.__init__(self)
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(ControlHandlerFactory):
control_handler_class = ModifyApplicationDefaults
class ShowServerStatus(ControlHandler):
prompt_description = "Show the status of the server"
def __init__(self, lbry_service):
self.lbry_service = lbry_service
def handle_line(self, line):
assert line is None, "Show server status should not be passed any arguments"
return True, self._get_status()
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"
return defer.succeed(status_string)
class ShowServerStatusFactory(ControlHandlerFactory):
control_handler_class = ShowServerStatus
class StartServer(ControlHandler):
prompt_description = "Start the server"
def __init__(self, lbry_service):
self.lbry_service = lbry_service
def handle_line(self, line):
assert line is None, "Start server should not be passed any arguments"
d = self.lbry_service.start_server()
d.addCallback(lambda _: self.lbry_service.settings.save_server_running_status(running=True))
d.addCallback(lambda _: "Successfully started the server")
return True, d
class StartServerFactory(ControlHandlerFactory):
control_handler_class = StartServer
class StopServer(ControlHandler):
prompt_description = "Stop the server"
def __init__(self, lbry_service):
self.lbry_service = lbry_service
def handle_line(self, line):
assert line is None, "Stop server should not be passed any arguments"
d = self.lbry_service.stop_server()
d.addCallback(lambda _: self.lbry_service.settings.save_server_running_status(running=False))
d.addCallback(lambda _: "Successfully stopped the server")
return True, d
class StopServerFactory(ControlHandlerFactory):
control_handler_class = StopServer
class ModifyServerDataPaymentRate(ModifyPaymentRate):
prompt_description = "Modify server data payment rate"
def __init__(self, payment_rate_manager, settings):
ModifyPaymentRate.__init__(self)
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 True, d
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(ControlHandlerFactory):
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(ControlHandler):
def __init__(self, query_handlers, query_handler, settings):
self.query_handlers = query_handlers
self.query_handler = query_handler
self.settings = settings
def handle_line(self, line):
assert line is None, "DisableQueryHandler should not be passed any arguments"
self.query_handlers[self.query_handler] = False
d = self.settings.disable_query_handler(self.query_handler.get_primary_query_identifier())
d.addCallback(lambda _: "Disabled the query handler")
return True, d
class DisableQueryHandlerFactory(ControlHandlerFactory):
control_handler_class = DisableQueryHandler
def get_prompt_description(self):
query_handler = self.args[1]
return "Disable " + str(query_handler.get_description())
class EnableQueryHandler(ControlHandler):
def __init__(self, query_handlers, query_handler, settings):
self.query_handlers = query_handlers
self.query_handler = query_handler
self.settings = settings
def handle_line(self, line):
assert line is None, "EnableQueryHandler should not be passed any arguments"
self.query_handlers[self.query_handler] = True
d = self.settings.enable_query_handler(self.query_handler.get_primary_query_identifier())
d.addCallback(lambda _: "Enabled the query handler")
return True, d
class EnableQueryHandlerFactory(ControlHandlerFactory):
control_handler_class = EnableQueryHandler
def get_prompt_description(self):
query_handler = self.args[1]
return "Enable " + str(query_handler.get_description())
class ModifyServerEnabledQueries(RecursiveControlHandler):
prompt_description = "Modify which queries the server will respond to"
def __init__(self, query_handlers, settings):
self.query_handlers = query_handlers
self.settings = settings
RecursiveControlHandler.__init__(self, reset_after_each_done=True)
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(ControlHandlerFactory):
control_handler_class = ModifyServerEnabledQueries
class ImmediateAnnounceAllBlobs(ControlHandler):
prompt_description = "Immediately announce all blob hashes to the DHT"
def __init__(self, blob_manager):
self.blob_manager = blob_manager
def handle_line(self, line):
assert line is None, "Immediate Announce should not be passed any arguments"
d = self.blob_manager.immediate_announce_all_blobs()
d.addCallback(lambda _: "Done announcing")
return True, d
class ImmediateAnnounceAllBlobsFactory(ControlHandlerFactory):
control_handler_class = ImmediateAnnounceAllBlobs
class ModifyServerSettings(RecursiveControlHandler):
prompt_description = "Modify server settings"
def __init__(self, lbry_service):
self.lbry_service = lbry_service
RecursiveControlHandler.__init__(self, reset_after_each_done=True)
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(ControlHandlerFactory):
control_handler_class = ModifyServerSettings
class PeerChooser(RecursiveControlHandler):
def __init__(self, peer_manager, factory_class, *args, **kwargs):
"""
@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
RecursiveControlHandler.__init__(self, **kwargs)
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(ControlHandlerFactory):
def get_prompt_description(self):
peer = self.args[0]
return str(peer)
class ShowPeerStats(ControlHandler):
prompt_description = "Show the peer's stats"
def __init__(self, peer):
self.peer = peer
def handle_line(self, line):
return True, defer.succeed(self._get_peer_stats_string())
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(ControlHandlerFactory):
control_handler_class = ShowPeerStats
class PeerStatsAndSettings(RecursiveControlHandler):
def __init__(self, peer):
self.peer = peer
RecursiveControlHandler.__init__(self, reset_after_each_done=True)
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"
def __init__(self, peer_manager):
PeerChooser.__init__(self, peer_manager, PeerStatsAndSettingsFactory)
class PeerStatsAndSettingsChooserFactory(ControlHandlerFactory):
control_handler_class = PeerStatsAndSettingsChooser