Merge branch 'master' into electron

This commit is contained in:
jobevers 2017-02-21 10:47:28 -06:00
commit 1d30ae447a
13 changed files with 111 additions and 45 deletions

View file

@ -20,5 +20,5 @@ values =
search = [Unreleased] search = [Unreleased]
replace = [Unreleased] replace = [Unreleased]
\#\# [{new_version}] - {now:%Y-%m-%d} \#\# [{new_version}] - {now:%Y-%m-%d}

1
.gitattributes vendored Normal file
View file

@ -0,0 +1 @@
/CHANGELOG.md merge=union

View file

@ -8,12 +8,36 @@ can and probably will change functionality and break backwards compatability
at anytime. at anytime.
## [Unreleased] ## [Unreleased]
\#\# [0.8.6] - 2017-02-19
\#\# [0.8.6rc0] - 2017-02-19
### Changed
* Add `file_get` by stream hash
* Add utils.call_later to replace reactor.callLater
###
* Fix unhandled error in `get`
* Fix sd blob timeout handling in `get_availability`, return 0.0
## [0.8.5] - 2017-02-18
## [0.8.5rc0] - 2017-02-18
### Fixed
* Fix result expected by ui from file_get for missing files
## [0.8.4] - 2017-02-17
## [0.8.4rc0] - 2017-02-17
### Changed ### Changed
* Remove unused upload_allowed option * Remove unused upload_allowed option
* Remove code related to packaging as that step is now done in the electron client * Remove code related to packaging as that step is now done in the electron client
* Remove lbryum version check; use lbry-electron as version source * Remove lbryum version check; use lbry-electron as version source
* Include download url in version check * Include download url in version check
### Fixed
* add misssing traceback to logging
## [0.8.3] - 2017-02-15 ## [0.8.3] - 2017-02-15
### Fixed ### Fixed
* Get lbry files with pending claims * Get lbry files with pending claims

View file

@ -67,6 +67,7 @@ class Events(object):
'module': log_record.module, 'module': log_record.module,
'lineno': log_record.lineno, 'lineno': log_record.lineno,
'name': log_record.name, 'name': log_record.name,
'traceback': log_record.exc_text,
} }
return self._event('Error', properties) return self._event('Error', properties)

View file

@ -8,4 +8,7 @@ class Handler(logging.Handler):
logging.Handler.__init__(self, level) logging.Handler.__init__(self, level)
def emit(self, record): def emit(self, record):
# We need to call format to ensure that record.message and
# record.exc_text attributes are populated
self.format(record)
self.manager.send_error(record) self.manager.send_error(record)

View file

@ -11,5 +11,8 @@ class DummyHashAnnouncer(object):
def add_supplier(self, *args): def add_supplier(self, *args):
pass pass
def hash_queue_size(self):
return 0
def immediate_announce(self, *args): def immediate_announce(self, *args):
pass pass

View file

