diff --git a/lbrynet/core/LBRYMetadata.py b/lbrynet/core/LBRYMetadata.py deleted file mode 100644 index 5c101078b..000000000 --- a/lbrynet/core/LBRYMetadata.py +++ /dev/null @@ -1,135 +0,0 @@ -import json - -from copy import deepcopy -from lbrynet.conf import CURRENCIES -from lbrynet.core import utils -import logging - -log = logging.getLogger(__name__) - -BITTREX_FEE = 0.0025 - -# Metadata version -SOURCE_TYPES = ['lbry_sd_hash', 'url', 'btih'] -NAME_ALLOWED_CHARSET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0987654321-' -BASE_METADATA_FIELDS = ['title', 'description', 'author', 'language', 'license', 'content-type', 'sources'] -OPTIONAL_METADATA_FIELDS = ['thumbnail', 'preview', 'fee', 'contact', 'pubkey'] - -MV001 = "0.0.1" -MV002 = "0.0.2" -CURRENT_METADATA_VERSION = MV002 - -METADATA_REVISIONS = {} -METADATA_REVISIONS[MV001] = {'required': BASE_METADATA_FIELDS, 'optional': OPTIONAL_METADATA_FIELDS} -METADATA_REVISIONS[MV002] = {'required': ['nsfw', 'ver'], 'optional': ['license_url']} - -# Fee version -BASE_FEE_FIELDS = ['amount', 'address'] - -FV001 = "0.0.1" -CURRENT_FEE_REVISION = FV001 - -FEE_REVISIONS = {} -FEE_REVISIONS[FV001] = {'required': BASE_FEE_FIELDS, 'optional': []} - - -def verify_name_characters(name): - for c in name: - assert c in NAME_ALLOWED_CHARSET, "Invalid character" - return True - - -class LBRYFeeValidator(dict): - def __init__(self, fee_dict): - dict.__init__(self) - assert len(fee_dict) == 1 - self.fee_version = None - self.currency_symbol = None - - fee_to_load = deepcopy(fee_dict) - - for currency in fee_dict: - self._verify_fee(currency, fee_to_load) - - self.amount = self._get_amount() - self.address = self[self.currency_symbol]['address'] - - def _get_amount(self): - amt = self[self.currency_symbol]['amount'] - if isinstance(amt, float): - return amt - else: - try: - return float(amt) - except TypeError: - log.error('Failed to convert %s to float', amt) - raise - - def _verify_fee(self, currency, f): - # str in case someone made a claim with a wierd fee - assert currency in CURRENCIES, "Unsupported currency: %s" % str(currency) - self.currency_symbol = currency - self.update({currency: {}}) - for version in FEE_REVISIONS: - self._load_revision(version, f) - if not f: - self.fee_version = version - break - assert f[self.currency_symbol] == {}, "Unknown fee keys: %s" % json.dumps(f.keys()) - - def _load_revision(self, version, f): - for k in FEE_REVISIONS[version]['required']: - assert k in f[self.currency_symbol], "Missing required fee field: %s" % k - self[self.currency_symbol].update({k: f[self.currency_symbol].pop(k)}) - for k in FEE_REVISIONS[version]['optional']: - if k in f[self.currency_symbol]: - self[self.currency_symbol].update({k: f[self.currency_symbol].pop(k)}) - - -class Metadata(dict): - @classmethod - def load_from_hex(cls, metadata): - return cls(json.loads(metadata.decode('hex'))) - - def __init__(self, metadata): - dict.__init__(self) - self.meta_version = None - metadata_to_load = deepcopy(metadata) - - self._verify_sources(metadata_to_load) - self._verify_metadata(metadata_to_load) - - def _load_revision(self, version, metadata): - for k in METADATA_REVISIONS[version]['required']: - assert k in metadata, "Missing required metadata field: %s" % k - self.update({k: metadata.pop(k)}) - for k in METADATA_REVISIONS[version]['optional']: - if k == 'fee': - self._load_fee(metadata) - elif k in metadata: - self.update({k: metadata.pop(k)}) - - def _load_fee(self, metadata): - if 'fee' in metadata: - self['fee'] = LBRYFeeValidator(metadata.pop('fee')) - - def _verify_sources(self, metadata): - assert "sources" in metadata, "No sources given" - for source in metadata['sources']: - assert source in SOURCE_TYPES, "Unknown source type" - - def _verify_metadata(self, metadata): - for version in METADATA_REVISIONS: - self._load_revision(version, metadata) - if not metadata: - self.meta_version = version - if utils.version_is_greater_than(self.meta_version, "0.0.1"): - assert self.meta_version == self['ver'], "version mismatch" - break - assert metadata == {}, "Unknown metadata keys: %s" % json.dumps(metadata.keys()) - - def serialize(self): - return json.dumps(self).encode("hex") - - def as_json(self): - return json.dumps(self) diff --git a/lbrynet/core/LBRYWallet.py b/lbrynet/core/LBRYWallet.py index 571f981d6..53a7a961e 100644 --- a/lbrynet/core/LBRYWallet.py +++ b/lbrynet/core/LBRYWallet.py @@ -25,7 +25,7 @@ from lbrynet.interfaces import IRequestCreator, IQueryHandlerFactory, IQueryHand from lbrynet.core.client.ClientRequest import ClientRequest from lbrynet.core.Error import UnknownNameError, InvalidStreamInfoError, RequestCanceledError from lbrynet.core.Error import InsufficientFundsError -from lbrynet.core.LBRYMetadata import Metadata +from lbrynet.metadata.LBRYMetadata import Metadata log = logging.getLogger(__name__) alert = logging.getLogger("lbryalert." + __name__) @@ -322,7 +322,7 @@ class LBRYWallet(object): assert k in r, "getvalueforname response missing field %s" % k def _log_success(claim_id): - log.info("lbry://%s complies with %s, claimid: %s", name, metadata.meta_version, claim_id) + log.info("lbry://%s complies with %s, claimid: %s", name, metadata.version, claim_id) return defer.succeed(None) if 'error' in result: @@ -377,7 +377,7 @@ class LBRYWallet(object): result = {} try: metadata = Metadata(json.loads(claim['value'])) - meta_ver = metadata.meta_version + meta_ver = metadata.version sd_hash = metadata['sources']['lbry_sd_hash'] d = self._save_name_metadata(name, txid, sd_hash) except AssertionError: @@ -1256,7 +1256,7 @@ class LBRYumWallet(LBRYWallet): def _send_name_claim_update(self, name, claim_id, txid, value, amount): def send_claim_update(address): decoded_claim_id = claim_id.decode('hex')[::-1] - metadata = Metadata(value).as_json() + metadata = json.dumps(Metadata(value)) log.info("updateclaim %s %s %f %s %s '%s'", txid, address, amount, name, decoded_claim_id.encode('hex'), json.dumps(metadata)) cmd = known_commands['updateclaim'] func = getattr(self.cmd_runner, cmd.name) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index a2db34560..768e62a69 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -40,10 +40,9 @@ from lbrynet.lbrynet_daemon.LBRYDownloader import GetStream from lbrynet.lbrynet_daemon.LBRYPublisher import Publisher from lbrynet.lbrynet_daemon.LBRYExchangeRateManager import ExchangeRateManager from lbrynet.lbrynet_daemon.Lighthouse import LighthouseClient -from lbrynet.core.LBRYMetadata import Metadata +from lbrynet.metadata.LBRYMetadata import Metadata, verify_name_characters from lbrynet.core import log_support from lbrynet.core import utils -from lbrynet.core.LBRYMetadata import verify_name_characters from lbrynet.core.utils import generate_id from lbrynet.lbrynet_console.LBRYSettings import LBRYSettings from lbrynet.conf import MIN_BLOB_DATA_PAYMENT_RATE, DEFAULT_MAX_SEARCH_RESULTS, \ diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index 94c3b5f4f..20b89078e 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 InsufficientFundsError, KeyFeeAboveMaxAllowed from lbrynet.core.PaymentRateManager import PaymentRateManager from lbrynet.core.StreamDescriptor import download_sd_blob -from lbrynet.core.LBRYMetadata import LBRYFeeValidator +from lbrynet.metadata.LBRYFee import LBRYFeeValidator from lbrynet.lbryfilemanager.LBRYFileDownloader import ManagedLBRYFileDownloaderFactory from lbrynet.conf import DEFAULT_TIMEOUT, LOG_FILE_NAME diff --git a/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py b/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py index 22d0cb4a2..e896c8b0f 100644 --- a/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py +++ b/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py @@ -6,7 +6,7 @@ import googlefinance from twisted.internet import defer, reactor from twisted.internet.task import LoopingCall -from lbrynet.core.LBRYMetadata import LBRYFeeValidator +from lbrynet.metadata.LBRYFee import LBRYFeeValidator log = logging.getLogger(__name__) diff --git a/lbrynet/lbrynet_daemon/LBRYPublisher.py b/lbrynet/lbrynet_daemon/LBRYPublisher.py index ec7b908dc..f162ca24c 100644 --- a/lbrynet/lbrynet_daemon/LBRYPublisher.py +++ b/lbrynet/lbrynet_daemon/LBRYPublisher.py @@ -10,7 +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.metadata.LBRYMetadata import Metadata from lbrynet.lbryfilemanager.LBRYFileDownloader import ManagedLBRYFileDownloader from lbrynet import reflector from lbrynet.conf import LOG_FILE_NAME, REFLECTOR_SERVERS @@ -143,8 +143,8 @@ class Publisher(object): def _update_metadata(self): filename = os.path.join(self.lbry_file.download_directory, self.lbry_file.file_name) - self.metadata['content-type'] = get_content_type(filename) - self.metadata['ver'] = CURRENT_METADATA_VERSION + self.metadata['content_type'] = get_content_type(filename) + self.metadata['ver'] = Metadata.current_version def _show_publish_error(self, err): log.info(err.getTraceback()) diff --git a/lbrynet/metadata/LBRYFee.py b/lbrynet/metadata/LBRYFee.py new file mode 100644 index 000000000..7954135f7 --- /dev/null +++ b/lbrynet/metadata/LBRYFee.py @@ -0,0 +1,116 @@ +import logging + +from lbrynet.metadata.Validator import Validator, skip_validate +from lbrynet.conf import CURRENCIES + +log = logging.getLogger(__name__) + + +def verify_supported_currency(fee): + assert len(fee) == 1 + for c in fee: + assert c in CURRENCIES + return True + + +def verify_amount(x): + return isinstance(x, float) and x > 0 + + +class LBCFeeValidator(Validator): + FV001 = "0.0.1" + CURRENT_FEE_VERSION = FV001 + + FEE_REVISIONS = {} + + FEE_REVISIONS[FV001] = [ + (Validator.REQUIRE, 'amount', verify_amount), + (Validator.REQUIRE, 'address', skip_validate), + ] + + FEE_MIGRATIONS = [] + + current_version = CURRENT_FEE_VERSION + versions = FEE_REVISIONS + migrations = FEE_MIGRATIONS + + def __init__(self, fee): + Validator.__init__(self, fee) + + +class BTCFeeValidator(Validator): + FV001 = "0.0.1" + CURRENT_FEE_VERSION = FV001 + + FEE_REVISIONS = {} + + FEE_REVISIONS[FV001] = [ + (Validator.REQUIRE, 'amount',verify_amount), + (Validator.REQUIRE, 'address', skip_validate), + ] + + FEE_MIGRATIONS = [] + + current_version = CURRENT_FEE_VERSION + versions = FEE_REVISIONS + migrations = FEE_MIGRATIONS + + def __init__(self, fee): + Validator.__init__(self, fee) + + +class USDFeeValidator(Validator): + FV001 = "0.0.1" + CURRENT_FEE_VERSION = FV001 + + FEE_REVISIONS = {} + + FEE_REVISIONS[FV001] = [ + (Validator.REQUIRE, 'amount',verify_amount), + (Validator.REQUIRE, 'address', skip_validate), + ] + + FEE_MIGRATIONS = [] + + current_version = CURRENT_FEE_VERSION + versions = FEE_REVISIONS + migrations = FEE_MIGRATIONS + + def __init__(self, fee): + Validator.__init__(self, fee) + + +class LBRYFeeValidator(Validator): + CV001 = "0.0.1" + CURRENT_CURRENCY_VERSION = CV001 + + CURRENCY_REVISIONS = {} + + CURRENCY_REVISIONS[CV001] = [ + (Validator.OPTIONAL, 'BTC', BTCFeeValidator.validate), + (Validator.OPTIONAL, 'USD', USDFeeValidator.validate), + (Validator.OPTIONAL, 'LBC', LBCFeeValidator.validate), + ] + + CURRENCY_MIGRATIONS = [] + + current_version = CURRENT_CURRENCY_VERSION + versions = CURRENCY_REVISIONS + migrations = CURRENCY_MIGRATIONS + + def __init__(self, fee_dict): + Validator.__init__(self, fee_dict) + self.currency_symbol = self.keys()[0] + self.amount = self._get_amount() + self.address = self[self.currency_symbol]['address'] + + def _get_amount(self): + amt = self[self.currency_symbol]['amount'] + if isinstance(amt, float): + return amt + else: + try: + return float(amt) + except TypeError: + log.error('Failed to convert %s to float', amt) + raise diff --git a/lbrynet/metadata/LBRYMetadata.py b/lbrynet/metadata/LBRYMetadata.py new file mode 100644 index 000000000..3bd477ee1 --- /dev/null +++ b/lbrynet/metadata/LBRYMetadata.py @@ -0,0 +1,86 @@ +import logging + +from lbrynet.metadata.Validator import Validator, skip_validate +from lbrynet.metadata.LBRYFee import LBRYFeeValidator, verify_supported_currency +from lbrynet.conf import SOURCE_TYPES + +log = logging.getLogger(__name__) +NAME_ALLOWED_CHARSET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0987654321-' + + +def verify_name_characters(name): + for c in name: + assert c in NAME_ALLOWED_CHARSET, "Invalid character" + return True + + +def validate_sources(sources): + for source in sources: + assert source in SOURCE_TYPES, "Unknown source type: %s" % str(source) + return True + + +class Metadata(Validator): + MV001 = "0.0.1" + MV002 = "0.0.2" + MV003 = "0.0.3" + CURRENT_METADATA_VERSION = MV003 + + METADATA_REVISIONS = {} + + METADATA_REVISIONS[MV001] = [ + (Validator.REQUIRE, 'title', skip_validate), + (Validator.REQUIRE, 'description', skip_validate), + (Validator.REQUIRE, 'author', skip_validate), + (Validator.REQUIRE, 'language', skip_validate), + (Validator.REQUIRE, 'license', skip_validate), + (Validator.REQUIRE, 'content-type', skip_validate), + (Validator.REQUIRE, 'sources', validate_sources), + (Validator.OPTIONAL, 'thumbnail', skip_validate), + (Validator.OPTIONAL, 'preview', skip_validate), + (Validator.OPTIONAL, 'fee', verify_supported_currency), + (Validator.OPTIONAL, 'contact', skip_validate), + (Validator.OPTIONAL, 'pubkey', skip_validate), + ] + + METADATA_REVISIONS[MV002] = [ + (Validator.REQUIRE, 'nsfw', skip_validate), + (Validator.REQUIRE, 'ver', skip_validate), + (Validator.OPTIONAL, 'license_url', skip_validate), + ] + + METADATA_REVISIONS[MV003] = [ + (Validator.REQUIRE, 'content_type', skip_validate), + (Validator.SKIP, 'content-type'), + (Validator.OPTIONAL, 'sig', skip_validate), + (Validator.IF_KEY, 'sig', (Validator.REQUIRE, 'pubkey', skip_validate), Validator.DO_NOTHING), + (Validator.IF_KEY, 'pubkey', (Validator.REQUIRE, 'sig', skip_validate), Validator.DO_NOTHING), + ] + + MIGRATE_MV001_TO_MV002 = [ + (Validator.IF_KEY, 'nsfw', Validator.DO_NOTHING, (Validator.LOAD, 'nsfw', False)), + (Validator.IF_KEY, 'ver', Validator.DO_NOTHING, (Validator.LOAD, 'ver', MV002)), + ] + + MIGRATE_MV002_TO_MV003 = [ + (Validator.IF_KEY, 'content-type', (Validator.UPDATE, 'content-type', 'content_type'), Validator.DO_NOTHING), + (Validator.IF_VAL, 'ver', MV002, (Validator.LOAD, 'ver', MV003), Validator.DO_NOTHING), + ] + + METADATA_MIGRATIONS = [ + MIGRATE_MV001_TO_MV002, + MIGRATE_MV002_TO_MV003, + ] + + current_version = CURRENT_METADATA_VERSION + versions = METADATA_REVISIONS + migrations = METADATA_MIGRATIONS + + def __init__(self, metadata, process_now=True): + Validator.__init__(self, metadata, process_now) + self.meta_version = self.get('ver', Metadata.MV001) + self._load_fee() + + def _load_fee(self): + if 'fee' in self: + self.update({'fee': LBRYFeeValidator(self['fee'])}) diff --git a/lbrynet/metadata/Validator.py b/lbrynet/metadata/Validator.py new file mode 100644 index 000000000..b08ed64e2 --- /dev/null +++ b/lbrynet/metadata/Validator.py @@ -0,0 +1,155 @@ +import json +import logging +from copy import deepcopy +from distutils.version import StrictVersion +from lbrynet.core.utils import version_is_greater_than + +log = logging.getLogger(__name__) + + +def skip_validate(value): + return True + + +def processor(cls): + for methodname in dir(cls): + method = getattr(cls, methodname) + if hasattr(method, 'cmd_name'): + cls.commands.update({method.cmd_name: methodname}) + return cls + + +def cmd(cmd_name): + def wrapper(func): + func.cmd_name = cmd_name + return func + return wrapper + + +@processor +class Validator(dict): + """ + Base class for validated dictionaries + """ + + # override these + current_version = None + versions = {} + migrations = [] + + # built in commands + DO_NOTHING = "do_nothing" + UPDATE = "update_key" + IF_KEY = "if_key" + REQUIRE = "require" + SKIP = "skip" + OPTIONAL = "optional" + LOAD = "load" + IF_VAL = "if_val" + + commands = {} + + @classmethod + def load_from_hex(cls, hex_val): + return cls(json.loads(hex_val.decode('hex'))) + + @classmethod + def validate(cls, value): + if cls(value): + return True + else: + return False + + def __init__(self, value, process_now=False): + dict.__init__(self) + self._skip = [] + value_to_load = deepcopy(value) + if process_now: + self.process(value_to_load) + self._verify_value(value_to_load) + self.version = self.get('ver', "0.0.1") + + def process(self, value): + if self.migrations is not None: + self._migrate_value(value) + + @cmd(DO_NOTHING) + def _do_nothing(self): + pass + + @cmd(SKIP) + def _add_to_skipped(self, rx_value, key): + if key not in self._skip: + self._skip.append(key) + + @cmd(UPDATE) + def _update(self, rx_value, old_key, new_key): + rx_value.update({new_key: rx_value.pop(old_key)}) + + @cmd(IF_KEY) + def _if_key(self, rx_value, key, if_true, if_else): + if key in rx_value: + return self._handle(if_true, rx_value) + return self._handle(if_else, rx_value) + + @cmd(IF_VAL) + def _if_val(self, rx_value, key, val, if_true, if_else): + if key in rx_value: + if rx_value[key] == val: + return self._handle(if_true, rx_value) + return self._handle(if_else, rx_value) + + @cmd(LOAD) + def _load(self, rx_value, key, value): + rx_value.update({key: value}) + + @cmd(REQUIRE) + def _require(self, rx_value, key, validator=None): + if key not in self._skip: + assert key in rx_value, "Key is missing: %s" % key + if isinstance(validator, type): + assert isinstance(rx_value[key], validator), "%s: %s isn't required %s" % (key, type(rx_value[key]), validator) + elif callable(validator): + assert validator(rx_value[key]), "Failed to validate %s" % key + self.update({key: rx_value.pop(key)}) + + @cmd(OPTIONAL) + def _optional(self, rx_value, key, validator=None): + if key in rx_value and key not in self._skip: + if isinstance(validator, type): + assert isinstance(rx_value[key], validator), "%s type %s isn't required %s" % (key, type(rx_value[key]), validator) + elif callable(validator): + assert validator(rx_value[key]), "Failed to validate %s" % key + self.update({key: rx_value.pop(key)}) + + def _handle(self, cmd_tpl, value): + if cmd_tpl == Validator.DO_NOTHING: + return + command = cmd_tpl[0] + f = getattr(self, self.commands[command]) + if len(cmd_tpl) > 1: + args = (value,) + cmd_tpl[1:] + f(*args) + else: + f() + + def _load_revision(self, version, value): + for k in self.versions[version]: + self._handle(k, value) + + def _verify_value(self, value): + val_ver = value.get('ver', "0.0.1") + # verify version requirements in reverse order starting from the version asserted in the value + versions = sorted([v for v in self.versions if not version_is_greater_than(v, val_ver)], key=StrictVersion, reverse=True) + for version in versions: + self._load_revision(version, value) + assert value == {} or value.keys() == self._skip, "Unknown keys: %s" % json.dumps(value) + + def _migrate_value(self, value): + for migration in self.migrations: + self._run_migration(migration, value) + + def _run_migration(self, commands, value): + for cmd in commands: + self._handle(cmd, value) + diff --git a/lbrynet/metadata/__init__.py b/lbrynet/metadata/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/unit/core/test_LBRYExchangeRateManager.py b/tests/unit/core/test_LBRYExchangeRateManager.py index e07b49194..68596fc38 100644 --- a/tests/unit/core/test_LBRYExchangeRateManager.py +++ b/tests/unit/core/test_LBRYExchangeRateManager.py @@ -1,5 +1,5 @@ import mock -from lbrynet.core import LBRYMetadata +from lbrynet.metadata import LBRYMetadata from lbrynet.lbrynet_daemon import LBRYExchangeRateManager from twisted.trial import unittest diff --git a/tests/unit/core/test_LBRYMetadata.py b/tests/unit/core/test_LBRYMetadata.py index e5c3255b3..f76a05f4e 100644 --- a/tests/unit/core/test_LBRYMetadata.py +++ b/tests/unit/core/test_LBRYMetadata.py @@ -1,13 +1,26 @@ -from lbrynet.core import LBRYMetadata +from lbrynet.metadata import LBRYMetadata from twisted.trial import unittest class MetadataTest(unittest.TestCase): - def test_assertion_if_source_is_missing(self): + def test_assertion_if_no_metadata(self): metadata = {} with self.assertRaises(AssertionError): LBRYMetadata.Metadata(metadata) + def test_assertion_if_source_is_missing(self): + metadata = { + 'license': 'Oscilloscope Laboratories', + 'description': 'Four couples meet for Sunday brunch only to discover they are stuck in a house together as the world may be about to end.', + 'language': 'en', + 'title': "It's a Disaster", + 'author': 'Written and directed by Todd Berger', + 'content-type': 'audio/mpeg', + 'thumbnail': 'http://ia.media-imdb.com/images/M/MV5BMTQwNjYzMTQ0Ml5BMl5BanBnXkFtZTcwNDUzODM5Nw@@._V1_SY1000_CR0,0,673,1000_AL_.jpg', + } + with self.assertRaises(AssertionError): + LBRYMetadata.Metadata(metadata) + def test_metadata_works_without_fee(self): metadata = { 'license': 'Oscilloscope Laboratories', @@ -18,15 +31,14 @@ class MetadataTest(unittest.TestCase): 'sources': { 'lbry_sd_hash': '8d0d6ea64d09f5aa90faf5807d8a761c32a27047861e06f81f41e35623a348a4b0104052161d5f89cf190f9672bc4ead'}, 'content-type': 'audio/mpeg', - 'thumbnail': 'http://ia.media-imdb.com/images/M/MV5BMTQwNjYzMTQ0Ml5BMl5BanBnXkFtZTcwNDUzODM5Nw@@._V1_SY1000_CR0,0,673,1000_AL_.jpg' + 'thumbnail': 'http://ia.media-imdb.com/images/M/MV5BMTQwNjYzMTQ0Ml5BMl5BanBnXkFtZTcwNDUzODM5Nw@@._V1_SY1000_CR0,0,673,1000_AL_.jpg', } m = LBRYMetadata.Metadata(metadata) - self.assertFalse('key' in m) + self.assertFalse('fee' in m) def test_assertion_if_invalid_source(self): metadata = { 'license': 'Oscilloscope Laboratories', - 'fee': {'LBC': {'amount': 50.0, 'address': 'bRQJASJrDbFZVAvcpv3NoNWoH74LQd5JNV'}}, 'description': 'Four couples meet for Sunday brunch only to discover they are stuck in a house together as the world may be about to end.', 'language': 'en', 'title': "It's a Disaster", @@ -34,7 +46,7 @@ class MetadataTest(unittest.TestCase): 'sources': { 'fake': 'source'}, 'content-type': 'audio/mpeg', - 'thumbnail': 'http://ia.media-imdb.com/images/M/MV5BMTQwNjYzMTQ0Ml5BMl5BanBnXkFtZTcwNDUzODM5Nw@@._V1_SY1000_CR0,0,673,1000_AL_.jpg' + 'thumbnail': 'http://ia.media-imdb.com/images/M/MV5BMTQwNjYzMTQ0Ml5BMl5BanBnXkFtZTcwNDUzODM5Nw@@._V1_SY1000_CR0,0,673,1000_AL_.jpg', } with self.assertRaises(AssertionError): LBRYMetadata.Metadata(metadata) @@ -57,7 +69,6 @@ class MetadataTest(unittest.TestCase): def test_version_is_001_if_all_fields_are_present(self): metadata = { 'license': 'Oscilloscope Laboratories', - 'fee': {'LBC': {'amount': 50.0, 'address': 'bRQJASJrDbFZVAvcpv3NoNWoH74LQd5JNV'}}, 'description': 'Four couples meet for Sunday brunch only to discover they are stuck in a house together as the world may be about to end.', 'language': 'en', 'title': "It's a Disaster", @@ -65,29 +76,26 @@ class MetadataTest(unittest.TestCase): 'sources': { 'lbry_sd_hash': '8d0d6ea64d09f5aa90faf5807d8a761c32a27047861e06f81f41e35623a348a4b0104052161d5f89cf190f9672bc4ead'}, 'content-type': 'audio/mpeg', - 'thumbnail': 'http://ia.media-imdb.com/images/M/MV5BMTQwNjYzMTQ0Ml5BMl5BanBnXkFtZTcwNDUzODM5Nw@@._V1_SY1000_CR0,0,673,1000_AL_.jpg' + 'thumbnail': 'http://ia.media-imdb.com/images/M/MV5BMTQwNjYzMTQ0Ml5BMl5BanBnXkFtZTcwNDUzODM5Nw@@._V1_SY1000_CR0,0,673,1000_AL_.jpg', } - m = LBRYMetadata.Metadata(metadata) - self.assertEquals('0.0.1', m.meta_version) + m = LBRYMetadata.Metadata(metadata, process_now=False) + self.assertEquals('0.0.1', m.version) def test_assertion_if_there_is_an_extra_field(self): metadata = { - 'license': 'NASA', - 'fee': {'USD': {'amount': 0.01, 'address': 'baBYSK7CqGSn5KrEmNmmQwAhBSFgo6v47z'}}, - 'ver': '0.0.2', - 'description': 'SDO captures images of the sun in 10 different wavelengths, each of which helps highlight a different temperature of solar material. Different temperatures can, in turn, show specific structures on the sun such as solar flares, which are gigantic explosions of light and x-rays, or coronal loops, which are stream of solar material travelling up and down looping magnetic field lines', + 'license': 'Oscilloscope Laboratories', + 'description': 'Four couples meet for Sunday brunch only to discover they are stuck in a house together as the world may be about to end.', 'language': 'en', - 'author': 'The SDO Team, Genna Duberstein and Scott Wiessinger', - 'title': 'Thermonuclear Art', + 'title': "It's a Disaster", + 'author': 'Written and directed by Todd Berger', 'sources': { - 'lbry_sd_hash': '8655f713819344980a9a0d67b198344e2c462c90f813e86f0c63789ab0868031f25c54d0bb31af6658e997e2041806eb'}, - 'nsfw': False, - 'content-type': 'video/mp4', - 'thumbnail': 'https://svs.gsfc.nasa.gov/vis/a010000/a012000/a012034/Combined.00_08_16_17.Still004.jpg', + 'lbry_sd_hash': '8d0d6ea64d09f5aa90faf5807d8a761c32a27047861e06f81f41e35623a348a4b0104052161d5f89cf190f9672bc4ead'}, + 'content-type': 'audio/mpeg', + 'thumbnail': 'http://ia.media-imdb.com/images/M/MV5BMTQwNjYzMTQ0Ml5BMl5BanBnXkFtZTcwNDUzODM5Nw@@._V1_SY1000_CR0,0,673,1000_AL_.jpg', 'MYSTERYFIELD': '?' } with self.assertRaises(AssertionError): - LBRYMetadata.Metadata(metadata) + LBRYMetadata.Metadata(metadata, process_now=False) def test_version_is_002_if_all_fields_are_present(self): metadata = { @@ -104,8 +112,26 @@ class MetadataTest(unittest.TestCase): 'content-type': 'video/mp4', 'thumbnail': 'https://svs.gsfc.nasa.gov/vis/a010000/a012000/a012034/Combined.00_08_16_17.Still004.jpg' } - m = LBRYMetadata.Metadata(metadata) - self.assertEquals('0.0.2', m.meta_version) + m = LBRYMetadata.Metadata(metadata, process_now=False) + self.assertEquals('0.0.2', m.version) + + def test_version_is_003_if_all_fields_are_present(self): + metadata = { + 'license': 'NASA', + 'fee': {'USD': {'amount': 0.01, 'address': 'baBYSK7CqGSn5KrEmNmmQwAhBSFgo6v47z'}}, + 'ver': '0.0.3', + 'description': 'SDO captures images of the sun in 10 different wavelengths, each of which helps highlight a different temperature of solar material. Different temperatures can, in turn, show specific structures on the sun such as solar flares, which are gigantic explosions of light and x-rays, or coronal loops, which are stream of solar material travelling up and down looping magnetic field lines', + 'language': 'en', + 'author': 'The SDO Team, Genna Duberstein and Scott Wiessinger', + 'title': 'Thermonuclear Art', + 'sources': { + 'lbry_sd_hash': '8655f713819344980a9a0d67b198344e2c462c90f813e86f0c63789ab0868031f25c54d0bb31af6658e997e2041806eb'}, + 'nsfw': False, + 'content_type': 'video/mp4', + 'thumbnail': 'https://svs.gsfc.nasa.gov/vis/a010000/a012000/a012034/Combined.00_08_16_17.Still004.jpg' + } + m = LBRYMetadata.Metadata(metadata, process_now=False) + self.assertEquals('0.0.3', m.version) def test_version_claimed_is_001_but_version_is_002(self): metadata = { @@ -123,7 +149,55 @@ class MetadataTest(unittest.TestCase): 'thumbnail': 'https://svs.gsfc.nasa.gov/vis/a010000/a012000/a012034/Combined.00_08_16_17.Still004.jpg' } with self.assertRaises(AssertionError): - LBRYMetadata.Metadata(metadata) + LBRYMetadata.Metadata(metadata, process_now=False) + def test_version_claimed_is_002_but_version_is_003(self): + metadata = { + 'license': 'NASA', + 'fee': {'USD': {'amount': 0.01, 'address': 'baBYSK7CqGSn5KrEmNmmQwAhBSFgo6v47z'}}, + 'ver': '0.0.2', + 'description': 'SDO captures images of the sun in 10 different wavelengths, each of which helps highlight a different temperature of solar material. Different temperatures can, in turn, show specific structures on the sun such as solar flares, which are gigantic explosions of light and x-rays, or coronal loops, which are stream of solar material travelling up and down looping magnetic field lines', + 'language': 'en', + 'author': 'The SDO Team, Genna Duberstein and Scott Wiessinger', + 'title': 'Thermonuclear Art', + 'sources': { + 'lbry_sd_hash': '8655f713819344980a9a0d67b198344e2c462c90f813e86f0c63789ab0868031f25c54d0bb31af6658e997e2041806eb'}, + 'nsfw': False, + 'content_type': 'video/mp4', + 'thumbnail': 'https://svs.gsfc.nasa.gov/vis/a010000/a012000/a012034/Combined.00_08_16_17.Still004.jpg' + } + with self.assertRaises(AssertionError): + LBRYMetadata.Metadata(metadata, process_now=False) + def test_version_001_ports_to_003(self): + metadata = { + 'license': 'Oscilloscope Laboratories', + 'description': 'Four couples meet for Sunday brunch only to discover they are stuck in a house together as the world may be about to end.', + 'language': 'en', + 'title': "It's a Disaster", + 'author': 'Written and directed by Todd Berger', + 'sources': { + 'lbry_sd_hash': '8d0d6ea64d09f5aa90faf5807d8a761c32a27047861e06f81f41e35623a348a4b0104052161d5f89cf190f9672bc4ead'}, + 'content-type': 'audio/mpeg', + 'thumbnail': 'http://ia.media-imdb.com/images/M/MV5BMTQwNjYzMTQ0Ml5BMl5BanBnXkFtZTcwNDUzODM5Nw@@._V1_SY1000_CR0,0,673,1000_AL_.jpg', + } + m = LBRYMetadata.Metadata(metadata, process_now=True) + self.assertEquals('0.0.3', m.version) + def test_version_002_ports_to_003(self): + metadata = { + 'license': 'NASA', + 'fee': {'USD': {'amount': 0.01, 'address': 'baBYSK7CqGSn5KrEmNmmQwAhBSFgo6v47z'}}, + 'ver': '0.0.2', + 'description': 'SDO captures images of the sun in 10 different wavelengths, each of which helps highlight a different temperature of solar material. Different temperatures can, in turn, show specific structures on the sun such as solar flares, which are gigantic explosions of light and x-rays, or coronal loops, which are stream of solar material travelling up and down looping magnetic field lines', + 'language': 'en', + 'author': 'The SDO Team, Genna Duberstein and Scott Wiessinger', + 'title': 'Thermonuclear Art', + 'sources': { + 'lbry_sd_hash': '8655f713819344980a9a0d67b198344e2c462c90f813e86f0c63789ab0868031f25c54d0bb31af6658e997e2041806eb'}, + 'nsfw': False, + 'content-type': 'video/mp4', + 'thumbnail': 'https://svs.gsfc.nasa.gov/vis/a010000/a012000/a012034/Combined.00_08_16_17.Still004.jpg' + } + m = LBRYMetadata.Metadata(metadata, process_now=True) + self.assertEquals('0.0.3', m.version) \ No newline at end of file