From 9790e9f8432df60b09d0e709b52409ac6fed6622 Mon Sep 17 00:00:00 2001 From: Jimmy Kiselak Date: Sun, 20 Sep 2015 00:49:29 -0400 Subject: [PATCH] put more fields into the value of a name claim, and fix bug where creating a plain stream descriptor file fails if no file name is given --- lbrynet/core/LBRYcrdWallet.py | 15 +- lbrynet/lbryfile/LBRYFileMetadataManager.py | 13 +- lbrynet/lbryfile/StreamDescriptor.py | 10 +- lbrynet/lbrylive/StreamDescriptor.py | 5 +- lbrynet/lbrynet_console/ControlHandlers.py | 217 +++++++++++++++++--- lbrynet/lbrynet_console/LBRYConsole.py | 3 +- 6 files changed, 231 insertions(+), 32 deletions(-) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index 30f7fa58a..3c3f69c41 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -245,9 +245,18 @@ class LBRYcrdWallet(object): d.addCallback(get_stream_info_from_value) return d - def claim_name(self, stream_hash, name, amount): - value = json.dumps({"stream_hash": stream_hash}) - d = threads.deferToThread(self._claim_name, name, value, amount) + def claim_name(self, name, sd_hash, amount, stream_length=None, description=None, key_fee=None, + key_fee_address=None): + value = {"stream_hash": sd_hash} + if stream_length is not None: + value['stream_length'] = stream_length + if description is not None: + value['description'] = description + if key_fee is not None: + value['key_fee'] = key_fee + if key_fee_address is not None: + value['key_fee_address'] = key_fee_address + d = threads.deferToThread(self._claim_name, name, json.dumps(value), amount) return d def get_available_balance(self): diff --git a/lbrynet/lbryfile/LBRYFileMetadataManager.py b/lbrynet/lbryfile/LBRYFileMetadataManager.py index 8de86435d..45612f9d7 100644 --- a/lbrynet/lbryfile/LBRYFileMetadataManager.py +++ b/lbrynet/lbryfile/LBRYFileMetadataManager.py @@ -223,11 +223,20 @@ class DBLBRYFileMetadataManager(object): @rerun_if_locked def _save_sd_blob_hash_to_stream(self, stream_hash, sd_blob_hash): - return self.db_conn.runQuery("insert into lbry_file_descriptors values (?, ?)", - (sd_blob_hash, stream_hash)) + log.info("Saving sd blob hash %s to stream hash %s", str(sd_blob_hash), str(stream_hash)) + d = self.db_conn.runQuery("insert into lbry_file_descriptors values (?, ?)", + (sd_blob_hash, stream_hash)) + + def ignore_duplicate(err): + err.trap(sqlite3.IntegrityError) + log.info("sd blob hash already known") + + d.addErrback(ignore_duplicate) + return d @rerun_if_locked def _get_sd_blob_hashes_for_stream(self, stream_hash): + log.info("Looking up sd blob hashes for stream hash %s", str(stream_hash)) d = self.db_conn.runQuery("select sd_blob_hash from lbry_file_descriptors where stream_hash = ?", (stream_hash,)) d.addCallback(lambda results: [r[0] for r in results]) diff --git a/lbrynet/lbryfile/StreamDescriptor.py b/lbrynet/lbryfile/StreamDescriptor.py index 0dcfb8761..be802fec2 100644 --- a/lbrynet/lbryfile/StreamDescriptor.py +++ b/lbrynet/lbryfile/StreamDescriptor.py @@ -132,9 +132,15 @@ class LBRYFileStreamDescriptorValidator(object): size_so_far = 0 for blob_info in self.raw_info.get("blobs", []): size_so_far += int(blob_info['length']) - info.append(("stream_size", str(size_so_far))) + info.append(("stream_size", str(self.get_length_of_stream()))) suggested_file_name = self.raw_info.get("suggested_file_name", None) if suggested_file_name is not None: suggested_file_name = binascii.unhexlify(suggested_file_name) info.append(("suggested_file_name", suggested_file_name)) - return info \ No newline at end of file + return info + + def get_length_of_stream(self): + size_so_far = 0 + for blob_info in self.raw_info.get("blobs", []): + size_so_far += int(blob_info['length']) + return size_so_far \ No newline at end of file diff --git a/lbrynet/lbrylive/StreamDescriptor.py b/lbrynet/lbrylive/StreamDescriptor.py index 7fad48973..07f9a40be 100644 --- a/lbrynet/lbrylive/StreamDescriptor.py +++ b/lbrynet/lbrylive/StreamDescriptor.py @@ -131,4 +131,7 @@ class LBRYLiveStreamDescriptorValidator(object): def info_to_show(self): info = [] info.append(("stream_name", binascii.unhexlify(self.raw_info.get("stream_name")))) - return info \ No newline at end of file + return info + + def get_length_of_stream(self): + return None \ No newline at end of file diff --git a/lbrynet/lbrynet_console/ControlHandlers.py b/lbrynet/lbrynet_console/ControlHandlers.py index 86302435f..18c75da68 100644 --- a/lbrynet/lbrynet_console/ControlHandlers.py +++ b/lbrynet/lbrynet_console/ControlHandlers.py @@ -6,9 +6,9 @@ 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 +from lbrynet.core.StreamDescriptor import download_sd_blob, BlobStreamDescriptorReader from lbrynet.core.Error import UnknownNameError, InvalidBlobHashError, InsufficientFundsError -from twisted.internet import defer +from twisted.internet import defer, threads import os @@ -54,7 +54,7 @@ class RecursiveControlHandler(ControlHandler): self._set_control_handlers() def _get_control_handler_factories(self): - pass + raise NotImplementedError() def _set_control_handlers(self): self.control_handlers = {i + 1: handler for i, handler in enumerate(self._get_control_handler_factories())} @@ -813,7 +813,6 @@ class CreatePlainStreamDescriptorChooserFactory(ControlHandlerFactory): class CreatePlainStreamDescriptor(ControlHandler): prompt_description = "Create a plain stream descriptor file for an LBRY File" - line_prompt = "Stream Descriptor file name (blank for default):" def __init__(self, lbry_file, lbry_file_manager): self.lbry_file = lbry_file @@ -822,11 +821,20 @@ class CreatePlainStreamDescriptor(ControlHandler): def handle_line(self, line): if line is None: - return False, defer.succeed(self.line_prompt) + return False, self._get_file_name_prompt() self.sd_file_name = line - return True, self._create_sd() + d = threads.deferToThread(self._get_file_name) + d.addCallback(self._create_sd) + return True, d - def _create_sd(self): + 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: @@ -839,6 +847,9 @@ class CreatePlainStreamDescriptor(ControlHandler): 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) @@ -937,31 +948,191 @@ class ModifyLBRYFileOptionsFactory(LBRYFileChooserFactory): class ClaimName(ControlHandler): - prompt_description = "Associate a short name with a stream descriptor hash" - line_prompt = "Stream descriptor hash:" - line_prompt_2 = "Short name:" - line_prompt_3 = "Amount:" + 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, name_resolver): - self.name_resolver = name_resolver + 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.short_name = None - self.amount = 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.line_prompt) + 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.line_prompt_2) - if self.short_name is None: - self.short_name = line - return False, defer.succeed(self.line_prompt_3) - self.amount = line - return True, self._claim_name() + 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): - return self.name_resolver.claim_name(self.sd_hash, self.short_name, float(self.amount)) + 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): diff --git a/lbrynet/lbrynet_console/LBRYConsole.py b/lbrynet/lbrynet_console/LBRYConsole.py index 261d301e7..09cd77e65 100644 --- a/lbrynet/lbrynet_console/LBRYConsole.py +++ b/lbrynet/lbrynet_console/LBRYConsole.py @@ -305,7 +305,8 @@ class LBRYConsole(): AddStreamFromLBRYcrdNameFactory(self.sd_identifier, self.session, self.session.wallet)), ('General', - ClaimNameFactory(self.session.wallet)), + ClaimNameFactory(self.session. wallet, self.lbry_file_manager, + self.session.blob_manager, self.sd_identifier)), ('General', GetNewWalletAddressFactory(self.session.wallet)) ]