diff --git a/lbrynet/conf.py b/lbrynet/conf.py index 736381c4a..fd5e24f54 100644 --- a/lbrynet/conf.py +++ b/lbrynet/conf.py @@ -51,4 +51,6 @@ SOURCE_TYPES = ['lbry_sd_hash', 'url', 'btih'] CURRENCIES = [ {'BTC': {'type': 'crypto'}}, {'LBC': {'type': 'crypto'}}, + {'USD': {'type': 'fiat'}}, + ] diff --git a/lbrynet/core/LBRYFee.py b/lbrynet/core/LBRYFee.py deleted file mode 100644 index 09b7dbd39..000000000 --- a/lbrynet/core/LBRYFee.py +++ /dev/null @@ -1,39 +0,0 @@ -import requests -import json - -from lbrynet.conf import CURRENCIES - - -class LBRYFee(object): - def __init__(self, currency, amount, address=None): - assert currency in [c.keys()[0] for c in CURRENCIES], "Unsupported currency: %s" % str(currency) - self.address = address - self.currency_symbol = currency - self.currency = [c for c in CURRENCIES if self.currency_symbol in c][0] - if not isinstance(amount, float): - self.amount = float(amount) - else: - self.amount = amount - - def convert_to(self, to_currency, rate_dict={}): - if to_currency is self.currency_symbol: - return self.as_dict() - if self.currency[self.currency_symbol]['type'] is 'fiat': - raise NotImplemented - else: - if to_currency not in rate_dict: - params = {'market': '%s-%s' % (self.currency_symbol, to_currency)} - r = requests.get("https://bittrex.com/api/v1.1/public/getticker", params) - last = json.loads(r.text)['result']['Last'] - converted = self.amount / float(last) - else: - converted = self.amount / float(rate_dict[to_currency]['last']) - - return LBRYFee(to_currency, converted, self.address).as_dict() - - def as_dict(self): - return {self.currency_symbol: {'amount': self.amount, 'address': self.address}} - - def from_dict(self, fee_dict): - s = fee_dict.keys()[0] - return LBRYFee(s, fee_dict[s]['amount'], fee_dict[s]['address']) \ No newline at end of file diff --git a/lbrynet/core/LBRYMetadata.py b/lbrynet/core/LBRYMetadata.py index 565a10785..efbb334de 100644 --- a/lbrynet/core/LBRYMetadata.py +++ b/lbrynet/core/LBRYMetadata.py @@ -1,13 +1,63 @@ +import requests import json -BASE_METADATA_FIELDS = ['title', 'description', 'author', 'language', 'license', 'content-type'] +from googlefinance import getQuotes +from lbrynet.conf import CURRENCIES + +SOURCE_TYPES = ['lbry_sd_hash', 'url', 'btih'] + +BASE_METADATA_FIELDS = ['title', 'description', 'author', 'language', 'license', 'content-type', 'sources'] OPTIONAL_METADATA_FIELDS = ['thumbnail', 'preview', 'fee', 'contact', 'pubkey'] #v0.0.1 metadata METADATA_REVISIONS = {'0.0.1': {'required': BASE_METADATA_FIELDS, 'optional': OPTIONAL_METADATA_FIELDS}} #v0.0.2 metadata additions -METADATA_REVISIONS['0.0.2'] = {'required': ['nsfw'], 'optional': []} +METADATA_REVISIONS['0.0.2'] = {'required': ['nsfw', 'ver'], 'optional': ['licence_url']} + +CURRENT_METADATA_VERSION = '0.0.2' + +class LBRYFee(object): + def __init__(self, currency, amount, address=None): + assert currency in [c.keys()[0] for c in CURRENCIES], "Unsupported currency: %s" % str(currency) + self.address = address + self.currency_symbol = currency + self.currency = next(c for c in CURRENCIES if self.currency_symbol in c) + if not isinstance(amount, float): + self.amount = float(amount) + else: + self.amount = amount + + def __call__(self): + return {self.currency_symbol: {'amount': self.amount, 'address': self.address}} + + def convert(self, amount_only=False): + if self.currency_symbol == "LBC": + r = round(float(self.amount), 5) + elif self.currency_symbol == "BTC": + r = round(float(self.BTC_to_LBC(self.amount)), 5) + elif self.currency_symbol == "USD": + r = round(float(self.BTC_to_LBC(self.USD_to_BTC(self.amount))), 5) + + if not amount_only: + return {'LBC': {'amount': r, 'address': self.address}} + else: + return r + + def USD_to_BTC(self, usd): + r = float(getQuotes('CURRENCY:%sBTC' % self.currency_symbol)[0]['LastTradePrice']) * float(usd) + return r + + def BTC_to_LBC(self, btc): + r = requests.get("https://bittrex.com/api/v1.1/public/getticker", {'market': 'BTC-LBC'}) + last = json.loads(r.text)['result']['Last'] + converted = float(btc) / float(last) + return converted + + +def fee_from_dict(fee_dict): + s = fee_dict.keys()[0] + return LBRYFee(s, fee_dict[s]['amount'], fee_dict[s]['address']) class Metadata(dict): @@ -15,6 +65,14 @@ class Metadata(dict): dict.__init__(self) self.metaversion = None m = metadata.copy() + + if 'fee' in metadata: + assert fee_from_dict(metadata['fee']) + + assert "sources" in metadata, "No sources given" + for source in metadata['sources']: + assert source in SOURCE_TYPES, "Unknown source type" + for version in METADATA_REVISIONS: for k in METADATA_REVISIONS[version]['required']: assert k in metadata, "Missing required metadata field: %s" % k @@ -25,4 +83,4 @@ class Metadata(dict): if not len(m): self.metaversion = version break - assert m == {}, "Unknown metadata keys: %s" % json.dumps(m.keys()) \ No newline at end of file + assert m == {}, "Unknown metadata keys: %s" % json.dumps(m.keys()) diff --git a/lbrynet/core/LBRYWallet.py b/lbrynet/core/LBRYWallet.py index e5f7f1889..278396186 100644 --- a/lbrynet/core/LBRYWallet.py +++ b/lbrynet/core/LBRYWallet.py @@ -4,7 +4,8 @@ from lbrynet.core.client.ClientRequest import ClientRequest from lbrynet.core.Error import UnknownNameError, InvalidStreamInfoError, RequestCanceledError from lbrynet.core.Error import InsufficientFundsError from lbrynet.core.sqlite_helpers import rerun_if_locked -from lbrynet.conf import BASE_METADATA_FIELDS, SOURCE_TYPES, OPTIONAL_METADATA_FIELDS +from lbrynet.conf import SOURCE_TYPES +from lbrynet.core.LBRYMetadata import Metadata from lbryum import SimpleConfig, Network from lbryum.lbrycrd import COIN, TYPE_ADDRESS @@ -316,7 +317,6 @@ class LBRYWallet(object): return d def _get_stream_info_from_value(self, result, name): - r_dict = {} if 'value' in result: value = result['value'] @@ -324,16 +324,11 @@ class LBRYWallet(object): value_dict = json.loads(value) except (ValueError, TypeError): return Failure(InvalidStreamInfoError(name)) - r_dict['sources'] = value_dict['sources'] - for field in BASE_METADATA_FIELDS: - r_dict[field] = value_dict[field] - for field in value_dict: - if field in OPTIONAL_METADATA_FIELDS: - r_dict[field] = value_dict[field] - + m = Metadata(value_dict) if 'txid' in result: - d = self._save_name_metadata(name, r_dict['sources']['lbry_sd_hash'], str(result['txid'])) - d.addCallback(lambda _: r_dict) + d = self._save_name_metadata(name, m['sources']['lbry_sd_hash'], str(result['txid'])) + d.addCallback(lambda _: log.info("lbry://%s complies with %s" % (name, m.metaversion))) + d.addCallback(lambda _: m) return d elif 'error' in result: log.warning("Got an error looking up a name: %s", result['error']) @@ -342,21 +337,14 @@ class LBRYWallet(object): log.warning("Got an error looking up a name: %s", json.dumps(result)) return Failure(UnknownNameError(name)) - def claim_name(self, name, bid, sources, metadata, fee=None): - value = {'sources': {}} - for k in SOURCE_TYPES: - if k in sources: - value['sources'][k] = sources[k] - if value['sources'] == {}: - return defer.fail("No source given") - if fee is not None: - if "LBC" in fee: - value['fee'] = {'LBC': {'amount': fee['LBC']['amount'], 'address': fee['LBC']['address']}} + def claim_name(self, name, bid, m): - d = self._send_name_claim(name, json.dumps(value), bid) + metadata = Metadata(m) + + d = self._send_name_claim(name, json.dumps(metadata), bid) def _save_metadata(txid): - d = self._save_name_metadata(name, value['sources']['lbry_sd_hash'], txid) + d = self._save_name_metadata(name, metadata['sources']['lbry_sd_hash'], txid) d.addCallback(lambda _: txid) return d diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 38c6b861a..fb510a0cc 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -54,8 +54,6 @@ from lbrynet.lbryfilemanager.LBRYFileManager import LBRYFileManager from lbrynet.lbryfile.LBRYFileMetadataManager import DBLBRYFileMetadataManager, TempLBRYFileMetadataManager # from lbryum import LOG_PATH as lbryum_log -log = logging.getLogger(__name__) - if sys.platform != "darwin": log_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") @@ -67,13 +65,12 @@ if not os.path.isdir(log_dir): lbrynet_log = os.path.join(log_dir, LOG_FILE_NAME) -log = logging.getLogger(__name__) - # TODO: configuring a logger on module import drastically reduces the # amount of control the caller of this code has over logging # # Better would be to configure all logging at runtime. handler = logging.handlers.RotatingFileHandler(lbrynet_log, maxBytes=2097152, backupCount=5) +log = logging.getLogger(__name__) log.addHandler(handler) log.setLevel(logging.INFO) @@ -1908,19 +1905,30 @@ class LBRYDaemon(jsonrpc.JSONRPC): Returns: Claim txid """ - # start(self, name, file_path, bid, metadata, fee=None, sources=None): + name = p['name'] bid = p['bid'] file_path = p['file_path'] - metadata = Metadata(p['metadata']) + metadata = p['metadata'] + + def _set_address(address): + metadata['fee']['address'] = address + return defer.succeed(None) + if 'fee' in p: - fee = LBRYFee.from_dict(p['fee']) + metadata['fee'] = p['fee'] + if 'address' not in metadata['fee']: + d = self.session.wallet.get_new_address() + d.addCallback(_set_address) + else: + d = defer.succeed(None) else: - fee = None + d = defer.succeed(None) + pub = Publisher(self.session, self.lbry_file_manager, self.session.wallet) - d = pub.start(name, file_path, bid, metadata, fee) + d.addCallback(lambda _: pub.start(name, file_path, bid, metadata)) d.addCallbacks(lambda msg: self._render_response(msg, OK_CODE), lambda err: self._render_response(err.getTraceback(), BAD_REQUEST)) diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index eddffa0b5..7ae0ac13f 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -11,7 +11,7 @@ from twisted.internet.task import LoopingCall from lbrynet.core.Error import InvalidStreamInfoError, InsufficientFundsError from lbrynet.core.PaymentRateManager import PaymentRateManager from lbrynet.core.StreamDescriptor import download_sd_blob -from lbrynet.core.LBRYFee import LBRYFee +from lbrynet.core.LBRYMetadata import Metadata, fee_from_dict from lbrynet.lbryfilemanager.LBRYFileDownloader import ManagedLBRYFileDownloaderFactory from lbrynet.conf import DEFAULT_TIMEOUT, LOG_FILE_NAME @@ -48,8 +48,7 @@ class GetStream(object): self.wallet = wallet self.resolved_name = None self.description = None - self.key_fee = None - self.key_fee_address = None + self.fee = None self.data_rate = data_rate self.name = None self.file_name = file_name @@ -59,7 +58,7 @@ class GetStream(object): self.sd_identifier = sd_identifier self.stream_hash = None self.max_key_fee = max_key_fee - self.stream_info = None + self.metadata = None self.stream_info_manager = None self.d = defer.Deferred(None) self.timeout = timeout @@ -87,22 +86,14 @@ class GetStream(object): def start(self, stream_info, name): self.resolved_name = name - self.stream_info = stream_info - if 'sources' in self.stream_info: - self.stream_hash = self.stream_info['sources']['lbry_sd_hash'] - else: - raise InvalidStreamInfoError(self.stream_info) - if 'description' in self.stream_info: - self.description = self.stream_info['description'] - if 'fee' in self.stream_info: - self.fee = LBRYFee.from_dict(stream_info['fee']) - else: - self.fee = None - if self.key_fee > self.max_key_fee: - log.info("Key fee %f above limit of %f didn't download lbry://%s" % (self.key_fee, self.max_key_fee, self.resolved_name)) - return defer.fail(None) - else: - pass + self.metadata = stream_info + self.stream_hash = self.metadata['sources']['lbry_sd_hash'] + + if 'fee' in self.metadata: + fee = fee_from_dict(self.metadata['fee']) + if fee.convert(amount_only=True) > self.max_key_fee: + log.info("Key fee %f above limit of %f didn't download lbry://%s" % (self.key_fee, self.max_key_fee, self.resolved_name)) + return defer.fail(None) def _cause_timeout(): self.timeout_counter = self.timeout * 2 @@ -132,8 +123,8 @@ class GetStream(object): def _start_download(self, downloader): def _pay_key_fee(): - 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 self.fee is not None: + reserved_points = self.wallet.reserve_points(self.fee.address, self.fee.convert(amount_only=True)) if reserved_points is None: return defer.fail(InsufficientFundsError()) log.info("Key fee: %f --> %s" % (self.key_fee, self.key_fee_address)) diff --git a/lbrynet/lbrynet_daemon/LBRYPublisher.py b/lbrynet/lbrynet_daemon/LBRYPublisher.py index bd5c50e4b..10f787209 100644 --- a/lbrynet/lbrynet_daemon/LBRYPublisher.py +++ b/lbrynet/lbrynet_daemon/LBRYPublisher.py @@ -10,6 +10,7 @@ from lbrynet.core.Error import InsufficientFundsError from lbrynet.lbryfilemanager.LBRYFileCreator import create_lbry_file from lbrynet.lbryfile.StreamDescriptor import publish_sd_blob from lbrynet.core.PaymentRateManager import PaymentRateManager +from lbrynet.core.LBRYMetadata import Metadata, CURRENT_METADATA_VERSION from lbrynet.lbryfilemanager.LBRYFileDownloader import ManagedLBRYFileDownloader from lbrynet.conf import LOG_FILE_NAME from twisted.internet import threads, defer @@ -42,10 +43,9 @@ class Publisher(object): self.verified = False self.lbry_file = None self.txid = None - self.sources = {} - self.fee = None + self.metadata = {} - def start(self, name, file_path, bid, metadata, fee=None, sources={}): + def start(self, name, file_path, bid, metadata): def _show_result(): log.info("Published %s --> lbry://%s txid: %s", self.file_name, self.publish_name, self.txid) @@ -54,7 +54,6 @@ class Publisher(object): self.publish_name = name self.file_path = file_path self.bid_amount = bid - self.fee = fee self.metadata = metadata d = self._check_file_path(self.file_path) @@ -75,16 +74,6 @@ class Publisher(object): 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 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) @@ -102,7 +91,9 @@ class Publisher(object): self.lbry_file.stream_hash) def set_sd_hash(sd_hash): - self.sources['lbry_sd_hash'] = sd_hash + if 'sources' not in self.metadata: + self.metadata['sources'] = {} + self.metadata['sources']['lbry_sd_hash'] = sd_hash d.addCallback(set_sd_hash) return d @@ -110,11 +101,10 @@ class Publisher(object): def _claim_name(self): self.metadata['content-type'] = mimetypes.guess_type(os.path.join(self.lbry_file.download_directory, self.lbry_file.file_name))[0] + self.metadata['ver'] = CURRENT_METADATA_VERSION d = self.wallet.claim_name(self.publish_name, self.bid_amount, - self.sources, - self.metadata, - fee=self.fee) + Metadata(self.metadata)) def set_tx_hash(txid): self.txid = txid