@ -5,7 +5,7 @@ from lbrynet import interfaces
from lbrynet import conf from lbrynet import conf
from lbrynet.core.client.ClientProtocol import ClientProtocolFactory from lbrynet.core.client.ClientProtocol import ClientProtocolFactory
from lbrynet.core.Error import InsufficientFundsError from lbrynet.core.Error import InsufficientFundsError
from lbrynet.core import utils
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -19,7 +19,6 @@ class PeerConnectionHandler(object):
class ConnectionManager(object): class ConnectionManager(object):
implements(interfaces.IConnectionManager) implements(interfaces.IConnectionManager)
callLater = reactor.callLater
MANAGE_CALL_INTERVAL_SEC = 1 MANAGE_CALL_INTERVAL_SEC = 1
def __init__(self, downloader, rate_limiter, def __init__(self, downloader, rate_limiter,
@ -54,7 +53,7 @@ class ConnectionManager(object):
def start(self): def start(self):
log.debug("%s starting", self._get_log_name()) log.debug("%s starting", self._get_log_name())
self._start() self._start()
self._next_manage_call = self.callLater(0, self.manage) self._next_manage_call = utils.call_later(0, self.manage)
return defer.succeed(True) return defer.succeed(True)
@ -156,7 +155,7 @@ class ConnectionManager(object):
self._manage_deferred.callback(None) self._manage_deferred.callback(None)
self._manage_deferred = None self._manage_deferred = None
if not self.stopped and schedule_next_call: if not self.stopped and schedule_next_call:
self._next_manage_call = self.callLater(self.MANAGE_CALL_INTERVAL_SEC, self.manage) self._next_manage_call = utils.call_later(self.MANAGE_CALL_INTERVAL_SEC, self.manage)
def _rank_request_creator_connections(self): def _rank_request_creator_connections(self):
"""Returns an ordered list of our request creators, ranked according """Returns an ordered list of our request creators, ranked according

View file

@ -40,7 +40,7 @@ class DHTPeerFinder(object):
def find_peers_for_blob(self, blob_hash, timeout=None): def find_peers_for_blob(self, blob_hash, timeout=None):
def _trigger_timeout(): def _trigger_timeout():
if not finished_deferred.called: if not finished_deferred.called:
log.warning("Peer search for %s timed out", short_hash(blob_hash)) log.debug("Peer search for %s timed out", short_hash(blob_hash))
finished_deferred.cancel() finished_deferred.cancel()
bin_hash = binascii.unhexlify(blob_hash) bin_hash = binascii.unhexlify(blob_hash)

View file

@ -3,14 +3,13 @@ import collections
import logging import logging
import time import time
from twisted.internet import defer, reactor from twisted.internet import defer
from lbrynet.core import utils
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class DHTHashAnnouncer(object): class DHTHashAnnouncer(object):
callLater = reactor.callLater
ANNOUNCE_CHECK_INTERVAL = 60 ANNOUNCE_CHECK_INTERVAL = 60
CONCURRENT_ANNOUNCERS = 5 CONCURRENT_ANNOUNCERS = 5
@ -26,7 +25,7 @@ class DHTHashAnnouncer(object):
def run_manage_loop(self): def run_manage_loop(self):
if self.peer_port is not None: if self.peer_port is not None:
self._announce_available_hashes() self._announce_available_hashes()
self.next_manage_call = self.callLater(self.ANNOUNCE_CHECK_INTERVAL, self.run_manage_loop) self.next_manage_call = utils.call_later(self.ANNOUNCE_CHECK_INTERVAL, self.run_manage_loop)
def stop(self): def stop(self):
log.info("Stopping %s", self) log.info("Stopping %s", self)
@ -79,7 +78,7 @@ class DHTHashAnnouncer(object):
log.debug('Announcing blob %s to dht', h) log.debug('Announcing blob %s to dht', h)
d = self.dht_node.announceHaveBlob(binascii.unhexlify(h), self.peer_port) d = self.dht_node.announceHaveBlob(binascii.unhexlify(h), self.peer_port)
d.chainDeferred(announce_deferred) d.chainDeferred(announce_deferred)
d.addBoth(lambda _: self.callLater(0, announce)) d.addBoth(lambda _: utils.call_later(0, announce))
else: else:
self._concurrent_announcers -= 1 self._concurrent_announcers -= 1

View file

@ -43,6 +43,13 @@ def datetime_obj(*args, **kwargs):
return datetime.datetime(*args, **kwargs) return datetime.datetime(*args, **kwargs)
def call_later(delay, func, *args, **kwargs):
# Import here to ensure that it gets called after installing a reator
# see: http://twistedmatrix.com/documents/current/core/howto/choosing-reactor.html
from twisted.internet import reactor
return reactor.callLater(delay, func, *args, **kwargs)
def generate_id(num=None): def generate_id(num=None):
h = get_lbry_hash_obj() h = get_lbry_hash_obj()
if num is not None: if num is not None:

View file

@ -105,6 +105,7 @@ class FileID:
NAME = 'name' NAME = 'name'
SD_HASH = 'sd_hash' SD_HASH = 'sd_hash'
FILE_NAME = 'file_name' FILE_NAME = 'file_name'
STREAM_HASH = 'stream_hash'
# TODO add login credentials in a conf file # TODO add login credentials in a conf file
@ -819,17 +820,13 @@ class Daemon(AuthJSONRPCServer):
""" """
timeout = timeout if timeout is not None else conf.settings['download_timeout'] timeout = timeout if timeout is not None else conf.settings['download_timeout']
try:
helper = _DownloadNameHelper(self, name, timeout, download_directory, file_name,
wait_for_write)
except Exception as err:
log.exception(err)
raise err
helper = _DownloadNameHelper(self, name, timeout, download_directory, file_name,
wait_for_write)
if not stream_info: if not stream_info:
self.waiting_on[name] = True self.waiting_on[name] = True
stream_info = yield self._resolve_name(name) stream_info = yield self._resolve_name(name)
del self.waiting_on[name]
lbry_file = yield helper.setup_stream(stream_info) lbry_file = yield helper.setup_stream(stream_info)
sd_hash, file_path = yield helper.wait_or_get_stream(stream_info, lbry_file) sd_hash, file_path = yield helper.wait_or_get_stream(stream_info, lbry_file)
defer.returnValue((sd_hash, file_path)) defer.returnValue((sd_hash, file_path))
@ -1029,6 +1026,12 @@ class Daemon(AuthJSONRPCServer):
return lbry_file return lbry_file
raise Exception("File %s not found" % file_name) raise Exception("File %s not found" % file_name)
def _find_lbry_file_by_stream_hash(self, stream_hash):
for lbry_file in self.lbry_file_manager.lbry_files:
if lbry_file.stream_hash == stream_hash:
return lbry_file
raise NoSuchStreamHash(stream_hash)
@defer.inlineCallbacks @defer.inlineCallbacks
def _get_lbry_file_by_uri(self, name): def _get_lbry_file_by_uri(self, name):
try: try:
@ -1049,6 +1052,11 @@ class Daemon(AuthJSONRPCServer):
lbry_file = yield self._get_lbry_file_by_file_name(file_name) lbry_file = yield self._get_lbry_file_by_file_name(file_name)
defer.returnValue(lbry_file) defer.returnValue(lbry_file)
@defer.inlineCallbacks
def _get_lbry_file_by_stream_hash(self, stream_hash):
lbry_file = yield self._find_lbry_file_by_stream_hash(stream_hash)
defer.returnValue(lbry_file)
@defer.inlineCallbacks @defer.inlineCallbacks
def _get_lbry_file(self, search_by, val, return_json=True): def _get_lbry_file(self, search_by, val, return_json=True):
helper = _GetFileHelper(self, search_by, val, return_json) helper = _GetFileHelper(self, search_by, val, return_json)
@ -1057,7 +1065,7 @@ class Daemon(AuthJSONRPCServer):
defer.returnValue(lbry_file) defer.returnValue(lbry_file)
except Exception as err: except Exception as err:
# TODO: do something with the error, don't return None when a file isn't found # TODO: do something with the error, don't return None when a file isn't found
defer.returnValue(None) defer.returnValue(False)
def _get_lbry_files(self): def _get_lbry_files(self):
def safe_get(sd_hash): def safe_get(sd_hash):
@ -1376,7 +1384,10 @@ class Daemon(AuthJSONRPCServer):
Returns: Returns:
list list
""" """
return self._render_response(sorted(self.callable_methods.keys())) return self._render_response(sorted(
[command for command in self.callable_methods.keys()
if 'DEPRECATED' not in getattr(self, "jsonrpc_" + command).__doc__]
))
def jsonrpc_get_balance(self): def jsonrpc_get_balance(self):
""" """
@ -1453,22 +1464,35 @@ class Daemon(AuthJSONRPCServer):
def jsonrpc_file_get(self, **kwargs): def jsonrpc_file_get(self, **kwargs):
""" """
Get a file Get a file, if no matching file exists returns False
Args: Args:
'name': get file by lbry uri, 'name': get file by lbry uri,
'sd_hash': get file by the hash in the name claim, 'sd_hash': get file by the hash in the name claim,
'file_name': get file by its name in the downloads folder, 'file_name': get file by its name in the downloads folder,
'stream_hash': get file by its stream hash
Returns: Returns:
'completed': bool 'completed': bool,
'file_name': string 'file_name': str,
'key': hex string 'download_directory': str,
'points_paid': float 'points_paid': float,
'stopped': bool 'stopped': bool,
'stream_hash': base 58 string 'stream_hash': str (hex),
'stream_name': string 'stream_name': str,
'suggested_file_name': string 'suggested_file_name': str,
'sd_hash': string 'sd_hash': str (hex),
'lbry_uri': str,
'txid': str (b58),
'claim_id': str (b58),
'download_path': str,
'mime_type': str,
'key': str (hex),
'total_bytes': int,
'written_bytes': int,
'code': str,
'message': str
'metadata': Metadata dict if claim is valid, otherwise status str
}
""" """
d = self._get_deferred_for_lbry_file(kwargs) d = self._get_deferred_for_lbry_file(kwargs)
d.addCallback(lambda r: self._render_response(r)) d.addCallback(lambda r: self._render_response(r))
@ -1557,6 +1581,7 @@ class Daemon(AuthJSONRPCServer):
'stream_hash': hex string 'stream_hash': hex string
'path': path of download 'path': path of download
""" """
timeout = timeout if timeout is not None else self.download_timeout timeout = timeout if timeout is not None else self.download_timeout
download_directory = download_directory or self.download_directory download_directory = download_directory or self.download_directory
sd_hash = get_sd_hash(stream_info) sd_hash = get_sd_hash(stream_info)
@ -2451,7 +2476,11 @@ class Daemon(AuthJSONRPCServer):
blob_hashes = [blob.blob_hash for blob in blobs] blob_hashes = [blob.blob_hash for blob in blobs]
if need_sd_blob: if need_sd_blob:
# we don't want to use self._download_descriptor here because it would create a stream # we don't want to use self._download_descriptor here because it would create a stream
sd_blob = yield self._download_blob(sd_hash, timeout=sd_timeout) try:
sd_blob = yield self._download_blob(sd_hash, timeout=sd_timeout)
except Exception as err:
response = yield self._render_response(0.0)
defer.returnValue(response)
decoded = read_sd_blob(sd_blob) decoded = read_sd_blob(sd_blob)
blob_hashes = [blob.get("blob_hash") for blob in decoded['blobs'] blob_hashes = [blob.get("blob_hash") for blob in decoded['blobs']
if blob.get("blob_hash")] if blob.get("blob_hash")]
@ -2488,10 +2517,7 @@ class Daemon(AuthJSONRPCServer):
def get_sd_hash(stream_info): def get_sd_hash(stream_info):
if not stream_info: if not stream_info:
return None return None
try: return stream_info['sources']['lbry_sd_hash']
return stream_info['sources']['lbry_sd_hash']
except KeyError:
return stream_info.get('stream_hash')
class _DownloadNameHelper(object): class _DownloadNameHelper(object):
@ -2510,7 +2536,7 @@ class _DownloadNameHelper(object):
@defer.inlineCallbacks @defer.inlineCallbacks
def setup_stream(self, stream_info): def setup_stream(self, stream_info):
sd_hash = get_sd_hash(stream_info) sd_hash = get_sd_hash(stream_info)
lbry_file = yield self.daemon._get_lbry_file_by_sd_hash(sd_hash) lbry_file = yield self.daemon._get_lbry_file(FileID.SD_HASH, sd_hash)
if self._does_lbry_file_exists(lbry_file): if self._does_lbry_file_exists(lbry_file):
defer.returnValue(lbry_file) defer.returnValue(lbry_file)
else: else:
@ -2673,6 +2699,8 @@ class _GetFileHelper(object):
return self.daemon._get_lbry_file_by_sd_hash(self.val) return self.daemon._get_lbry_file_by_sd_hash(self.val)
elif self.search_by == FileID.FILE_NAME: elif self.search_by == FileID.FILE_NAME:
return self.daemon._get_lbry_file_by_file_name(self.val) return self.daemon._get_lbry_file_by_file_name(self.val)
elif self.search_by == FileID.STREAM_HASH:
return self.daemon._get_lbry_file_by_stream_hash(self.val)
raise Exception('{} is not a valid search operation'.format(self.search_by)) raise Exception('{} is not a valid search operation'.format(self.search_by))
def _get_json(self, lbry_file): def _get_json(self, lbry_file):
@ -2806,7 +2834,7 @@ def report_bug_to_slack(message, installation_id, platform_name, app_version):
def get_lbry_file_search_value(search_fields): def get_lbry_file_search_value(search_fields):
for searchtype in (FileID.SD_HASH, FileID.NAME, FileID.FILE_NAME): for searchtype in (FileID.SD_HASH, FileID.NAME, FileID.FILE_NAME, FileID.STREAM_HASH):
value = search_fields.get(searchtype) value = search_fields.get(searchtype)
if value: if value:
return searchtype, value return searchtype, value

View file

@ -3,7 +3,7 @@ import time
import logging import logging
from lbrynet.core import log_support from lbrynet.core import log_support
from lbrynet.core.client.ConnectionManager import ConnectionManager #from lbrynet.core.client.ConnectionManager import ConnectionManager
from lbrynet.core.client.ClientRequest import ClientRequest from lbrynet.core.client.ClientRequest import ClientRequest
from lbrynet.core.server.ServerProtocol import ServerProtocol from lbrynet.core.server.ServerProtocol import ServerProtocol
from lbrynet.core.RateLimiter import RateLimiter from lbrynet.core.RateLimiter import RateLimiter
@ -16,6 +16,7 @@ from twisted.internet import defer, reactor, task
from twisted.internet.task import deferLater from twisted.internet.task import deferLater
from twisted.internet.protocol import Protocol, ServerFactory from twisted.internet.protocol import Protocol, ServerFactory
from lbrynet import conf from lbrynet import conf
from lbrynet.core import utils
from lbrynet.interfaces import IQueryHandlerFactory, IQueryHandler, IRequestCreator from lbrynet.interfaces import IQueryHandlerFactory, IQueryHandler, IRequestCreator
from zope.interface import implements from zope.interface import implements
@ -122,11 +123,12 @@ class TestIntegrationConnectionManager(unittest.TestCase):
self.downloader = MocDownloader() self.downloader = MocDownloader()
self.rate_limiter = RateLimiter() self.rate_limiter = RateLimiter()
self.primary_request_creator = MocRequestCreator([self.TEST_PEER]) self.primary_request_creator = MocRequestCreator([self.TEST_PEER])
self.clock = task.Clock()
utils.call_later = self.clock.callLater
from lbrynet.core.client.ConnectionManager import ConnectionManager
self.connection_manager = ConnectionManager(self.downloader, self.rate_limiter, self.connection_manager = ConnectionManager(self.downloader, self.rate_limiter,
[self.primary_request_creator], []) [self.primary_request_creator], [])
self.clock = task.Clock()
self.connection_manager.callLater = self.clock.callLater
self.connection_manager._start() self.connection_manager._start()
self.server_port = None self.server_port = None

View file

@ -2,9 +2,7 @@ import os
import binascii import binascii
from twisted.trial import unittest from twisted.trial import unittest
from twisted.internet import defer,task from twisted.internet import defer,task
from lbrynet.core.server.DHTHashAnnouncer import DHTHashAnnouncer,DHTHashSupplier from lbrynet.core import log_support, utils
from lbrynet.core.utils import random_string
from lbrynet.core import log_support
class MocDHTNode(object): class MocDHTNode(object):
@ -35,8 +33,9 @@ class DHTHashAnnouncerTest(unittest.TestCase):
self.blobs_to_announce.append(binascii.b2a_hex(os.urandom(32))) self.blobs_to_announce.append(binascii.b2a_hex(os.urandom(32)))
self.clock = task.Clock() self.clock = task.Clock()
self.dht_node = MocDHTNode() self.dht_node = MocDHTNode()
utils.call_later = self.clock.callLater
from lbrynet.core.server.DHTHashAnnouncer import DHTHashAnnouncer,DHTHashSupplier
self.announcer = DHTHashAnnouncer(self.dht_node, peer_port=3333) self.announcer = DHTHashAnnouncer(self.dht_node, peer_port=3333)
self.announcer.callLater = self.clock.callLater
self.supplier = MocSupplier(self.blobs_to_announce) self.supplier = MocSupplier(self.blobs_to_announce)
self.announcer.add_supplier(self.supplier) self.announcer.add_supplier(self.supplier)